深度学习数据预处理:从Pandas清洗到PyTorch张量
发布时间:2026/10/15 19:36:49 锦皓数字建站

1. 这个预处理到底在做什么如果你刚把深度学习的环境配好、正向传播反向传播背得滚瓜烂熟然后第一次打开实际数据集准备练手多半会遇到一个尴尬情况模型还没跑起来先报一堆数据格式的错。我见过不少同学在这个环节卡住甚至有人误以为是自己代码写错了反复调学习率调了半天最后发现是输入的数据里有空值、有文本列张量形状还对不上。这一课聊的数据预处理就是解决这个“模型之前”的问题。数据预处理解决的是“数据能不能直接喂给模型”的问题。原始数据很少是干净的大概率包含缺失值、异常值、非数值类型的类别特征以及量纲差距很大的数值列。如果这些内容不管不顾直接扔进模型轻则训练崩掉重则模型训出来了但结果完全没意义。比如一个特征量纲是0到1另一个特征动辄几千梯度更新就会被大数值特征主导模型学的其实是“那个数字的大小”而不是数据的实际规律。这一课的内容适合所有刚开始接触深度学习的人尤其是已经会用框架搭简单网络、但还没处理过真实数据集的朋友。课程里的内容不复杂核心就是pandas读取数据、处理缺失值、编码类别特征、标准化数值特征最后把处理完的数据转成深度学习框架能用的张量格式。整套流程走一遍你就能理解深度学习项目的起点不是网络结构而是拿到一份能用的数据。2. 整体设计思路为什么用pandas而不是纯NumPy2.1 pandas在深度学习流程中的定位很多人一开始会有个疑问为什么数据预处理不直接用NumPy毕竟深度学习框架的张量操作都是基于NumPy风格的转起来也更顺。这个想法单独看没错但真实世界的数据集几乎不会以纯数值矩阵的形式出现。一个典型的原始数据集往往包含ID列、类别列比如城市、颜色、产品类型、日期列甚至还有缺失值这些内容用NumPy处理非常别扭。pandas真正擅长的是表格数据的操作。它提供了DataFrame这种二维表格结构可以给每一列命名可以按列做筛选、映射、填充还可以方便地处理异构数据——也就是一列是数字、另一列是文本的情况。而NumPy的ndarray要求所有元素类型一致一旦数据里混入了NaN空值或者字符串操作起来就会很麻烦。课程里选择pandas作为预处理工具不是因为深度学习框架必须要pandas而是因为pandas是最适合“人类理解和清洗数据”的工具。预处理流程的核心目的不是做高性能计算而是把原始数据整理成框架能够直接消费的格式。这个“整理”过程用pandas做起来最顺手效率也最高。我在实际项目中处理几万行、几十列的数据pandas的开销完全在可接受范围内真正大规模的数据集也不需要一次性载入内存那是另一个话题了。2.2 为什么预处理和模型训练要分开课程里把数据预处理单独拎出来讲而不是和模型训练混在一起这个设计是有讲究的。预处理和数据加载是两个阶段。预处理是把原始数据变成干净、规整的结构比如填充缺失值、把字符串类别变成数字编号、把数值特征缩放到合理范围。这个阶段通常是一次性的处理完可以保存成新的文件下次直接加载。数据加载则是训练循环里的高频操作每次迭代都要把一批数据送到模型里。如果把预处理逻辑塞进数据加载的代码里每加载一次就要重复执行一次填充和编码既浪费时间又容易因为不同批次的处理方式不一致而出问题。课程这个安排的合理之处在于它让读者从一开始就养成“预处理前置”的习惯。这里还要说明一个关键的概念训练和测试数据处理方式必须保持一致。什么意思假如训练集用均值填充了缺失值测试集也必须用同一个均值去填充而不是重新计算测试集的均值。如果测试集单独算均值和标准差就可能造成数据分布不一致模型在训练时学到的特征变换规则和测试时对不上最终预测结果就会偏移。这个细节在后面的实操演示里会具体看到。3. 核心细节解析从DataFrame到模型输入3.1 pandas读取数据的两个关键点课程用的是pandas的read_csv函数读取CSV格式的数据文件这是最常见的数据格式。这里有个容易被新手忽略的点读入数据之后先看一眼数据长什么样再动手处理。课程里使用print()打印DataFrame确实是最直接的方式但更好的做法是掌握df.head()、df.info()、df.describe()这几个方法。head()默认展示前5行让你快速了解数据的列结构和大致内容。info()可以看到每一列的非空值数量、数据类型这是发现缺失值最快的方法。describe()则是对数值列做统计摘要可以直观看到均值、标准差、最小值、最大值帮助判断哪些列的量纲差异过大。真实项目的经验是拿到任何数据集先跑这三条命令比直接写处理逻辑要靠谱得多。课程里的数据规模很小只有几行用print就能看全。但换成实际项目一个数据集动辄几十万行你不可能把全部数据打印出来看。学会用这些轻量级的查看方法是数据预处理的基本功。还有一个容易被忽略的点是文件路径问题比如CSV文件不在当前工作目录下会提示找不到文件。我建议在项目里用相对路径并且每次都确认一下当前工作目录是什么避免这种低级错误消耗时间。3.2 如何观察数据先看再动手课程提供了一个非常典型的小数据集示例包含两个数值列和一个类别列其中既有正常的房价数值也有明显异常的一行面积和房价都是空值。这个设计其实是在模拟真实数据里的两种典型问题数值列缺失和类别列多样性。在处理这类数据的时候一定要先搞清楚每一列的含义和取值范围再决定处理策略。比如课程里的类别列是房屋的巷子类型取值相对集中用one-hot编码是合理的。但如果你拿到一个真实项目的数据集某个类别列有几十种取值并且有少数类别出现频率极低就需要考虑是否合并这些稀有类别否则编码之后会产生大量稀疏列占用内存而且对模型帮助不大。这就是我反复强调的预处理不是一个机械的执行步骤而是需要结合业务理解来做出的决策。课程里的代码虽然看似简单背后代表的是“观察数据→发现问题→选择方案→验证结果”的完整思路。你在实际项目中处理自己的数据时也需要重复这个流程。数据的问题往往是拼凑出来的你需要结合上下文判断哪些是真实信号哪些是脏数据。3.3 缺失值处理的两个核心思路对于数值列的空值课程里有两个处理思路插值法和删除法。插值法就是用某种规则把缺失的位置填上课程里用的是均值填充。删除法则是直接把包含空值的行丢弃。这两种方法各有适用场景不能一概而论。均值填充适用于缺失比例较低、且缺失值对整体分布影响较小的情况。比如一个几十万行的数据集里只有几百行缺失用均值填充几乎不会改变分布。但如果缺失比例很高比如某一列有30%都是空的均值填充就会引入很大的偏差因为真实数据的分布可能和填充后的分布差距很大这种情况需要更谨慎地评估甚至可能需要考虑这一列是否还有使用价值。删除法则有前提条件被删除的行不能太多。如果因为删除缺失行导致数据量骤减模型可能学不到足够的模式。课程里的示例数据只有几行删除一行影响不大但真实场景下一个好的经验是先算一下缺失行占总数据量的百分比再决定用哪种策略。顺带一提对于某些特定类型的特征缺失值本身可能是有信息量的。比如在用户行为数据里“某用户没填写年龄”和“某用户年龄为0”含义完全不同。对于这种情况可以单独生成一个布尔特征标记该值是否缺失让模型自己学习这个信号。课程里的方法是一个很好的基础模板但真正动手处理自己的数据时这些扩展思路会更有用。3.4 类别特征编码为什么用one-hot类别特征直接喂给模型是不行的因为大多数模型只能处理数值。常见方案有两种标签编码和one-hot编码。标签编码就是把类别值映射成数字比如A变成0、B变成1。但这种方式有一个隐含的危险类别之间没有大小关系数字的大小纯粹是人为规定的如果模型把这些数字当成连续数值来理解就会强行学习到一个不存在的大小顺序。课程中使用的是pd.get_dummies()做one-hot编码这是处理类别特征的主流方案。one-hot的原理很简单假设“巷子类型”有A、B两个取值就生成两列分别表示“是否为A”和“是否为B”取值是0或1。这样既消除了无序类别之间的人为大小关系又保留了完整的信息。如果类别有K种取值就会生成K列课程里有一个细节是用了dummy_naTrue这会额外生成一列表示缺失情况所以两列变成了三列。我在自己的项目里对于类别数不多的列比如少于几十种取值通常直接用one-hot编码。对于类别特别多的列会考虑先用频次统计把低频类别归为一类再做one-hot。课程中虽然没展开这些内容但原理是同一个理解了基础版本之后这些都属于自然的延伸。3.5 标准化为什么要让数值落在一个合理的区间标准化或者说归一化是深度学习数据预处理里几乎必不可少的一步。课程里的示例数据只有一个数值列不太能看出标准化的必要性但这个步骤在实际项目里极其关键。深度学习模型在训练时依赖梯度下降来更新参数。如果不同特征的数值范围差异过大比如一个特征是0到1另一个是10000到50000那么梯度更新时会受到大数值特征的主导模型会偏向于重点学习那个数值大的特征而忽略数值小的特征。这不是模型“偷懒”而是梯度数值天然会被大规模特征“带偏”。更麻烦的是有些激活函数比如sigmoid在输入值过大或过小时会进入饱和区梯度趋近于零训练几乎停滞。课程里用的是(x - min) / (max - min)的min-max归一化方法把数值缩放到0到1之间。这个方法的优点是简单直观缺点是受异常值影响很大如果数据里有一个极大的离群值那么大部分正常数据都会被压缩到非常小的区间内。实际项目中更常用的是z-score标准化即(x - mean) / std让数据变成均值为0、标准差为1的分布它对异常值的鲁棒性更好。课程选择min-max归一化是为了入门友好但你需要知道两种方法各有适用场景并且要能把它们灵活应用。4. 实操过程与核心环节实现4.1 完整代码实现读取到张量转换下面这段代码基本复刻了课程的核心流程我加了一些注释方便你理解每一步到底在做什么。跑通这段代码之后你就具备了一条完整的数据预处理流水线。import pandas as pd import torch # 读取CSV文件这里使用课程里的示例数据 data pd.read_csv(house_tiny.csv) print(原始数据) print(data) # 1. 处理缺失值数值列用均值填充 # 这里inputs是原始数据里去掉目标列价格之后的部分 inputs, outputs data.iloc[:, 0:2], data.iloc[:, 2] # 对数值列填充均值 inputs inputs.fillna(inputs.mean(numeric_onlyTrue)) print(填充后的数据) print(inputs) # 2. 处理类别特征one-hot编码 # dummy_naTrue会额外生成一列表示缺失情况 inputs pd.get_dummies(inputs, dummy_naTrue) print(编码后的数据) print(inputs) # 3. 转为NumPy数组再转为PyTorch张量 X torch.tensor(inputs.to_numpy(dtypefloat)) y torch.tensor(outputs.to_numpy(dtypefloat)) print(特征张量X) print(X) print(目标张量y) print(y)4.2 逐步拆解每个关键动作的意图data.iloc[:, 0:2]的意思是取所有行的前两列索引0和1data.iloc[:, 2]是取第三列索引2。这里需要理解iloc的切片规则0:2包含索引0但不包含索引2所以实际取到的是第0列和第1列。这是pandas里非常常见的操作方式在实际写代码时我会额外确认一次列的顺序对不对避免把特征列和目标列搞混。fillna(inputs.mean(numeric_onlyTrue))这一行代码背后有一个重要细节mean()默认计算的是所有数值列的平均值返回一个Series对象fillna()拿到这个Series之后会自动按照列名对齐填充。如果某些列不是数值类型mean()会直接忽略它们但如果DataFrame里还有非数值列pandas在旧版本里可能会悄悄把非数值列跳过新版就会明确警告甚至需要指定numeric_onlyTrue。课程里的代码贴出了这个参数说明作者踩过这个坑。我在处理真实项目时也遇到过这个问题pandas的版本变更可能会影响行为代码里明确写出参数会减少很多不必要的麻烦。pd.get_dummies(inputs, dummy_naTrue)是one-hot编码的核心。默认情况下get_dummies只对数据类型是object或category的列做编码数值列会原样保留。如果不加dummy_naTrue缺失值不会被单独编码成一列加上之后系统会额外生成一列标记原值是否为NaN。这样的好处是模型可以学习“缺失与否”本身带来的规律。从这个细节你可以看到课程在数据预处理上是考虑了实际场景的不是随便写了个最小示例。把pandas的DataFrame转成PyTorch张量中间需要一个中转先调用to_numpy(dtypefloat)转成NumPy的浮点数组再调用torch.tensor()转成张量。这里要注意dtypefloat如果不指定pandas可能会保留整数类型但DataFrame里存在缺失值的时候pandas会直接给你一个浮点数类型因为NaN只能在浮点数里表示。4.3 一个完整的模拟数据实操记录为了让你更好理解这套流程我用一个稍真实一些的例子来走一遍。假设手头有一份房屋数据包含面积、房龄、所在区域、总价四列其中面积和总价各有少量缺失值区域是类别列市区/郊区/远郊代码如下import pandas as pd import torch data pd.DataFrame({ area: [50, 80, None, 120, 95], age: [1, 5, 15, None, 3], region: [市区, 郊区, 远郊, 市区, 郊区], price: [300, 480, None, 720, 560] }) print(data) # 划分特征和目标 X_data data.drop(columns[price]) y_data data[price] # 数值列均值填充 X_data X_data.fillna(X_data.mean(numeric_onlyTrue)) # 类别列one-hot编码 X_data pd.get_dummies(X_data, dummy_naTrue) X_tensor torch.tensor(X_data.to_numpy(dtypefloat)) y_tensor torch.tensor(y_data.to_numpy(dtypefloat)) print(X_tensor) print(y_tensor)在这个例子里可以看到几个有趣的细节。面积列有一个缺失值填充之后用的是其余四个非缺失面积的平均值。房龄列也有一个缺失值同样处理。对象区域列做了one-hot之后会生成三列市区/郊区/远郊再加上dummy_na又生成了一列但这个例子里区域列本身没有空值所以dummy_na那一列全为0这种情况下它其实没有贡献多少信息但保留了也无妨。价格列没有做填充因为它是我们要预测的目标。测试阶段如果目标有明显异常要谨慎处理不能直接把它当作普通特征清洗掉。真实项目中如果预测目标的缺失比例过高要考虑是不是数据采集本身有问题如果只是个别缺失直接删除这些样本可能更安全因为预测目标完全是靠标签学出来的不能拿均值去糊弄它。4.4 训练集和测试集统一处理贯穿全局课程里没有显式讨论训练集和测试集的拆分因为核心是预处理流程本身。但实际做深度学习项目时必须注意一个问题预处理规则要从训练集上计算然后应用到测试集上这样才能防止数据泄露。数据泄露举个具体的例子如果先对整个数据集做均值填充、计算归一化参数再划分训练集和测试集那么测试集的信息就已经“泄露”进了训练流程。模型在训练时看到的均值、最小最大值其实是包含测试样本信息的这相当于提前偷看了答案会导致评估结果虚高。在真实项目里模型上线后表现远不如验证集就是这个原因造成的。正确的做法是先划分训练集和测试集然后只在训练集上计算填充值均值、编码规则类别列表、归一化参数min和max或mean和std再把这些规则原封不动地用到测试集上。具体到代码层面比如均值填充需要先保存训练集的均值再分别对训练集和测试集做填充one-hot编码要保证训练集和测试集有完全相同的列归一化的min、max也要用训练集的值来计算。课程里的代码是面向教学场景的简化版本逻辑上是内核正确的但你在自己的项目里一定要加上“先切分再统一处理”的思路。5. 常见问题与排查技巧实录5.1 数据读取阶段的高频报错我经常会收到初学者的问题其中最多的就是文件读取失败。FileNotFoundError大概率是工作目录不对或者文件路径写错了。排查思路很简单先打印当前工作目录os.getcwd()确认文件应该放在哪里或者直接使用绝对路径。还有一种情况是用pandas读取CSV时遇到编码问题如果文件是从Excel导出的可能默认编码不是UTF-8需要在read_csv()里指定encodinggbk或encodingutf-8取决于文件实际格式。遇到ParserError通常说明CSV文件里某个字段包含了未转义的引号或分隔符可以尝试指定quoting参数或换一种分隔符方式。5.2 pandas编码过程中的低版本兼容问题get_dummies这个函数本身很稳定但如果你遇到“所有列都变成了一堆0和1数值列全没了”的情况很可能是DataFrame里的数值列被意外地转成了object类型也就是字符串类型。排查方法是打印df.dtypes看每一列到底是什么类型。此外pandas不同版本对fillna(inputs.mean())的行为有细微差异旧版本会自动忽略非数值列新版本则会明确要求你指定numeric_onlyTrue遇到报错不要慌照着提示加上参数就行。5.3 张量转换时的类型陷阱to_numpy()之后经常出现一个问题DataFrame里的缺失值填充完了但某列还是object类型转出来的NumPy数组也是字符串类型再交给torch.tensor()就直接报错。这个问题的根源是类型不够干净而不是张量转换写错了。解决办法是在to_numpy()时显式指定dtypefloat强制所有数据都转成浮点数。如果转换时报“无法将字符串转换为浮点数”说明你的数据里还有漏网的非数值内容需要返回上一步检查。5.4 一个容易忽略的列用户ID类特征真实数据里经常会遇到用户ID、样本编号这类列。它们看起来是数字但本质上只是标识符完全没有数值含义。如果直接把ID列当作特征喂给模型模型会非常“聪明”地记住每个ID对应的答案导致训练集分数极高测试集一塌糊涂这叫过拟合。处理方式是任何ID类、日期类、文本描述类的列在预处理阶段就要确定是否保留。日期列可以做特征工程提取年、月、星期几等有用信息但原始字符串最好不要直接进模型。ID列则通常会直接删除除非你有充分理由相信ID本身携带了某种先后顺序或分组信息。5.5 处理列名本身的一个小坑在真实项目里列名可能会包含空格、特殊符号甚至是以数字开头。这些列名在用data.列名这种属性访问方式时很可能会报错因为pandas只允许合法的Python标识符作为属性访问。规避方法是统一使用data[列名]这种方式并且在一开始就把列名清洗一遍比如去掉首尾空格、替换空格为下划线、检查是否有重复列名。这是一个很小但能避免后续大量麻烦的习惯。6. 从课程到实战扩展思路与个人体会课程里的这套流程是一个地基真正到实际项目里会遇到更多复杂情况。比如超大文件怎么分段读取、图像和文本数据的预处理方式完全不同、时间序列数据不能随机打乱等等。但只要你把这一课里的核心逻辑吃透了后面这些场景都可以顺着同一个思路去拆解先观察数据再决定处理策略最后把数据转成模型能吃的格式。我个人在实际操作中最深的体会是数据预处理不是一个“做完一遍就结束”的步骤。你很可能在跑完模型之后发现效果不理想回过头来调整预处理方案比如换一种缺失值填充策略、把数值型特征做一次分箱、合并低频类别然后重新训练。这种循环在真实项目里非常常见。所以建议你在一开始就给数据预处理留出充足的时间不要想着“快速跑通一个基线”而是尽量把数据质量搞扎实这样后续调模型时能省下大量精力。另一个让我印象深刻的是训练集和测试集必须统一处理这件事。我第一次做真实项目的时候因为偷懒对全量数据直接做了归一化再切分结果验证集分数虚高模型上线后效果崩得一塌糊涂。那次之后我养成了一个习惯所有预处理相关的参数只从训练集上计算测试集一律用训练集的参数来转换。这个教训在课程里可能只是几句话但实际踩过一次坑之后才真正理解它的分量。最后推荐一个值得动手做的练习找一份真实数据集不用太大几百行就够了按照这一课的流程完整走一遍然后故意制造几种错误场景比如把数值列改成字符串类型再跑代码看看报错长什么样。这个过程会让你对数据预处理的细节理解得更扎实。这些报错信息以后会经常见到见多了自然就不怕了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。