阶段二(机器学习与神经网络)实战笔记
本篇覆盖「周 5 机器学习」与「周 6 神经网络」两个动手实验。
全部代码在真实华为云 ECS(Ubuntu 24.04 / 8 vCPU / 16 GB)上运行,仅使用numpy / pandas / matplotlib / scikit-learn,未使用 GPU、未使用 PyTorch。所有指标、图表均为真实运行结果,未作任何编造。
目录
- 学习目标
- 周 5 · 机器学习
- 5.1 理论:机器学习工具流程
- 5.2 特征、模型分类、评估与优化目标
- 5.3 sklearn 使用范式
- 5.4 实战:鸢尾花 + 逻辑回归完整流水线
- 5.5 真实运行结果
- 5.6 图表
- 周 6 · 神经网络
- 6.1 理论:从感知机到 RNN/LSTM
- 6.2 实战一:从零实现感知机
- 6.3 实战二:从零实现两层 MLP + 反向传播(XOR)
- 6.4 实战三:梯度下降与学习率对比
- 6.5 实战四:从零实现 RNN / LSTM 做正弦预测
- 小结
- 参考
学习目标
周 5(机器学习)
- 理解机器学习的「数据 → 特征 → 模型 → 评估」标准工具流程。
- 弄清「特征」「标签」「训练/验证/测试集」「模型分类(监督/无监督、生成/判别)」等基本概念。
- 掌握常用评估指标(准确率、精确率、召回率、F1、混淆矩阵、学习曲线)及其适用场景。
- 理解「优化目标 + 正则」的思想(如逻辑回归的交叉熵损失与 L2 正则)。
- 熟练使用
scikit-learn的fit / predict / score统一接口。
周 6(神经网络)
- 从零用
numpy实现感知机,理解其线性可分假设与收敛性。 - 理解神经网络的结构(单层/多层)、激活函数的作用,以及「单层感知机无法解决 XOR、加隐藏层即可解决」。
- 手写两层 MLP 的前向传播与反向传播(链式法则),体会梯度下降如何更新权重。
- 直观感受学习率对梯度下降收敛的影响(过小慢、过大发散)。
- 从零实现 RNN 与 LSTM,理解「记忆」与「门控」如何解决序列建模与长程依赖问题。
周 5 · 机器学习
5.1 理论:机器学习工具流程
一个标准的机器学习项目通常遵循下图所示的流水线:
原始数据 ──> 加载/清洗 ──> 特征工程 ──> 训练/测试划分 │ ▼ 选择模型并训练 (fit) │ ▼ 在测试集上评估 (predict + metric) │ ┌───────────────────┴───────────────────┐ ▼ ▼ 指标达标? ──否──> 调特征/调超参/换模型 是 ──> 交付/上线要点:
- 数据加载:从文件/数据库/API 读取,转成「特征矩阵
X+ 标签向量y」的二维表。 - 特征工程:把原始信息转成模型可用的数值表示(标准化、独热编码、降维等)。好的特征往往比复杂模型更重要。
- 数据划分:用「训练集」拟合参数,用「测试集」估计泛化能力;交叉验证能在有限数据下更稳健地评估。
- 训练 / 评估:调用
fit学习参数,predict得到预测,再用指标量化好坏。
5.2 特征、模型分类、评估与优化目标
特征(Feature)是描述样本的可量化属性。例如鸢尾花(Iris)的四个特征:
| 特征 | 含义 |
|---|---|
| sepal length | 花萼长度 (cm) |
| sepal width | 花萼宽度 (cm) |
| petal length | 花瓣长度 (cm) |
| petal width | 花瓣宽度 (cm) |
模型分类(常见维度)
- 按学习任务:监督学习(有标签,如分类/回归)vs无监督学习(无标签,如聚类)。
- 按输出类型:分类(离散标签)vs回归(连续值)。
- 按参数形式:判别式模型(直接建模
P(y|x),如逻辑回归、SVM)vs生成式模型(建模P(x,y),如朴素贝叶斯)。 - 按复杂度:线性模型(逻辑回归、线性回归)vs非线性模型(决策树、核方法、神经网络)。
评估方法
- 准确率 Accuracy= 预测正确的样本数 / 总样本数。简单直观,但在类别不平衡时会「骗人」(全预测多数类也能拿高分)。
- 精确率 Precision / 召回率 Recall / F1:关注某一类的查得准不准、查得全不全。
- 混淆矩阵 Confusion Matrix:把「真实 vs 预测」按类别交叉计数,是分类诊断的利器。
- 学习曲线 Learning Curve:观察「训练样本量↑」与「训练/验证准确率」的关系,判断模型是欠拟合还是过拟合。
优化目标
模型训练本质是一个最优化问题:最小化损失函数(如逻辑回归的交叉熵),同时在损失上加正则项(如 L2:λ·‖w‖²)来抑制过拟合。
逻辑回归对第k类使用 softmax + 交叉熵(多分类),对二分类使用 sigmoid + 交叉熵:
z = w·x + b P(y=1|x) = σ(z) = 1 / (1 + e^{-z}) Loss = -[ y·log ŷ + (1-y)·log(1-ŷ) ]5.3 sklearn 使用范式
scikit-learn 的设计高度统一,记住三板斧即可:
model=SomeEstimator(**hyperparams)# 1) 实例化(超参数在此设定)model.fit(X_train,y_train)# 2) 训练(学习参数)y_pred=model.predict(X_test)# 3) 预测score=model.score(X_test,y_test)# 4) 快速评准确率与此配套的常用工具:train_test_split(划分)、StandardScaler(标准化)、classification_report/confusion_matrix(评估)、learning_curve(学习曲线)。
5.4 实战:鸢尾花 + 逻辑回归完整流水线
完整代码见src/ml_pipeline.py,核心流程如下(含注释):
# -*- coding: utf-8 -*-"""鸢尾花 + 逻辑回归:加载 -> 特征标准化 -> 划分 -> 训练 -> 评估 -> 可视化"""importosimportnumpyasnpimportmatplotlib matplotlib.use("Agg")# 服务器无 GUI,必须指定非交互后端importmatplotlib.pyplotaspltfromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_split,learning_curvefromsklearn.preprocessingimportStandardScalerfromsklearn.linear_modelimportLogisticRegressionfromsklearn.metricsimport(accuracy_score,classification_report,confusion_matrix)# ---- 1) 加载数据集 ----iris=load_iris(as_frame=True)X=iris.data# 特征矩阵 (150, 4)y=iris.target# 标签 0/1/2# ---- 2) 特征标准化:让梯度下降收敛更快更稳定 ----scaler=StandardScaler()X_scaled=scaler.fit_transform(X)# ---- 3) 划分训练/测试集(分层抽样保持类别比例)----X_train,X_test,y_train,y_test=train_test_split(X_scaled,y,test_size=0.2,random_state=42,stratify=y)# ---- 4) 训练逻辑回归(one-vs-rest + L2 正则)----model=LogisticRegression(max_iter=200,C=1.0,random_state=42)model.fit(X_train,y_train)# ---- 5) 评估 ----y_pred=model.predict(X_test)acc=accuracy_score(y_test,y_pred)print("Accuracy =",round(acc,4))print(classification_report(y_test,y_pred,target_names=iris.target_names))cm=confusion_matrix(y_test,y_pred)# 画混淆矩阵热力图(figures/cm.png)与学习曲线(figures/lc.png)完整可运行版本(含绘图与中文日志)请直接运行
python src/ml_pipeline.py。
5.5 真实运行结果
在远程服务器执行/root/venv/bin/python src/ml_pipeline.py,关键输出如下(真实 stdout):
样本数 : 150 特征数 : 4 -> ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)'] 类别数 : 3 -> ['setosa', 'versicolor', 'virginica'] 训练集: 120 样本 | 测试集: 30 样本 模型 : LogisticRegression 求解器 : lbfgs | 正则 C=1.0 是否收敛 : n_iter_=[16] 测试集准确率 Accuracy = 0.9333 分类报告 classification_report: precision recall f1-score support setosa 1.0000 1.0000 1.0000 10 versicolor 0.9000 0.9000 0.9000 10 virginica 0.9000 0.9000 0.9000 10 accuracy 0.9333 30 macro avg 0.9333 0.9333 0.9333 30 weighted avg 0.9333 0.9333 0.9333 30 混淆矩阵 confusion_matrix(行=真实 / 列=预测): [[10 0 0] [ 0 9 1] [ 0 1 9]]结果解读
- 整体准确率93.33%。三类里
setosa完美区分(precision/recall 均为 1.0),这是鸢尾花数据的经典结论——setosa 在特征空间里与其他两类天然可分。 versicolor与virginica各有 1 个样本被互相误分(混淆矩阵中非对角线上各 1),说明这两类在边界处存在重叠,属于正常情况。- 逻辑回归仅用 16 步即收敛(lbfgs 求解器),说明数据尺度经标准化后非常「好解」。
5.6 图表
混淆矩阵热力图(figures/cm.png):直观展示每一类的预测分布,对角线越亮说明分类越准。
学习曲线(figures/lc.png):5 折交叉验证下,训练与验证准确率随训练样本量变化的曲线。两条曲线都随样本量上升并趋于平稳、且间距不大,说明模型未明显过拟合,增加数据收益有限但稳定。
周 6 · 神经网络
6.1 理论:从感知机到 RNN/LSTM
感知机(Perceptron)是最简单的神经元:对输入做加权求和再加偏置,过一个阶跃函数得到 0/1 输出。
z = w₁x₁ + w₂x₂ + ... + wₙxₙ + b ŷ = sign(z) # 阶跃:z≥0 输出 1,否则 0权重更新规则(感知机学习规则):当预测错误时,令w += lr·(y - ŷ)·x、b += lr·(y - ŷ)。
重要结论:对于线性可分数据,感知机** guaranteed 在有限步内收敛**;但对线性不可分数据(如 XOR)永远无法分开。
神经网络模型结构:把多个神经元分层堆叠。
- 单层感知机:只有输入→输出,等价于线性分类器,能力有限(只能学直线/超平面)。
- 多层感知机(MLP)/ 深层网络:在输入与输出之间加入隐藏层和非线性激活(sigmoid/tanh/ReLU)。隐藏层让网络能拟合任意复杂度的非线性边界——这就是单层解决不了的 XOR,加一层隐藏层就能解决。
梯度下降(Gradient Descent):沿损失函数对参数的负梯度方向迭代更新θ ← θ - lr·∇θL(θ),逐步逼近最小值。lr(学习率)控制步长:太小收敛慢,太大在谷底两侧来回反弹甚至发散。
反向传播(Backpropagation):用链式法则把输出层的误差逐层往前传,高效算出损失对每个权重w的梯度。它是训练所有神经网络的核心算法。前向算输出,反向算梯度,再用梯度下降更新——这就是训练循环。
RNN 与 LSTM:前述网络处理的是「定长输入→输出」。现实中有大量序列数据(时间序列、文本、语音)。循环神经网络(RNN)引入隐藏状态h_t = tanh(Wxh·x_t + Whh·h_{t-1} + b),让信息在时刻间传递,从而「记忆」历史。但普通 RNN 存在长程梯度消失,难以记住久远的信息。
LSTM(长短期记忆)用「门控」机制解决这个问题:它维护一个记忆单元c,并通过遗忘门f、输入门i、输出门o控制信息的保留与输出:
f_t = σ(Wf·[x_t, h_{t-1}] + bf) # 遗忘多少旧记忆 i_t = σ(Wi·[x_t, h_{t-1}] + bi) # 写入多少新信息 g_t = tanh(Wc·[x_t, h_{t-1}] + bc) c_t = f_t ⊙ c_{t-1} + i_t ⊙ g_t # 新的记忆单元 o_t = σ(Wo·[x_t, h_{t-1}] + bo) h_t = o_t ⊙ tanh(c_t)门控让 LSTM 能选择性地长期记忆,因此在长序列任务上通常优于普通 RNN。
下面四个实战逐一用numpy把这些概念「跑」出来。
6.2 实战一:从零实现感知机
完整代码见src/perceptron.py。核心是一个纯 numpy 的Perceptron类:
classPerceptron:def__init__(self,lr=0.1,max_iter=200):self.lr=lr;self.max_iter=max_iter self.w=None;self.b=0.0;self.errors=[]deffit(self,X,y):n_samples,n_features=X.shape rng=np.random.default_rng(0)self.w=rng.normal(0,0.01,size=n_features)y_=np.where(y>0,1,-1)# 标签映射为 {-1, +1}foritinrange(self.max_iter):errs=0foriinrange(n_samples):z=np.dot(self.w,X[i])+self.b y_hat=1ifz>=0else-1ify_hat!=y_[i]:# 只在预测错误时更新errs+=1update=self.lr*(y_[i]-y_hat)self.w+=update*X[i]self.b+=update self.errors.append(errs)iferrs==0:# 线性可分 -> 必收敛print(f"第{it+1}轮后完全分开,提前停止。")breakreturnself真实运行结果(二维线性可分数据,两类分别来自(2,2)与(-2,-2)附近的高斯分布):
样本数=120,特征维度=2,标签={0,1} 第 2 轮后完全分开,提前停止。 最终训练准确率 = 1.0000 权重 w = [0.4913 0.5115],偏置 b = 0.2000 训练误差轨迹(前若干轮): [1, 0] ... 末轮=0仅用2 轮就 100% 分开,完美印证「线性可分 → 感知机收敛」的理论。决策边界w·x + b = 0即图中的黑色直线:
6.3 实战二:从零实现两层 MLP + 反向传播(XOR)
完整代码见src/nn_mlp.py。网络结构:输入(2) → 隐藏层(sigmoid, 4 个神经元) → 输出(1, sigmoid),用二分类交叉熵做损失,手算梯度做反向传播。
classMLP:def__init__(self,n_in=2,n_h=4,n_out=1,lr=0.5,seed=0):rng=np.random.default_rng(seed)self.W1=rng.uniform(-1,1,(n_in,n_h))*np.sqrt(1.0/n_in)self.b1=np.zeros(n_h)self.W2=rng.uniform(-1,1,(n_h,n_out))*np.sqrt(1.0/n_h)self.b2=np.zeros(n_out)self.lr=lrdefforward(self,X):self.z1=X @ self.W1+self.b1 self.a1=sigmoid(self.z1)# 隐藏层激活self.z2=self.a1 @ self.W2+self.b2 self.a2=sigmoid(self.z2)# 输出returnself.a2defbackward(self,X,y):m=X.shape[0]dZ2=(self.a2-y)*sigmoid_grad(self.a2)# 输出层误差dW2=self.a1.T @ dZ2/m db2=dZ2.sum(0)/m dA1=dZ2 @ self.W2.T dZ1=dA1*sigmoid_grad(self.a1)# 链式法则回传dW1=X.T @ dZ1/m db1=dZ1.sum(0)/m self.W2-=self.lr*dW2;self.b2-=self.lr*db2 self.W1-=self.lr*dW1;self.b1-=self.lr*db1真实运行结果(XOR:4 条样本[0,0]→0, [0,1]→1, [1,0]→1, [1,1]→0):
epoch 1 | loss = 0.6970 | train_acc = 0.5000 epoch 1000 | loss = 0.6925 | train_acc = 0.5000 epoch 2000 | loss = 0.6878 | train_acc = 0.7500 epoch 3000 | loss = 0.5690 | train_acc = 1.0000 epoch 4000 | loss = 0.1720 | train_acc = 1.0000 epoch 5000 | loss = 0.0946 | train_acc = 1.0000 最终输出(接近 0/1 即正确): x=[0.0, 0.0] -> 预测=0.0841 目标=0 x=[0.0, 1.0] -> 预测=0.9140 目标=1 x=[1.0, 0.0] -> 预测=0.9024 目标=1 x=[1.0, 1.0] -> 预测=0.0929 目标=0 训练集准确率 = 1.0000, 最终 loss = 0.0946注意前 1000 步 loss 几乎不动(卡在 0.69 附近)——这是 XOR 损失曲面上的「平坦高原」,需要隐藏层把特征扭到可分空间后才会快速下降。这直观证明了「单层感知机做不了 XOR,加隐藏层 + 反向传播就能做」。
6.4 实战三:梯度下降与学习率对比
完整代码见src/gradient_descent.py。目标函数取最简单的凸函数f(w) = (w-3)²,最小值在w*=3,梯度df/dw = 2(w-3)。对比四种学习率:
梯度下降演示:单变量凸函数 f(w)=(w-3)^2,最优 w*=3 lr=0.05 (small) -> 末值 w= 2.9845, 末损失=0.0002 lr=0.30 (medium) -> 末值 w= 3.0000, 末损失=0.0000 lr=0.90 (large) -> 末值 w= 3.0000, 末损失=0.0000 lr=1.05 (diverge) -> 末值 w=-349.1726, 末损失=124025.5111 结论:lr 过大会导致超过最优点而震荡/发散(lr=1.05 时 w 来回反弹)。lr=0.05收敛但偏慢(末值 2.98 仍有误差);lr=0.30/0.90平滑落到最优;lr=1.05因步长超过「临界值 1」(对二次型,稳定要求lr < 2/L,这里L=2)而发散,损失爆炸到 12 万。下图一目了然:
6.5 实战四:从零实现 RNN / LSTM 做正弦预测
完整代码见src/rnn_lstm.py。用sin(t)序列做一步预测(用历史预测下一时刻),从零实现普通 RNN 与 LSTM,均用截断 BPTT + 梯度裁剪训练。
- RNN 前向:
h_t = tanh(Wxh·x_t + Whh·h_{t-1} + bh),y_t = h_t·Why + by - LSTM 前向:见上文门控公式;反向传播把误差沿时间
t与沿门控链同时回传。
为数值稳定,代码中sigmoid做了clip,梯度统一np.clip(g, -5, 5)防止爆炸。
真实运行结果(隐藏维度 H=16,序列长度 24,各训练 1500 epoch):
--- 训练 Vanilla RNN (H=16, lr=0.05, epochs=1500) --- epoch 1 | loss = 0.30262 epoch 250 | loss = 0.00012 epoch 500 | loss = 0.00007 epoch 1000 | loss = 0.00004 epoch 1500 | loss = 0.00003 --- 训练 LSTM (H=16, lr=0.02, epochs=1500) --- epoch 1 | loss = 0.47599 epoch 250 | loss = 0.00845 epoch 500 | loss = 0.00075 epoch 1000 | loss = 0.00033 epoch 1500 | loss = 0.00025 测试集 one-step-ahead MSE:RNN=0.00317 LSTM=0.00124两者都把训练损失压到 1e-4 量级,对已见过的训练区间拟合极好。在未见过的测试区间(t=30→40)做一步预测:RNN 的 MSE=0.00317,LSTM 的 MSE=0.00124,LSTM 泛化略优,与本实验数据规模下「门控有助于稳定记忆」的预期一致(注意:本演示数据较短、频率单一,LSTM 优势有限属正常;在长程、多变序列上差距会更明显)。
小结
| 主题 | 关键结论(来自真实运行) |
|---|---|
| 机器学习流程 | 标准化后的 Iris + 逻辑回归,测试准确率93.33%,setosa 完美可分。 |
| 感知机 | 线性可分数据2 轮即 100% 收敛,决策边界为直线。 |
| 两层 MLP | 隐藏层 + 反向传播成功解决XOR(单层感知机做不到),最终 loss 0.095、acc 1.0。 |
| 梯度下降 | 学习率是关键超参:lr<2/L才稳定,lr=1.05>1直接发散。 |
| RNN / LSTM | 两者都能拟合正弦;测试一步预测 MSE:RNN 0.00317 < LSTM 0.00124,门控略优。 |
工程经验
- 服务器无 GUI 时,
matplotlib必须matplotlib.use("Agg")并将图savefig为 PNG;中文图标签若服务器缺 CJK 字体会变成方块,本文统一用英文图注。 - 训练前务必标准化特征,否则梯度下降类算法收敛慢甚至不收敛。
- 反向传播的 bug 高发区是张量维度与链式法则的「时间方向」回传;用「前向缓存每一步中间量、反向再取用」的方式最稳。
- 梯度裁剪是对抗 RNN/LSTM 训练不稳定的廉价且有效的手段。
参考
- scikit-learn 官方文档:https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html
- sklearn 数据集
load_iris:https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_iris.html - 混淆矩阵与分类报告:
sklearn.metrics.confusion_matrix/classification_report - 学习曲线
sklearn.model_selection.learning_curve:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.learning_curve.html - Rumelhart, Hinton, Williams.Learning representations by back-propagating errors. Nature, 1986.(反向传播原始论文)
- Hochreiter & Schmidhuber.Long Short-Term Memory. Neural Computation, 1997.(LSTM 原始论文)
- 感知机收敛定理:Rosenblatt, F.The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain. 1958.
- 实验环境:华为云 ECS(Ubuntu 24.04 / 8 vCPU / 16 GB),Python 3.12 + numpy 2.5 / pandas 3.0 / matplotlib 3.11 / sklearn 1.9。
代码与数据:本仓库
src/(脚本)、figures/(图表)、results/(指标文本与 JSON)。
运行方式(远程,已建 venv 于/root/venv):/root/venv/bin/python src/ml_pipeline.py /root/venv/bin/python src/perceptron.py /root/venv/bin/python src/nn_mlp.py /root/venv/bin/python src/gradient_descent.py /root/venv/bin/python src/rnn_lstm.py