资讯动态

从KNN到反向传播:CS231n第一份作业的图像分类算法实战解析

发布时间:2026/9/9 20:08:41 来源:尧图企业网站定制
简介面向深度学习初学者的CS231n课程第一份作业完整配套资料涵盖作业要求、参考答案与数据集覆盖计算机视觉中的经典分类任务。内容以Jupyter Notebook交互代码为主搭配Python脚本与Shell整理命令并随包附上cifar-10数据解压后可在Anaconda环境直接运行调测。整套资料共54个文件大小约561.98MB主要包含7个notebook、17个Python源码、Shell脚本、XML配置以及数据压缩包等结构清晰便于对照学习。已有2002人学习浏览适合想要系统攻克CS231n作业、深入理解K近邻、支持向量机、Softmax与两层神经网络原理的读者。除标准答案外还可获得可运行代码与数据预处理脚本帮助循序渐进掌握特征提取、梯度计算、参数更新和交叉验证等关键环节notebook内更配有分步实现与可视化对比便于逐段理解模型训练、决策边界与损失变化是一份能直接动手实践的完整参考。1. 作业整体拆解Assignment1到底在训练什么能力说到cs231n这门课很多人的第一反应都是“理论难、作业更难”尤其是入门的第一份编程作业Assignment1卡住过不知道多少初学者。我陆陆续续带过几批朋友做这份作业发现大家踩的坑其实高度一致而且大部分问题根本不是算法原理没看懂而是对作业的定位理解偏了——你以为自己在写分类器实际上这份作业真正想训练的是你“把数学公式变成可运行代码”的能力以及“调试算法而非调试bug”的思维转换。Assignment1的核心任务是让你从零手写图像分类的经典方法完全不依赖深度学习框架。它包含五个金字塔式的子任务KNN最近邻分类器、SVM多分类器、Softmax分类器、两层神经网络以及最后的图像特征实验。从难度曲线上看前两个是热身第三第四个是主体攻坚最后一个则是大开眼界的“原来还能这样玩”。每一个任务都用同一份CIFAR-10数据集只是特征提取和分类决策方式不同。我当时做这份作业最大的体会是它巧妙地把“机器学习的基础模块”全部串了一遍。数据预处理、权重初始化、损失函数计算、梯度推导与数值校验、参数更新、超参数调优、结果可视化整套标准流水线你都会亲手摸一遍。即使你后面完全用PyTorch或TensorFlow这套底层思维依然贯穿始终。有些人会问既然现在框架这么成熟一行model.fit()就完事为什么还要手写这些老古董我可以用一个很生活化的类比你学做饭直接买料理包当然也能吃饱但只有当你知道“盐放多少、火开多大、肉什么时候下锅”这些基础逻辑你才能在料理包断货时自己做饭也才能在餐厅里吃出哪道菜火候不对。Assignment1逼着你放下料理包回到厨房从头练基本功。适合什么人做这份作业我认为只要满足两个条件就可以直接上手一是对Python和NumPy有最基本的了解知道矩阵怎么算就行二是线性代数和微积分还没完全还给老师。这两条门槛其实不算高我见过不少非科班出身的朋友只要肯踏实看讲义、动手调参两周左右也都能啃完。2. 环境准备与数据处理的三个关键选择在正式进入五个任务之前有两件事值得先花半小时搞定环境搭建和数据集加载。看似不起眼但这两步如果处理不好后面会反复恶心你。2.1 环境搭建没必要在版本上折腾cs231n官方代码是基于Python 2时代的产物但课程后来更新过Python 3版本所以你现在拿到手的框架代码基本都是适配Python 3的。我的建议是直接用Anaconda创建一个干净的环境Python版本3.6到3.9之间都可以关键是NumPy、SciPy和Matplotlib这三个库别装出冲突。我在实操中踩过最大的坑是SciPy版本问题。老版本的cs231n代码里有个scipy.misc.imresize函数用来做图像缩放但这个函数在新版SciPy1.3以上中被移除了。解决办法有两种一是把SciPy锁定在1.2.x版本但这样会跟新系统的其他包产生兼容性麻烦二是干脆找到作业代码中用到imresize的地方直接改成用skimage.transform.resize一行代码的事效果完全一样。我倾向于第二种方案毕竟如今装新版库更省心官方代码早就提供了兼容补丁。2.2 数据加载与归一化别小看这一步的改变CIFAR-10数据集总共6万张32x32的彩色图片分10个类别。作业提供的加载脚本会自动帮你下载数据但这里有个网络问题——官方下载源在国外服务器国内加载时不时就会超时。我当时的处理方法是先手动把cifar-10-batches-py压缩包下载好然后在代码里把CIFAR10.download()那部分改成直接指向本地文件破解起来非常简单找到下载路径参数填进去就行。数据加载之后有个非常关键的预处理操作归一化。图像数据原始的像素值是0到255的整数而绝大多数机器学习算法对数据的尺度非常敏感。以KNN为例它依赖欧氏距离判断相似度如果一个维度的数值范围天然比另一个维度大很多那个大范围维度就会主导距离计算其他维度的信息直接被淹没了。你想想如果用原始像素值做KNN“亮度”这个全局因素就会压过“颜色分布”和“纹理结构”等更细腻的视觉特征分类效果自然打折扣。作业中推荐的做法是X - np.mean(X, axis0)即对每个像素位置减去该位置在所有样本上的均值实现“零中心化”。另外还有一个常用的(X - X.min()) / (X.max() - X.min())可以把数据压到0到1区间这在特征实验部分对HOG等特征尤其有用。我各试过一次实测下来对最终准确率的影响大约在1到3个百分点不大但足以让你在调参时产生“为什么我比别人低几分”的困惑。2.3 数据维度从四维到二维的思维转换CIFAR-10图像的原始形状是(num_samples, 32, 32, 3)分别对应样本数、宽、高、通道数。但是作业里绝大多数算法都期望输入是一个二维矩阵(num_samples, num_features)每一行是一个样本每一列是一个特征。所以你必须做reshape把每个32x32x3的图片展平成3072维的向量。这个操作看似机械但背后有个重要的理解展平操作意味着你抛弃了图像的二维空间结构——相邻像素之间的局部相关性全部丢失了。这对KNN和线性分类器来说是无法弥补的信息损失所以这些方法在CIFAR-10上准确率天花板很低最好的线性分类器也就40%左右。你现在先接受这个设定等做到最后一节特征实验时你会发现通过人工设计特征可以在一定程度上补偿这种结构信息的丢失。这个“先丢失再补偿”的过程恰恰是课程设计者想让你体会的。3. KNN与交叉验证第一个完工的分类器任务一是实现K最近邻分类器。这个算法本身极其简单对每一个测试样本计算它与所有训练样本的距离找出距离最近的K个训练样本让它们投票决定测试样本的类别。但简单并不意味着容易拿分实现时有很多细节值得反复打磨。3.1 三步递进实现从两层循环到向量化作业框架里已经帮你写好了两个函数compute_distances_two_loops和compute_distances_one_loop它们分别是双重循环和单重循环实现。而你需要自己写的是完全向量化的compute_distances_no_loops。我先说前两个函数的定位。two_loops是最直觉的写法外层循环遍历每个测试样本内层循环遍历每个训练样本用np.sqrt(np.sum((X[i] - X[j])**2))计算欧氏距离。这种写法的时间复杂度是O(N_test * N_train * D)在500个测试样本和5000个训练样本下大概要跑好几秒速度慢但逻辑直观。one_loop用广播机制消掉了一层循环速度提升了一个数量级但还不是最优解。真正高效的是no_loops核心是用一个看似魔幻实则精巧的公式拆解欧氏距离(a-b)^2 a^2 b^2 - 2ab展开计算就是对每个测试样本和训练样本的距离矩阵等于测试样本的平方和矩阵加上训练样本的平方和矩阵再减去两倍的点积。用NumPy实现就是一行代码dists np.sqrt( np.sum(X_train**2, axis1)[None, :] np.sum(X_test**2, axis1)[:, None] - 2 * X_test.dot(X_train.T) )这里有个细节必须注意X_test.dot(X_train.T)的结果是(num_test, num_train)而两个平方和向量相加时要通过[None, :]和[:, None]分别扩展成行向量和列向量广播机制会自动把结果扩展成完整矩阵。我第一次写时没加这两个维度控制直接报维度不匹配排查了半天才想起来广播规则。为什么作业非要逼着你写出向量化版本因为这是你第一次切身感受到“在Python里写for循环做矩阵运算是多么低效”。KNN只是个热身到了后面的SVM和神经网络权重更新动辄就是几十上百个epoch一个循环多一点就意味着训练时间多出一大截。向量化思维是深度学习工程师的基本功这份作业从第一题就在帮你练这个。3.2 交叉验证为什么K值不是越大越好实现完距离计算和预测下一步就是用交叉验证选择K值。作业给了你k_choices列表和准确率统计框架你要做的是对每个K值在5折交叉验证下求平均准确率并画图。先说K值选择的原则。K太小模型对噪声敏感分类边界过于曲折容易过拟合K太大模型过于平滑会把不同类别的细节都抹掉导致欠拟合。在CIFAR-10的5000个训练样本下我测下来K7到K10之间准确率最高大约是28%到30%。这个数字看着很低千万别气馁这是KNN在原始像素上的合理水平毕竟每张图只有3072个像素特征信息量确实不足。交叉验证本身倒不复杂本质就是把训练集切成5份每次拿4份训练、1份验证轮流做5次最后对准确率取平均。关键在于你得理解为什么要这样做如果你直接用整个训练集调K值那你的验证结果会偏乐观因为模型已经见过所有数据了交叉验证相当于让你在“没见过的那部分数据”上测试得到的是对泛化性能更诚实的估计。实操中有个小技巧作业默认的训练样本是5000个计算所有测试样本和5000个训练样本的距离矩阵大约是(500, 5000)的浮点矩阵内存占用不大但如果你把训练样本调成全部50000个距离矩阵会膨胀到25亿个元素直接吃光内存。我做实验时尝试过跑一次就卡死了。所以做交叉验证时用5000个训练样本是科学的选择既保证速度又能得到稳定的数值。3.3 KNN的局限性提前埋下的伏笔完成了KNN你不妨做个小实验把测试准确率和随机猜测10%对比一下会发现KNN明显强于瞎猜但也就强那么多。这个结果本身就是一个重要的教学信号——课程想让你亲眼看到“基于原始像素距离”的简单方法在复杂图像分类任务上有多么力不从心。如果你感兴趣还可以把某些被分错的图片打印出来看看。我印象最深的是很多狗和猫的图片互相混分还有一些背景复杂、主体不突出的图片被分到完全不相关的类别。原因也很直接KNN只看像素距离完全不懂“狗有耳朵”“猫有胡子”这种高级语义。这为后面引出“特征提取”和“学习表示”的概念埋下了伏笔。4. SVM与Softmax两类线性分类器的细节打磨第二个和第三个任务分别是实现SVM多分类器和Softmax分类器它们都属于线性分类器核心区别在于损失函数的构造方式不同。作业要求你手写损失函数和梯度并用梯度下降法训练权重矩阵W。4.1 SVM的损失函数与梯度推导逐行拆解SVM的损失函数是多类合页损失Multi-class Hinge Loss直观理解就是对于一张真实类别为y_i的图片模型输出的第y_i类得分应该比其他所有类别的得分都高出至少一个间隔通常设为1。如果没达到这个条件就产生损失。公式是L_i Σ_j≠y_i max(0, s_j - s_{y_i} 1)其中s_j是第j类的得分。实现这个函数不难难的是求梯度。作业给了你指导意见先通过数值梯度校验梯度公式的正确性再写分析梯度。我先说分析梯度怎么推导。设margin_j max(0, s_j - s_{y_i} 1)对每个错误类别j当margin_j 0时说明该类别得分过高需要受到惩罚此时损失对s_j的梯度是1因为损失随s_j增大而增大对s_{y_i}的梯度是-1因为损失随正确类别得分增大而减小。当margin_j 0时梯度为0。正确类别自身的总梯度是所有“被激活”的错误类别数量的负值。用代码表述的话关键在于把mask矩阵用得很干净margins scores - scores[np.arange(N), y][:, None] 1 margins[np.arange(N), y] 0 # 正确类别的margin不参与计算 margins[margins 0] 0 # 把非正数置零 loss np.sum(margins) / N # 计算梯度 margins[margins 0] 1 row_sum np.sum(margins, axis1) margins[np.arange(N), y] -row_sum dW X.T.dot(margins) / N这里有几个细节值得注意。margins[margins 0] 1这一步把激活的margin全部变成1相当于一个指示函数然后再把正确类别的梯度设为所有激活margin数量的负值。最后除以N是求平均梯度。我当时在这个梯度计算上卡了一个晚上反复对不上数值梯度的结果最后发现是忘记把正确类别的margin先置零了——如果不置零正确类别的margin永远是1因为s_j - s_yi 1在jy_i时为1梯度就会多算一坨。调试时强烈建议用作业自带的grad_check.sparse_gradient_check函数做数值校验。数值梯度的原理是用中心差分近似(f(xh) - f(x-h)) / (2h)取h1e-5左右。我第一次跑数值校验时相对误差在1e-5量级说明梯度推导正确后来有一次改了代码忘了同步改梯度相对误差飙到1e-1立刻就能定位到问题。这套“数值梯度校验分析梯度”的流程是深度学习里极其重要的调试手段千万别跳过。4.2 SVM调参delta和学习率的博弈SVM里有个超参数delta即公式中的间隔1作业代码里写的是1但我建议你亲自试几个不同的值比如0.5、1、5、10观察对准确率的影响。原理上delta越大模型对分类边界的“自信度”要求越高正则化压力也越大容易让权重变小、模型更简单delta太小则模型可能过于自信边界紧贴着训练数据泛化能力下降。在CIFAR-10上我实测delta在1左右效果最好差异其实不大。真正影响准确率的是学习率。作业给出的标准流程是先用很小的学习率跑几个epoch观察损失是否下降再逐渐增大。我当时用学习率1e-3跑SVM损失曲线波动得很厉害后来调到5e-3才稳定下降。但学习率也不是越大越好太大时损失会不降反升甚至变成NaN。这里的本质是梯度下降的步长和损失曲面曲率要匹配你可以把它理解为下山时每一步迈多大——迈太大一脚踩空迈太小天黑都下不了山。训练完成后把测试准确率打印出来看看我当时训练集准确率约39%测试集约36%差距很小说明这个模型主要是欠拟合——线性分类器的表达能力有限即使训练集也无法完全记住。这个观察在第4节还会再次出现。4.3 Softmax的数值稳定性一个经典的坑Softmax和SVM的不同在于损失函数用了交叉熵把得分转换成了概率分布。它的表达式是L_i -log( exp(s_{y_i}) / Σ_j exp(s_j) )每个样本的损失是正确类别概率的负对数。如果概率趋近于1损失趋近于0如果概率趋近于0损失趋近于无穷大。这里有一个数值稳定性的经典问题如果得分s_j很大比如100那么exp(100)会直接溢出变成无穷大Python的float最大约1.8e308。解决办法是在计算softmax之前先减去得分最大值s_j - np.max(scores, axis1, keepdimsTrue)这样所有得分归一到最大值不超过0exp的结果就不超过1完全避免溢出。而且数学上减掉最大值并不会改变softmax的结果因为分子分母同时除以了同一个常数。这个技巧在深度学习中无处不在PyTorch的log_softmax内部也是这么实现的提前在这里掌握后面会少踩很多坑。Softmax的梯度公式推导相对SVM要直接一些。设概率向量为p则dL/ds_j p_j - 1[j y_i]也就是每个类别的得分梯度等于该类别预测概率减去一个指示函数只对正确类别减1。用代码写就是probs np.exp(scores - np.max(scores, axis1, keepdimsTrue)) probs / np.sum(probs, axis1, keepdimsTrue) dscores probs.copy() dscores[np.arange(N), y] - 1 dscores / N dW X.T.dot(dscores)这个公式的美妙之处是它和SVM的梯度几乎一模一样的结构区别只在于“指示函数是否被截断”。SVM只有margin0才贡献梯度所以梯度稀疏Softmax所有类别的梯度都非零所以每个样本都会推动所有类别的权重更新。我个人的体会是理解了这两个梯度的异同你对“合页损失 vs 交叉熵”的理解能上一个台阶。4.4 调参与可视化损失曲线的读法最后一个环节是调学习率、正则化系数然后画出损失曲线和分类权重可视化图。权重可视化非常直观每个类别的权重矩阵可以reshape回图像大小可视化后会形成类似“该类别的模板”——比如飞机类别的模板可以看出蓝色的天空和白色的机身轮廓汽车类别的模板则有轮子和车身的影子。这也是我第一次直观感受到“线性分类器到底学到了什么”。损失曲线怎么看核心是看两点一是损失是否稳定下降二是训练集和验证集准确率是否同步上升。如果损失下降很慢大概率是学习率太小如果损失初期下降很快但后期震荡剧烈学习率可能偏大如果训练集准确率高但验证集低就是过拟合需要增大正则化强度。5. 两层神经网络与反向传播从“调库”到“懂原理”第四个任务是实现一个两层神经网络输入层-隐藏层-ReLU激活-输出层。这也是Assignment1最具含金量的部分因为你将第一次手写反向传播。虽然只有两层但所有深度网络的核心机制都已具备。5.1 网络结构与前向传播网络结构可以这样理解输入是3072维的向量展平图像经过第一层线性变换h1 X.dot(W1) b1得到隐藏层我设隐藏单元数为100再过ReLU激活函数relu np.maximum(0, h1)最后经过第二层线性变换scores h1_relu.dot(W2) b2得到10个类别的得分。前向传播不难难的是反向传播。反向传播的本质是链式法则你要计算损失对每个参数的梯度只需要从输出层开始一层一层往回传。作业的two_layer_net.py框架已经把注释写得非常详细你只需要把梯度公式填进去。5.2 反向传播的几个关键步骤梯度流的核心我先给你一个整体的梯度流走向再逐个步骤解释。最后一步得分梯度dscores和Softmax一模一样p - 1[y]不用重新推。第二步由dscores传到W2。scores h1_relu.dot(W2) b2所以dW2 h1_relu.T.dot(dscores)db2 np.sum(dscores, axis0)。第三步把梯度传到ReLU之前。dh1_relu dscores.dot(W2.T)然后经过ReLU的反向传播当输入大于0时梯度原样传递小于等于0时梯度为0。用代码写就是dh1 dh1_relu * (h1 0)。第四步由dh1传到W1。h1 X.dot(W1) b1所以dW1 X.T.dot(dh1)db1 np.sum(dh1, axis0)。这里面最容易出错的是第三步dh1 dh1_relu * (h1 0)中的h1是ReLU之前的线性输出而不是ReLU之后的值。我第一次写反了用ReLU之后的数值做mask结果梯度反复对不上数值校验最后打印中间变量才找到问题。另一个易错点是在第2和第4步中W2和W1的转置方向搞反这会导致维度错误好在NumPy会直接报维度不匹配不会默默算错。5.3 超参数搜索隐藏层大小和学习率的搭配这个部分的final task是训练并调优神经网络。我当时的做法是固定隐藏层100个神经元然后做一组网格搜索学习率在[1e-3, 5e-4, 1e-4]正则化系数在[0.01, 0.1, 1.0]。每组跑2000次迭代做完之后选验证集准确率最高的那组超参数再加大迭代次数跑一轮。实际结果让我印象很深学习率1e-3配上正则化0.1时训练集准确率能到55%验证集约50%明显比SVM的39%高出一截而学习率太大5e-3时训练过程直接发散变成NaN只能从日志里看到损失在第100次迭代时就已经爆到几千。这个“爆炸”的过程也很值得体验一次你会深刻理解学习率为什么是深度学习里最敏感的超参数。还有一个小细节初始化权重时一般用np.random.randn乘以一个小的标准差比如0.1或1/sqrt(n_input)。如果初始权重太大ReLU输出会很大会让后续梯度要么爆炸要么饱和如果太小梯度则可能消失。作业默认用0.1我试过换成1e-3网络学得特别慢损失下降肉眼可见地缓慢。实际操作中你可以在作业代码里做个简单实验把初始化标准差从0.1改成1然后看前向传播后得分是不是全是NaN——这个直观感受比任何理论描述都有说服力。5.4 训练过程的损失曲线读法用训练好的模型跑一遍测试集结果大概在50%左右比SVM高出10个百分点但和现代CNN的90%相比还是差距明显。这个50%的数字不是终点而是里程碑它让你清楚地看到“两层全连接网络人工特征”和“深度卷积网络”之间的差距也让你对未来要学的卷积结构有了一种“我知道它为什么强”的期待。训练过程中顺手画一下损失曲线你会发现训练集损失能降到很低0.3左右但验证集准确率停在一个平台不再上升这是典型的欠拟合信号。因为单隐藏层100个神经元的表达能力确实有限增加隐藏层宽度或者加一个卷积层都会有明显提升但这不是Assignment1要你做的事留个悬念到Assignment2即可。6. 图像特征实验人工特征的最后一舞最后一个小任务是特征实验代码框架里给了HOG方向梯度直方图和色彩直方图两种特征提取器然后让你把两种特征拼接起来再训练一个线性分类器。这个任务在整份作业中最“轻松”但带给我的震撼最大。6.1 HOG与色彩直方图的原理人工特征的智慧HOG特征的核心思想是一张图像里最有辨识力的信息不是单个像素值而是边缘和梯度的方向统计。它的实现流程大致是把图像划分成小的cell比如8x8像素在每一个cell内统计各梯度方向的直方图再归一化得到局部特征。这样做的合理性在于即使两张图的整体亮度不同只要边缘轮廓相似HOG特征就会比较接近。色彩直方图就更简单了对每个颜色通道统计像素值的分布忽略空间位置信息。它捕捉的是“这张图主色调是什么”对于区分蓝色天空为主的飞机和绿色为主的青蛙这类任务非常有效。特征工程的本质是用人的知识去引导模型关注“最重要的信息”压缩掉无关的细节。这在数据量小、模型弱的时代是唯一出路而深度学习的革命性在于它把这个“特征设计”的过程也交给模型自己学习了。但即便在今天理解HOG等传统特征仍然有用——比如在目标检测领域的一些轻量级应用、在数据量极小场景下的基线模型以及作为理解深度学习“到底在学什么”的对比参考。6.2 特征拼接与分类器训练的惊喜结果作业框架已经把特征提取的代码写好了你只需要把特征拿到后训练一个线性分类器即可。我当时直接用之前写好的Softmax分类器去训练HOG色彩直方图特征5000个训练样本下测试准确率直接干到40%左右比原始像素上的SVM36%高而且训练速度快很多。如果把训练样本换成全部49000个再用特征训练准确率能到55%以上。这里有个值得品味的现象在原始像素上模型规模翻倍提升有限但在特征空间里数据量翻倍的效果立竿见影。这说明好的特征更多的数据可以弥补模型表达能力的一部分不足。我当时做完特征实验后心里冒出过一句感慨如果现在有个非常小的数据集你会先用传统特征线性分类器还是直接上深度学习我的建议是都可以试但传统特征方法往往能给你一个低成本、强解释性的基线尤其是在你没有GPU、数据量又只有几千张的情况下传统方法依然是不可忽视的选项。6.3 实验报告与结果对比准确率的阶梯式上升做完五个任务你自己就能画出一张准确率阶梯图KNN约28%、SVM约36%、Softmax约35%、两层神经网络约50%、特征分类器约55%。这个阶梯本身就是cs231n教材编排的暗线从简单到复杂从人工到自动从低准确率到高准确率一步步逼近“为什么需要深度学习”的答案。我想特别提醒一点这部分作业做完后不要把权重可视化图、损失曲线、准确率表格丢在Jupyter里不管花点时间整理成实验报告。格式不用很正式就是记录每步实验用了什么参数、得到什么结果、发现了什么现象。我自己后来找工作面试时这份作业报告里的曲线和可视化图就是我讲解“如何调试模型”的最好素材比单纯说“我会用PyTorch”有说服力得多。7. 高频报错与避坑实录做Assignment1的过程中几乎每个人都会碰到下面这几类问题。我把排查思路和解决方法整理成一张速查表希望能帮你少走弯路。问题现象可能原因排查与解决办法损失变成NaN学习率太大导致梯度爆炸降低学习率核对初始化权重是否过大数值梯度与分析梯度对不上梯度公式推导有误或忘记正确类别的mask操作打印中间变量逐项核对用数值梯度做参照scipy.misc.imresize不存在SciPy版本过新函数被移除改用skimage.transform.resize或降级SciPy距离矩阵形状错误广播机制的维度没控制好检查[None, :]和[:, None]是否正确训练集准确率高但验证集低过拟合增大正则化系数或减少迭代次数/隐藏层大小损失下降太慢学习率太小或权重初始化太小增大学习率或调大初始化标准差CIFAR-10下载失败网络问题访问不了原始地址手动下载并修改加载路径指向本地文件关于调试本身我强烈建议你善用print和matplotlib画图而不要一味依赖IDE断点。数值类算法的问题往往不是“程序崩溃”而是“结果不对”这时候打印中间变量的形状和数值会比你一步步断点跟踪效率高得多。我调试Softmax梯度时就是打印了dscores的均值和范围才确定问题出在mask没加对。另外一个小建议多跑几次交叉验证KNN和超参数搜索时可以把num_folds从5改成10试试。折叠数增大会让每次训练数据更多K值选择会更稳定但运行时间也翻倍。在作业规模下差别不大但你有机会直观体会bias-variance tradeoff在交叉验证中的应用。最后再分享一个我在做这个作业时养成的好习惯每写完一个函数先跑作业自带的test单元测试再跑数值梯度校验最后才去调超参数。三步走下来代码正确性有了保障后面的调参结果才可信。如果跳过了前两步直接拿一个有bug的模型去调参数你会在错误的代码上浪费大量时间还会得出完全错误的调参结论。这套“先验证正确性再优化性能”的顺序放到任何工程项目里都适用。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价