资讯详情

资讯详情

深度学习入门:鱼书代码实战,用Python与NumPy跑通MNIST识别

简介经典日系教材《深度学习入门基于Python的理论与实现》即“鱼书”的全章节配套源码面向零基础起步的深度学习初学者和想对照原著动手复现实验的开发者覆盖从感知机、神经网络到卷积网络与深度网络的完整学习链路。压缩包共67个文件以59个Python脚本为核心辅以3个pkl预训练参数、2个Markdown说明文档、2张图像示例及gitignore配置整体仅4.44MB轻量易用。目前已有2534人浏览学习。代码按原书章节ch01至ch08清晰分目录包含MNIST手写数字识别、梯度下降与误差反向传播、权重初始化对比、Batch Norm、Dropout、多种优化器比较、简单卷积网络与深度卷积网络等实验脚本并附带训练好的预训练参数文件可直接加载运行并可视化中间结果。读者能边读书边跑代码直观理解每章算法的实现细节与调参效果是系统掌握深度学习原理和Python底层实现的高质量辅助资源。1. 深度学习入门为什么很多人卡在“看不懂代码”上很多人学深度学习不是被数学吓退的而是卡在第一步书翻开了Python也装好了结果看到的代码不是一行行能照着敲的完整例子而是一堆半截代码。这种感觉就像学游泳教练在岸上把动作讲得头头是道你一下水还是呛水。鱼书《深度学习入门基于Python的理论与实现》的配套代码就是那块能让你先下水扑腾的浮板。这本书的特别之处在于它从头到尾不用 TensorFlow、PyTorch 这类现成框架而是用 Python 和 NumPy 一行一行把神经网络写出来。配套代码则是把书里每一章的示例都整理成能直接运行的程序从最基础的手写数字识别到两层神经网络、反向传播再到卷积神经网络全部按章节排列好。也就是说你不光是在读理论而是真能亲眼看到电脑从一堆像素里学会认数字的全过程。这份资源最适合两类人一类是 Python 刚入门、想搞懂深度学习到底在干什么的零基础新手另一类是看了不少理论文章但从来没自己跑通过一个训练程序的人。如果你是这两种接下来跟着我从零开始先把第一个例子跑起来。2. 跑起来之前先搞懂鱼书代码在解什么题2.1 深度学习在干嘛先记住“看图猜数字”这一个画面如果你只记住一件事那就记住深度学习处理的最典型问题就是“给一张图片输出一个答案”。鱼书整本书的核心例子是 MNIST 手写数字识别数据是 28×28 像素的灰度图片每张图里写了一个 0 到 9 之间的数字你的任务是让程序看完像素之后说出一句“这张图是 7”。听起来不难但难在“程序怎么知道什么是 7”。人眼看到 7知道它有一条横线加一条斜线机器看到的只是一串数字比如某个像素亮度是 0另一个像素亮度是 255。深度学习要做的是让计算机自己从几万张带答案的例子里总结出一套判断规则。这套规则在鱼书里不是装在某个现成框架里的黑匣子而是用矩阵乘法一步步算出来的。所以你学这份代码时心里始终要有一个画面前面是一个 784 维的像素向量中间经过几层计算最后出来 10 个数字代表“是 0 的概率、是 1 的概率……”。谁最大就预测成谁。后面第三章、第五章的所有代码都是在把这个画面变成一行行能跑的 Python 语句。2.2 鱼书代码包里有什么先认识四个关键位置下载下来的代码不是一堆散文件而是按章节组织的目录。我第一次打开时也发懵后来发现只需要先认准四个位置就能开始。fishbook/ ├── ch01/ # 第一章Python 基础入门先看 ├── ch02/ # 第二章感知机理解神经网络的雏形 ├── ch03/ # 第三章神经网络第一个完整的推理程序在这 │ ├── neuralnet_mnist.py # 用预训练权重做手写数字识别 │ └── sample_weight.pkl # 已经训练好的权重文件 ├── ch04/ # 第四章两层神经网络的实现与训练 ├── ch05/ # 第五章误差反向传播深度学习的关键 ├── ch06/ # 第六章各种优化技巧 ├── ch07/ # 第七章卷积神经网络 CNN ├── ch08/ # 第八章深度学习实战 ├── common/ # 公共函数库各章共用 │ ├── functions.py │ ├── gradient.py │ └── layers.py └── dataset/ └── mnist.py # MNIST 数据集的下载与读取新手最容易犯的错是把 ch03 里的某个文件单独拷出来运行。别这么做因为代码里大量使用了from dataset.mnist import load_mnist这种跨目录导入必须保持整体目录结构。你只需要在命令行里进入fishbook这个根目录然后运行对应章节的文件即可。2.3 零基础环境准备Python 装好、三件套装齐这份代码对环境要求极低不需要 GPU、不需要装深度学习框架只需要 Python 和三个第三方库。但我还是建议你先建一个独立环境这相当于给自己留一颗后悔药环境弄坏了删掉重建就行不会影响系统里其他项目。# 用 conda 创建独立环境命名为 deeplearn指定 Python 3.8 conda create -n deeplearn python3.8 # 激活这个环境 conda activate deeplearn # 安装鱼书代码用到的三个第三方库 pip install numpy matplotlib pillow这是我的血泪经验鱼书代码是几年前写的当时主流的 Python 版本比较老。用 Python 3.8 跑兼容性最好代码基本不用改。装完库之后你可以验证一下环境是否正常在命令行里敲一句python -c import numpy, matplotlib, PIL; print(环境OK)能看到环境OK就说明三件套都装好了。注意不要装一堆乱七八糟的深度学习框架插件入门阶段纯 CPU 加 NumPy 完全够用。软件层面装得越少后面出问题的概率越低。2.4 为什么这份代码不依赖深度学习框架你可能会问既然现在大家都在用框架写神经网络为什么还要学一份老代码答案恰恰是这份代码的价值就在于它“老”老到每一行都自己算。如果你直接上手 PyTorch一个nn.Linear(784, 50)就把一层网络封装好了你根本看不到里面发生了什么。而鱼书代码里同一层是这样写的把输入和权重做np.dot点积再逐元素加上偏置再过一个激活函数。每一步都是显式的数学运算你就知道“哦原来所谓的一层网络本质上就是一次矩阵乘法加一个偏移量”。这对于零基础入门来说特别重要。因为深度学习里的梯度下降、反向传播这些概念用框架一句话就能调用但新手完全不知道背后是什么遇到报错也没法排查。先把这份不用框架的代码跑通相当于给自己补上了地基。之后再学任何框架看到的都是“哦这就是把之前手写的步骤封装起来了”而不是面对一个完全陌生的黑匣子。3. 第一个例子把 MNIST 手写数字识别跑通3.1 第一步先不写代码看看 MNIST 数据长什么样MNIST 是深度学习入门的经典数据集训练集有 6 万张、测试集有 1 万张 28×28 的灰度图片。所谓 28×28就是横向 28 个像素、纵向 28 个像素每个像素的亮度用 0 到 255 的整数表示。0 是全黑255 是全白。我们先跑一小段代码把数据读进来看看它的形状# 从 dataset 目录导入 MNIST 的加载函数 from dataset.mnist import load_mnist # 同时读入训练集和测试集 (x_train, t_train), (x_test, t_test) load_mnist( flattenTrue, # 把 28x28 的图片展平成 784 个像素的一维向量 normalizeFalse # 先不做归一化保持 0-255 的原始亮度值 ) print(x_train.shape) # (60000, 784) 训练集有 6 万张图 print(t_train.shape) # (60000,) 每张图对应的数字标签load_mnist是鱼书代码自带的函数负责下载或读取数据集。flattenTrue表示把二维图片拉成一维向量因为神经网络的输入通常是向量。normalizeFalse暂时不做归一化等会儿推理的时候我们再打开。如果你运行这段代码时卡在下载先别急第四章会专门讲怎么处理。这一步的关键是让你理解所谓“一张图片”在程序里就是一个 784 维的数字数组所谓“标签”就是一个 0 到 9 的整数。深度学习要学的就是从这 784 个数字到那个整数的映射关系。3.2 加载一张图片把它显示出来光看 shape 没有实感我们把第一张图真的显示出来你会第一次亲眼看到“数据长什么样”。import numpy as np from PIL import Image # 取训练集的第一张图 img x_train[0] # 把 784 个像素还原成 28 行 28 列 img img.reshape(28, 28) # 转换成 PIL 图片对象并保存到当前目录 pil_img Image.fromarray(np.uint8(img)) pil_img.save(first_number.png) print(这张图的标签是, t_train[0])这段代码里reshape(28, 28)是把一维数组恢复成二维形状这样我们才能用图片的方式查看它。np.uint8是因为 PIL 库要求像素值是无符号 8 位整数正好对应 0 到 255 的范围。运行之后去当前目录找first_number.png这张图片用看图软件打开它。我第一次看到的时候还挺惊讶的——原来所谓的手写数字数据集就是这种不太工整、但人眼能认出来的数字图片。保存这张图还有一个好处就是后面你换了其他图片输入时能直观对比“程序眼中的输入”和“人眼看到的图”之间的差别。3.3 用预训练权重跑一次推理现在进入正题用已经训练好的权重对测试集做预测。这一步不涉及训练只是把写好的参数代入神经网络计算输出。import pickle import numpy as np from dataset.mnist import load_mnist def sigmoid(x): 把任意数值压缩到 0 到 1 之间 return 1 / (1 np.exp(-x)) def softmax(a): 把一组数值变成概率分布所有值加起来等于 1 c np.max(a) # 减去最大值防止 exp 指数溢出 exp_a np.exp(a - c) return exp_a / np.sum(exp_a) def get_data(): 读取测试集并做归一化把 0-255 缩放到 0-1 (x_train, t_train), (x_test, t_test) load_mnist( normalizeTrue, flattenTrue, one_hot_labelFalse ) return x_test, t_test def init_network(): 从 sample_weight.pkl 读取已训练好的权重和偏置 with open(sample_weight.pkl, rb) as f: network pickle.load(f) return network def predict(network, x): 用神经网络做一次前向传播返回 10 个类别的概率 W1, b1 network[W1], network[b1] W2, b2 network[W2], network[b2] W3, b3 network[W3], network[b3] # 第一层加权求和 偏置 sigmoid 激活 a1 np.dot(x, W1) b1 z1 sigmoid(a1) # 第二层同样操作 a2 np.dot(z1, W2) b2 z2 sigmoid(a2) # 第三层输出层用 softmax 转成概率 a3 np.dot(z2, W3) b3 y softmax(a3) return y这段代码看着长其实每个函数只干一件事。get_data里把normalize设为True是因为归一化能加快计算让每个像素值都落在 0 到 1 之间。init_network用 pickle 读取权重文件这个.pkl文件是别人训练好之后保存下来的我们直接拿来做推理省去了自己训练的时间。注意predict里的矩阵乘法x是 784 维向量W1是 784×50 的矩阵np.dot(x, W1)算出来的结果是 50 维向量再加上偏置b1。这就是第三章里反复强调的“层”的本质。3.4 跑一遍完整验证预测一万张图算准确率有了上面的函数我们就可以对测试集做完整的准确率统计x_test, t_test get_data() network init_network() accuracy_cnt 0 for i in range(len(x_test)): y predict(network, x_test[i]) # 对第 i 张图做预测 p np.argmax(y) # 取概率最大的类别 if p t_test[i]: accuracy_cnt 1 print(准确率:, accuracy_cnt / len(x_test))np.argmax(y)返回概率最大那个位置的索引。如果最大概率在索引 7 处就说明模型认为这张图是数字 7。把预测结果和真实标签t_test[i]比一比相同就累加计数。最终用累计数除以测试集总量得出准确率。我第一次跑出来准确率大约是 0.93也就是 93% 左右。这个成绩并不惊艳但要知道这是用一个只有三层、总共才几万个参数的神经网络实现的而且代码全部是 Python 写的。你亲手跑出了这个数字之后再回头看理论感受会完全不同。最后给你讲一个小优化上面的代码是一张一张处理比较慢。改成一次处理一批图片会快很多这就是鱼书后面反复提到的 batch批概念batch_size 100 accuracy_cnt 0 for i in range(0, len(x_test), batch_size): x_batch x_test[i:ibatch_size] y_batch predict(network, x_batch) p_batch np.argmax(y_batch, axis1) accuracy_cnt np.sum(p_batch t_test[i:ibatch_size]) print(准确率:, accuracy_cnt / len(x_test))区别在于原来每次输入一张 784 维向量现在每次输入 100 张组成的二维数组。NumPy 的矩阵运算在批量处理时效率更高因为底层把循环交给了经过优化的 C 语言库。你先记住这个结论第四章实现训练过程时会反复用到。4. 避坑指南新手跑鱼书代码最容易翻车的 5 个地方4.1 现象提示 No module named dataset现象你运行neuralnet_mnist.py报错ModuleNotFoundError: No module named dataset。原因Python 导入包时会按sys.path里的路径去查找dataset这个目录。如果你直接用编辑器打开了 ch03 目录下的文件或者把文件单独拷到了别的地方Python 就找不到上一级的dataset文件夹。解决保持目录结构从fishbook根目录运行。如果实在要在子目录里运行也可以手动把父目录加入搜索路径import sys, os sys.path.append(os.pardir) # 把当前目录的上一级加入搜索路径os.pardir就是..也就是父目录。加了这一句之后from dataset.mnist import load_mnist就能被找到。我自己的习惯是偷懒永远在根目录下用命令行运行这样任何代码都不会出现路径问题。4.2 现象卡在下载 MNIST 数据集进度条不动现象第一次调用load_mnist时终端一直显示下载状态等了好几分钟没有反应甚至直接报连接错误。原因MNIST 原始数据集托管在国外的服务器上国内访问时速度很不稳定。load_mnist第一次运行会先检查本地有没有缓存文件没有就联网下载。解决最省心的办法是手动下载数据集文件放到dataset目录下再重新运行。load_mnist发现本地存在对应文件后就不会再去联网了。如果你下载下来的文件是压缩包形式保持原始文件名即可代码会自动识别和缓存为.pkl格式。我第一次就是卡在这里后来看到有个说法是“把下载这一步替换成国内镜像源”。具体做法是打开dataset/mnist.py在里面找到原始下载地址那一行替换成可用的镜像地址。改的时候注意只改 URL别动其他逻辑。这一步做完之后所有章节都能顺利读取数据再也不用等待下载。4.3 现象print 出来一长串数字根本看不懂现象你想看看图片数据是什么样的于是print(x_train[0])结果终端刷出一大串 0 和 255 的数字完全看不出所以然。原因x_train[0]本来就是 784 个像素值组成的数组直接打印当然是一串数字人眼很难从 784 个数字里看出图片内容。解决数据分析时少看原始值多看形状和可视化结果。先用print(x_train.shape)确认维度再用上一章的reshape和Image.save把图片保存出来看。如果是在命令行里也可以这样快速预览print(x_train[0].reshape(28, 28))终端会把数字按 28 行 28 列排开虽然很粗糙但能隐约看出数字轮廓。这个习惯很重要以后你处理任何新数据第一件事永远是打印shape而不是打印内容。4.4 现象代码能跑但特别慢等半天不出结果现象第三章的推理还好到了第四章训练时跑了很久进度条还在原地你不知道它是死机了还是正常工作中。原因鱼书代码是纯 Python 实现没有调用 GPU 加速。训练涉及几万次迭代每轮迭代又要跑几百张图片的前向和反向计算CPU 上确实会慢。但这不代表程序死了而是本来就需要时间。解决调试阶段先缩小数据量和训练轮数。第四章训练代码里有iters_num迭代次数这个变量先把它从 10000 改成 1000甚至 200确认整个流程能跑通、loss 值在下降再把轮数加回去。我的习惯是先让程序“能跑”再让程序“跑得快”别一上来就追求完整训练效果。4.5 现象改了参数之后准确率反而下降现象你想试试调参把learning_rate从 0.1 改成 0.5结果损失值乱跳准确率反而不如默认配置。原因调参不是越大越好。学习率太大参数会在最优值附近来回震荡甚至发散学习率太小收敛又慢得让人绝望。隐藏层神经元数量改多了在小训练集上也很容易过拟合。解决调参要一次只动一个变量并且按数量级来试。比如学习率先试 0.1、0.01、0.001 三档哪个 loss 曲线最平滑就用哪个。建议你每次调参都记一个表参数试过的值效果学习率0.1 / 0.01 / 0.0010.1 震荡0.001 太慢0.01 最优隐藏层大小50 / 100 / 200100 精度略高但训练更慢调参在入门阶段看起来像玄学其实就是控制变量加记录日志。你踩过一次坑后面再调任何参数都会习惯性地先看 loss 曲线。5. 读懂核心代码三层神经网络的前向传播是怎么算出来的5.1 先把神经网络想成一条流水线很多入门教程一上来就画复杂的神经网络结构图新手看了头皮发麻。我建议你把三层网络想成一条流水线原料就是一张 784 维像素向量它要经过三道工序。第一道工序把这 784 个像素和 50 组权重做加权求和每组权重相当于一个“特征探测器”有的负责探测横线有的负责探测圆弧。第二道工序把第一层的 50 个输出再经过 100 组权重组合成更抽象的特征。第三道工序把上一层的 100 个结果映射成 10 个数字每个数字代表“这是几”的概率。每一道工序做的事情都一样加权求和、加偏置、过激活函数。差别只在于输入输出的维度不同。理解了这条流水线再看代码就不会乱了。5.2 逐行拆解 predict 函数我们回到第三章的predict把它当成解剖对象。假设输入x是一张 784 维的图片def predict(network, x): # 先去取出每一层的权重 W 和偏置 b W1, b1 network[W1], network[b1] W2, b2 network[W2], network[b2] W3, b3 network[W3], network[b3] # 第一层矩阵乘法 偏置 a1 np.dot(x, W1) b1 z1 sigmoid(a1) # 第二层重复同样的操作 a2 np.dot(z1, W2) b2 z2 sigmoid(a2) # 第三层输出层换成 softmax 得到概率 a3 np.dot(z2, W3) b3 y softmax(a3) return ynp.dot(x, W1)就是矩阵乘法。x的形状是(784,)W1的形状是(784, 50)结果就是(50,)的一维数组代表这一层 50 个节点的得分。加上b1这个 50 维的偏置向量后每个节点的分数再传入sigmoid压缩到 0 到 1 之间作为这一层最终的输出。第二层同理输入从 784 变成了 50权重形状是(50, 100)输出是 100 维。第三层的权重形状是(100, 10)输出是 10 维。你可以这样记每一层的权重形状永远是“上一层的输出维度 × 这一层的节点数”。层权重形状偏置形状输出形状输入→第1层(784, 50)(50,)(50,)第1层→第2层(50, 100)(100,)(100,)第2层→输出层(100, 10)(10,)(10,)这个表你对照代码看一遍整段predict就再也没有神秘感了。它本质上就是三次乘法和加法中间穿插两个激活函数。5.3 sigmoid、ReLU、softmax三个函数各管一件事鱼书第三章里出现频率最高的三个函数新手容易搞混它们的使用场景。我把它们拆开讲清楚sigmoid的公式是1 / (1 exp(-x))作用是压缩到 0 到 1 之间。它用于隐藏层特点是非常平滑但有个缺点当输入绝对值很大时输出几乎贴到 0 或 1梯度会变得很小这就是所谓的梯度消失问题。鱼书第三章用它是因为它好理解到第五章之后你会看到更多代码改用 ReLU。ReLU的公式是max(0, x)简单粗暴负数一律变成 0正数保持不变。它训练速度快因为没有 sigmoid 那种饱和区间现在大多数网络都在隐藏层用它。鱼书第五章之后sigmoid基本只在理论演示里出现实际代码多用ReLU。softmax只用于最后一层作用是把任意一组实数变成一组加起来等于 1 的概率。注意它有一个细节计算前先减去这批数据里的最大值防止exp指数溢出。这就是为什么softmax函数里第一行是c np.max(a)。三个函数使用位置的区别你现在不用背但要知道一个原则隐藏层用 ReLU 用的多输出层做分类任务用 softmax。这个记忆点足够支撑你读完前六章。5.4 为什么“推理”和“训练”必须分开看学到这里你可能会有一个疑问predict 函数里只是算了一遍加权求和那网络的权重 W 是从哪来的答案是别人用训练过程算出来的。推理前向传播指的是输入数据顺着流水线往前流最终得到预测概率。这一过程不需要标签不需要算损失只需要有权重就能算。程序做的事就是上文的矩阵乘法没什么好神秘的。训练反向传播则是拿着有标签的数据先做一次前向传播得到预测结果然后和真实标签比较算出误差再从输出层往回一层一层地修改权重让下次预测更准。鱼书第四章会手写实现这个训练过程包括梯度下降更新参数。新手最容易犯的错是还没跑通推理就急着看训练代码结果被反向传播里的一堆公式劝退。我建议你严格按这个顺序来先把推理代码反复读到能默写再进入训练部分。前向传播搞明白了反向传播不过就是它的“逆过程”顺带还涉及微积分里的链式法则但鱼书会用手写代码帮你绕开数学推导。6. 下一步学什么从“跑通代码”到“改得动代码”6.1 三个递进练法把代码变成自己的跑通第一个例子只算入门真正学进大脑靠的是动手改。我推荐三个递进的练法难度逐级上升。练法一改超参数。把第四章two_layer_net.py里的学习率改成0.01、0.001隐藏层大小改成100然后重新训练看 loss 曲线的变化。这一步让你亲身体会参数如何影响训练结果。练法二换激活函数。把隐藏层的sigmoid全部替换成ReLU再次训练。你会发现代码只要改一行训练中的 loss 下降趋势就不一样了。这就是为什么后面章节要专门讲激活函数的选择。练法三改网络结构。把两层网络改成三层或者把隐藏层节点从 50 改成 200再对比训练时间和准确率。这个练习能让你理解“深度”到底带来了什么。每改一次都建议把结果记成一张表练习内容改哪里观察什么学习率训练代码里的 learning_rate收敛速度、loss 是否震荡隐藏层大小two_layer_net 里的 hidden_size训练耗时和最终准确率激活函数两层之间的 sigmoid 换成 ReLU训练曲线的平滑程度网络层数在 predict 中多加一层表达能力是否变强6.2 之后的学习路径建议如果你把第三章和第四章都跑通了下一站是第五章“误差反向传播”。这一章是全书最硬核的部分但你不需要背公式只需要跟着代码里的layers.py走一遍正向和反向的计算流程理解每一层保存了什么、更新了什么。之后再进入第七章 CNN你会发现卷积神经网络的整体结构你早就见过了无非是多了一些卷积层和池化层。至于要不要尽快去学 PyTorch、TensorFlow我的建议是暂缓。鱼书这份代码的价值在于脱离框架、看见本质。等你能不看答案就自己写出一层网络的前向传播时再转框架会顺畅得多。那时候你会觉得框架里nn.Linear只是一个封装好的工具而你知道盒子里的齿轮是怎么转的。说起来我从那以后每次拿到一份新代码都强制自己先跑通、再改参数、再读源码这个顺序帮我少踩了一半的坑。希望你也能从这个最小例子开始一步步把鱼书从第三章走到第八章体会到那种“原来我也能让电脑认数字”的掌控感。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →