在职前端Leader学习/转行 AI Agent -DAY64

2026.09.25 13:16

学习路线

rust 复制代码
Python -> Python数据分析 -> LangChain -> LangGraph -> FastAPI + SQLAIchemy -> 机器学习 -> 深度学习 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding -> Hermes -> Docker

复习巩固总结

1. pytorch框架

text 复制代码
pytorch框架
├── 创建张量                              ← 已做完
├── 张量类型转换                          ← 已做完
├── ① 张量运算
│   ├── 基本运算    + - * / ,add / sub / mul / div / neg
│   │                 add_ / sub_ / ... 改原数据;t1 + 10
│   ├── 点乘        t1 * t2 ,t1.mul(t2)
│   └── 矩阵乘      t1 @ t2 ,t1.matmul(t2)
├── ① 张量运算函数
│   ├── 有 dim      sum / mean / max / min
│   └── 逐元素      pow / sqrt / exp / log / log2 / log10
├── ② 张量索引操作    t1[行, 列]
│   ├── 作用        tensor[0轴下标, 1轴下标, ...]
│   ├── 下标取值    tensor[0] → 0轴第一个
│   │               tensor[:, 0] → 1轴第一个
│   ├── 列表取值    tensor[[0,1], [2,4]]
│   │               → 0轴第1个×1轴第3个,0轴第2个×1轴第5个
│   ├── 范围取值    tensor[起始:结束:步长]
│   └── 布尔取值    tensor[:, 0] > 10          得到 True/False
│                   tensor[tensor[:, 0] > 10]  按条件抠行
├── ② 张量形状操作
│   ├── 不改摊平顺序    reshape / unsqueeze / squeeze / view
│   └── 换轴(摊平会变) transpose 一次两轴;permute 一次全排
├── ② 张量拼接操作
│   ├── cat      沿已有轴接,维数不变
│   └── stack    先开新轴再叠,维数 +1
├── ③ 自动微分模块
└── pytorch构建回归模型
text 复制代码
        w ──► * ──► + ──► z(预测值)──► loss ←── y(真实值)
        b ──────────┘                        │
                                             │ 求导
                                             ▼
                              BP 把梯度送回 w、b,改参数

2. 张量

2.1 创建

2.1.1 按数据 / 形状 / 指定类型
API 记住 掌握
torch.tensor(数据) 按已有数据,dtype 推断(整数 → int64,小数 → float32) ✓
torch.Tensor(...) 按形状或按数据,一律 float32 ;Tensor(10) 是长度 10 的空壳
torch.IntTensor / FloatTensor 指定 int32 / float32
2.1.2 线性和随机
API 记住 掌握
arange(起, 止, 步长) 步长固定,不含终点,整数默认 int64 ✓
linspace(起, 止, 个数) 个数固定,两端都含,float32 ✓
initial_seed() 看当前种子(默认时间戳)
manual_seed(n) 锁定随机序列,才能复现 ✓
rand(形状) (0,1)(0,1) (0,1) 均匀,float32
randn(形状) N(0,1)\mathcal{N}(0,1) N(0,1),float32
randint(low, high, size) [low,high)[low, high) [low,high) 整数,int64 ✓
2.1.3 全 0 / 全 1 / 指定值
灌什么 自己写形状 抄已有张量 掌握
全 0 zeros(形状) zeros_like(t) ✓
全 1 ones(形状) ones_like(t)
全是 vv v full(形状, v) full_like(t, v) ✓
2.1.4 元素类型转换
方法 dtype 掌握
.type(...) 传入目标类型,如 torch.float64 ✓
.half() float16
.float() float32
.double() float64
.short() int16
.int() int32
.long() int64

2.2 基本运算

运算 不改原数据 改原数据 符号
加 add add_ ++ +
减 sub sub_ −- −
乘 mul mul_ ∗* ∗
除 div div_ // /
取负 neg neg_ −t-t −t

2.3 点乘

A= 1 2 3 4 ,B= 5 6 7 8 A=\begin{bmatrix}1&2\\3&4\end{bmatrix},\quad B=\begin{bmatrix}5&6\\7&8\end{bmatrix} A=1324,B=5768
A∘B= 1×5 2×6 3×7 4×8 = 5 12 21 32 A\circ B=\begin{bmatrix}1\times5&2\times6\\3\times7&4\times8\end{bmatrix} =\begin{bmatrix}5&12\\21&32\end{bmatrix} A∘B=1×53×72×64×8=5211232

形状必须一样(或能广播)。这就是 张量的基本运算 里的 mul / *,不是线性层里那一下。

2.4 矩阵乘法

第一个形状 (n,m)(n,m) (n,m),第二个 (m,p)(m,p) (m,p),中间维 mm m 相等 ,结果 (n,p)(n,p) (n,p)。

PyTorch NumPy
矩阵乘 A @ B,torch.matmul(A, B) A @ B,np.dot(A, B)
点乘 A * B,A.mul(B) A * B

@ 专做矩阵乘。matmul 对更高维也行,只要最后两维 满足 (n,m)×(m,p)(n,m)\times(m,p) (n,m)×(m,p)。NumPy 二维矩阵乘常用 dot()。

还是上面的 AA A、 BB B,中间过程:
AB= 1×5+2×7 1×6+2×8 3×5+4×7 3×6+4×8 = 19 22 43 50 A B=\begin{bmatrix} 1\times5+2\times7 & 1\times6+2\times8 \\ 3\times5+4\times7 & 3\times6+4\times8 \end{bmatrix} =\begin{bmatrix}19&22\\43&50\end{bmatrix} AB=1×5+2×73×5+4×71×6+2×83×6+4×8=19432250

和点乘的 5 12 21 32 \begin{bmatrix}5&12\\21&32\end{bmatrix} 5211232 完全不是一串数。线性层 z=wx+bz=wx+b z=wx+b 走的是矩阵乘 @ / matmul。

2.5 运算函数

拆成两拨------有没有 dim。

一拨 函数 dim 掌握
沿轴归约 sum 有。dim=0 按列,dim=1 按行;不写 dim 就是整份张量 ✓
沿轴归约 max 同上 ✓
沿轴归约 min 同上 ✓
沿轴归约 mean 同上 ✓
逐元素 pow、sqrt、exp、log、log2、log10 没有。每个数单独算,形状不变

一张 2×32\times 3 2×3 的表 1 2 3 4 5 6 \begin{bmatrix}1&2&3\\4&5&6\end{bmatrix} 142536:

调用 结果在算什么 掌握
sum(dim=0) 每列相加 → 5, 7, 95,\\ 7,\\ 9 5, 7, 9 ✓
sum(dim=1) 每行相加 → 6, 156,\\ 15 6, 15 ✓
sum() 六个数全加 → 2121 21 ✓
max(dim=0) 每列最大 → 4, 5, 64,\\ 5,\\ 6 4, 5, 6(还会带上最大值在该列的位置) ✓
max() 全局最大 → 66 6 ✓
min() 全局最小 → 11 1 ✓
mean() 六个数的平均 → 3.53.5 3.5(整数张量要先转成浮点,否则 mean 会报错) ✓

min 和 max 同一套,只是取最小。

