week3深度学习处理文本(上)

Adam

SGD梯度下降回顾

优化器是什么?

在知道梯度后,决定一步走多远的是优化器。我要做权重更新,我在知道梯度的情况下,将权重从1更新到0.8,还是从1更新到0.5,还是从1更新到0.2,控制一步走多远的,这是优化器所做的事情。

之前讲的优化器是如下(SGD梯度下降):

θ ← θ − η · ∇L(θ)

η = 学习率(全局固定) ∇L = 梯度

θ 是原始的权重

∇L梯度也就是导数

η 是学习率,是标量,如0.1,学习率有控制更新速度的作用

SGD核心问题

✗ 学习率难调 过大震荡,过小收敛慢

✗ 各参数同等对待 不同特征梯度尺度差异大

✗ 鞍点/平坦区停滞 梯度接近零时无法前进

✗ 无动量 每步独立,方向可能抖动

Adam如何解决?

m ^ t m̂_{t} m^t 一阶矩(动量):累积梯度方向,平滑震荡

v ^ t v̂_{t} v^t 二阶矩(自适应):按参数梯度量级缩放学习率

β 偏差修正:消除初期动量估计的偏差

Adam 原理 --- 动量与自适应学习率

每步更新流程

1.计算梯度

当前参数处的损失梯度 g t = ∇ L ( θ t − 1 ) g_t = \nabla L(\theta_{t-1}) gt=∇L(θt−1)

"在当前参数 θ t − 1 \theta_{t-1} θt−1的位置,计算损失函数 L 的梯度。"‌

  • ∇ L ( θ t − 1 ) \nabla L(\theta_{t-1}) ∇L(θt−1):整体表示损失函数 L L L 关于参数 θ \theta θ 的梯度,并在参数值为 θ t − 1 \theta_{t-1} θt−1 处进行计算。

符号拆解

  • t t t:表示当前的时间步(或迭代次数、批次索引)。在训练神经网络时,数据是分批次(Batch)处理的, t = 1 , 2 , 3... t=1, 2, 3... t=1,2,3... 代表第 1 次、第 2 次、第 3 次参数更新。
  • θ t − 1 \theta_{t-1} θt−1:表示在第 t t t 次更新之前的模型参数集合。
    • θ \theta θ (Theta) 代表神经网络中的所有可学习参数(如权重 weights 和偏置 biases)。
    • 下标 t − 1 t-1 t−1 强调这是"旧"的参数,即上一轮迭代结束后的参数状态。
  • L ( ⋅ ) L(\cdot) L(⋅):损失函数(Loss Function)。它衡量模型预测值与真实标签之间的差异。我们的目标是最小化这个函数。
  • ∇ \nabla ∇:梯度算子(Nabla/Del)。在多维空间中,梯度是一个向量,指向函数值增长最快的方向。
  • g t g_t gt:在时间步 t t t 计算出的梯度向量。
求导公式与优化器是独立的。

梯度的计算和SGD无关,梯度计算只和所选的损失函数和模型结构有关,可以查看 神经网络基础原理-使用梯度下降训练二次函数模型简单示例

这篇文章了解梯度计算只和所选的损失函数和模型结构有关。

当损失函数和模型结构确定了,求导公式就已经确定了。Loss函数中包含模型的输出,Loss和model是一体的,这两组成的大公式,用w和b分别对这个大公式求导,得到梯度。后面是把数带入,把梯度具体数值算出来。

2.更新一阶矩(动量)

公式: m t m_{t} mt = β₁ · m t − 1 m_{t-1} mt−1 + (1−β₁) · g t g_{t} gt

β₁ ≈ 0.9,对梯度做指数移动平均,减少噪声

计算系数是β₁ ,β₁ 是个标量,默认值是0.9

m 0 m_{0} m0 = 0。最初的 m 0 m_{0} m0 我们默认初始化为0.

g t g_{t} gt 是第t轮计算的梯度

m 1 m_{1} m1 = β₁ · m 0 m_{0} m0 + (1−β₁) · g 1 g_{1} g1

m 1 m_{1} m1 = (1 - 0.9 )· g 1 g_{1} g1 = 0.1· g 1 g_{1} g1

m 2 m_{2} m2 = β₁ · m 1 m_{1} m1 + (1−β₁) · g 2 g_{2} g2

m 2 m_{2} m2 = 0.9· m 1 m_{1} m1 + 0.1· g 2 g_{2} g2

m 2 m_{2} m2 中包含 m 1 m_{1} m1 , m 1 m_{1} m1中又包含 g 1 g_{1} g1,也就是上一轮的梯度,相当于将上一轮的梯度和本轮的梯度做了差值, m 2 m_{2} m2 中同时包含 g 1 g_{1} g1和 g 2 g_{2} g2

m 3 m_{3} m3 = β₁ · m 2 m_{2} m2 + (1−β₁) · g 3 g_{3} g3

m 3 m_{3} m3 = 0.9 · m 2 m_{2} m2 + 0.1· g 3 g_{3} g3

m 3 m_{3} m3 同时包含 g 1 g_{1} g1 、 g 2 g_{2} g2 、 g 3 g_{3} g3的差值。

也就是前三轮的梯度一定程度上都混合在了 m 3 m_{3} m3 中,以此类推, m 4 m_{4} m4 混合了前4轮的梯度, m t m_{t} mt 混合了前t轮的梯度。

总之, m t m_t mt 动量实际上是前 t 轮梯度的混合,有一个系数 β₁ 控制了混合的比例。

动量(一阶矩)的作用

就像一个球滚下山坡------不仅响应当前坡度,

还保留之前滚动方向的惯性。

• 梯度方向一致 → 加速

• 梯度方向震荡 → 阻尼(平滑)

β₁=0.9 时:动量更新示意

t=1: m₁ = 0.9×0 + 0.1×g₁ = 0.1 g₁

t=2: m₂ = 0.9×m₁ + 0.1×g₂

t→∞: mₜ ≈ 加权平均梯度

3.更新二阶矩(梯度平方)

v t = β 2 ⋅ v t − 1 + ( 1 − β 2 ) ⋅ g t 2 v_t = \beta_2 \cdot v_{t-1} + (1-\beta_2) \cdot g_t^2 vt=β2⋅vt−1+(1−β2)⋅gt2

β₂ ≈ 0.999,估计梯度的方差(无偏方差代理)

v t v_{t} vt 最开始为0,即 v 0 v_{0} v0 = 0

v t v_{t} vt每轮都是前一轮 v t − 1 v_{t-1} vt−1 的差值。

相较于一阶矩,主要差别在一个是前n轮梯度gₜ的混合差值,一个是前n轮梯度平方 g t g_{t} gt² 的混合差值。

4.偏差修正

m ^ t = m t 1 − β 1 t \hat{m}_t = \frac{m_t}{1-\beta_1^t} m^t=1−β1tmt

v ^ t = v t 1 − β 2 t \hat{v}_t = \frac{v_t}{1-\beta_2^t} v^t=1−β2tvt

t 步时 β 1 t → 0 \beta_1^t \to 0 β1t→0,修正初期动量被低估的问题

β₁ = 0.9

β₂ ≈ 0.999

两者的分母都是随着t的增大,越来越趋近于1,分母越来越大的趋势,两者在训练后期都比前期是减弱了。

我的训练后期比训练的前期走的慢一些,使得我的训练先快后慢。

5.参数更新

θ t = θ t − 1 − η ⋅ m ^ t v ^ t + ϵ \theta_t = \theta_{t-1} - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt=θt−1−η⋅v^t +ϵm^t

ϵ ≈ 1 e − 8 \epsilon \approx 1e-8 ϵ≈1e−8,防止除零;梯度大的参数步长自动缩小

θ t − 1 \theta_{t-1} θt−1 是模型的权重w

η 是学习率

原来的SGD中, m ^ t v ^ t + ϵ \frac{\hat{m}_t}{\sqrt{\hat{v}t} + \epsilon} v^t +ϵm^t这块直接是 g t g{t} gt梯度。

实际上相当于是adam优化中将原来SGD公式的θ ← θ − η · ∇L(θ)

∇L(θ) 这个梯度换成了 m ^ t v ^ t + ϵ \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} v^t +ϵm^t这个公式。

ε可以不用关注,只是数学上防止除零的操作。

所以实际上我们要关注的是 m ^ t v ^ t \frac{\hat{m}_t}{\sqrt{\hat{v}t}} v^t m^t为什么比用 g t g{t} gt好?

关于 m ^ t \hat{m}_t m^t 的意义:

我们下山的时候,有三波人和你说往下的方向走,后面又遇到一个人,和你说往上的方向走,那我们我们会觉得这个人可能不太认识路,毕竟有那么多的人都告诉你往下走。那么我们是不是可以仍然往下走,我们不回头向上走,但是少走一点。尽可能规避错误样本给我们的错误指导意见。所以我们要尽可能相信大多数人。容忍我们的训练数据中可能有标错的。

