十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从CSV到深度学习数据集:四步Python预处理实战

从CSV到深度学习数据集:四步Python预处理实战 简介面向深度学习初学者与数据预处理环节资源聚焦CSV格式数据在模型训练前的规范处理提供了从缺失值清洗、标准化、特征编码到训练集划分的完整示例适合需要快速上手Python数据预处理流程的读者。压缩包体积十分精简仅2KB共含4个Python脚本以代码脚本为主要载体分别覆盖数据读取、按行操作、数据集拆分、整体流程串联等功能适合用来快速理解pandas、NumPy与Scikit-Learn在数据预处理中的常见配合方式。已有327人学习下载作为轻量级入门参考能够帮助解决“CSV数据如何转变为模型可用的张量”这一典型痛点。脚本整体模块划分清晰读者可对照描述中的预处理步骤将其中代码直接修改调用于自己的项目节省搭建基础数据管道的时间也能在实现过程中加深对深度学习数据准备环节的理解。1. 从CSV到深度学习数据集这个zip里没有模型只有四个Python脚本一个名为“处理csv文件深度学习.zip”的压缩包拆开里面没有预训练权重也没有网络结构文件只有四个Python脚本csvfile.py、main.py、movecsvrow.py、splitdataset.py。很多深度学习者会在这里卡住模型代码能照抄跑通但CSV数据就是喂不进去。pandas读出来的DataFrame里全是NaN、字符串类别、重复行直接塞给DataLoader报错会一路炸到深夜。这个zip的价值不在模型有多强而在于它把数据准备拆成了四个可复用的环节读取清洗、行迁移、数据集划分、主流程串联。本篇文章就按这四个文件展开把CSV文件从原始表变成深度学习输入张量的完整过程、参数选择和常见坑位讲清楚。适合刚接触深度学习但被数据预处理绊住的人也可以给正在搭训练管线的工程师做参考。2. csvfile.py里的数据清洗类型推断、缺失值填充与编码陷阱2.1 导入CSV文件时的类型参数dtype、parse_dates与低内存模式最常见的错误是直接pd.read_csv(file_path)让pandas自动推断所有列类型。对于几十万行的CSV文件这种偷懒会带来两个后果一是内存成倍增长二是同一列在不同数据块里被推断成不同类型最终得到一个object列后续连df[age].mean()都会崩。我在读取时就明确指定类型避免后期返工import pandas as pd def load_csv_typed(file_path): df pd.read_csv( file_path, dtype{user_id: str, age: float32}, parse_dates[reg_date], low_memoryFalse ) return dfdtype接收一个字典键是列名值是numpy类型或pandas类型。这里把user_id强制成字符串防止长ID被读成int64后精度丢失也避免后续匹配时出现前导零被吞掉的问题。age用float32节省一半内存。parse_dates可以把日期字符串解析成datetime64类型这样在做时间窗口特征时可以直接进行加减法。low_memoryFalse让pandas在读取时一次性处理所有字段避免分块解析导致同一列类型不一致。要注意文件真的很大时这个参数会显著抬高内存合理的替代方案是用usecols只保留需要的列而不是把整张表读进来再删。读进来之后先做探查再清洗。我一般会跑这两个命令print(df.info()) print(df.describe(includeall))info()能看到每列的非空数量空值多不多一目了然。describe(includeall)会同时输出数值列和类别列的统计量比如唯一值数量。这一步不是形式主义它决定了后面用哪种清洗策略。2.2 缺失值处理fillna的三种填充策略与适用范围深度学习模型对NaN基本零容忍Tensor本身不支持NaN输入训练时一种表现是loss直接变成NaN另一种是反向传播时报错。最省事的dropna()往往不可用因为真实数据几乎每行都有缺项删完就没数据了。所以要做填充。填充策略代码写法适用场景固定值填充df[col].fillna(0)缺失本身有业务含义比如金额缺失代表没有交易统计量填充df[col].fillna(df[col].median())数值分布有偏中位数比均值更抗异常值前后向填充df[col].ffill()时间序列数据用上一个有效观测值填补固定值填充的好处是稳定模型训练和推理时行为一致。统计量填充要注意一点统计量只能从训练集计算不能整个数据集一起算否则验证集和测试集的信息会泄漏到训练集里。ffill()在时序数据里很常用但如果缺失值出现在序列开头ffill()会留下NaN此时要再补一层bfill()或者统一用前一个时间点的值填充。还有一个容易被忽略的坑数据里的异常值不会表现为NaN而是Inf。有些CSV文件是业务系统导出的失败请求可能记录成-inf。深度学习的损失函数一旦遇到Inf整个训练就会崩。所以填充之前要做一次替换df df.replace([float(inf), float(-inf)], float(nan)).fillna(0)replace先把Inf统一换成NaN再用fillna的规则处理这样后面numpy转Tensor时才不会出问题。csvfile.py里的清洗函数通常就是把这几步串起来返回一个干净的DataFrame。2.3 分类特征编码get_dummies的边界与接续处理分类特征不能直接进深度模型pandas里最简单的方法是get_dummies一行就能one-hot编码df_encoded pd.get_dummies(df, columns[category])但这行代码有两个坑。第一如果某个类别只在测试集里出现训练集编码时没有这一列后面拼接模型输入时维度直接不一致。第二get_dummies会默认把所有object类型列都编码可能误伤原本应该做标签编码的有序类别比如学历这种带有层级关系的特征。正确的做法是先在有标签的训练集上定义好列集合再对验证集和测试集做对齐train_encoded pd.get_dummies(train_df, columns[category]) test_encoded pd.get_dummies(test_df, columns[category]) test_encoded test_encoded.reindex(columnstrain_encoded.columns, fill_value0)reindex的作用是用训练集的列顺序去重排测试集的列测试集缺少的列补0。这样两边特征维度完全一致模型输入的shape才稳定。如果类别特征的基数很高比如用户ID直接做one-hot会产生超高维稀疏矩阵这种情况下常见的做法是换embedding或者先做频率编码把出现次数少于某个阈值的类别合并成other。3. splitdataset.py与movecsvrow.py训练集划分与行迁移的可靠做法3.1 train_test_split的分层随机划分训练集、验证集、测试集的划分直接决定模型评估结果是否可信。最常用的划分工具是sklearn的train_test_splitfrom sklearn.model_selection import train_test_split train_df, valid_df train_test_split( df, test_size0.2, random_state42, stratifydf[label] )test_size0.2表示拿出20%做验证集。random_state固定了随机种子保证每次运行划分结果一致这在复现实验时非常重要。stratify用于分类任务它会让训练集和验证集中每个类别的占比和原始数据保持一致。如果不加stratify当某个类别只有几十个样本时随机划分后可能出现验证集里完全没有该类别的极端情况。如果CSV文件行数极大比如上千万行sklearn的划分会在内存里复制一份索引导致内存翻倍。这时可以用numpy的索引切分import numpy as np perm np.random.permutation(len(df)) split_idx int(0.8 * len(df)) train_df df.iloc[perm[:split_idx]].reset_index(dropTrue) valid_df df.iloc[perm[split_idx:]].reset_index(dropTrue)perm是打乱后的行号数组iloc按行号取值。reset_index(dropTrue)是必不可少的它会把原来的索引丢弃重新生成0到n-1的连续索引。如果不做这一步后面一旦执行pd.concat或df.loc会因为索引重复而取错行。划分方式优点缺点train_test_split支持分层代码短大数据集内存开销高numpy索引切分内存可控速度快分层逻辑要自己写按时间切分贴合业务时序场景分布可能漂移需要监控对于日志型CSV我更倾向按时间切分取前80%的时间段做训练后20%做验证。这样更接近模型上线后的真实分布但也可能因为时间跨度导致分布漂移需要额外检查。3.2 movecsvrow.py用于按条件迁移行movecsvrow.py从命名推断它的职责是把符合特定条件的行从源CSV中移到另一个CSV。一个典型场景是模型预测结果中有部分badcase你想把这些样本从训练集里摘出来做人工分析同时不让它们影响后续训练。简单实现如下import pandas as pd source pd.read_csv(train.csv) mask source[pred] ! source[label] badcase source[mask].copy() clean source[~mask].copy() badcase.to_csv(badcase.csv, indexFalse) clean.to_csv(train_clean.csv, indexFalse)这里的mask是一个布尔Seriessource[mask]取出所有预测错误的行source[~mask]取出剩余行。copy()很重要它生成了新的DataFrame避免后续操作触发SettingWithCopyWarning而~mask是对mask取反。移动之后要做一次数量校验assert len(badcase) len(clean) len(source)这个断言确认两边行数之和等于原文件行数防止在切片或to_csv过程中意外丢行。多一个文件生成就多一次校验这条规则在数据处理脚本里永远适用。3.3 划分后如何维持类别平衡即使使用了stratify也只能保证训练集和验证集的比例一致不能解决原始数据本身的不平衡。如果在训练集中正样本只占1%模型很容易把所有样本都预测为负类。常见的解决办法是先对少数类做重采样或者给每个类别设置不同的样本权重。在PyTorch里可以在DataLoader中传入WeightedRandomSampler权重与类别样本数成反比。在TensorFlow里model.fit支持直接传入class_weight字典。这两种方案都不会额外增加CSV文件行数更适合在训练时动态调整。movecsvrow.py这种按条件迁移行的方法如果被用来单独迁移大量难样本到验证集会导致训练集分布和真实分布偏离。做这种操作前最好先记录原始分布的直方图迁移后再次对比两侧差异超过几个百分点就要重新设计迁移规则。4. main.py里的预处理流水线从DataFrame到numpy数组再到Tensor4.1 特征与标签拆分的顺序main.py是整套流程的编排入口。我见过很多人在主脚本里直接写df.iloc[:, :-1]来取特征最后一列是标签。这种写法一旦列顺序变动整个训练就废了。更稳的方式是在开头明确定义列名FEATURE_COLS [age, income, category_encoded] LABEL_COL target x_df data[FEATURE_COLS] y_df data[LABEL_COL]用列名而不是列序号好处是代码自解释而且CSV文件里新增一列也不会影响切片结果。更重要的一点是填充缺失值的统计量必须先在这部分计算比如mean_age data.loc[:, age].mean() data.loc[:, age] data[age].fillna(mean_age)这里data.loc[:, age]是为了显式地操作原始列。如果直接data[age].fillna(mean_age)而不赋值不会修改原DataFrame后面模型拿到的仍然是有NaN的数据。4.2 数值特征与类别特征分别处理数值特征和类别特征不能放在一起做标准化。对age做Z-score让均值为0标准差为1可以加快梯度收敛。对one-hot后的category列做同样的操作会把0/1特征变成负数破坏稀疏结构的含义。推荐用scikit-learn的ColumnTransformer把两部分拼在一个流程里from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder preprocessor ColumnTransformer([ (num, StandardScaler(), [age, income]), (cat, OneHotEncoder(handle_unknownignore), [category]) ]) x_train preprocessor.fit_transform(x_train_df) x_valid preprocessor.transform(x_valid_df)fit_transform在训练集上计算均值和标准差transform在验证集上复用这些统计量。handle_unknownignore让编码器在遇到训练集没见过的类别时不做编码而是返回全零向量而不是直接报错。这个参数在验证集和线上推理时会经常救命。4.3 转换成PyTorch或TensorFlow张量的两种写法数据最终要变成张量。PyTorch的常规写法import torch train_x torch.tensor(x_train, dtypetorch.float32) train_y torch.tensor(y_train, dtypetorch.long)dtype是最容易出错的地方。特征用float32分类标签用long。如果标签是浮点或int32CrossEntropyLoss会直接抛类型错误。TensorFlow侧则常用import tensorflow as tf train_dataset tf.data.Dataset.from_tensor_slices( (x_train, y_train) ).batch(32).shuffle(1000)from_tensor_slices要求传入numpy数组或嵌套元组传入DataFrame会报错所以之前的x_train要确保是numpy数组。batch(32)表示每批32个样本shuffle(1000)在每次迭代前先打乱前面1000个样本的缓冲区避免模型学到顺序信息。在main.py中我一般会把读取、清洗、划分、转换封装成一个load_and_prepare(config)函数返回train_loader和valid_loader再由训练脚本调用。这样训练脚本里不需要出现任何pandas代码后期替换数据源时也只改一个函数。5. 处理csv文件时最常见的排错现场与修复5.1 read_csv报错或导入csv文件后不是表格很多人在PyCharm里直接双击生成的CSV文件看到内容全挤在一列里并不是理想中的表格。这通常不是文件坏了而是分隔符或编码不匹配。如果是从Excel导出的CSVWindows环境下的分隔符可能是分号也可能是带BOM的UTF-8。用pandas读取时需要明确指定df pd.read_csv(data.csv, sep;, encodinggbk)sep;让pandas按分号切分encodinggbk解决中文Windows下从Excel保存导致的乱码。如果你的csv和txt混在一起sep参数还支持正则表达式例如sep\t读取tab分隔的TSV文件。如果上述办法仍然报错可以先看文件前几行with open(data.csv, r, encodingutf-8) as f: for _ in range(5): print(f.readline())这样能看到真实分隔符和内容格式比自己盲猜编码快得多。错误来源表现修复方向分隔符不一致一列数据被读成一整串显式指定sep参数编码不一致UnicodeDecodeError或乱码尝试utf-8、gbk、utf-8-sig数据内容含逗号列数忽多忽少生成时使用quotingcsv.QUOTE_ALL5.2 路径、编码和分隔符的三个坑路径中带中文或空格是Windows用户的常态。pandas的read_csv路径可以直接传原始字符串但如果在路径中使用转义符比如C:\data\new.csv\n会被当成换行符。解决方案是使用rC:\data\new.csv原始字符串或者把反斜杠替换成正斜杠。编码方面很多时候你从数据库导出的CSV是UTF-8但经过某次手动另存为后变成GBK再读就会报错。我一般统一用encodingutf-8-sig导出它会在文件开头写入BOMExcel打开不会乱码pandas读取也没问题。分隔符的第三个坑是数据字段本身包含逗号比如商品描述“好吃, 便宜”。如果导出时没有加引号pandas会把一行拆成两行。解决这个问题的源头是在to_csv时加参数df.to_csv(out.csv, indexFalse, quotingcsv.QUOTE_ALL)quotingcsv.QUOTE_ALL会让每个字段都加引号读取时pandas默认就能正确处理引号内的逗号不把它当作分隔符。5.3 移动行时索引错乱的问题movecsvrow.py这类脚本在切片后索引不会自动重排。直接输出CSV时行号还是原文件中的行号表面上没有问题。但如果你在脚本里执行过df.loc[0]取到的可能不是预期的第一行而是原文件中索引为0的行。解决办法是移动后统一重置索引alive_df source[source[valid] 1].reset_index(dropTrue) moved_df source[source[valid] 0].reset_index(dropTrue)reset_index(dropTrue)会把新DataFrame的索引变成从0开始的连续值dropTrue表示不把旧索引保存为新列。如果后续需要追踪原始行号应该在操作之前把原索引复制到一个source_id列里而不是依赖默认索引。6. 用MD5校验和维度快照验证预处理结果6.1 对生成的csv文件做MD5校验确认完好性预处理脚本跑完第一件事不是看训练曲线而是对生成的CSV做完整性校验。文件在写入磁盘或从zip压缩包解压时可能因为意外中断产生损坏表现为某一行数据缺列或出现乱码。用MD5校验能快速发现这种问题md5sum train_clean.csv valid_clean.csv在Windows PowerShell环境使用Get-FileHash -Algorithm MD5 train_clean.csvmd5sum输出一串长度为64的十六进制字符串文件内容任何一字节发生变化这个值都会不同。把首次生成正常数据的哈希值保存到checksum.txt之后每次重新生成数据再对比一次。如果哈希一致说明预处理脚本的输出没有被外部改动。要注意MD5只做文件完整性校验不适合安全场景但在数据处理管线上完全够用。6.2 数据快照与张量形状自检除了校验文件还要校验数据语义。可以写一个自检函数在训练开始前做断言import pandas as pd def validate_split(train_df, valid_df, feature_cols, label_col): assert train_df[label_col].nunique() valid_df[label_col].nunique() assert train_df[feature_cols].isna().sum().sum() 0 assert train_df.shape[1] valid_df.shape[1] print(train shape:, train_df.shape) print(valid shape:, valid_df.shape)nunique检查两边标签类别数量一致避免验证集里突然缺少某个类别。isna().sum().sum()统计所有特征列里的NaN总数结果为0才允许训练。shape[1]检查两边列数一致如果之前编码对齐没做好这里就会暴露。更严格的检查是打印每个特征在训练集和验证集的均值与方差快照。如果某个特征在两边均值相差超过3倍标准差说明数据划分不够随机需要检查是否按时间或ID分组划分导致的分布漂移。在训练主流程里每隔几个epoch打印一次batch的形状for batch_x, batch_y in train_loader: print(batch_x.shape, batch_y.shape) break这一步能提前发现维度不匹配问题比如特征维度多一列或少一列。数据预处理脚本的维护成本并不比模型代码低在csvfile.py和main.py这些脚本里加上这些校验逻辑后续换数据源、加特征时才不会因为一个静默错误浪费几天训练时间。本文还有配套的精品资源点击获取
返回列表