逐元素那拨,形状不变。输入 1,4,91,4,9 1,4,9:

调用 在算什么 结果 掌握
sqrt() 开方 1, 2, 31,\\ 2,\\ 3 1, 2, 3
exp() exe^x ex e, e4, e9e,\\ e\^4,\\ e\^9 e, e4, e9
log() ln⁡\ln ln ln⁡1, ln⁡4, ln⁡9\\ln 1,\\ \\ln 4,\\ \\ln 9 ln1, ln4, ln9
log2() log⁡2 \log_2 log2 0, 2, log⁡2 90,\\ 2,\\ \\log_2 9 0, 2, log29
log10() log⁡10 \log_{10} log10 0, log⁡10 4, 2 0,\\ \\log_{10}4,\\ 2 0, log104, 2
pow(n) 每个数的 nn n 次方 如 pow(2) → 1, 16, 811,\\ 16,\\ 81 1, 16, 81

2.6 索引操作

取某些格子出来处理或改掉。准备一张 4×54\times 5 4×5:
0 7 6 5 9 6 8 3 1 0 6 3 8 7 3 4 9 5 3 1 \begin{bmatrix} 0&7&6&5&9\\ 6&8&3&1&0\\ 6&3&8&7&3\\ 4&9&5&3&1 \end{bmatrix} 06647839638551739031

种类 写法 取出什么 掌握
简单行列 data[0] 第 0 行 0,7,6,5,90,7,6,5,9 0,7,6,5,9 ✓
简单行列 data[:, 0] 第 0 列 0,6,6,40,6,6,4 0,6,6,4 ✓
列表 data[[0,1], [1,2]] 两个点 (0,1)(0,1) (0,1)、 (1,2)(1,2) (1,2) → 7, 37,\\ 3 7, 3
列表 data[[[0],[1]], [1,2]] 第 0、1 行 × 第 1、2 列,共 4 个数 7 6 8 3 \begin{bmatrix}7&6\\8&3\end{bmatrix} 7863
范围 data[:3, :2] 前 3 行、前 2 列 0 7 6 8 6 3 \begin{bmatrix}0&7\\6&8\\6&3\end{bmatrix} 066783 ✓
范围 data[2:, :2] 第 2 行到末、前 2 列 6 3 4 9 \begin{bmatrix}6&3\\4&9\end{bmatrix} 6439 ✓
布尔 data[data[:, 2] > 5] 第 2 列 >5>5 >5 的那些行(第 0、2 行)
布尔 data[:, data[1] > 5] 第 1 行 >5>5 >5 的那些列(第 0、1 列)

列表那两行不是一回事:[0,1] 配 [1,2] 是配对取点 ;行写成列向量 [[0],[1]] 才和 [1,2] 拉开成一块小矩形。

范围左闭右开,和 Python 切片一样::3 是 0,1,20,1,2 0,1,2,2: 从第 2 行一直到头。

布尔:第 2 列是 6,3,8,56,3,8,5 6,3,8,5,大于 5 的是第 0、2 行;第 1 行是 6,8,3,1,06,8,3,1,0 6,8,3,1,0,大于 5 的是第 0、1 列。

多维按轴抠。下面这块形状是 (2,3,4)(2,3,4) (2,3,4):0 轴两块,每块一张 3×43\times4 3×4。

text 复制代码
[  0轴,2 个元素(两块)
   [  1轴,3 个元素(每块 3 行)
      [3, 4, 6, 5],     ← 2轴,4 个元素
      [8, 8, 8, 3],
      [4, 9, 6, 7]
   ],
   [  1轴,3 个元素
      [2, 8, 8, 5],
      [6, 4, 2, 2],
      [2, 7, 9, 4]
   ]
]

写成两张矩阵,一眼能对上:
第 0 块 3 4 6 5 8 8 8 3 4 9 6 7 第 1 块 2 8 8 5 6 4 2 2 2 7 9 4 \text{第 0 块}\quad \begin{bmatrix} \mathbf{3}&4&6&5\\ \mathbf{8}&8&8&3\\ \mathbf{4}&9&6&7 \end{bmatrix} \qquad \text{第 1 块}\quad \begin{bmatrix} \mathbf{2}&8&8&5\\ \mathbf{6}&4&2&2\\ \mathbf{2}&7&9&4 \end{bmatrix} 第 0 块 384489686537 第 1 块 262847829524

加粗是每行开头,也就是 2 轴第 0 个。

轴 几个元素 对应谁
0 轴 2 上面两张表(两块)
1 轴 3 每一张的 3 行
2 轴 4 每一行的 4 个数

: 表示这一轴全要,只钉死要的那一轴。

写法 取出什么 掌握
data[0, :, :] 0 轴第 0 块:左边那张 3 4 6 5 8 8 8 3 4 9 6 7 \begin{bmatrix}3&4&6&5\\8&8&8&3\\4&9&6&7\end{bmatrix} 384489686537 ✓
data[:, 0, :] 1 轴第 0 行:两张表各自第一行 3,4,6,53,4,6,5 3,4,6,5、 2,8,8,52,8,8,5 2,8,8,5 ✓
data[:, :, 0] 2 轴第 0 个数(加粗): 3,8,43,8,4 3,8,4 和 2,6,22,6,2 2,6,2 ✓

2.7 形状操作

数一个不改,改的是怎么排。先按「摊平后顺序变不变」拆成两拨,再逐个看怎么用。

函数 干什么 摊平后顺序 掌握
reshape(...) 另排形状,个数必须对上 不变 ✓
unsqueeze(dim) 指定轴塞一个长度为 1 的维(升维) 不变 ✓
squeeze() 删掉所有长度为 1 的维(降维) 不变
view(...) 另排形状,只吃连续张量 不变 ✓
transpose(a, b) 一次只换两个轴 变
permute(...) 一次按新顺序排完全部轴 变 ✓
contiguous() 按当前显示顺序,把内存拷成连续 ---
is_contiguous() 查内存顺序和显示顺序是否一致 ---

源数据 6,9,9,2,8,76,9,9,2,8,7 6,9,9,2,8,7,排成 (2,3)(2,3) (2,3):
6 9 9 2 8 7 \begin{bmatrix}6&9&9\\2&8&7\end{bmatrix} 629897

不改变内容 改变内容
排成 6 9 9 2 8 7 \begin{bmatrix}6&9\\9&2\\8&7\end{bmatrix} 698927 6 2 9 8 9 7 \begin{bmatrix}6&2\\9&8\\9&7\end{bmatrix} 699287
再摊平 6,9,9,2,8,76,9,9,2,8,7 6,9,9,2,8,7 和源数据一样 6,2,9,8,9,76,2,9,8,9,7 6,2,9,8,9,7 顺序已经换了

左边按行接着读。右边按列抽:先第 0 列 6,26,2 6,2,再 9,89,8 9,8,再 9,79,7 9,7。reshape / unsqueeze / squeeze / view 走左边;transpose / permute 走右边。

2.7.1 unsqueeze / squeeze

只多一层壳或少一层壳,数的顺序不变。 (2,3)(2,3) (2,3) 上:

写法 形状 注意 掌握
unsqueeze(0) (1,2,3)(1,2,3) (1,2,3) 最外面加一块 dim 只能是 0∼0\sim 0∼ 当前维数(含两端) ✓
unsqueeze(1) (2,1,3)(2,1,3) (2,1,3) 每行外面加一层 ✓
unsqueeze(2) (2,3,1)(2,3,1) (2,3,1) 每个数竖成一列 和 unsqueeze(-1) 一样,最后一轴 ✓
unsqueeze(3) 越界,报错 (2,3)(2,3) (2,3) 没有第 3 轴可插
squeeze() (2,1,3,1,1)→(2,3)(2,1,3,1,1)\to(2,3) (2,1,3,1,1)→(2,3) 所有长度为 1 的维一起删,不是只删一个

dim=0 往最外面加一层;dim=-1 往最后加。squeeze() 不写轴就全挤;只想挤某一轴要写成 squeeze(dim)。

2.7.2 transpose / permute

轴对调,摊平顺序会变。transpose 一次只换两个轴 ;permute 一次按新顺序排完全部轴。

形状 (2,3,4)(2,3,4) (2,3,4):

写法 轴怎么走 新形状 掌握
transpose(0, 1) 只换 0 和 1 (3,2,4)(3,2,4) (3,2,4)
transpose(0, 2) / transpose(0, -1) 只换 0 和最后一轴 (4,3,2)(4,3,2) (4,3,2)
permute(2, 0, 1) 新顺序 = 旧的 2,0,12,0,1 2,0,1 维 (4,2,3)(4,2,3) (4,2,3) ✓

形状 (3,4,5)(3,4,5) (3,4,5) 要变成 (4,5,3)(4,5,3) (4,5,3):permute(1, 2, 0) 一次写完;用 transpose 得先 transpose(0, 1) 再 transpose(1, 2)。

permute(1, 2, 0) 读法:新第 0 轴拿旧第 1 轴(长度 4),新第 1 轴拿旧第 2 轴(长度 5),新第 2 轴拿旧第 0 轴(长度 3)。* 是位置参数,轴号按顺序塞进去,和传列表 [1, 2, 0] 一样。

2.7.3 view / contiguous

view 也能改形状,但只吃连续张量 :内存里挨着存的顺序,要和逻辑上按行读的顺序一致。transpose / permute 之后这两套对不上,直接 view 会报错。先 contiguous() 按显示顺序拷一份,再 view。is_contiguous() 用来查。
10 20 30 40 50 60 (2,3)连续 True \begin{bmatrix}10&20&30\\40&50&60\end{bmatrix} \quad(2,3)\quad\text{连续 True} 104020503060(2,3)连续 True

步骤 得到 连续? 掌握
view(3, 2) 10 20 30 40 50 60 \begin{bmatrix}10&20\\30&40\\50&60\end{bmatrix} 103050204060 摊平仍是 10,20,30,40,50,6010,20,30,40,50,60 10,20,30,40,50,60 True ✓
transpose(0, 1) 10 40 20 50 30 60 \begin{bmatrix}10&40\\20&50\\30&60\end{bmatrix} 102030405060 False
直接 view(2, 3) 报错
contiguous().view(2, 3) 10 40 20 50 30 60 \begin{bmatrix}10&40&20\\50&30&60\end{bmatrix} 105040302060 True

和 reshape 的差别:个数都得对上;不连续时 reshape 往往自己拷一份,view 必须你先 contiguous()。新张量默认连续,转置过后才断。

2.8 拼接操作

两张表接在一起。先看差在哪,再分别写 dim。

函数 维数 形状要求 掌握
torch.cat((A, B), dim) 不增加,2 维还是 2 维 没被指定的那几维长度要相同 ✓
torch.stack((A, B), dim) 多出一个新轴,2 维变成 3 维 参加拼接的张量形状必须完全一样 ✓

cat 是把已有的轴拉长;stack 是先 unsqueeze 再 cat。

2.8.1 torch.cat:沿已有轴接

两块都是 (1,2,3)(1,2,3) (1,2,3):
A= 7 8 7 6 3 6 B= 3 6 5 7 5 0 A=\begin{bmatrix}7&8&7\\6&3&6\end{bmatrix} \qquad B=\begin{bmatrix}3&6&5\\7&5&0\end{bmatrix} A=768376B=376550

dim 在接哪 形状 结果
00 0 最外层叠块 (2,2,3)(2,2,3) (2,2,3) 上下两块 AA A、 BB B
11 1 行方向接 (1,4,3)(1,4,3) (1,4,3) 7 8 7 6 3 6 3 6 5 7 5 0 \begin{bmatrix}7&8&7\\6&3&6\\3&6&5\\7&5&0\end{bmatrix} 763783657650
22 2 列方向接 (1,2,6)(1,2,6) (1,2,6) 7 8 7 3 6 5 6 3 6 7 5 0 \begin{bmatrix}7&8&7&3&6&5\\6&3&6&7&5&0\end{bmatrix} 768376376550

两张 (2,3)(2,3) (2,3) 更直观:cat(dim=0) → (4,3)(4,3) (4,3) 往下接行;cat(dim=1) → (2,6)(2,6) (2,6) 往右接列。

没被指定的那几维长度要相同,否则接不上。 (2,3)(2,3) (2,3) 配 (2,4)(2,4) (2,4):dim=0 报错(列 3 对 4),dim=1 能接成 (2,7)(2,7) (2,7)。 (2,3)(2,3) (2,3) 配 (5,3)(5,3) (5,3):dim=1 报错(行 2 对 5),dim=0 能接成 (7,3)(7,3) (7,3)。

2.8.2 torch.stack:先开一个新轴再叠

两张 (2,3)(2,3) (2,3):
A= 6 2 8 9 8 3 B= 9 2 8 0 7 9 A=\begin{bmatrix}6&2&8\\9&8&3\end{bmatrix} \qquad B=\begin{bmatrix}9&2&8\\0&7&9\end{bmatrix} A=692883B=902789

dim 新轴插在哪 形状 结果
00 0 最外面 (2,2,3)(2,2,3) (2,2,3) 第 0 块是 AA A,第 1 块是 BB B
11 1 行之间 (2,2,3)(2,2,3) (2,2,3) 每张表的第 ii i 行对到一起
22 2 / -1 列之间 (2,3,2)(2,3,2) (2,3,2) 6 9 2 2 8 8 \begin{bmatrix}6&9\\2&2\\8&8\end{bmatrix} 628928 和 9 0 8 7 3 9 \begin{bmatrix}9&0\\8&7\\3&9\end{bmatrix} 983079 (对应位置的数配成一对)
33 3 越界,报错 (2,3)(2,3) (2,3) 最多插到第 2 轴

stack 无论新旧轴,两边形状都得一模一样: (2,3)(2,3) (2,3) 配 (2,4)(2,4) (2,4) 或 (5,3)(5,3) (5,3) 都会报错。dim=-1 插在最后一轴,二维时和 dim=2 一样。

2.9 和Numpy之间互相转换

CPU 上的张量和 NumPy 的 ndarray 能互转。关键点是改一边,另一边会不会跟着变(是否共享内存)。

方向 API 共享内存 掌握
张量 → ndarray t.numpy() 是,改 n 会改 t
张量 → ndarray t.numpy().copy() 否
张量 → ndarray(带梯度时) t.detach().numpy() 先切断计算图再转 ✓
ndarray → 张量 torch.from_numpy(n) 是
ndarray → 张量 torch.tensor(n) 否,拷一份 ✓
单元素张量 → Python 数 t.item() --- ✓

3. 自动微分

一句话:自动微分就是求导 。对损失函数求导,结合反向传播,更新权重 ww w、偏置 bb b。

3.1 小问题_detach函数

python 复制代码
"""
案例:
    演示 detach() 函数的功能,解决 自动微分的弊端

回顾:
    自动微分 = 求导,即:基于损失函数,计算梯度
    结合权重更新公式:W新 = W旧 - 学习率 * 梯度,来更新权重的

问题:
    一个张量一旦设置了自动微分,这个张量就不能直接转成 nunmpy 的 ndarray 对象了,需要通过 detach() 函数解决
"""
# 导包
import torch
import numpy as np

# 1. 定义张量
# 参1:数据  参2:是否需要自动微分  参3:数据类型
# t1 = torch.tensor([10, 20], dtype=torch.float)
t1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float)
print(f't1: {t1}, type: {type(t1)}')

# 2. 尝试把上述的张量 -> numpy对象
# n1 = t1.numpy()  # 报错
# print(f'n1: {n1}, type: {type(n1)}')

# 3. 解决办法:通过 detach() 函数,拷贝一份张量,然后转换
t2 = t1.detach()
print(f't2: {t2}, type: {type(t2)}')

# 4. 测试上述的t1和t2是否共享一块空间 -> 共享
# t1[0] = 100
# print(t1.data)
t1.data[0] = 100
print(f't1: {t1}, type: {type(t1)}')
print(f't2: {t2}, type: {type(t2)}')
print('-' * 30)

# 5. 查看t1和t2谁可以自动微分
print(f't1.requires_grad: {t1.requires_grad}')  # True
print(f't2.requires_grad: {t2.requires_grad}')  # False
print('-' * 30)

# 6. 把t2转numpy对象
n2 = t2.numpy()
print(f'n2: {n2}, type: {type(n2)}')

# 7. 最终版
n3 = t1.detach().numpy()
print(f'n3: {n3}, type: {type(n3)}')

3.2 更新一次参数

PyTorch 只支持标量对张量求导 。 xx x 可以是一串数, yy y 必须是一个数 才能 backward()。损失若是向量,先 loss.sum().backward() 收成标量。.grad 会累加上一次的梯度,一步更新看完要心里有数。

走一遍 L=2w2L=2w^2 L=2w2, w=10w=10 w=10,学习率 0.010.01 0.01。创建时 requires_grad=True,并且用浮点。
L=2w2=2×102=200, dLdw =4w=40L=2w^2=2\times10^2=200,\qquad \frac{dL}{dw}=4w=40 L=2w2=2×102=200,dwdL=4w=40

步骤 干什么 得到
正向 loss = 2 * w ** 2 200200 200
反向 loss.sum().backward()(这里本就是标量,.sum() 可省) w.grad=40 w.\mathrm{grad}=40 w.grad=40
更新 w←w−0.01×40w\leftarrow w-0.01\times 40 w←w−0.01×40,写在 w.data 上以免再进计算图 10−0.4=9.610-0.4=9.6 10−0.4=9.6

和手推那刀一样。自动微分只替你算出这个 4040 40。

python 复制代码
"""
案例:
    演示自动微分模块,具体如何求导

回顾:
    权重更新公式:
        W新 = W旧 - 学习率 * 梯度
        梯度 = 损失函数的导数

    关于损失函数的导数,无需我们手动计算,因为非常常用,所以 PyTorch 模块内置有 自动微分模块,专门实现针对于不同的损失函数求导,
    从而实现结合反向传播,更新 权重参数w 和 偏置参数b

细节:
    只有标量张量才能求导,且大多数底层操作的都是 浮点型,记得转型
"""
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
# 导包
import torch

# 1. 定义变量,记录:初始的权重w(旧)
# 参1:初始值  参2:是否自动微分(求导)  参3:数据类型
w = torch.tensor(10.0, requires_grad=True, dtype=torch.float)

# 2. 定义loss变量,表示损失函数
loss = 2 * w ** 2  # loss = 2w² -> 求导:4w

# 3. 打印梯度函数类型(了解)
print(f'梯度函数类型: {type(w.grad_fn)}')  # <class 'MulBackward0'>
print(loss.sum())  # 2 * 100² = 200

# 4. 计算梯度,梯度 = 损失函数的导数,计算完毕后,会记录到 w.grad 属性中
# loss.backward()       # 因为这里y本身就是标量,可以不写sum()
loss.sum().backward()   # 保证loss是1个标量

# 5. 代入 权重更新公式:w新 = w旧 - 学习率 * 梯度
w.data = w.data - 0.01 * w.grad

# 6. 打印最终结果
print(f'更新后的权重w: {w}')  # 9.6

3.3 循环更新参数

公式还是
w←w−r⋅gradw\leftarrow w-r\cdot\mathrm{grad} w←w−r⋅grad

rr r 是学习率。.grad 会累加,每轮 backward() 之前 要 w.grad.zero_() 清掉上一次。第一轮还没反传过,.grad 是 None,得先判断非空再清。

求 y=w2+20y=w^2+20 y=w2+20 的极小值。开口向上,最低在 w=0w=0 w=0,那时 y=20y=20 y=20。起点 w=10w=10 w=10,浮点、requires_grad=True,学习率 0.010.01 0.01,转上百轮(有的写法写 1000)。
y=w2+20=120,y′=2w=20y=w^2+20=120,\qquad y'=2w=20 y=w2+20=120,y′=2w=20

每一轮:

顺序 干什么
1 正向 用当前 ww w 算 y=w2+20y=w^2+20 y=w2+20
2 清零 zero_(),否则梯度叠在旧的上面
3 反向 y.sum().backward(),.grad 里是 2w2w 2w
4 更新 w.data = w.data - 0.01 * w.grad

第一刀: 10−0.01×20=9.810-0.01\times20=9.8 10−0.01×20=9.8, yy y 从 120120 120 往下降。再走, ww w 往 00 0 贴, yy y 往 2020 20 贴。改的是 w.data,避免更新本身再进计算图。

python 复制代码
"""feature_cols
案例:
    演示自动微分模块,循环实现 计算梯度,更新参数

需求:
    求 y = x ** 2 + 20 的极小值点,并打印y是最小时 w的值(梯度)

解题步骤:
    1. 定一点 w = 10 requires_grad = True, dtype = float
    2. 定义函数 loss = w ** 2 + 20
    3. 利用梯度下降法 循环迭代 1000 求最优解
    3.1 正向计算(前向传播)
    3.2 梯度清零 w.grad.zero_()
    3.3 反向传播
    3.4 梯度更新 w.data = w.data - 0.01 * w.grad

回顾:
    W新 = W旧 - 学习率 * 梯度
"""
# 导包
import torch

# 1. 定一点 w = 10 requires_grad = True, dtype = float
# 参1:初始值  参2:自动微分(求导)  参3:数据类型,浮点型
w = torch.tensor(10.0, requires_grad=True, dtype=torch.float)

# 2. 定义函数 loss = w ** 2 + 20
loss = w ** 2 + 20  # 求导:loss' = 2w

# 3. 利用梯度下降法 循环迭代 100 求最优解
print(f'开始 权重初始值:{w}, (0.01 * w.grad):无,loss:{loss}')  # 10,无,120

# 迭代100次,求最优解
for i in range(1, 101):
    # 3.1 正向计算(前向传播)
    loss = w ** 2 + 20  # 求导:loss' = 2w

    # 3.2 梯度清零 w.grad.zero_() 默认梯度会累加
    # 至此(第一次的时候),还没有计算梯度,所以w.grad=None,需要做非空判断
    if w.grad is not None:
        w.grad.zero_()

    # 3.3 反向传播
    # loss.backward()
    loss.sum().backward()
    
    # 3.4 梯度更新 w.data = w.data - 0.01 * w.grad
    print(f'梯度值为:{w.grad}')
    w.data = w.data - 0.01 * w.grad

    # 3.5 打印本次梯度更新后的权重参数结果
    print(f'第{i}次 权重参数更新后:{w}, (0.01 * w.grad):{0.01 * w.grad:.5f}, loss:{loss:.5f}')

# 4. 打印最终结果
print(f'最终 权重参数:{w}, 梯度:{w.grad:.5f}, loss:{loss:.5f}')

3.4 真实应用场景

python 复制代码
"""
案例:
    演示自动微分的真实应用场景

结论:
    1. 先前向传播(正向传播)计算出 预测值(z)
    2. 基于损失函数,结合 预测值(z)和 真实值(y),来计算 梯度
    3. 结合权重更新公式:W新 = W旧 - 学习率 * 梯度,来更新权重
"""
# 导包
import torch

# 1. 定义x,表示:特征(输入数据),假设:2行5列,全1矩阵
x = torch.ones(2, 5)
print(f'x: {x}')

# 2. 定义y,表示:真标签(真实值),假设:2行3列,全0矩阵
y = torch.zeros(2, 3)
print(f'y: {y}')

# 3. 初始化(可自动微分的)权重和偏置
w = torch.randn(5, 3, requires_grad=True)  # x @ w + b
print(f'w: {w}')

b = torch.randn(3, requires_grad=True)
print(f'b: {b}')

# 4. 前向传播(正向传播)计算出 预测值(z)
# z = torch.matmul(x, w) + b
z = x @ w + b
print(f'z: {z}')

# 5. 定义损失函数
criterion = torch.nn.MSELoss()  # neural network:神经网络
loss = criterion(z, y)          # loss = 损失
print(f'loss: {loss}')

# 6. 进行自动微分,求导,结合反向传播,更新权重
loss.backward()

# 7. 打印w, b用来更新的梯度
print(f'w.grad: {w.grad}')
print(f'b.grad: {b.grad}')

# 后续就是:W新 = W旧 - 学习率 * 梯度,来更新权重

4. PyTorch模拟线性回归

用手写的 ww w、 bb b、循环改参数,换成 PyTorch 组件。整条流程四步:

步 干什么 对应组件
1 准备训练集 make_regression → 张量 →(后面)TensorDataset / DataLoader
2 构建模型 继承 nn.Module;假设函数用 nn.Linear
3 损失和优化器 nn.MSELoss、optim.SGD
4 训练循环 正向 → 反向 → 更新

手写那套和组件的对应:

原来自己写 换成
平方损失 L=(z−y)2L=(z-y)^2 L=(z−y)2 nn.MSELoss()
按批喂数 data.DataLoader
w←w−r⋅gradw\leftarrow w-r\cdot\mathrm{grad} w←w−r⋅grad optim.SGD
z=wx+bz=wx+b z=wx+b nn.Linear
python 复制代码
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
# 导入相关模块
import torch
from torch.utils.data import TensorDataset, DataLoader  # 构造数据集对象,数据加载器(numpy对象 -> 张量Tensor -> 数据集对象TensorDataset -> 数据加载器DataLoader)
from torch import nn                                    # nn模块中有平方损失函数和假设函数
from torch import optim                                 # optim模块中有优化器函数
from sklearn.datasets import make_regression            # 创建线性回归模型数据集
import matplotlib.pyplot as plt                         # 可视化

plt.rcParams['font.family'] = ['SimHei']                # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False              # 用来正常显示负号

# 1. 定义函数,创建线性回归样本数据
def create_dataset():
    # 1.1 创建数据集对象
    x, y, coef = make_regression(
        n_samples=100,  # 100条样本(100个样本点)
        n_features=1,   # 1个特征(1个特征点)
        noise=10,       # 噪声,噪声越大,样本点越散,噪声越小,样本点越集中
        coef=True,      # 是否返回系数,默认为False,返回值为None
        bias=14.5,      # 偏置
        random_state=3  # 随机种子,随机种子相同,输出数据相同
    )
    # print(type(x))  # <class 'numpy.ndarray'>

    # 1.2 把上述的数据封装成张量对象
    x = torch.tensor(x, dtype=torch.float)
    y = torch.tensor(y, dtype=torch.float)

    # 1.3 返回结果
    return x, y, coef

# 2. 定义函数,表示模型训练
def train(x, y, coef):
    # pass
    # 2.1 创建数据集对象,把 tensor -> 数据集对象 -> 数据加载器
    dataset = TensorDataset(x, y)

    # 2.2 创建数据加载器对象
    # 参1:数据集对象  参2:批量大小  参3:是否打乱数据(训练集打乱,测试集不打乱)
    dataloader = DataLoader(dataset, batch_size=16, shuffle=True)

    # 2.3 创建初始的 线性回归模型
    # 参1:输入特征维度  参2:输出特征维度
    model = nn.Linear(1, 1)

    # 2.4 创建损失函数对象
    criterion = nn.MSELoss()

    # 2.5 创建优化器对象
    # 参1:模型参数  参2:学习率
    optimizer = optim.SGD(model.parameters(), lr=0.01)

    # 2.6 具体的训练过程
    # 2.6.1 定义变量,分别表示:训练轮数,每轮的(平均)损失值,训练总损失值,训练的样本数
    epochs, loss_list, total_loss, total_sample = 100, [], 0, 0
    # 2.6.2 开始训练,按轮训练
    for epoch in range(epochs):  # epoch的值:0, 1, 2, ..., 99
        # 每轮重新累计,否则平均损失会混进前面所有轮
        total_loss, total_sample = 0, 0
        # 2.6.3 每轮是分批次训练的,所以从数据加载器中获取批次数据
        for train_x, train_y in dataloader:  # 7批(16, 16, 16, 16, 16, 16, 4)
            # 2.6.3.1 模型预测
            y_pred = model(train_x)
            # 2.6.3.2 计算(每批的平均)损失值
            loss = criterion(y_pred, train_y.reshape(-1, 1))  # -1 自动计算
            # 2.6.3.3 计算总损失 和 批次个数
            total_loss += loss.item()
            total_sample += 1
            # 2.6.3.4 梯度清零 + 反向传播 + 梯度更新
            optimizer.zero_grad()  # 梯度清零
            loss.backward()        # 反向传播,计算梯度
            optimizer.step()       # 梯度更新

        # 2.6.4 把本轮的(平均)损失值,添加到列表中
        loss_list.append(total_loss / total_sample)
        print(f'轮数:{epoch+1}, 平均损失值:{loss_list[-1]:.4f}')

    # 2.7 打印(最终的)训练结果
    print(f'{epochs}轮的平均损失分别为:{loss_list}')
    print(f'模型参数,权重:{model.weight}, 偏置:{model.bias}')

    # 2.8 绘制损失曲线
    #               100轮  每轮的平均损失值
    plt.plot(range(epochs), loss_list)
    plt.title('损失值曲线变化图')
    plt.grid()  # 绘制网格线
    plt.show()

    # 2.9 绘制预测值和真实值的关系
    # 2.9.1 绘制样本点分布情况
    plt.scatter(x, y)
    # 2.9.2 绘制训练模型的预测值
    # x: 100个样本点的特征
    y_pred = torch.tensor(data = [v * model.weight + model.bias for v in x])
    # 2.9.3 计算真实值
    y_true = torch.tensor(data = [v * coef + 14.5 for v in x])
    # 2.9.4 绘制预测值和真实值的折线图
    plt.plot(x, y_pred, color='red', label='预测值')
    plt.plot(x, y_true, color='blue', label='真实值')
    # 2.9.5 图例,网格
    plt.legend()
    plt.grid()
    # 2.9.6 显示图像
    plt.show()

