十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深度学习课程七大模型:从梯度下降到强化学习的代码实践与调参指南

深度学习课程七大模型:从梯度下降到强化学习的代码实践与调参指南 简介神经网络与深度学习课程配套练习代码适合正在学习机器学习/深度学习基础的学生边学边写。内容覆盖numpy热身、线性回归、SVM与Softmax回归、全连接前馈神经网络、CNN处理MNIST分类、RNN唐诗生成、seq2seq与注意力机制下的序列逆置以及高斯混合模型和深度强化学习实现章节划分清晰从简单矩阵操作到完整模型训练循序渐进。资源共60个文件压缩包14.15MB以19个.ipynb练习/示例为主配合12个txt语料或数据文件、11个Markdown说明、9个Python脚本另有PDF文档和图片素材能支撑实验运行与理论对照。已有248人学习下载。拿到手后可按目录逐章运行也可借助题目要求和说明快速定位所需代码作为课程实验、期末复习或面试前手推模型的参考资料都较实用。1. 课程练习代码里的七个模型一条训练主线“神经网络与深度学习课程练习代码”这个标题看着朴素实际把深度学习课程的主干模型都收进来了。线性回归给出最朴素的梯度下降范式前馈神经网络把隐藏层和激活函数加回来CNN处理空间结构RNN和注意力机制解决序列依赖GMM代表概率生成模型深度强化学习则把训练从静态数据集搬到交互决策。把这七个模型全部写通等于亲手过了一遍从“拟合”到“决策”的完整演进。这篇文章顺着课程练习的常见顺序把每个模型的代码形态、核心参数和容易踩的坑铺开讲。适合正在上课需要补代码的学生也适合想快速把模型管线串起来的工程师。2. 线性回归与前馈神经网络共用一套训练管线的两个模型2.1 线性回归手写代码闭式解、梯度下降与损失函数选择线性回归的课程练习通常不会只调一行sklearn而是要你回答一个问题梯度下降每一步到底在更新什么。理解这件事后面的前馈神经网络只是把“一层线性变换”换成“两层线性变换加激活函数”训练逻辑完全一样。我一般会让学生先手写一个不带框架的版本把前向计算、损失、反向梯度三个步骤拆开。import numpy as np def mse_loss(y_true, y_pred): return np.mean((y_true - y_pred) ** 2) def train_linear_regression(X, y, lr0.01, epochs500): n_features X.shape[1] w np.zeros(n_features) b 0.0 for epoch in range(epochs): y_pred X w b # 对MSE求w的偏导: (2/N) * X^T (y_pred - y) dw 2 * X.T (y_pred - y) / len(y) # 对b的偏导: (2/N) * sum(y_pred - y) db 2 * np.mean(y_pred - y) w - lr * dw b - lr * db if epoch % 100 0: print(fepoch {epoch}, loss {mse_loss(y, y_pred):.4f}) return w, b代码里最关键的是注释中那两行梯度推导。dw的维度必须和w一致X.T (y_pred - y)把每个特征的误差累加到一起db是误差的均值对应偏置只接受全局方向的更新。lr控制每一步移动的幅度太大会震荡太小会慢课程练习里一般从0.01起步观察 loss 曲线再调整。损失函数选择上不同任务侧重点不同我做练习时会画一张对比表贴在笔记里损失表达式特点适用场景MSEmean((y - y_pred)^2)对大误差惩罚重梯度与误差成正比回归默认选项MAEmean(abs(y - y_pred))对离群点不敏感但在0处梯度不平滑含噪声数据Huber小误差用MSE大误差用线性兼顾两者需调delta混合异常值的回归2.2 把前馈神经网络接进同一管线的三个要点前馈神经网络的练习代码重点不是把网络写得复杂而是建立一条可以复用的训练管线。我通常用一个三层 MLP 在 MNIST 或类似的小数据集上验证等这个管线稳定了后面 CNN、RNN 只需要换模型定义和数据预处理。import torch import torch.nn as nn class MLP(nn.Module): def __init__(self, in_dim784, hidden_dim256, out_dim10): super().__init__() self.net nn.Sequential( nn.Linear(in_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, out_dim) ) def forward(self, x): return self.net(x) model MLP() optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() for epoch in range(5): for x_batch, y_batch in train_loader: optimizer.zero_grad() logits model(x_batch) loss criterion(logits, y_batch) loss.backward() optimizer.step()CrossEntropyLoss内部已经包含了 softmax不要在模型输出层再手动加 softmax否则梯度会不稳定。Adam是课程练习里最省心的优化器自带学习率自适应但这不代表lr可以乱设从1e-3起步loss 不下降时优先降到1e-4而不是去调 batch size。三个值得每轮练习都检查的参数输入标准化像素值除以 255 或做 z-score否则第一层梯度会被大数值输入主导。权重初始化PyTorch 默认初始化已经够用不要自己用torch.ones初始化所有权重那会让同一层神经元学到完全一样的特征。验证集划分训练 loss 降到很低不代表泛化好课程练习里最常见的问题就是没有留验证集最终测试集分数和预期差一大截。3. 卷积神经网络的课程练习从手写卷积到背得住的超参3.1 用一次手写卷积理解局部连接与参数共享CNN 的练习代码里最值得手写一次的就是二维卷积算子本身。虽然实际训练不可能用这种循环实现但把卷积的计算过程写一遍kernel_size、stride、padding的含义就再也忘不掉了。import numpy as np def conv2d_numpy(x, kernel, stride1, padding0): h, w x.shape kh, kw kernel.shape h_out (h 2 * padding - kh) // stride 1 w_out (w 2 * padding - kw) // stride 1 x_padded np.pad(x, padding) out np.zeros((h_out, w_out)) for i in range(h_out): for j in range(w_out): region x_padded[i * stride:i * stride kh, j * stride:j * stride kw] out[i, j] np.sum(region * kernel) return out这段代码把你调过的nn.Conv2d拆成了三个显式步骤padding 扩张输入、按stride滑动窗口、窗口内与kernel做逐元素乘加。h_out的计算公式要背下来它决定了网络每一层特征图的尺寸变化。输出尺寸公式本身是h_out (h 2 * padding - kernel_size) // stride 1如果h_out不是整数说明padding或stride设置不匹配模型会在forward时报尺寸错误排查时先算这个式子。3.2 CNN 练习里最值得背的一组参数课程练习里反复出现的 CNN 结构核心就四件事卷积层提特征、池化层降分辨率、BatchNorm 稳定分布、ReLU 加非线性。下面这个结构可以跑通 CIFAR-10 的简单版本参数量小单卡 CPU 也能在可接受时间内完成一个 epoch。import torch.nn as nn class SmallCNN(nn.Module): def __init__(self, num_classes10): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 16, kernel_size3, padding1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(32 * 8 * 8, num_classes), ) def forward(self, x): return self.classifier(self.features(x))输入3x32x32的图像经过两次MaxPool2d(2)后空间尺寸变成8x8所以全连接层输入维度是32 * 8 * 8。这个数字不是随便写的修改输入尺寸后要跟着重新算。常用参数速查表如下参数常用范围练习建议kernel_size1, 3, 53x3 最稳多个小卷积核替代大卷积核stride1 或 2下采样优先用池化stride2 会丢细节paddingkernel_size // 2维持特征图尺寸不变通道数16 → 32 → 64每层翻倍是常见做法不是唯一做法两个坑需要在练习时注意。第一个是池化层放置位置先Conv再ReLU再Pool是标准顺序把Pool放在Conv前会让卷积感受野变小特征提取效果变差。第二个是 BatchNorm 与batch_size的关系BatchNorm 在一个 batch 上统计均值和方差batch size 设为 1 或 2 时统计不稳定测试阶段准确率波动会很大课程练习里如果显存受限优先把输入图像调小而不是把 batch size 压到 4 以下。4. 循环神经网络与注意力机制把序列依赖写成可训练权重4.1 循环神经网络的梯度问题与 LSTM/GRU 的练习选择RNN 的练习代码第一步应该先跑一个 LSTM 文本分类而不是从头实现 LSTM 内部逻辑。LSTM 的门控结构在代码里体现为四个仿射变换手写容易把forget_gate和input_gate的更新公式搞混先用框架跑通再对照公式逐行读源码效率更高。PyTorch 里调用很简单import torch import torch.nn as nn lstm nn.LSTM(input_size128, hidden_size256, num_layers2, batch_firstTrue, dropout0.2) # 输入形状: (batch_size, seq_len, input_size) x torch.randn(8, 20, 128) out, (h_n, c_n) lstm(x) # out: 最后一层每个时间步的隐状态 # h_n, c_n: 最后一层最后时间步的隐状态和单元状态input_size是每个时间步输入的特征数对文本任务来说就是 embedding 维度。hidden_size决定隐状态维度也是 LSTM 输出维度num_layers堆叠层数两层已经是大多数课程练习的上限再深收益很小且训练变慢。batch_firstTrue让输入输出都保持(batch, seq, feature)排列省掉转置操作新手不容易把维度搞错。如果做情感分类一般只取h_n最后一层的结果接全连接如果做序列标注才需要保留out全部时间步。课程练习中最常见的错误是把out和h_n混用导致forward维度不匹配报错信息里seq_len和batch数字对不上多半就是这里。4.2 注意力机制代码从加性注意力到点积注意力注意力机制在课程练习里通常以两种形态出现一种是在 seq2seq 里给解码器每个时间步计算上下文向量另一种是 Transformer 里的自注意力。两者的核心都是计算一组权重把 value 按权重加权求和。我建议从缩放点积注意力入手因为它代码最短参数最少理解后再去看多头注意力里的QKV拆分会更顺。import torch import torch.nn.functional as F def scaled_dot_product_attention(q, k, v, maskNone): # q, k, v: (batch_size, seq_len, head_dim) d_k q.size(-1) scores q k.transpose(-2, -1) / (d_k ** 0.5) if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) weights F.softmax(scores, dim-1) return weights v, weightsq k.transpose(-2, -1)计算每个查询和所有键的相似度d_k ** 0.5是缩放因子防止维度增大时点积结果过大导致 softmax 梯度消失。mask的作用是屏蔽无效位置——最常见的是处理不同长度序列时padding 位置的权重必须置零如果不加这个掩码softmax 会把注意力分配给填充符编码结果被污染。在参数设置上注意力机制里真正需要调的参数其实很少更多的坑在形状处理上参数练习建议原因head_dim通常取 32 或 64过小表达力不足过大训练变慢注意力头数8 是稳妥选择每个头关注不同子空间8头性价比高dropout0.1 ~ 0.2注意力权重上做dropout防止过拟合缩放因子必须存在去掉缩放训练后期loss波动明显变大训练时还有一个值得做的事把注意力权重weights可视化看模型是不是真的把注意力集中在关键 token 上。如果可视化结果显示权重接近均匀分布先检查是不是没训练够如果某个无关 token 一直拿到最高权重再检查 mask 是否生效。5. 高斯混合模型的练习形态EM算法与似然最大化5.1 GMM练的不是神经网络是概率密度估计课程练习里放进高斯混合模型意图和前面的模型不同。GMM 不是一个判别模型它不学习“输入到输出”的映射而是直接估计数据本身的概率密度假设数据由 K 个高斯分布混合生成。和神经网络的梯度下降训练不同GMM 的标准训练方式是 EM 算法一步步交替迭代。练习时最忌讳直接调GaussianMixture类库那样会漏掉两个核心概念E 步计算后验概率M 步更新参数。5.2 用 NumPy 手写一个 GMM 核心步骤我用一维或二维数据手写 EM 来加深理解代码可以把问题限定在 3 个高斯分量以内方便可视化中间结果。下面这段是 E 步和 M 步的骨架需要配合scipy.stats.multivariate_normal使用。import numpy as np from scipy.stats import multivariate_normal from scipy.special import logsumexp def e_step(X, pi, mu, cov): n_samples, n_components X.shape[0], pi.shape[0] log_prob np.zeros((n_samples, n_components)) for k in range(n_components): log_prob[:, k] multivariate_normal.logpdf(X, meanmu[k], covcov[k]) log_prob np.log(pi) log_prob - logsumexp(log_prob, axis1, keepdimsTrue) return np.exp(log_prob) def m_step(X, gamma): n_samples, n_components gamma.shape pi gamma.mean(axis0) mu gamma.T X / gamma.sum(axis0)[:, None] cov np.zeros((n_components, X.shape[1], X.shape[1])) for k in range(n_components): diff X - mu[k] cov[k] (gamma[:, k:k1] * diff).T diff / gamma[:, k].sum() return pi, mu, covE 步的输出gamma是一个(n_samples, n_components)的矩阵每个元素代表第 i 个样本属于第 k 个高斯分量的后验概率。代码里用logpdf和对数加法而不是直接乘概率是为了数值稳定性——多个高斯分量概率相乘会得到极小的数直接下溢成 0。M 步的更新公式看着复杂拆开看其实就是每个分量的加权均值和加权协方差权重就是后验概率哪个分量对某个样本的责任大这个样本对它的参数更新影响就大。参数常用值作用n_components2 到 5分量太少拟合不了多峰分布太多过拟合covariance_typefull / diag / tiedfull 最通用diag 省参数n_init5 以上EM 对初值敏感多跑几次取最优reg_covar1e-6防止协方差矩阵奇异最大的坑是协方差矩阵退化当某个分量的数据点太少协方差变成奇异矩阵logpdf直接报错。常见做法是在 M 步迭代中给协方差对角线加一个1e-6的微小扰动或者检查更新后每个分量的有效样本数是否小于阈值如果小于就重新初始化该分量的均值。6. 深度强化学习入门DQN骨架与训练稳定性技巧6.1 一个能跑通CartPole的DQN代码骨架深度强化学习的课程练习思路和前几个模型完全不一样。它没有现成的(x, y)训练集数据来自智能体与环境的在线交互。DQN 是最适合入门的算法它复用神经网络做 Q 值拟合但多出两个关键机制经验回放和目标网络。下面这个骨架不需要特定环境库把环境交互部分抽象成env.step(action)和env.reset()这两个接口即可接入常见环境。import torch import torch.nn as nn import random class DQN(nn.Module): def __init__(self, obs_dim4, act_dim2, hidden128): super().__init__() self.q nn.Sequential( nn.Linear(obs_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, act_dim), ) def forward(self, obs): return self.q(obs) def dqn_train(env, episodes400, batch_size64, gamma0.99, lr1e-3, buffer_size10000): online_net DQN() target_net DQN() target_net.load_state_dict(online_net.state_dict()) optimizer torch.optim.Adam(online_net.parameters(), lrlr) replay_buffer [] for episode in range(episodes): obs env.reset() total_reward 0 while True: epsilon max(0.05, 1.0 - episode / 100) if random.random() epsilon: action env.action_space.sample() else: q_values online_net(torch.tensor(obs, dtypetorch.float32)) action q_values.argmax().item() next_obs, reward, done, _ env.step(action) replay_buffer.append((obs, action, reward, next_obs, done)) if len(replay_buffer) buffer_size: replay_buffer.pop(0) total_reward reward obs next_obs if done: break return online_net代码里epsilon从 1.0 线性衰减到 0.05这是探索与利用平衡的最简实现。target_net承担一个延迟更新的 Q 值计算角色防止同一个网络同时做预测和更新时产生自举偏差。经验回放的关键效果是打破相邻样本的强相关性避免模型在短时间内反复学习高度相似的连续状态。参数建议值调参方向gamma0.99越大越看重远期收益小任务直接 0.99epsilon_decay1.0 → 0.05衰减太快探索不足太慢收敛变慢buffer_size10000太小失去回放意义太大旧样本过时target_update_freq每 100 步拷贝一次更新太频繁等于没有目标网络6.2 训练不稳定时先检查这四件事深度强化学习练习的难点不是让模型跑起来而是让训练曲线稳定上升。我每次检查都会按固定顺序排除问题避免盲目调参。第一检查回报曲线的滑动平均不要看单次 episode 曲线。强化学习单次交互的回报方差极大直接画原始曲线会看到剧烈毛刺误以为训练失败。计算滑动平均窗口可以取 20 或 50等训练结束后再画两条线叠加对比。第二固定所有随机种子再训练。环境、网络初始化、动作采样三处同时固定确认复现结果是否一致如果固定种子后曲线仍然忽高忽低说明超参数本身不稳定。第三检查epsilon的衰减速度。CartPole 这类简单任务线性衰减到 0.05 的步数不宜超过总 episode 数的一半衰减过慢会导致模型已经学到好策略却还在大量随机探索。第四确认done标志是否正确传入目标值计算。目标值应该写成reward gamma * max_q(next_state) * (1 - done)漏掉(1 - done)维度直接导致 Q 值在终止状态被高估训练曲线呈现“涨一段、突然崩掉”的特征。每次训练完把回报曲线和最终模型保存下来和上一个版本对比比任何理论分析都直观。本文还有配套的精品资源点击获取
返回列表