尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
cs231n作业assignment1实战:从kNN到反向传播的梯度检查与避坑指南
简介斯坦福CS231n课程的第一份编程作业assignment1完整资料包专为正在入门计算机视觉与深度学习的同学准备。资源包含作业原题、参考答案与CIFAR-10数据集代码基于Anaconda的Jupyter Notebook编写覆盖KNN、SVM、Softmax分类器与两层神经网络等经典模型从数据预处理、特征提取到训练与可视化均有完整实现可帮助掌握图像分类的完整流程。包体共54个文件以7个ipynb作业脚本、17个py源码、7个pyc缓存为主要内容另含sh提交脚本、数据集压缩包与说明文档整体约561.98MB目录结构按照作业各模块分类便于对照学习和提交。资源内还保留了部分中间运行结果与可视化输出可帮助读者自查梯度计算、损失变化和超参数调优中的常见问题减少踩坑。目前已有2003人学习下载是CS231n作业实践环节不可多得的优质参考。1. 第一次跑 cs231n 作业 assignment1我差点把梯度公式背反第一次做 cs231n 作业 assignment1 时我以为不过是调几个现成分类器结果打开代码看到 kNN、SVM、Softmax、两层神经网络和图像特征五组任务整个人是懵的。这份资源不是把作业答案甩给你而是把每一段的推导、向量化写法、验证方式和最容易翻车的细节拆开讲。适合刚看完深度学习理论但还没动手写过反向传播的新手也适合已经写完但准确率一直诡异上不去的熟手。它能解决的核心问题是让每个任务都能在本地跑通并给你一套验证“梯度到底写对没有”的方法。2. 任务概览与环境配置作业1到底在考什么2.1 五个任务模块的隐藏关联cs231n 作业 assignment1 表面上分五个独立文件实际上是一条递进链。kNN 是最简单的非参数方法它不需要训练只是拿测试样本和训练样本算距离SVM 和 Softmax 都属于线性分类器需要手写损失和梯度两层神经网络在线性分类器基础上加了隐藏层和 ReLU 激活最后的图像特征实验则把输入从原始像素换成手工特征再套用前面的线性分类器。建议实现顺序别乱跳。先过 kNN熟悉数据集格式和交叉验证流程再写 SVM因为它的梯度比 Softmax 直观不容易漏正负号然后写 Softmax验证自己的向量化功底最后把两层网络里的反向传播和前面线性分类器对比着做。图像特征实验放最后当作一个综合小项目。在实际动手前还有一个容易被忽略的环节了解每个任务要交付什么。kNN 部分要完成距离矩阵的循环版和向量化版并在交叉验证中调出最好的 k。SVM 和 Softmax 要实现完全向量化的损失和梯度训练完成后要把权重可视化看看学出来的分类器长什么样。两层神经网络要能过梯度检查并在测试集上拿到稳定提升。图像特征实验要写特征提取函数并对比原始像素和特征表示在测试集上的准确率。明确这些输出后再去读代码骨架会清楚很多。我见过不少人一上来直接补线性分类器的循环补完才发现向量化部分只要求了结果没要求过程白白浪费一下午。所以先把每个文件里的 TODO 和“需要交付的曲线”过一遍再动手写。2.2 环境配置Python、numpy 和数据集准备作业1的代码骨架是好几年前的但核心依赖不多numpy、matplotlib、scipy再加一个 pillow 用于图像读取跑特征实验还需要 scikit-image。环境上我一般用虚拟环境隔离避免和本机其他项目打架。# 创建并激活虚拟环境 python -m venv cs231n_env source cs231n_env/bin/activate # Windows 下使用 cs231n_env\Scripts\activate # 安装基础依赖 pip install numpy matplotlib scipy pillow scikit-image这里有几个细节值得注意。Python 版本建议 3.7 到 3.10太新的版本配旧版 scipy 有时会碰到兼容警告但不影响作业本身。numpy 没有强制版本我习惯用 1.21 左右因为太新的版本在矩阵广播时如果代码写得比较野会报一些形状不匹配的警告干扰调错。使用 conda 也可以但关键是要保证同一个 Python 环境里只有一套 numpy别出现系统里两个 Python 互相抢包的情况。验证环境是否正常直接跑一段小脚本import numpy as np import matplotlib.pyplot as plt from scipy import ndimage print(numpy, np.__version__) print(scipy, ndimage.__version__)如果这几行能不出错打印出来说明基础环境到位了。剩下是数据集。作业里用的是 CIFAR-10 的 Python 版本解压后会得到data_batch_1到data_batch_5以及test_batch文件。我一般会把数据放到作业根目录下的data文件夹并确认代码里的相对路径能正确找到它。# 假设数据集已下载到 cifar-10-batches-py # 在作业根目录创建一个 data 软链接省得每次改路径 ln -s /absolute/path/to/cifar-10-batches-py data这段命令在 Linux/macOS 下有效Windows 下可以用 mklink /J 做目录联接也可以直接复制文件夹。目的是让代码里的相对路径data稳定指向数据集。很多人加载失败不是代码问题而是当前工作目录不对。启动 Jupyter 时务必先 cd 到作业根目录再运行否则相对路径全乱。接下来是数据读取。作业骨架里通常会提供load_CIFAR10函数但自己实现一遍更容易理解文件结构import glob import os import pickle import numpy as np def load_CIFAR10(root): xs [] ys [] for batch_file in sorted(glob.glob(os.path.join(root, data_batch_*))): with open(batch_file, rb) as f: batch pickle.load(f, encodinglatin1) xs.append(batch[data]) ys.append(batch[labels]) X_train np.concatenate(xs).reshape(-1, 3, 32, 32).transpose(0, 2, 3, 1) y_train np.concatenate(ys) return X_train, y_train注意pickle.load必须加encodinglatin1因为 CIFAR-10 的 pickle 文件是用 Python 2 序列化的不加会报编码错误。reshape 成[N, 3, 32, 32]后再转成[N, 32, 32, 3]方便后面可视化。读入后一般把像素缩放到 0 到 1 区间再展平成[N, 3072]的二维矩阵。归一化要在划分训练集和验证集之后做否则统计量会泄露后面避坑章节会单独展开。3. 关键算法实现从循环版 kNN 到向量化 SVM3.1 kNN距离矩阵的两层循环与全向量化kNN 的核心是计算测试集和训练集中每对样本的欧氏距离。最开始可以先用两层循环把逻辑跑通再逐步改成向量化。# 两层循环版本 # X_test: [num_test, D]X_train: [num_train, D] num_test X_test.shape[0] num_train X_train.shape[0] dists np.zeros((num_test, num_train)) for i in range(num_test): for j in range(num_train): diff X_test[i] - X_train[j] dists[i, j] np.sqrt(np.sum(diff ** 2))这个版本的好处是每个距离的语义都很清楚公式直接写在代码里。缺点是当 num_test 是 500、num_train 是 5000 时两层循环差不多要跑几百毫秒甚至更久在交叉验证里会非常难受。向量化版本利用一个代数恒等式两个向量差的平方等于各自平方和减去两倍内积。# 向量化版本 X_test_sq np.sum(X_test ** 2, axis1, keepdimsTrue) # [num_test, 1] X_train_sq np.sum(X_train ** 2, axis1).reshape(1, -1) # [1, num_train] cross np.dot(X_test, X_train.T) # [num_test, num_train] dists np.sqrt(np.maximum(0.0, X_test_sq X_train_sq - 2 * cross))这里维度是关键。X_test_sq 形状是[num_test, 1]X_train_sq 是[1, num_train]相加时 numpy 会把它们广播成[num_test, num_train]再减去交叉项。如果不加 keepdims 或者不 reshape广播就会出错。还有一个容易踩的坑因为浮点计算顺序某些元素会被减到微小的负数比如 -1e-10直接开方就得到 NaN所以必须先用 np.maximum 把负数裁剪成 0。写完距离后预测函数按行对测试样本取距离最小的 k 个训练集标签用 np.argsort 排序后取前 k 个再用 np.bincount 统计投票。def predict_labels(dists, y_train, k1): num_test dists.shape[0] y_pred np.zeros(num_test) for i in range(num_test): closest_y y_train[np.argsort(dists[i])[:k]] counts np.bincount(closest_y, minlength10) y_pred[i] np.argmax(counts) return y_predminlength 设为类别数 10是为了保证 argmax 始终落在 0 到 9。平票时 np.argmax 返回第一个最大值的索引正好满足作业要求里“选排序靠前的类别”。完成距离计算和预测后kNN 就算写完接下来用交叉验证选 k。交叉验证的常见做法是把训练集拆成若干折循环取其中一折当验证集。建议把 k 的候选范围放在 1 到 20 之间配合折数 5 和 10 看趋势。时间紧张时先固定折数为 5用粗粒度 k 步长 2 跑一遍再在最优附近细调。3.2 SVM 与 Softmax 的损失和梯度一份公式一套代码进入线性分类器前先明确矩阵形状输入 X 是[N, D]权重 W 是[D, C]偏置 b 一般并入 W 后在 X 最后一列补 1或者单独处理。作业骨架里通常是 W 直接包含偏置X 已经加了全 1 列。这里按包含偏置的写法来。多类 SVM 用的是 margin loss要求正确类别分数比其他类别至少高出一个 delta通常取 1。# 多类 SVM 损失与梯度 scores X.dot(W) # [N, C] margins np.maximum(0, scores - scores[np.arange(N), y].reshape(-1, 1) 1) margins[np.arange(N), y] 0 # 正确类别不计入损失 loss np.sum(margins) / N reg * np.sum(W * W) # 计算梯度 binary (margins 0).astype(scores.dtype) binary[np.arange(N), y] -np.sum(binary, axis1) dW np.dot(X.T, binary) / N 2 * reg * W这里最需要理解的是 binary 矩阵的含义。margins 0 的位置标记那些产生了损失的错误类别梯度为正推动权重往降低这些误分配的方向走正确类别那一行的梯度等于所有错误类别产生的正梯度的负和这样损失对正确类别权重的整体影响是负的把正确类别的分数往上推。正则项使用2 * reg * W是因为损失里的 L2 正则写的是对 W 所有元素平方求和求导后要乘 2。写 Softmax 时损失变成交叉熵。为了防止 exp 溢出先对每行分数减去该行最大值这个操作不影响概率。# Softmax 损失与梯度 shifted_scores scores - np.max(scores, axis1, keepdimsTrue) exp_scores np.exp(shifted_scores) probs exp_scores / np.sum(exp_scores, axis1, keepdimsTrue) loss -np.log(probs[np.arange(N), y]) loss np.sum(loss) / N reg * np.sum(W * W) dscores probs.copy() dscores[np.arange(N), y] - 1 # softmax 减去正确类别 one-hot dscores / N dW np.dot(X.T, dscores) 2 * reg * W两条代码放一起对比就很直接SVM 的梯度 mask 是硬阈值Softmax 的梯度是概率减去 one-hot。后者天然连续所以调参时通常比 SVM 稳定。训练时用最简单的随机梯度下降# 训练循环核心代码 learning_rate 1e-3 for it in range(2000): idx np.random.choice(num_train, batch_size) X_batch X[idx] y_batch y[idx] loss, dW svm_loss_grad(X_batch, y_batch, reg) W - learning_rate * dW学习率选择一半靠经验一半靠看曲线。我一般先试 1e-3观察 loss 走势震荡就降 10 倍下降太慢就加一点调参区间基本落在 1e-4 到 1e-2 之间。正则强度先设 0.5 左右跑一轮再根据验证集准确率微调。4. 两层神经网络与图像特征反向传播实战4.1 两层网络前向与反向计算图拆解作业的两层神经网络结构是输入层、一个隐藏层、输出层隐藏层激活用 ReLU。前向计算相对容易关键在反向传播不要背公式要从计算图一层层回推。以小批量为例# 前向 Z1 X.dot(W1) b1 # [N, H] A1 np.maximum(0, Z1) # ReLU Z2 A1.dot(W2) b2 # [N, C] scores Z2 # 假设已经得到 probs 和 loss dscores probs.copy() dscores[np.arange(N), y] - 1 dscores / N # 反向第二层 dW2 A1.T.dot(dscores) db2 np.sum(dscores, axis0) dA1 dscores.dot(W2.T) # 反向第一层通过 ReLU dZ1 dA1 * (Z1 0) # 缓存 Z1而不是 A1 dW1 X.T.dot(dZ1) db1 np.sum(dZ1, axis0)注意 dA1 是通过输出层往输入回传的梯度它和隐藏层输出 A1 的关系经由 W2 建立。到了 ReLU 那里激活函数的梯度是 mask前向时 Z1 大于 0 的位置梯度保留否则置零。很多人习惯在缓存里只存 A1然后反向时拿A1 0当 mask这在 ReLU 下实际上和前向的Z1 0等价但语义上不对因为我们判断的是“输入 z 是否大于 0”而不是“输出 a 是否大于 0”。如果激活函数换成 Leaky ReLU这两者的区别会立即导致梯度错误。实现完反向之后作业里有个强制步骤梯度检查。数值梯度的写法是中心差分# 对某一参数 p 做数值梯度 delta 1e-5 cost_plus loss_fn(p delta) cost_minus loss_fn(p - delta) numerical_grad (cost_plus - cost_minus) / (2 * delta) # 相对误差 |num - analytic| / max(1, |num|, |analytic|)相对误差应当小于 1e-7 左右如果只有 1e-4说明梯度大概率错了别急着调参先回头推公式。训练时随机权重初始化W 的标准差一般取1/sqrt(H)。作业默认隐藏单元数是 100你可以改成 50 或 200 观察效果。SGD 的 batch_size 可以是全部训练集也可以设 256资源不吃紧就全批量梯度更稳定。4.2 图像特征实验用 HOG 和颜色直方图提升线性分类器图像特征模块的目的是告诉大家手工特征比原始像素更容易被线性分类器分开。作业里常用的是方向梯度直方图和颜色直方图组合。方向梯度直方图要算图像梯度方向再分方格统计直方图工程上直接调 scikit-image 的 hog 接口更省事。# 颜色直方图的简化实现 def extract_color_histogram(images, bins32): # images: [N, 32, 32, 3]像素范围 0-1 num_images images.shape[0] features np.zeros((num_images, bins * 3)) for i in range(num_images): for channel in range(3): hist, _ np.histogram( images[i, :, :, channel], binsbins, range(0, 1) ) features[i, channel * bins:(channel 1) * bins] hist return features这里的 bins 控制直方图维度32 是常见选择。取 16 会损失太多信息取 64 会让特征维度变大同时容易过拟合。颜色直方图对亮度和简单颜色分布敏感但丢失了像素之间的空间关系所以通常再配一个方向梯度直方图来补充纹理信息。提取完特征后要对特征做归一化。常见做法是每个特征维度独立做均值方差归一化然后在归一化后的特征上套用上一章的线性分类器。需要注意归一化参数必须在训练集上计算再用同一套参数处理验证集和测试集。我一般会在特征实验里同时跑原始像素版本和特征版本得到两张准确率曲线。原始像素版通常在 0.35 到 0.40 之间特征版能到 0.50 以上。如果你的特征版比像素版还低优先检查特征归一化是否有 bug。5. 作业1避坑指南五个让新手抓狂的细节5.1 梯度验证一直失败误差始终在 1e-2 附近降不下去现象无论把 delta 调多小数值梯度和解析梯度的相对误差都在 1e-2 左右有时候还在 0.5 上下跳动。原因最常见的是在损失函数里把总损失除以 N但梯度只除以了部分项或者正则项梯度没乘 2。另一个高频原因是在计算 Softmax 梯度时忘了把 dscores 除以 N相当于梯度被放大了 batch size 倍。解决不要只看最终误差。把梯度检查拆开先检查不带正则项的梯度再单独检查正则项的梯度两部分单独都对合起来还错就检查 loss 里是否把正则项加重复了。梯度检查通过后再训练否则后面所有调参都是白费。5.2 kNN 向量化版本出现大量 NaN现象两层循环版距离正常向量化版本的距离矩阵里出现 NaN预测准确率直接变成 0。原因数值问题。广播求和后部分元素因浮点误差变成负数直接套用 np.sqrt 产生 NaN。解决在开方前调用 np.maximum(0, ...)。不要用 abs 去修正因为负数的绝对值会让距离计算产生不可忽略的偏差。改成dists np.sqrt(np.maximum(0.0, X_test_sq X_train_sq - 2 * cross))后一般立刻恢复。5.3 模型一直在 0.25 左右纹丝不动现象SVM 或 Softmax 训练 2000 轮训练准确率和验证准确率都贴在 0.25 附近loss 几乎不变。原因学习率选择不当。学习率过大loss 会出现一条水平线并伴随明显震荡过小则 loss 下降慢到像没动。另一个常见原因是权重初始化把所有 W 设成了 0所有样本的分数相同梯度也对称模型完全无法打破对称性。解决先确认初始化用了随机值。再把学习率用对数刻度扫一遍比如 1e-4、1e-3、1e-2、1e-1画出每个学习率下的 loss 曲线选下降最快且不震荡的那个。正则强度暂时固定为 0 或很小值排除干扰。5.4 交叉验证中知识泄漏模型疯狂过拟合现象在 kNN 交叉验证时训练准确率高到 0.99但各折验证准确率只有 0.28 左右有时候在所有 k 下都一样差。原因在划分折之前就把整个数据集做了零均值归一化或标准化统计量来自全量数据相当于把验证集的信息提前透露给了模型。kNN 虽然无参数但距离计算依赖全局尺度泄漏后果非常明显。解决把预处理移到一个独立流程。先对每一折的训练部分计算均值和标准差用这些统计量变换当前折的训练和验证部分而不是在拆分前对全量数据统一做。作业里最简单的做法是 kNN 前不做标准化直接对原始像素归一化到 0-1 区间因为像素范围本身已知不算泄露但如果要做 z-score就必须遵循拆分原则。5.5 权重可视化看起来全是噪声现象训练完成后把 W 每行 reshape 成图像显示发现全是雪花点看不出任何类别纹理。原因正则强度太高强到模型把所有权重都压向零或者学习率太大导致权重在最优解附近震荡画出来自然不收敛。解决降低正则强度到 0.1 以下重新训练。同时检查最后一轮 loss如果仍然明显下降说明还没收敛应该增加迭代次数或提高学习率。权重可视化是判断线性分类器是否学到东西的直观指标出现清晰轮廓才是正常的颜色偏淡但结构可见是典型可接受状态。6. 梯度检查、超参数搜索与最终验收作业1的正确打开方式完成所有模块后很多人习惯直接拿测试集看准确率。其实作业1最该重视的是“中间过程对不对”而不是最终数字。我的习惯是先跑一遍梯度检查再做一次网格搜索最后才看测试集。梯度检查建议覆盖每一层的 W 和 b。可以用 scipy 里的 check_grad也可以自己写中心差分。作业里通常有 grad_check 函数但不要只跑默认参数要主动把 delta 从 1e-5 改到 1e-7并且随机抽 10 个维度检查。如果某个维度总是错不要怀疑数值方法直接回看反向传播代码。超参数搜索可以用一个简单的双层循环把学习率和正则强度各自从粗到细跑两轮。第一轮学习率取 1e-4、1e-3、1e-2正则强度取 0.1、1.0、10.0得到一张 3x3 的验证准确率热力图。第二轮在最优格子周围加密。搜索时记住一个关键点必须固定相同的数据划分否则每个超参数组合用不同验证子集比较没有意义。选中超参数后最终的验收不是测试集跑一次就看结果而是重新随机初始化几次分别训练观察验证准确率的均值和方差。如果三次结果波动超过两个百分点说明学习率可能偏大或正则偏弱。稳定后再去测试集上跑一次得到的数字才可信。我还做过一件后悔事在梯度检查通过之前就跳到调参结果所有分析都建立在错误梯度上白调了两天。从那以后我每次完成一个模块都强制自己先跑一遍梯度检查再开始训模型。写作业时这个顺序帮我省下大量返工时间。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

人脸融合技术原理与Demo实战:关键点、mask与泊松融合

人脸融合技术原理与Demo实战:关键点、mask与泊松融合

简介:这是一份基于PHP的腾讯AI人脸融合Demo源码,面向PHP开发者与AI接口调用学习者,实现了类似微信“换军装”H5效果的人脸融合流程,可用于快速理解并接入腾讯AI开放平台的人脸融合能力。资源共10个文件,压缩包仅73KB&a…

📅 2026/10/12 11:58:42
llama.cpp与GGUF:本地大模型部署的裸引擎与量化实战

llama.cpp与GGUF:本地大模型部署的裸引擎与量化实战

第一次用图形化一键部署工具跑本地模型时,我一直被同一个问题折磨:为什么同一个模型文件,放到不同电脑上,聊着聊着就出现完全不同的结果?排查到最后才明白,模型文件、推理引擎、外层服务是三个互相独立的环…

📅 2026/10/12 11:58:42
Hadoop+Spark信贷风控系统实战:从环境搭建到窗口函数特征工程

Hadoop+Spark信贷风控系统实战:从环境搭建到窗口函数特征工程

简介:这是一套面向计算机相关专业学生与项目实战学习者的毕业设计级资源,主题为基于Hadoop与Spark的大数据金融信贷风险控制系统,适合用作毕设、课程设计或期末大作业,也可作为大数据方向求职者的练手项目。压缩包共69个文件&…

📅 2026/10/12 11:58:42
MORE NEWS

更多资讯

📰

SAP云平台Cockpit使用指南:从子账户到服务实例的完整实操路径

做了几年SAP相关项目之后,我发现一个挺有意思的现象:很多人第一次打开SAP云平台(现在官方全名叫SAP Business Technology Platform,行业里还是习惯叫SAP云平台)的Cockpit时,盯着满屏的英文菜单和卡片&#…

📰

Python工程化入门:面向对象、文件读写与异常处理实战笔记

1. 学习笔记三:走出语法舒适区,开始接触真正工程化的 Python进入这套 100 天学习路线图的第三阶段,最大的感受是:前面几章还停留在"认识 Python"的层面,到了这一部分,才真正开始"用 Python …

📰

R语言基础入门:向量、循环法则与NA/NaN处理

搞R语言也有几年了,每次带新人入门,我都会让他们先把向量、循环法则、NA和NaN这几个概念弄明白,再往后学才是正路。别小看这些基础,多少人在实际处理数据时,被循环法则静默回收坑得怀疑人生,被NA和NaN的组合…

📰

南开大学编译原理作业实战:简单C语言编译器从词法到代码生成

简介:这份资源是南开大学软件学院编译原理课程的作业成果,包含一个可运行的简单C语言编译器完整源码与配套文档,面向计算机、人工智能、通信工程等专业的在校学生及自学者,适合用作课程设计、毕业设计参考或编译原理入门实践。压缩…

📰

基于Docker的大学生兼职平台部署:容器化设计与避坑实践

简介:基于Docker的大学生兼职平台项目完整资源包,服务于计算机专业学生与后端开发人员,也适合作为毕业设计选题参考。项目围绕兼职信息发布、岗位检索、用户管理等核心功能展开,采用Java语言结合常见微服务开发框架,后…

📰

AI数据合规全链路指南:从训练语料到模型输出的落地实践

前阵子跟一个做AI客服系统的朋友喝茶,他一句话让我记了很久:功能做完了,评审会开了一个月,最后砍掉了一半的训练语料。原因不复杂——语料里全是真实用户的聊天记录,里面有手机号、有住址,甚至还有用户照着…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