# 3. 测试
if __name__ == '__main__':
    # 3.1 创建数据集
    x, y, coef = create_dataset()
    # print(f'x: {x}, y: {y}, coef: {coef}')

    # 3.2 模型训练
    train(x, y, coef)

深度学习

34. 如何构建神经网络(neural network)

机器学习、深度学习都是实现 AI 的一种途径。

怎么来的
机器学习 数学好,公式能推出来
深度学习 解释性稍差。6 层比 5 层好,为什么好往往说不清,是实验试出来的

后面按这条往下:神经网络 → 损失函数 → 网络优化方法 → 正则化方法 → 价格分类案例。这一节先把网络本身怎么搭起来。

34.1 什么是神经网络

人工神经网络(Artificial Neural Network,ANN),也简称神经网络(NN)。它是模仿生物神经网络结构和功能的计算模型。

人脑就是一堆神经元连起来的。神经元传电信号:树突接收输入,处理完,通过轴突把信号送出去。电信号从树突进到细胞核,电荷慢慢攒;攒到一定电位,细胞被激活,再从轴突发出去。

生物 网络里
树突,输入信号 输入层
细胞核里攒电荷,到电位才激活 隐藏层:先线性算出预测值,再过激活函数
轴突末梢,输出 输出层

一条计算链:输入数据 → 线性模型算出预测值 → Sigmoid → 概率,落在 0,10,1 0,1。

34.2 一个神经元

神经网络由多个神经元组成。构建神经网络,就是在构建神经元。

每个输入 xi x_i xi 带一个权重 wi w_i wi,再加偏置 bb b。先做线性的加权求和,再交给激活函数 ff f 做非线性,吐出 f(x)f(x) f(x)。分类时这个输出当概率。
z=b+ ∑i=1n xiwi=wx+b,y=f(z) z=b+\sum_{i=1}^{n}x_i w_i=wx+b,\qquad y=f(z) z=b+i=1∑nxiwi=wx+b,y=f(z)

一段 结构 干什么
加权求和 ∑\sum ∑ 线性 不同树突带来的信息,各自乘上权重,在细胞里加起来
激活函数 ff f 非线性 把前面的和映射出去。常用 Sigmoid,映到 0,10,1 0,1

特征 → 线性回归得到预测值 → Sigmoid → 0,10,1 0,1。

例如用身高、体重、学历、专业、考试成绩、工作经验、性别去预测就业薪资:有几个特征,输入就有几路,每路一个权重。

34.3 多个神经元连成网

相邻层的神经元互相连接,每条连接分配一个强度(权重)。同层神经元之间互相隔离,不连。上一层每个都连下一层每一个,就是全连接(Full)。

有几个特征,输入层就有几个神经元。隐藏层可以看成若干个这样的人,每人收齐上一层全部输入。前一层的输出就是后一层的输入。

层 干什么
Input 输入层 特征个数 = 神经元个数
Hidden Layer 1 加权求和
Hidden Layer 2 激活函数
Output 输出层 得到 y1 y_1 y1、 y2 y_2 y2。激活常用 Sigmoid、Tanh、ReLU、Softmax

连续输出要收成类别:例如 0.250.25 0.25 近似当成 11 1;大于等于 00 0 的都当成 11 1。

权重还是那条更新公式:
W1=W0−学习率×梯度 W_1=W_0-\text{学习率}\times\text{梯度} W1=W0−学习率×梯度

35. 神经网络_文字介绍

信息只朝一个方向走:从输入节点向前,经过隐藏节点,再到输出节点。34.3 里的全连接,用文字钉成下面这几条。

35.1 三层

层 是什么 干什么
输入层 Input Layer 输入 xx x 的那一层。图像、文本、声音都从这里进 每个输入特征对应一个神经元,把数据传给下一层
隐藏层 Hidden Layers 夹在输入和输出之间,可以有多层 加权求和,再过激活函数,把结果往后传
输出层 Output Layer 输出 yy y 的那一层 给出结果

图像进网络之前,一种排法是 HWC:高、宽、通道。 (32,32,3)(32,32,3) (32,32,3) 就是高 32、宽 32、3 个通道。

三特征进第一层隐藏、出来 5 个神经元,写成 nn.Linear(3, 5)。输入层神经元个数 = 特征个数。再往后若仍是 5 个神经元,形状就是 (5,5)(5,5) (5,5)。

35.2 连接规则

  • 同一层的神经元之间没有连接,互相隔离
  • 第 NN N 层的每个神经元,和第 N−1N-1 N−1 层的所有神经元相连(全连接)
  • 第 N−1N-1 N−1 层的输出,就是第 NN N 层的输入
  • 每条连接都有权重: ww w 和 bb b
  • 全连接网络接收的样本数据是二维的

35.3 内部状态值和激活值

每个神经元干活时带四个数。前向传播产出两个,反向传播再产出两个梯度。

值 梯度
内部状态 内部状态值(加权求和) 内部状态值梯度
激活 激活值 激活值梯度

隐藏层里每个神经元都是这四格:左上内部状态值,右上激活值,左下内部状态值梯度,右下激活值梯度。

内部状态值是神经元里存着的那个数,反映当前收到的输入、历史信息,以及网络内部的状态。
z=W⋅x+bz=W\cdot x+b z=W⋅x+b

WW W 是权重矩阵, xx x 是输入, bb b 是偏置。

激活值 是用激活函数对 zz z 做非线性变换之后的结果。常见的有 ReLU、Sigmoid、Tanh。
a=f(z)a=f(z) a=f(z)

ff f 是激活函数, zz z 是内部状态值。前向先有 zz z,再有 aa a;反向前,梯度从激活值回到内部状态值。

36. 激活函数介绍

激活函数对每一层的输出做变换,给整个网络注入非线性。有了它,网络才能拟合各种曲线。

  1. 没有非线性的网络,等价于用一个线性模型去拟合
  2. 在输出上加激活函数,才引入非线性,网络才能逼近任意函数,复杂问题才拟合得动

36.1 不用激活函数,多层还是一条直线

两路输入 x0 x_0 x0、 x1 x_1 x1,中间两个神经元 c1 c_1 c1、 c2 c_2 c2,最后一个输出。中间不套 ff f,只做加权求和:
fc1 = w0c1 x0+ w1c1 x1+bc1 fc2 = w0c2 x0+ w1c2 x1+bc2 \begin{aligned} f_{c1}&=w_0^{c1}x_0+w_1^{c1}x_1+b^{c1}\\ f_{c2}&=w_0^{c2}x_0+w_1^{c2}x_1+b^{c2} \end{aligned} fc1fc2=w0c1x0+w1c1x1+bc1=w0c2x0+w1c2x1+bc2

输出再对 fc1 f_{c1} fc1、 fc2 f_{c2} fc2 做一次同样的线性组合:
fout = w0out fc1 + w1out fc2 +bout f_{\mathrm{out}}=w_0^{\mathrm{out}}f_{c1}+w_1^{\mathrm{out}}f_{c2}+b^{\mathrm{out}} fout=w0outfc1+w1outfc2+bout

把 fc1 f_{c1} fc1、 fc2 f_{c2} fc2 代进去,按 x0 x_0 x0、 x1 x_1 x1 合并系数:
k0 = w0out w0c1 + w1out w0c2 k1 = w0out w1c1 + w1out w1c2 c = w0out bc1+ w1out bc2+bout \begin{aligned} k_0&=w_0^{\mathrm{out}}w_0^{c1}+w_1^{\mathrm{out}}w_0^{c2}\\ k_1&=w_0^{\mathrm{out}}w_1^{c1}+w_1^{\mathrm{out}}w_1^{c2}\\ c&=w_0^{\mathrm{out}}b^{c1}+w_1^{\mathrm{out}}b^{c2}+b^{\mathrm{out}} \end{aligned} k0k1c=w0outw0c1+w1outw0c2=w0outw1c1+w1outw1c2=w0outbc1+w1outbc2+bout
fout =k0x0+k1x1+c f_{\mathrm{out}}=k_0 x_0+k_1 x_1+c fout=k0x0+k1x1+c

层数看起来多,展开后仍是 k0x0+k1x1+c k_0 x_0+k_1 x_1+c k0x0+k1x1+c,和一层线性回归同一句话。激活函数就是用来打断这次合并的。

36.2 在浏览器里看这条差别

Neural Network Playground 是 TensorFlow 的网页演示,不用装环境。左边选一批点(分类或回归),中间加隐藏层,上面换学习率、激活函数、正则。激活可选 ReLU、Tanh、Sigmoid、Linear。

选 Linear 时,中间层再多,决策边界也弯不起来,对应上面「没有非线性就还是一条直线」。换成 ReLU、Tanh 或 Sigmoid,同样的层才能把点分开、把曲线拟合出来。它只用来看结构怎么影响结果,不拿来训练真实数据。

37. Sigmoid激活函数介绍

把任意输入压到 (0,1)(0,1) (0,1)。 xx x 是前面的加权求和, xx x 越大,输出越靠近 11 1。
f(x)= 11+e−x f(x)=\frac{1}{1+e^{-x}} f(x)=1+e−x1

导数可以只用函数值自己写出来。记 a=f(x)a=f(x) a=f(x):
f′(x)=f(x)(1−f(x))=a(1−a)=a−a2f'(x)=f(x)\bigl(1-f(x)\bigr)=a(1-a)=a-a^2 f′(x)=f(x)(1−f(x))=a(1−a)=a−a2

a−a2a-a^2 a−a2 的最大值:对 aa a 求导, 1−2a=01-2a=0 1−2a=0,得 a=0.5a=0.5 a=0.5。代回去:
0.5×(1−0.5)=0.250.5\times(1-0.5)=0.25 0.5×(1−0.5)=0.25

所以导数落在 (0,0.25](0,0.25] (0,0.25],最高只有 0.250.25 0.25,出现在输出正好是 0.50.5 0.5 的时候。

37.1 哪一段还有差别

输入 输出 导数
−3,3-3,3 −3,3 差别比较明显 靠近峰值 0.250.25 0.25
−6,6-6,6 −6,6 还有可见差别 还没贴到 00 0
<−6<-6 <−6 或 >6>6 >6 几乎都挤在 00 0 或 11 1 接近 00 0

输入 100100 100 和 1000010000 10000 过 Sigmoid 之后都约等于 11 1。两个数相差 100100 100 倍,激活值里看不出来,这部分信息丢了。

37.2 梯度消失

输入 <−6<-6 <−6 或 >6>6 >6 时导数接近 00 0,参数更新极慢,甚至更新不动。层数一多,每层都乘一个不超过 0.250.25 0.25 的数,梯度很快被乘没。一般 55 5 层之内就会出现梯度消失。

它的输出在 (0,1)(0,1) (0,1),不是以 00 0 为中心,实践里隐藏层很少用。Sigmoid 一般只放在二分类的输出层,把结果当概率。

37.3 代码演示图像

python 复制代码
"""
案例:
    绘制激活函数Sigmoid的 函数图像 和 导数图像.

Sigmoid激活函数介绍:
    激活函数的目的:
        给模型增加非线性功能, 让模型(神经元)既可以做分类, 还可以做回归问题.
    激活函数的分类:
        Sigmoid:
        ReLU:
        Tanh:
        Softmax:

    Sigmoid激活函数:
        主要应用于 二分类的输出层, 且适用于 浅层神经网络(不超过5层).
        数据在 [-6, 6]之间有效果, 在[-3, 3]之间效果明显.
        求导后范围在 [0, 0.25]

细节:
    绘制激活函数图像时出现以下提示, 需要将 anaconda3/Lib/site-packages/torch/lib 目录下的
    libiomp5md.dll 文件删除
    OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
"""
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'  # 避免上面的 OpenMP 重复初始化;不必去删 dll
# 导包
import torch
import matplotlib.pyplot as plt

plt.rcParams['font.family'] = ['SimHei']        # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号

# 1. 创建画布和坐标轴,1行2列.
fig, axes = plt.subplots(nrows=1, ncols=2)

# 2. 生成 -20 ~ 20之间的 1000个数据点.
x = torch.linspace(-20, 20, 1000)
# print(f'x: {x}')

# 3. 计算上述1000个点,Sigmoid激活函数处理后的值.
y = torch.sigmoid(x)
# print(f'y: {y}')

# 4. 在第1个子图中绘制Sigmoid激活函数的图像.
axes[0].plot(x, y)
axes[0].set_title('Sigmoid激活函数图像')
axes[0].grid()

# 5. 在第2个图上,绘制Sigmoid激活函数的导数图像.
# 5.1 重新生成 -20 ~ 20之间的 1000个数据点.
# 参1: 起始值, 参2: 结束值, 参3: 元素的个数, 参4: 是否需要求导.
x = torch.linspace(-20, 20, 1000, requires_grad=True)

