资讯详情

资讯详情

深度学习入门:数据预处理与张量线性代数实战指南

1. 为什么第二章节值得反复读《动手学深度学习》这本书我完整啃过两遍如果说后面那些CNN、Transformer、优化算法是高楼大厦那第二章就是整栋楼的地基和承重墙。很多初学者急着去看卷积、看注意力机制结果反手就被维度不匹配、数据没清洗、loss变成NaN这些问题按在地上摩擦回头再看才发现问题全出在第二章没吃透。这一章用一句话概括就是教你怎么把真实世界里的脏数据变成深度学习模型能消化的干净张量同时把线性代数从数学课本翻译成代码直觉。具体来说它解决三个实际问题第一数据处理环节怎么做包括读CSV、处理缺失值、把类别特征变成数值第二张量Tensor作为深度学习的基本数据结构它的创建、索引、运算规则到底是什么第三从标量到向量再到矩阵这些线性代数概念在PyTorch里对应的API长什么样广播机制又是怎么回事。我建议的定位不是“读一遍就过”而是把它当成工具手册后面写模型遇到数据问题翻回来查预处理遇到维度报错翻回来查矩阵运算遇到广播出问题翻回来看这一章的图解。这本书第二章的代码量不多但每段代码背后都是一类高频实战场景的浓缩。2. 数据预处理先学会给模型“喂饭”2.1 为什么数据预处理这么重要我见过不少新手拿着公开数据集直接塞进模型结果损失不收敛、准确率上不去第一反应是调模型结构调了半天毫无进展。后来把数据拉出来一看要么有大量缺失值要么类别特征直接当成数字用了要么数值范围差的十万八千里。模型吃得不好再好的网络结构也白搭。深度学习模型本质上是一个大规模的数值计算系统它对输入数据有几个硬性要求必须是数值型张量、形状必须固定、数值范围不能太离谱、缺失值必须处理。原始数据很少天然满足这些条件所以预处理不是“可选项”而是每次训练前的必做项。书中用的工具是pandas我实际工作中也是这么做的。pandas擅长处理表格型数据而且接口简单从读取文件到清洗再到转换一条流水线下来非常顺。核心流程固定为四步读数据、清洗缺失值和异常值、编码类别特征转数值、转换转成张量格式。2.2 pandas读写数据用代码模拟真实场景书中第二章开头构造了一个简单的CSV文件这个例子看着简单我建议你亲手敲一遍因为后面的数据预处理技巧全是在这个基础上叠加的。我自己在操作时习惯把文件写入和读取分开方便查看中间状态。import os import pandas as pd # 构造一个带缺失值和类别特征的数据表 data { name: [Alice, Bob, Charlie, David], age: [25, 30, None, 35], city: [Beijing, Shanghai, None, Guangzhou], score: [88.5, 73.0, 92.0, 67.5] } df pd.DataFrame(data) # 写入CSV os.makedirs(os.path.join(.., data), exist_okTrue) df.to_csv(../data/student.csv, indexFalse) # 读回内存 df_read pd.read_csv(../data/student.csv) print(df_read)一个小提示to_csv里的indexFalse很重要如果漏了CSV里会多出一列无意义的索引等会儿读回来处理时容易踩坑。我一开始就犯过这个错误数据里凭空多了一列“Unnamed: 0”排查了半天才发现是索引被写进去了。读取的时候pandas会自动把空单元格识别成NaN这是它的默认行为。但也正因为如此很多人第一次看到NaN时不知道该怎么处理这就引出下一节的内容。2.3 缺失值处理三种策略的取舍第二章介绍了核心思路对NaN所在的列做插值或者删除。我实际用下来总结出三个策略按适用场景排列如下。策略一直接删除含缺失值的行或列。用dropna()实现。适用于缺失比例很低比如少于5%或者整列基本全是缺失值的情况。缺点是数据量会减少训练样本少的时候要谨慎。策略二用均值或中位数填充。这就是书里用fillna(df[age].mean())的做法。适用于数值型特征数据分布比较均匀时用均值有较多离群值时用中位数更稳。策略三用前后值填充或者插值。时间序列数据里常见pandas的ffill()、bfill()或interpolate()都能应付。深度学习里图像数据通常用0填充或者均值填充文本数据则会用一个特殊token表示缺失。书中的写法非常简洁但要注意一个细节fillna默认返回新对象不会就地修改原DataFrame。如果你想直接改要么赋值给原变量要么加inplaceTrue我个人习惯用赋值方式因为可读性更好。# 用均值填充数值列 df_read[age] df_read[age].fillna(df_read[age].mean()) # 用特殊占位符填充类别列 df_read[city] df_read[city].fillna(Unknown)2.4 类别特征怎么变成数值独热编码实战分类特征比如城市、颜色、性别是深度学习模型无法直接处理的因为模型只认数值而且分类值之间没有天然的“大小关系”。这里最常见的错误是直接给类别标数字比如“北京0上海1广州2”模型会误以为广州大于北京这在语义上完全错了。第二章用的是get_dummies这个方法会把每个类别拆成一列用0和1表示是否存在。我在多分类特征上实测下来独热编码的效果非常稳定。但要注意如果某个类别特征的可能性特别多比如用户ID独热编码会造成维度爆炸。这种情况下我会改用Embedding或者先做哈希分桶这些都是后话了。# 独热编码 df_encoded pd.get_dummies(df_read, columns[city]) print(df_encoded)输出会变成每行一个样本、每列一个特征的标准表格。这时候数据才勉强能喂给模型。2.5 从DataFrame到Tensor数据管线的最后一步处理完的DataFrame还是一个Python对象深度学习框架不认。第二步要把pandas数据转成NumPy数组再转成PyTorch张量。这一步我踩过一个坑默认转出来的是float64但PyTorch模型的默认参数是float32直接喂进去会报数据类型不匹配的错。import torch import numpy as np # 提取数值部分转成numpy数组再转成tensor data_np df_encoded.to_numpy(dtypenp.float32) tensor_data torch.tensor(data_np) print(tensor_data.dtype) # torch.float32这里有个容易被忽略的问题如果DataFrame里还有字符串列没处理干净to_numpy会把整列变成 object 类型转张量时直接报错。所以预处理流程中每完成一步就打印一遍dtypes和shape是好习惯能帮你快速定位问题出在哪一环。3. 线性代数把数学翻译成代码直觉3.1 标量、向量、矩阵和张量先搞清楚四个概念第二章的线性代数部分从四个基本概念讲起。我最初觉得这部分太基础了甚至想跳过后来发现很多人包括我自己对“张量”这个概念的理解是模糊的。这里用大白话再捋一遍标量一个数比如年龄25。在PyTorch里是0维张量。向量一串数比如某个样本的所有特征。是1维张量。矩阵一个二维表格比如多个样本的多个特征。是2维张量。张量任意维度数组的通称0维、1维、2维都是它。N维数组在深度学习框架里统一叫张量。PyTorch里创建它们的语法很统一只是torch.tensor传入的列表嵌套层数不同import torch # 标量 scalar torch.tensor(3.14) # 向量 vector torch.tensor([1.0, 2.0, 3.0]) # 矩阵 matrix torch.tensor([[1.0, 2.0], [3.0, 4.0]]) # 三维张量 tensor_3d torch.zeros((2, 3, 4))我自己的经验是维度概念一定要建立在“形状”上不要用“二维、三维”这种模糊说法。每个张量都有.shape属性打印出来是一串数字理解这串数字的含义比背定义有用得多。3.2 张量和NumPy的区别为什么深度学习用张量如果你已经熟悉NumPy会觉得PyTorch的张量API跟它很像。确实很多操作几乎一模一样。但两者有一个关键区别张量支持自动求导。在训练模型时我们需要计算损失函数对每个参数的梯度PyTorch的张量会在计算过程中自动记录计算图然后通过反向传播求出梯度。这个能力是深度学习的基石。另一个区别是设备管理。张量可以显式地放到GPU上用tensor.cuda()或tensor.to(cuda)就可以。NumPy数组只能在CPU上操作。所以理解张量不只要理解它“存数据”的功能还要理解它跟计算图、GPU加速之间的关系。3.3 逐元素运算、消除和广播三个高频操作这一节是第二章的重头戏。首先是逐元素运算就是两个形状相同的张量对应位置上的元素做加减乘除。代码很直白但工程意义很大深度学习中大量操作比如激活函数、损失计算本质上是逐元素运算。然后是消除也就是sum()、mean()、max()这类操作。书里特别强调了一个方向问题沿哪个轴消除。我最初总是分不清dim0是沿行还是沿列。后来总结了一个口诀dim等于几就消除第几个维度。比如形状为(3, 4)的矩阵dim0会消除第0维也就是行数3得到形状(4,)的结果dim1消除列数4得到(3,)。查阅torch.sum文档确认一下这个理解完全没有问题。这个规则在处理批量数据时非常重要比如一批(batch_size, features)的张量按特征维度归一化就需要dim0每个特征在所有样本上统计按样本维度操作则需要dim1每个样本自己统计。3.4 广播机制隐形的维度复制广播是很多新手写代码时最容易懵的地方。书中用了一个加法示例形状(3, 1)的矩阵加形状(1, 4)的矩阵结果自动变成(3, 4)。这个机制跟NumPy一脉相承当两个张量形状不完全一致时PyTorch会在满足条件的维度上自动“复制”数据使形状匹配。我理解的广播规则可以简化为三步从尾部维度开始向前比较每个维度上如果两个张量大小相同或者其中一个为1或者其中一个缺失就能广播不能满足就报错。a torch.ones((3, 1)) # 3行1列 b torch.ones((1, 4)) # 1行4列 c a b # 结果形状 (3, 4)这个机制的实用价值太大了。常见场景是给一批数据统一加偏置不需要手动复制偏置向量直接广播就行。但也是个大坑因为广播太“智能”形状明明不匹配的矩阵经常能“蒙混过关”导致模型计算结果跟你预期完全不一样却不报错。这种情况比报错还难排查。3.5 点积和矩阵乘法维度的舞蹈书中从点积讲到矩阵乘法。点积是两个等长向量对应元素相乘再求和几何意义是一个向量在另一个向量方向上的投影长度。矩阵乘法则是深度学习中最核心的运算全连接层、注意力机制、卷积操作本质上全是矩阵乘法。矩阵乘法有一个维度铁律A的行数任意A的列数必须等于B的行数结果的形状是A的行数乘B的列数。写代码时我建议先手算清楚形状再写实现别依赖debug试错。PyTorch里的矩阵乘法常见三种写法torch.mm、torch.bmm和运算符。其中最直观推荐直接用。要注意mm只能处理2维矩阵处理3维批量数据时需要bmm或者直接用。3.6 范数衡量向量大小的尺子范数用来衡量向量的大小。书中重点讲了L2范数和L1范数。L2范数是各元素平方和的平方根也就是向量的欧几里得长度L1范数是各元素绝对值之和。范数在深度学习中用处很大。最典型的是正则化L2正则化让权重尽量小防止过拟合L1正则化让权重变稀疏。另一个场景是梯度裁剪当梯度范数过大时按比例缩放防止梯度爆炸。理解了范数的概念后面看权重衰减、梯度裁剪这些优化技巧时会觉得非常自然。import torch vec torch.tensor([3.0, 4.0]) l2_norm torch.norm(vec) # 5.04. 实操数据预处理到矩阵运算的完整管线4.1 场景设计从零构造一个结构化数据集这一节我把它拆成一个完整的实操案例。假设我们要处理一个某地区房屋销售数据包含面积、房龄、卧室数量、是否配备电梯、成交价。这个数据集混合了数值特征、类别特征和缺失值足够覆盖第二章所有知识点。import pandas as pd import torch data { area: [85, 120, None, 66, 98, 140], age: [12, 5, 20, None, 8, 3], bedrooms: [2, 3, 2, 1, 3, 4], elevator: [Y, Y, N, N, Y, Y], price: [520, 780, 430, 310, 690, 980] } df pd.DataFrame(data) df.to_csv(../data/house.csv, indexFalse)现实世界的数据比这个乱得多但处理逻辑是完全一样的。4.2 预处理执行缺失值填充和类别编码对数值列area和age我用中位数填充因为房价数据受离群值影响大中位数比均值更稳健。同时检查每列缺失情况。# 查看缺失统计 print(df.isnull().sum()) # 用中位数填充数值列 df[area] df[area].fillna(df[area].median()) df[age] df[age].fillna(df[age].median()) # 类别列填充占位符 df[elevator] df[elevator].fillna(Missing) # 独热编码电梯列 df pd.get_dummies(df, columns[elevator]) print(df.head())这里有个小细节独热编码后列名会变成elevator_N和elevator_Y很多人在后续代码里引用列名时容易写错。我建议编码完立刻打印df.columns看一眼别凭记忆猜。4.3 转成张量并用线性代数运算做简单预测现在把DataFrame转成张量我只取特征列price作为标签暂时不放进去。import numpy as np features df[[area, age, bedrooms, elevator_N, elevator_Y]] labels df[price] X torch.tensor(features.to_numpy(dtypenp.float32)) y torch.tensor(labels.to_numpy(dtypenp.float32)) print(X.shape) # (6, 5) print(y.shape) # (6,)接下来我随便初始化一组权重和偏置用矩阵乘法做个线性预测演示一下“前向传播”的雏形。w torch.ones(X.shape[1], dtypetorch.float32) # 权重向量长度等于特征数 b torch.tensor(10.0) # 批量预测一次矩阵乘加广播 y_pred X w b print(y_pred)这一步就能体会到线性代数的工程力量了6个样本的预测一行X w b就完成了。这里的广播机制发挥了作用X w得到长度6的向量加标量b时自动广播到每个元素上。后面学的什么全连接层、多层感知机本质就是在这个操作外面套了更多层和更多非线性激活函数。4.4 数据标准化给所有特征一个公平的起跑线如果仔细观察原始数据面积是几十到一百多卧室数是1到4差距很大。这种尺度不一致会让模型训练时梯度更新不均匀收敛速度非常慢。所以预处理最后一步通常是标准化让每个特征的均值约等于0标准差约等于1。# 手动标准化 mean X.mean(dim0, keepdimTrue) std X.std(dim0, keepdimTrue) X_normalized (X - mean) / std print(X_normalized.mean(dim0)) # 接近0 print(X_normalized.std(dim0)) # 接近1这里的keepdimTrue也很关键。如果没有它X.mean(dim0)的形状是(5,)广播时虽然也能对上但语义不清晰容易埋坑。保持维度可以让我们清楚地看到均值向量的形状就是特征数跟原张量形状对齐。标准化之后再做矩阵运算数值稳定性会好很多。深度学习里输入数据的尺度问题几乎每次都影响训练效果我现在的习惯是只要有连续数值特征就默认标准化。5. 常见问题与排查技巧实录5.1 维度不匹配报错先把形状写在纸上我见过最多的报错是mat1 and mat2 shapes cannot be multiplied。解决思路很简单在写矩阵乘法前先打印参与运算的张量的.shape手动核对维度是否满足规则。print(A.shape, B.shape) C A B我自己犯过的错误把(batch, features)的数据跟(features, features)的权重相乘结果写反成weight x报错后一看形状就明白了。内存里自己记的维度常常是错的以代码输出的shape为准。5.2 广播机制导致的“静默错误”广播的问题是它不报错却可能在语义上做错。最典型的是本意是让两个形状相同的矩阵逐元素相加结果因为一个维度是1广播后“错位相加”了。排查这种问题最有效的方法是看结果形状是否符合预期。如果在某个环节后张量形状“意外”变了那多半是广播在起作用。我建议在关键中间步骤加上形状断点assert y_pred.shape y.shape, fShape mismatch: {y_pred.shape} vs {y.shape}5.3 缺失值没处理干净导致NaN扩散如果训练中损失出现NaN第一个怀疑对象永远是输入数据。用一个torch.isnan(x).any()就能找出所有NaN位置。if torch.isnan(X_normalized).any(): print(Found NaN in input!)预防措施是在数据管线的每个阶段结束后都检查一次NaN。宁可多检查几次也别等模型训到一半才开始怀疑人生。5.4 数据类型不匹配PyTorch对数据类型比较严格float32和float64之间常因为混用报错。我的习惯是所有输入张量统一用torch.float32模型默认精度所有整数标签用torch.longCrossEntropyLoss要求。如果报类型错误直接看报错信息里写的expected和actual一目了然。5.5 axis/dim方向永远弄反怎么办这是一个经典难点。我的实用方案是不管有多少维度先构造一个形状很小的张量比如(2, 3)然后在每个可能的dim值上做一次消除操作打印结果形状。花一分钟跑个测试比在那凭空想半天强得多。test torch.arange(6).reshape(2, 3) print(test.sum(dim0).shape) # (3,) print(test.sum(dim1).shape) # (2,)配合前面说的“dim几就消除第几维”的规律多试几次基本就记住了。6. 一些过来人的学习建议学这一章的时候最容易犯的毛病是“看懂了就划走”。代码确实短API也确实简单但真正内化需要的是动手做变形。我的做法是每学一个操作就手动构造一个非规则的张量尝试不同的dim值、不同的形状组合看结果跟预期是否一致。这个过程比“读十遍书”管用得多。数据预处理和线性代数之间有一条暗线预处理产出的干净张量正是线性代数运算的输入而线性代数运算的效率和正确性又反过来依赖预处理的规范性。第二章把这两件事放在一起讲其实是刻意为之。后面遇到的每个模型都逃不开这两套基本功。如果你也是刚入门建议按这样的顺序复习先只看数据预处理部分把自己手头的一个数据集完整处理成张量再看线性代数部分把处理好的张量做一遍。两件事连起来做一遍整个人就会有一种“通了”的感觉。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →