
开头直接讲这是“动手学深度学习”风格的第一课。很多朋友刚开始接触深度学习第一反应是去背神经网络结构、理解反向传播结果卡在数学公式里出不来。其实真正动手跑第一个模型之前最该先解决的恰恰是这些看起来有点无聊的底层问题数据怎么进来、张量怎么操作、预处理怎么处理脏数据。这一节课就是把地基打好后面模型再花哨数据操作不扎实跑起来处处是坑。我自己带过不少零基础的学员也踩过不少自己当年的坑。这节课的内容简单说就是三件事理解深度学习在做什么学会用张量表示和操作数据掌握从原始数据到干净张量的标准预处理流程。适合完全没有接触过深度学习、或者刚开始安装环境但不知道从哪下手的人。哪怕你现在只想先看懂代码这篇文章也能帮你把最基本的逻辑链路理顺。1. 深度学习入门第一课先搞清楚它在干什么1.1 从术语开始厘清关系“深度学习”这四个字被媒体和各种课程用得有点滥了但本质上它属于机器学习的一个分支核心是“用多层神经网络自动从数据中提取特征”。传统机器学习里做特征工程要靠人的经验比如图像识别要人工设计边缘检测算子深度学习则直接用大量数据“喂”给模型让网络自己学会从像素到语义的分层表达。这不是玄学而是十几年前硬件算力上来之后人们发现“多层大数据GPU”组合起来效果远超预期。这一节课的标题听起来是“介绍”但实际是把“张量操作”和“数据预处理”这两块基础功作为重中之重。为什么因为深度学习模型在训练时计算机内部一切数据都以张量Tensor的形式流动图片是四维张量文本经过向量化后也变成张量标签同样是一个一维或二维张量。不懂张量操作就等于开车不看仪表盘能跑但随时可能爆缸。1.2 为什么第一课不直接讲模型很多入门课程喜欢上来就搭一个全连接网络然后看着loss下降就完事。但这样做的坏处是一旦调参、改数据格式、加载自己的数据集立刻就会暴露基础不牢的问题。我自己碰到过太多学员模型代码跑通了但把真实数据换成自己的CSV文件之后报错一个接一个——shape对不上、dtype是object没法转换、有空值跑不进模型。这些问题全出在数据操作层面。所以要理解这一课的设计逻辑深度学习项目不是“从模型开始”而是“从数据开始”。你得先能熟练地创建张量、切片、变形、拼接、广播再把现实中的表格数据清洗成模型的输入格式。这两块技能熟练之后再去碰网络结构会发现一切顺利得多。2. 张量深度学习里最底层的“积木”2.1 张量到底是什么张量这个词听着吓人但把它拆开看就很简单。0维张量是标量就是一个数1维张量是向量像一排抽屉2维张量是矩阵像一张表格3维张量就相当于把多张表格叠起来像一摞纸。深度学习里面照片就是典型的三维张量高、宽、通道RGB。一个批量batch里有多张图就成了四维张量批量维度在最前面。你可以把张量想象成“装数据的集装箱”或者“乐高积木块”。在深度学习中不管数据多复杂最终都要变成这种矩形的多维数组。为什么强调“矩形”因为GPU的计算高度并行化只有形状规整的数据才能被高效批量处理。这也是深度学习里经常要padding补零的原因——把长短不一的句子统一成一样的长度才能塞进张量参与计算。2.2 张量的三个关键属性用PyTorch这种主流框架操作张量你必须时刻注意三个属性形状shape、数据类型dtype、设备device。形状决定张量是几维的、每维多大数据类型决定每个元素占多少字节是浮点还是整数设备决定数据在CPU内存里还是在GPU显存里。这三个属性一旦有一个不匹配运算就报错。我见过最典型的菜鸟错误是模型在GPU上输入数据却留在CPU上跑一次报一次“Expected all tensors to be on the same device”。还有的人用np.array操作习惯了在torch里面用len()、用Python列表思维写循环性能差得离谱。你得记住张量不是list它是一块连续内存形状信息是它的灵魂。2.3 为什么用GPU、为什么要深度学习框架提供张量深度学习的计算量太庞大了。处理一张256×256的彩色图片数据量就是256×256×3196608个数。如果做矩阵乘法手写循环算的话一次迭代都要等半天。GPU之所以适合深度学习是因为它里面有成千上万个计算核心适合做“一个指令同时处理大量数据”的并行运算。而张量作为统一的数据结构能让GPU司机框架准确调度每一次计算。这也是为什么直接学Python原生的列表来做数据操作不可行列表里存的是对象的引用分散在内存各处GPU没法直接高效地处理这种离散结构。而PyTorch的Tensor底层是连续的内存块配合CUDA可以在显存之间高速搬运和运算。所以第一课反复强调张量本质上是在教你“用GPU能理解的语言”去描述数据。3. 张量数据操作实操要点3.1 创建张量的几种常见方式实操时常用的创建方式有几种直接传入数据、生成全零全一、从已知范围生成等差序列、根据已有张量创建同形状或同形状不同值的张量。我尤其提醒新手生成随机张量时记得设置随机种子不然每次跑出的数据都不一样模型结果无法复现调试起来非常痛苦。比如我演示一段代码import torch # 从列表创建 a torch.tensor([1.0, 2.0, 3.0]) print(a.shape) # torch.Size([3]) print(a.dtype) # torch.float32 # 全零/全一 b torch.zeros(2, 3) # 2行3列 c torch.ones(2, 3) # 等差序列左闭右开 d torch.arange(0, 10, 2) # [0, 2, 4, 6, 8] # 从已有张量生成同形状 e torch.zeros_like(a) # shape和a相同值全为0很多初学者不知道torch.tensor()和torch.Tensor()的区别前者会尽量推断适合的dtype后者常用于创建一个空张量。实际写代码时我推荐显式指定dtype比如torch.tensor(..., dtypetorch.float32)因为后面很多模型要求float32输入一旦混入整数类型梯度计算直接报错。3.2 切片、索引和变形熟练到不用思考张量的切片和Python列表很相似但维度多了之后容易乱。核心心法是[]里用逗号分隔每一维例如x[行, 列, 通道]。多维切片时省略号...可以代表“剩下的所有维度”这是写代码时的大福音。变形操作里最常用的就是reshape。注意reshape和view的区别view要求张量内存连续reshape不要求。在实操中如果报错“view size is not compatible with input tensors size and stride”就换成reshape或者先调用contiguous()。举一个踩坑实例有一次我处理一批形状为(batch, height, width, channel)的图像数据模型要求输入形状是(batch, channel, height, width)两者数据一样但哪个维度排“通道”顺序不同。我当时直接reshape结果图像全部花了——因为reshape只是按顺序把内存重新切块不能完成维度的“换位”。正确操作应该用permute或transpose。# 错误直接reshape # image_batch.reshape(batch, channel, height, width) # 数据顺序错乱 # 正确维度换位 image_batch image_batch.permute(0, 3, 1, 2)这里要理解(batch, height, width, channel)到(batch, channel, height, width)不只是“换标签”而是内存里数据排布顺序的整体调整。reshape是把一条连续数据流按新形状硬划分而transpose/permute是重新安排数据的访问顺序二者语义完全不同。这个坑几乎每个接触图像数据的人都会踩一遍。3.3 广播机制零基础必懂但必小心广播机制是PyTorch和NumPy里最重要的规则之一。它允许形状不匹配的张量在特定规则下进行计算省去手动复制数据的麻烦。规则可以简化为两条从最后一个维度往前比对维度相等或者其中一个维度为1就可以对齐如果一个张量没有某个维度就用1来补位。举个直观的例子一个形状为(3, 1)的张量和一个形状为(1, 4)的张量相加会得到(3, 4)的输出相当于在行和列两个方向分别做了扩展。生活类比就是你有一张3行的价格表和一张4列的折扣率表广播机制自动把每行的价格配到每列的折扣上不需要手动复制。但广播也有让人掉坑的地方比如x torch.tensor([[1, 2, 3], [4, 5, 6]]) # shape (2, 3) y torch.tensor([[1], [2]]) # shape (2, 1) z x y # 结果是 (2, 3) # 但如果y是 (3,) 呢 y2 torch.tensor([10, 20, 30]) # shape (3,) z2 x y2 # 也OKy2被广播成 (2,3)新手容易犯的错误是把形状为(3,)的张量和形状为(2, 3)的张量相加以为会按列广播实际上2,3和3,不能直接广播因为倒数第一维3等于3可以但前面的2没有对应维度此时框架会直接报错而不是人性化地帮你做转置。我的建议是不要凭直觉猜写代码前先用print(x.shape, y.shape)打印确认必要时显式用reshape或unsqueeze把维度补全。4. 数据预处理从原始CSV到干净张量4.1 现实数据有多“脏”很多教程用到的数据集都是别人整理好的下载下来已经是数值、无缺失、直接能进模型。但真实项目中十有八九的数据是让人头疼的CSV文件里夹杂着中文字段、日期格式五花八门、数值列有空缺、分类变量还是字符串、同一个字段有的单位是“万”有的是“元”。这一环节如果处理不好后续无论模型多高级都白搭。数据预处理的目标就一句话把现实世界的表格变成模型能吃下的、形状规整的数值型张量。这一步通常靠pandas做读入和清洗再转成NumPy数组最后转成PyTorch张量。这三个环节的衔接是第一课里最实用的技能。4.2 缺失值处理别只会删行面对缺失值最粗暴的方式是drop掉有缺失的样本。但实际操作中如果数据本来就少或者缺失值集中在某些特征列里删行会让你损失大量有效信息。比较常用的是填充策略数值列可以用该列均值或中位数填充分类列可以用众数填充或者单独补一个“缺失”类别。我补充一下选择填充值的逻辑均值填充适合数据近似正态分布、缺失比例不高的情况中位数填充对离群值更稳健时间序列类的数据还会用前后值填充ffill/bfill。不要盲目看网上代码用什么就用什么先画个分布图大致了解数据长什么样再定填充策略。这一步是数据分析的基本功但在深度学习教程里常常被忽略。4.3 类别特征处理编码不是越复杂越好表格数据里的类别特征比如颜色“红、绿、蓝”没法直接进模型。常见编码方式有三种标签编码LabelEncoder、独热编码One-Hot、嵌入向量Embedding。深度学习里对于分类数不多的类别特征最稳妥的是独热编码但如果类别特别多比如有上万个独热编码会造成维度爆炸这时就需要用嵌入层让模型自己学一个稠密表示。例如用pandas里最简单的pd.get_dummies()就能实现独热编码但要注意它会把所有非数值列全部展开并且会引入“虚拟变量陷阱”之类的问题——如果原来有k个类别独热后会变成k列但这在通常的深度学习模型里不是问题因为模型不做线性回归那种严格的共线性检查。4.4 一个完整的预处理演示我就用一个非常典型的例子演示整个流程假设你有这样一个CSVage,income,gender,label 25,50000,M,1 30,,F,0 35,80000,F,1 ,70000,M,0处理逻辑是读入数据 → 数值列填充均值 → 类别列独热编码 → 分离特征与标签 → 转成浮点张量。import pandas as pd import numpy as np import torch df pd.read_csv(sample.csv) # 数值列填充均值 df[age] df[age].fillna(df[age].mean()) df[income] df[income].fillna(df[income].mean()) # 类别列独热编码 df pd.get_dummies(df, columns[gender], prefixgender) # 分离特征与标签 labels df[label].values.astype(np.float32) features df.drop(label, axis1).values.astype(np.float32) # 转成张量 X torch.tensor(features, dtypetorch.float32) y torch.tensor(labels, dtypetorch.float32) print(X.shape, y.shape)有人可能会问为什么不直接torch.tensor(df.values)因为pandas里如果有字符串列DataFrame的values会变成object类型你直接转成tensor会报错。所以必须先对特征列做数值化再统一转成NumPy的float32数组。还有一点pd.get_dummies生成的列顺序有默认规则如果训练和预测用的是不同批次的CSV要小心列顺序一致性问题否则数据错位模型效果会莫名其妙变差。4.5 归一化和标准化这一步不能省预处理里还有一道关键工序把数值特征缩放到相近的范围。常见做法是Min-Max归一化缩放到0-1区间和Z-score标准化变成均值为0、方差为1的分布。深度学习模型里梯度下降对特征尺度非常敏感如果收入是几十万而年龄是几十收入特征的梯度更新幅度就会碾压年龄特征模型训练不稳定收敛也很慢。我通常用sklearn的StandardScaler或者手写一个均值方差归一化。注意一个容易被忽略的细节统计均值、方差只能用训练集不能用全量数据。因为测试集和真实场景的数据在训练阶段是不可见的如果用全量数据计算均值和方差等于把测试集的信息泄露给了模型评估结果会虚高。5. 常见问题与排查技巧实录5.1 那几个让新手崩溃的报错深度学习的报错信息往往又长又吓人但其实高频问题就那么几类。我总结了一个排查速查表基本上解决掉这些新手期的痛苦能减少一大半报错类型原因解决办法Shape错配两个张量维度对不上打印所有参与计算的shape逐步对照Device不一致一个在CPU一个在GPU输入调用.to(device)模型也调用.to(device)dtype不匹配float和int混用统一用torch.float32必要时显式转换view尺寸不兼容内存不连续或形状逻辑错误改用reshape或先contiguous()numpy和tensor混用两种类型不能直接运算用.numpy()或torch.from_numpy()转换DataLoader线程卡死Windows下num_workers设置问题设置num_workers0调试这些报错本身不可怕可怕的是很多教程没有讲明白背后的规则导致新手只能复制报错去搜索搜一个改一个改完又冒出新问题。我的建议是遇到报错先冷静用两分钟把相关变量的shape和dtype都打印出来通常问题就一目了然了。5.2 张量操作的几个实战忌讳我在这里再多分享几个实操心得都是实际项目里踩过的坑常规文档里不会写这么细。第一不要用Python原生for循环去处理张量的每个元素。刚入门的人最容易写出这种代码遍历每一行、遍历每一列把每个值单独操作后塞回列表。这种做法在张量规模小的时候看不出问题一旦数据量到百万级速度会慢到想砸电脑。正确做法是尽可能用框架提供的向量化运算比如torch.where、torch.clamp、torch.maximum。第二要注意torch.tensor和torch.as_tensor的区别。torch.tensor永远会复制数据torch.as_tensor则尽可能共享内存。如果数据本身是NumPy数组直接用torch.from_numpy更高效因为不会产生不必要的复制。但要注意共享内存意味着对张量修改会影响原NumPy数组调试时容易出怪问题不确定时就用clone()。第三np.random.seed和torch.manual_seed都要设置各管各的。有的人只设置了NumPy的种子结果PyTorch这边每次随机初始化不同跑实验对比时结果不稳定还以为是模型问题。另外如果用了GPU最好加上torch.cuda.manual_seed_all(seed)虽然现在很多框架默认能管理但手动加总没坏处。第四批量数据操作里有个非常容易被忽略的点标签的形状。很多人处理分类问题时标签直接用一维张量模型输出是二维batch、类别数计算损失时一疏忽就形状错配。我建议一开始就把所有标签统一处理成列向量label.reshape(-1, 1)或者干脆放进DataLoader由框架自动处理。5.3 数据预处理的“看不见的坑”预处理部分的坑往往比张量操作更隐蔽。比如读CSV时有些列看起来是数字实际上因为混入了空字符串pandas会把它识别为object类型再比如某个数值列里出现了一个“unknown”整个列都会被当成字符串。我遇到这种问题习惯先执行df.info()看每一列的类型和缺失情况再执行df.describe()看数值分布花两分钟做一次摸底比报错之后再排查节省大量时间。还有日期列的处理很多数据里日期是字符串如果模型不需要时间特征直接删掉即可如果需要要把日期拆成年、月、日以及星期几等数值特征而且有些特征可能是周期性的比如月份12月应该和1月相邻需要做sin/cos变换才能让模型理解“12月之后是1月”这种循环关系。这些虽然超出了“第一课”的范畴但属于迟早要用的进阶预处理。最后说一个真实的项目经历当时做一个人口预测项目原始数据里有多个年份的表格年份跨度很大中间不少年份缺失。我一开始图省事把所有表格横向拼接结果张量形状怎么都对不上。后来冷静下来先按年份排序统一列名再逐年填充缺失值最后才转成张量。整个过程下来真正调模型的时间只占三成剩下七成都在清洗数据和对齐格式。这也是深度学习项目里非常普遍的比例——不是模型不够强而是数据操作不够扎实。第一课的内容到这里基本覆盖了从深度学习的定位到张量的概念和操作再到数据预处理的完整流程。这些内容看起来琐碎但它们决定了后续每一行模型代码能不能顺畅跑起来。如果你现在刚起步建议打开Jupyter Notebook把文中的代码亲手敲一遍打印每个步骤的shape体会一下从Python列表到NumPy再到PyTorch张量的数据流转过程。敲完这一步再进入下一课“线性回归”或者“神经网络”你会明显感觉到轻松很多。