# 5.2 具体的计算上述1000个点,Sigmoid激活函数导数后的值.
torch.sigmoid(x).sum().backward()

# 5.3 绘制图像.
axes[1].plot(x.detach(), x.grad)
axes[1].set_title('Sigmoid激活函数导数图像')
axes[1].grid()
plt.show()

37.4 拓展QA

激活函数和损失函数有什么关系?

激活函数和损失函数不是一回事,接在同一条链的两头。

激活函数 损失函数
放在哪 每一层神经元里面,输出层也算一层 整张网的最后,只算一次
吃什么 内部状态值 z=wx+bz=wx+b z=wx+b 预测值(最后一层激活的输出)和真实值 yy y
吐出什么 激活值 a=f(z)a=f(z) a=f(z),给下一层或当最终预测 一个标量,预测和真实差多少
导数干什么 决定这一层的梯度还能不能传回去 决定参数该往哪边改、改多大

前向:加权求和 → 激活 →(下一层再来一遍)→ 最后一层的激活值当成预测 → 损失拿预测和 yy y 比。反向:先对损失求导,再乘每一层激活函数的导数,才写进 w.grad w.\mathrm{grad} w.grad。激活的导数接近 00 0(Sigmoid 饱和),损失再大,梯度也传不回去,参数几乎不动。

常见搭配看最后一层在干什么,不是随便接:

任务 最后一层激活 损失
回归,预测一个实数 不加,或 Linear MSE
二分类,输出当概率 Sigmoid,压到 (0,1)(0,1) (0,1) 二元交叉熵
多分类 Softmax 交叉熵

隐藏层的激活(ReLU、Tanh)只负责把网络弯起来,不直接和损失配对。损失只认最后吐出来的那个预测值。

38. Tanh激活函数介绍

把任意输入压到 (−1,1)(-1,1) (−1,1)。 xx x 是前面的加权求和, xx x 越大越靠近 11 1, xx x 越小越靠近 −1-1 −1。图像以 00 0 为中心,关于 00 0 对称。
f(x)= 1−e−2x1+e−2x f(x)=\frac{1-e^{-2x}}{1+e^{-2x}} f(x)=1+e−2x1−e−2x

导数只用函数值自己写:
f′(x)=1−f(x)2=1−a2f'(x)=1-f(x)^2=1-a^2 f′(x)=1−f(x)2=1−a2

1−a21-a^2 1−a2 的最大值:对 aa a 求导, −2a=0-2a=0 −2a=0,得 a=0a=0 a=0。代回去是 11 1。所以导数落在 (0,1](0,1] (0,1],最高是 11 1,出现在输出正好是 00 0 的时候。Sigmoid 的峰值只有 0.250.25 0.25。

38.1 哪一段还有差别

还有差别 效果比较明显 输出 导数
Sigmoid −6,6-6,6 −6,6 −3,3-3,3 −3,3 (0,1)(0,1) (0,1) (0,0.25](0,0.25] (0,0.25]
Tanh −3,3-3,3 −3,3 −1,1-1,1 −1,1 (−1,1)(-1,1) (−1,1) (0,1](0,1] (0,1]

输入大概 <−3<-3 <−3 或 >3>3 >3 时,输出被挤到 −1-1 −1 或 11 1,导数接近 00 0。

38.2 和 Sigmoid 比

它以 00 0 为中心,梯度比 Sigmoid 大。更新还是 W新=W旧−学习率×梯度 W_{\text{新}}=W_{\text{旧}}-\text{学习率}\times\text{梯度} W新=W旧−学习率×梯度,梯度更大,一步走得更远,收敛更快,迭代次数更少。

两侧导数同样会到 00 0,层数一深还是梯度消失。用的话,隐藏层放 Tanh,输出层仍放 Sigmoid。

38.3 代码演示图像

python 复制代码
"""
案例:
    绘制激活函数Tanh的 函数图像 和 导数图像.

Sigmoid激活函数介绍:
    激活函数的目的:
        给模型增加非线性功能, 让模型(神经元)既可以做分类, 还可以做回归问题.
    激活函数的分类:
        Sigmoid:
        ReLU:
        Tanh:
        Softmax:

    Sigmoid激活函数:
        主要应用于 二分类的输出层, 且适用于 浅层神经网络(不超过5层).
        数据在 [-6, 6]之间有效果, 在[-3, 3]之间效果明显,会将数据值映射到:[0, 1]
        求导后范围在 [0, 0.25]

    Tanh:
        主要用于隐藏层(浅层),且适用于 浅层神经网络(不超过5层)
        数据在 [-3, 3]之间有效果, 在[-1, 1]之间效果明显,会将数据值映射到:[-1, 1]
        求导后范围在 [0, 1],较之于Sigmoid,收敛速度快

细节:
    绘制激活函数图像时出现以下提示, 需要将 anaconda3/Lib/site-packages/torch/lib 目录下的
    libiomp5md.dll 文件删除
    OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
"""
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'  # 避免上面的 OpenMP 重复初始化;不必去删 dll
# 导包
import torch
import matplotlib.pyplot as plt

plt.rcParams['font.family'] = ['SimHei']        # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号

# 1. 创建画布和坐标轴,1行2列.
fig, axes = plt.subplots(nrows=1, ncols=2)

# 2. 生成 -20 ~ 20之间的 1000个数据点.
x = torch.linspace(-20, 20, 1000)
# print(f'x: {x}')

# 3. 计算上述1000个点,Tanh激活函数处理后的值.
y = torch.tanh(x)
# print(f'y: {y}')

# 4. 在第1个子图中绘制Tanh激活函数的图像.
axes[0].plot(x, y)
axes[0].set_title('Tanh激活函数图像')
axes[0].grid()

# 5. 在第2个图上,绘制Tanh激活函数的导数图像.
# 5.1 重新生成 -20 ~ 20之间的 1000个数据点.
# 参1: 起始值, 参2: 结束值, 参3: 元素的个数, 参4: 是否需要求导.
x = torch.linspace(-20, 20, 1000, requires_grad=True)

# 5.2 具体的计算上述1000个点,Tanh激活函数导数后的值.
torch.tanh(x).sum().backward()

# 5.3 绘制图像.
axes[1].plot(x.detach(), x.grad)
axes[1].set_title('Tanh激活函数导数图像')
axes[1].grid()
plt.show()
相关推荐
风骏时光牛马1 小时前
AI服务线上响应异常故障
前端
IT_陈寒1 小时前
JavaScript的this指向问题又让我加了个班
前端·人工智能·后端
PC2005_cloud1 小时前
Nginx 学习笔记:Server 块配置详解,域名路由与多站点部署实战
前端·后端
YIAN1 小时前
LangChain.js 对话记忆体系(一):内存存储与文件持久化,让 AI 拥有对话记忆
前端·后端·langchain
IT_陈寒1 小时前
React状态管理这个坑,我是怎么翻车的
前端·人工智能·后端
flash俊杰1 小时前
工程文件版本迁移与崩溃恢复:schemaVersion、Migration 链与原子持久化
前端
风骏时光牛马1 小时前
大模型底层架构与AI源码深度解析
前端
parade岁月1 小时前
vtable-guild 被收录进 vuejs/awesome-vue 了 🎉
前端·vue.js