所以我们对于过去梯度的差值(一阶矩),本质上是在相信大多数人。那个和我说往上走的梯度和过去的梯度一总结,总的方向还是向下走,只是向下的幅度不那么大了。如果后面再遇到很多人和我说往上走,那么我才可能确实该回头了,我才会扭转过来。

不要抛弃过去几轮的经验,把过去的梯度继承下来。这是 m ^ t \hat{m}_t m^t 的价值

v ^ t \hat{v}_t v^t 的意义:

v ^ t \hat{v}_t v^t 是梯度的绝对值的大小。

如果梯度很大,那么我除以一个较大的数,会使得整体的值较小一点。

如果梯度较小,除以一个较小的数,会使得整体的值较大一点。

以下是详细解析:

  1. v ^ t \hat{v}_t v^t 的本质:梯度的"二阶矩"估计
  • 来源: v ^ t \hat{v}_t v^t 是梯度平方 g t 2 g_t^2 gt2 的指数移动平均(Exponential Moving Average, EMA),并经过偏差修正。
  • 物理意义:它反映了该参数在历史迭代中梯度的变化幅度或离散程度。
    • 如果某个参数的梯度一直很大且波动剧烈, v ^ t \hat{v}_t v^t 就会很大。
    • 如果某个参数的梯度很小且平稳, v ^ t \hat{v}_t v^t 就会很小。
  1. v ^ t \sqrt{\hat{v}_t} v^t 的具体作用:自适应调节步长
    在更新公式中, v ^ t \sqrt{\hat{v}_t} v^t 位于分母位置。这意味着参数更新的步长与该参数历史梯度的幅度成反比:
  • 对于梯度波动大(陡峭)的参数:

    • v ^ t \sqrt{\hat{v}_t} v^t 值较大。
    • 分母变大,导致整体更新步长 m ^ t v ^ t + ϵ \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} v^t +ϵm^t 变小。
    • 效果:防止在陡峭方向上因步长过大而产生震荡或发散,起到"刹车"作用。
  • 对于梯度波动小(平缓)的参数:

    • v ^ t \sqrt{\hat{v}_t} v^t 值较小。
    • 分母变小,导致整体更新步长变大。
    • 效果:在平缓方向上加速前进,避免陷入局部极小值或鞍点时停滞不前,起到"加速"作用。

总之就是梯度绝对值很大的时候,我们走小一点,梯度绝对值很小的时候,我们尽量走大一点。这是一种平衡,一种自适应。

和SGD相比,Adam的优化器原理

考虑了历史训练梯度,设计了学习率自适应的方式。

θ t = θ t − 1 − η ⋅ m ^ t v ^ t + ϵ \theta_t = \theta_{t-1} - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt=θt−1−η⋅v^t +ϵm^t

实际上可以看作如下(忽略 ϵ {\epsilon} ϵ,并且 m ^ t \hat{m}t m^t 可以视同 g t g{t} gt)

θ t = θ t − 1 − ( η v ^ t ) ⋅ g t \theta_t = \theta_{t-1} - \left(\frac{\eta}{\sqrt{\hat{v}t}}\right) \cdot g{t} θt=θt−1−(v^t η)⋅gt

( η v ^ t \frac{\eta}{\sqrt{\hat{v}_t}} v^t η )当作是学习率 。这个公式就和SGD差不多了。

所以我们说adam有一定的自适应学习率的情况。

自适应学习率(二阶矩)的作用

对梯度较大的参数缩小步长,

对梯度较小的参数放大步长。

adam解决SGD核心问题

✗ SGD 学习率难调 过大震荡,过小收敛慢

Adam有自适应学习率的处理。

✗ SGD 各参数同等对待 不同特征梯度尺度差异大

adam不同参数的绝对值不同,每个参数的梯度大小不同,使得调整的幅度不一样。

✗ SGD 鞍点/平坦区停滞 梯度接近零时无法前进

✗ SGD 无动量 每步独立,方向可能抖动

常用优化器AdamW

Adam官方文档

大部分场景下优化器都可以是adam,图像任务,文本任务等都适用,贝塔值不用调。

pytorch实现:

基本实现

基本参数说明

embedding层(离散符号 → 稠密向量 · 中文 NLP 实例)

为什么需要 Embedding? =》文字不能直接送入神经网络

One-Hot 编码(朴素方案)

每个字对应一个向量,每个向量的长度和你的词表的总长度相同。比如词表有3000个字,那么一个字对应的向量维度就是3000维度。

每个汉字占一个单独的位置,其他位置都是0,也就是2999个0,加一个1.

每个向量之间是正交垂直的,但是有的字表达是接近的,那么我们希望模型看待它的方式也接近。

Embedding解决方案

embedding层将文字转为数字,将每个词映射为固定维度的稠密实数向量

每个字都是同维度的向量。所有字的维度一定是一致的。(one-hot也一样)

每个字对应的向量一开始是随机的(随机初始化),但是训练中梯度更新,反向传播,会调整到合适的位置。我们会发现模型学习出来,那些语义相近的字,他们的向量也比较相近。

embedding层这个向量是可训练向量。one-hot中是不可训练的向量。

维度与词表大小解耦了,两者没有关系了。

Embedding 矩阵结构

Embedding 本质是一张查找表,为每个字准备了一个同维度的向量,它一开始是随机的,在训练过程中会被梯度更新,参与反向传播,最后调整到合适的位置(这一点非常重要)。

最后我们需要用模型做推理的时候,就用文字对应的向量作为输入。

查表过程

输入:词 id(整数)

输出:对应的 d 维向量

python 复制代码
import torch.nn as nn

# vocab_size=5, embed_dim=4
emb = nn.Embedding(5, 4)

# 查询 id=2('爱')
idx = torch.tensor([2])
vec = emb(idx)
# vec.shape → (1, 4)
关键特性

· 权重可反向传播更新(随机初始化)

· 可加载预训练词向量(如 Word2Vec)

多语种:一张字符表通吃

多语种和单语种对模型而言,没有本质区别,都是靠embedding层来完成。对模型而言,只是将字符映射成向量,至于字符原本是中文还是英文,模型并不在乎,最后拿到的都是数字。多语种完全可以一起训练,各个语言都放在同一张表里即可,让一个模型去通吃所有的语言,只是训练难度会比单语种大些。

随机为中文,英文,日文随机分配一个向量,然后让他去训练。

PAD & UNK

< PAD >:把长短不一的句子拼成矩阵

模型训练时,一个batch必须是固定的矩阵形状。

假设一个字对应一个10维度向量,

一个字是 1 * 10的矩阵。

五个字是 5 * 10的矩阵。

为了方便计算,需要保证是同尺寸的矩阵。

假设下图中每个字符是10维度的向量,我们设定句子的长度是8个字符,那么我们对不足8的长度的句子,需要用pad去补0,0的位置是10维度全0的向量。

7 * 10, 5 * 10 , 1 * 10的矩阵。通过pad,都补齐成 8 * 10的矩阵。

让如下三个样本的形状都补成 8 * 10,那么他们就可以放在一个batch里了。

那么如下,我们一个batch的整体的形状就是 3 * 8 * 10。batch_size是3,是如下三句话,三个样本,每个样本是8 * 10的维度。

pad不参加梯度的反向传播。

对于短句子,我们用pad补0,那么对于长句子,我们可以截断。

尽量保证95%的样本在最大长度以下。因为截断语句可能有歧义。

如果保证100%的样本在最大长度以下,可能比较浪费。不用特别考虑特长的极端样本,可能是录入有误,极端样本该踢就踢。

UNK:词表里没有的字

词表之外的字,只能落到 1 号槽位

unk字符,占据一个向量。词表里没有的字都对应到一个特殊的字符上,用这个字符的向量。

词级(Word-level)

新词、人名、网络用语常年不在词典里

→ UNK 一多,这些词的语义就直接丢了

字符级(Character-level)

字符集小且稳定,任何句子都能逐字拆

→ UNK 极少(本课数据训练集 UNK 占比 0.0%)

两个占位符,语义完全不同:

PAD = 这里没有内容 | UNK = 这个字我不认识

embedding_padding_demo.py

python 复制代码
#coding:utf8
"""
Embedding 层 与 Padding 操作教学
"""
import torch
import torch.nn as nn

# ── 1. Embedding 本质:一张可训练的查找表 ─────────────────────────────────────
#
#   nn.Embedding(num_embeddings, embedding_dim)
#   └─ 内部维护一个形状为 [num_embeddings, embedding_dim] 的权重矩阵
#   └─ 输入整数 index,直接取对应行,等价于 one-hot × 权重矩阵,但效率更高
#
vocab = {"<pad>":0, "<unk>":1, "你":2, "好":3, "中":4, "国":5, "欢":6, "迎":7}
VOCAB_SIZE = len(vocab)   # 8
EMBED_DIM  = 4

# 如下这句是在告诉embedding层,我有多少字符(VOCAB_SIZE)需要转向量,我希望向量是多少维度(EMBED_DIM)的。如下就是在声明 我有八个四维的向量要训练。
# embed_dim:每个向量需要是多少维度的
embedding = nn.Embedding(VOCAB_SIZE, EMBED_DIM)

print("── Embedding 权重矩阵(shape = 字符数 × 向量维度)──")
# 如下就是embedding帮我们随机初始化的8 * 4 的矩阵。这个矩阵的每一行,就是每个字符对应的向量
print(embedding.weight, "\n")

# 单个 token 查询:取第 4 行("中")
idx = torch.LongTensor([4])
print("'中' 的向量(第4行):", embedding(idx))

# ── 2. 为什么需要 Padding ──────────────────────────────────────────────────────
#
#   同一个 batch 中,句子长度不同,无法直接堆成矩阵。
#   解决方案:统一截断或补齐到 max_len,不足的位置填特殊 token [<pad>]=0。
#
#   "中国欢迎你"  → [4,5,6,7,2]         长度5,不需补
#   "你好中国"    → [2,3,4,5,0]         长度4,补1个 pad
#   "feda"       → [1,1,1,1,0]         全 unk,补1个 pad
#
MAX_LEN = 5

def encode(text, vocab, max_len):
    ids = [vocab.get(ch, vocab["<unk>"]) for ch in text][:max_len]
    ids += [vocab["<pad>"]] * (max_len - len(ids))   # 补齐
    return ids

sentences = ["中国欢迎你", "你好中国", "feda"]
batch_ids  = [encode(s, vocab, MAX_LEN) for s in sentences]
print("batch_ids",batch_ids)
print("\n── Padding 后的 token id ──")
for s, ids in zip(sentences, batch_ids):
    print(f"  {s!r:8s} → {ids}")

# ── 3. padding_idx:让 [pad] 向量永远为零 ─────────────────────────────────────
#
#   设置 padding_idx=0 后:
#   ① Embedding 初始化时第 0 行全为 0
#   ② 反向传播时第 0 行梯度不更新([pad] 不携带语义,不应影响训练)
#
embedding_with_pad = nn.Embedding(VOCAB_SIZE, EMBED_DIM, padding_idx=0)

print("\n── 设置 padding_idx=0 后的权重矩阵 ──")
print(embedding_with_pad.weight)
print("第0行([pad] 向量):", embedding_with_pad.weight[0])  # 全零

# ── 4. 完整 batch 送入 Embedding ──────────────────────────────────────────────
x = torch.LongTensor(batch_ids)          # shape: [3, 5]
out = embedding_with_pad(x)              # shape: [3, 5, 4]

print("\n── Embedding 输出(batch=3, seq_len=5, embed_dim=4)──")
print("shape:", out.shape)
print(out)
print("\n注意:每个句子最后的 pad 位置对应向量全为 0,不影响模型对有效 token 的建模。")

执行如上文件,打印如下内容:

python 复制代码
/opt/miniconda3/envs/py312/bin/python /Users/yuria/Agent/week3-深度学习常用组件/embedding_padding_demo.py 
── Embedding 权重矩阵(shape = 字符数 × 向量维度)──
Parameter containing:
tensor([[ 1.6163, -1.0457, -0.4053,  0.8748],
        [-0.0915, -0.2494,  2.2897,  0.3592],
        [ 0.1139, -1.2174,  0.7542,  1.4914],
        [-2.1809,  1.6299, -0.2293,  0.4906],
        [ 1.5605, -1.8889,  0.1665,  0.0144],
        [-0.3446,  0.3322,  0.7030, -0.5725],
        [-0.0071, -0.0051, -0.5192, -0.6743],
        [-1.9431, -1.1434,  0.1922, -0.6515]], requires_grad=True) 

'中' 的向量(第4行): tensor([[ 1.5605, -1.8889,  0.1665,  0.0144]], grad_fn=<EmbeddingBackward0>)
batch_ids [[4, 5, 6, 7, 2], [2, 3, 4, 5, 0], [1, 1, 1, 1, 0]]

── Padding 后的 token id ──
  '中国欢迎你'  → [4, 5, 6, 7, 2]
  '你好中国'   → [2, 3, 4, 5, 0]
  'feda'   → [1, 1, 1, 1, 0]

── 设置 padding_idx=0 后的权重矩阵 ──
Parameter containing:
tensor([[ 0.0000,  0.0000,  0.0000,  0.0000],
        [-0.4799, -0.8525, -1.1389,  0.3798],
        [ 0.0664,  1.1555,  1.2549,  0.8234],
        [-1.4880, -1.1912, -0.0732,  0.5310],
        [-1.1646, -0.4408, -0.6273,  0.4283],
        [-0.3654,  0.7572,  0.0212, -1.0341],
        [-0.2374,  0.6735, -1.0020, -1.7913],
        [ 0.3574, -1.5409,  0.3040, -0.6921]], requires_grad=True)
第0行([pad] 向量): tensor([0., 0., 0., 0.], grad_fn=<SelectBackward0>)

── Embedding 输出(batch=3, seq_len=5, embed_dim=4)──
shape: torch.Size([3, 5, 4])
tensor([[[-1.1646, -0.4408, -0.6273,  0.4283],
         [-0.3654,  0.7572,  0.0212, -1.0341],
         [-0.2374,  0.6735, -1.0020, -1.7913],
         [ 0.3574, -1.5409,  0.3040, -0.6921],
         [ 0.0664,  1.1555,  1.2549,  0.8234]],

        [[ 0.0664,  1.1555,  1.2549,  0.8234],
         [-1.4880, -1.1912, -0.0732,  0.5310],
         [-1.1646, -0.4408, -0.6273,  0.4283],
         [-0.3654,  0.7572,  0.0212, -1.0341],
         [ 0.0000,  0.0000,  0.0000,  0.0000]],

        [[-0.4799, -0.8525, -1.1389,  0.3798],
         [-0.4799, -0.8525, -1.1389,  0.3798],
         [-0.4799, -0.8525, -1.1389,  0.3798],
         [-0.4799, -0.8525, -1.1389,  0.3798],
         [ 0.0000,  0.0000,  0.0000,  0.0000]]], grad_fn=<EmbeddingBackward0>)

注意:每个句子最后的 pad 位置对应向量全为 0,不影响模型对有效 token 的建模。

进程已结束,退出代码为 0

为什么一开始初始化权重,就将padding行设置为全零?

因为我们不希望它影响训练,它只是为了把形状拉成一致才凑数进来的东西,我们不希望它对训练结果有影响。把它设置为全零,是为了消除它对后续计算影响的一种相对好的方法。

考虑多语种情况

如果有英文,也可以直接加在vocab中,中英混合没问题的。如下:

vocab = {"":0, "":1, "你":2, "好":3, "中":4, "国":5, "欢":6, "迎":7,"a":8,"b":9,"c":10}

权重的更新

随机初始化的向量会在后续模型的反向传播的训练中进行更新。

为什么不训练出来一个词表大家通用呢?

专用于某些特定场景的去训练,无效字符较少。

训练数据不同,中文,英文,西班牙语 训练数据语言占比不同。

神经网络只认数字不认字符,所以需要转换成向量。

如何获取某个文件的字符?如下即可去重获取某个文件中的字符:

CNN (卷积层)-局部感受野 · 权重共享 · 1D 卷积用于文本

使用卷积层的不多了。它是深度学习下的常见概念。只有理解了这个cnn和rnn,你才能更理解为什么transformer是更好的网络结构。

图像上的卷积(2D卷积-有两个方向,横向和纵向)

图像是天然的数字,本质本身就是一张天然的矩阵。

如1920*1080的分辨率屏显(电脑屏幕的像素)

这是个矩阵,这个矩阵中的每个值,就是像素值,对应到每个色块上。

输入图像和卷积核对位相乘再相加。

卷积核会横向扫描,横向移动,对位相乘再相加。

再纵向移动,对位相乘再相加。再横向移动,对位相乘再相加。

将输入的图扫描一遍。

最后得到的图的特征。

我们对这个对图像进行卷积的操作,叫做特征提取。我们这里实际上训练的是卷积核,卷积核是随机初始化的,我们在训练中会通过反向传播梯度更新卷积核,使得这个卷积核提取的特征更为合理,使得loss越来越小。这个就是卷积神经网络的做法。

图像是它的局部代表一定的信息,单看一个像素是看不出什么东西的,只有一部分的像素一起看,才能表达出来这张图片特定的信息。

所以卷积设计的思想就是,我每次看局部,看它能否给我带来一些特定的信息。我们在训练中会不断的微调卷积核的权重。

卷积核使用举例:

比如判断图像是否是猫,设置特定卷积核,结果得到很高值的矩阵。反映到输出层,会告诉我们是猫的概率很大。如果得到值很小的矩阵,是猫的概率很小。

文本上的卷积(1D卷积)

之所以叫做1D卷积,是因为文本只有一个方向,就是文本的方向。

经过embedding后,文本的一个字是对应一个向量的。

eg:我爱北京天,假设每个字是8维度的向量,那么这个5个字就是 5 * 8的矩阵。

卷积核在滑动的时候,是整列(也就是整个字8维度的高度去卷,不可能是只卷半个字,这没有意义)整列去顺着文本的方向滑动的。

计算的逻辑同图像卷积,也是随机初始化一个核(权重),这个卷积核的大小等于每个字的维度(也就是我们之前设置的embed_dim),横向就是我们指定的宽度(也就是一次需要卷几个字的宽度)。然后去进行移动,在训练过程中,也是调整卷积核的参数。

文本卷积的思路:有些字单独看,看不出来意思,但是把某些字结合着看,更能表达一些信息,所以说我们是用这种方式把这些字卷到一起去看。这就是文本上卷积的思想,我们用更专业的话来表达就是局部感受野。一次感受输入的局部信息,而不是某一个字的信息。

两大核心思想
局部感受野

每次只看输入的一个小窗口(卷积核),

不是一次看全局

权重共享

同一个卷积核在整个序列上滑动,

参数量不随输入变大

图像 vs 文本卷积

图像:Conv2d,核在 H×W 二维滑动

文本:Conv1d,核在序列长度一维滑动

文本卷积核对中文卷一次一般取2-4个字。

可设置一个卷积核,一次卷5个字。再设置一个卷积核,一次卷3个字。这样可以设置多个卷积核。

文本是按照字去卷的,每次都是把多个字的整个向量一起去卷的。卷积核的大小,其中一维是和embedding 的EMBED_DIM的维度一致的。另外一维度是可以调整的。

卷积核在扫描图像或文本的过程中,值是不变的。

只有在每次反向传播,更新了之后,才会变化。在下次扫描过程中还是不变化的。

关键参数:kernel_size · stride · padding

padding的作用:输入图像5 * 5,经过卷积核后,输出为 3 * 3 ,因为它只能横向纵向各移动三次。所以 5 * 5 的输入变成了 3 * 3 的输出。矩阵的形状变化了,但是有的时候,不利于堆叠很多层数,比如图越卷越小,后面再加上同样大小的卷积,就没法用了。比如 3 * 3输入图像,卷积核也是 3 * 3,这就无法卷积操作了。

那么我们可以在图像外额外加一圈padding,这样 就能使得 5 * 5的输入,经过卷积后,仍是5 * 5的输出。所以加padding的好处就是使得你的特征大小不变。

文本上也一样,如 5个字,卷积窗口为3,输出也是3.

我们可以给文本加padding,前后加一个字符,那么输出能保持仍然是5.

图像不需要embedding,它天然就是矩阵,不需要查表,embedding层的意义只是将字符转换成数字。多语种也只用一个embedding即可。embedding是文本类的第一层。卷积核不需要是正方形,也可以是 3 * 4.

卷积这一层,你可以理解是计算公式,代替的是线性层,然后你可以在后面接上其他的层,使得你的网络完整。然后你就可以继续做训练,反向传播。在反向传播的时候,所有层的权重都会更新,卷积层权重,cnn层,激活层,线性层等所有层的权重都会更新。

卷积作用:提取原始输入上的一些局部特征。本质是做了矩阵运算,矩阵运算根据矩阵的值的不同,得到不同的特征值,我们希望某种规律可以通过这个值来反映。

卷积核使用举例:

比如判断图像是否是猫,假如原图的像素分布非常像一只猫的话,它和一个特定训练后的卷积核相乘,得到的值是一个很大的值。如果不是猫的话,乘完是一个很小的值。

比如想根据输入的文本判断,这段文本是夸我的还是骂我的,卷积的矩阵乘上骂我的词,比如傻瓜,刚好得到一个很大的值,那么就告诉我这个文字倾向于是骂我的。

循环层(RNN/LSTM)(序列建模 · 隐状态 · 遗忘门 · 双向)

LSTM是RNN的变种

RNN是nlp领域的思路,天然适合文本的公式。

h0 是hidden_size 有128个零的数组

x1是文本输入(已经被embedding为向量的x1)

tanh 激活函数

w_h和w_x是可训练的,都是矩阵。

h2包含了一部分来自x2的信息,也包含了一部分来自x1的信息。

h3就包含了x1,x2,x3的信息。

h是不断的在更新的(被输入的序列去更新,包含了序列中的每一步)

h一开始是全零的

rnn只适合文本类。因为图像不能一溜一溜列的结合看。

h一直都是128维度的。

不管训练有多长,隐单元都是128维度。

x100 ,文本太长,rnn的效果就变差了,128不够用了。

为了解决这个问题,有了LSTM.

如果记笔记只有一页纸,第一节课,第二节都写在这张纸上。我们可以把第一节课的不重要的东西擦掉,给新的课腾地。这就是遗忘门。

遗忘门:本质也是rnn,

sigmoid激活(最外层的含义),1是打开,0是关闭状态

用计算的结果和我们的记忆做相乘,

0的位置,表示这部分记忆删掉。1的位置,表示这部分记忆保留。

输入门:也是做了激活,对新的输入做筛选

输出门??todo

增加了可训练参数,让他更好拟合规律。

四个rnn.

cnn卷积核,只能和相邻的字有关系,很难和很远的字去联系起来。一个卷积窗就3个长度。

而rnn更符合序列思路的思想。

三个门发生的位置不同。

rnnforward.py

对于rnn的手动实现

hidden_size隐单元的大小

lstmforward.py

如上两个不要求看明白,因为这两个模型使用的不是太多了。

cnn是邻接的

rnn是??todo

lstm是对rnn的改进(记忆遗忘,筛选,保证传递更长的序列)

tanh激活函数换成其他的激活函数一样成立。

rnn中x1是字,是embedding_size的维度,假设100

x2也是100,x3也是100

h是1128的维度
推算出来w_x是128
100的尺寸

w_h是128*128的矩阵

h整层输出是3*128尺寸。因为有三个输出

input_size是x的维度,即100

hidden_size是128

num_layers

双向是使用两个lstm,一个从前往后进行,一个从后往前进行。用的比较多。对序列做双向建模。

有时候人说话,先理解后面的,再理解前面的更好理解些。

输入顺序就是x3. x2. x1

模型可以先cnn,后rnn,也可以反过来先rnn,后cnn

池化层

有个向量,最大池化,就是将向量中只保留最大值。

平均池化,所有的数取平均。

105的矩阵,我们做池化,可以把它变成10 1的向量(因为我们每行取平均或者最大值)。

做5分类,我们的模型输出必须是5维度的

??

池化的作用是啥??

做池化是横向方向去处理。因为文字是横向去理解的。

3128所以池化完是1128。

池化把信息降低维度,后面可以接分类层。

取最大的0.9,其他的0.1变成0.2对模型不影响,微小的改动不影响模型结果,增加了模型的鲁棒性。

甚至0.9变动位置,数值发生翻转,也是一样的结果。

pooling层增加了抗性,增加了抗干扰能力。

但是一定程度上也是一种丢失。

刚embedding-rnn-cnn后才做池化。不是刚embedding就去做池化,会导致丢失很多东西。

rnn有两种用法:h1,h2,h3做池化,??todo

下采样

归一化层

上一层输出矩阵 3*128

接归一化层,可以做什么事情,可以计算所有值的平均数。所有数的方差。

假设有个向量是 -100,0,100

操作后成了 -1,0,1

大小关系是不变的,但是都等比例的被放缩了。

学习率大,爆炸了。小,不训练了。学习率去调整有局限。

Adam是为了反向存在的。

dropout

使得谁都不能偷懒,不能摸鱼。使得这个工作没了谁都能做。

dropout将好学生重置为0,剩下的就不好使了,那不行。

预测的时候,不带dropout层

dropout只在训练,前向计算的时候,用这个dropout层。

dropout.py

看每次结果是否都不一样

综合实战

相关推荐
Smoothcloud润云3 小时前
GPU算力平台推荐:复现实验时,如何判断租用资源是否合适?
服务器·人工智能·深度学习·gpu算力·gpu
yz_aiks3 小时前
WorkBuddy 使用技巧:Claude Code 国内平替,签到免费用、自动签到和手机远程控制电脑
人工智能·ai编程·效率工具·claude code·workbuddy
锋行天下3 小时前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
沃达德软件3 小时前
智慧无人机图像识别分析技术
人工智能·目标检测·计算机视觉·目标跟踪·视觉检测·无人机
染指11104 小时前
162.Agent-LangGraph-检查点的作用
人工智能·python·langchain·agents
FII工业富联科技服务4 小时前
从灯塔工厂到规模化智能运营:Omniverse Sim2Real与多智能体协同架构解析
人工智能·数字孪生·ai智能体·工业机器人·omniverse·柔性制造·灯塔工厂
合尘猫4 小时前
6GB 笔记本本地跑 Z-Image Turbo:一次从 cu121 到 cu130 的踩坑记录
人工智能·ai·大模型·本地部署
2401_832298104 小时前
当思考本身被解放:AI 时代的人与机器
人工智能
北方的银狐-Zero4 小时前
Palantir 平替产品出世:OntoL 8.0 发布,把「企业级本体决策平台」产品复刻出来了
人工智能·本体论·本体产品