how to

获取初始参数

Aug 16, 2024
notesjulyfun技术学习d2l
6 Minutes
1017 Words

梯度裁剪

类似于球形投影,使梯度的 L2 范数不超过阈值。

训练

注意训练时候每个批量的隐状态是分别存储的,不会相互影响。

  • 多久一次 backward?
    • 要将一个批量内所有样本的所有时间步(在程序开始时设定,比如样本 size = 32,单次输入时间步 num_steps 为 35)forward 以后(每次 forward 仅进行一个时间步的预测),才进行 backward。因此权重参数矩阵会累乘,容易导致梯度爆炸,故进行梯度裁剪。d2l 代码中,梯度裁剪在 backward 后,updater 之前执行。
    • 注意对每个批量而言,每次输入仅有一个 token,并非多个时间步 token 同时输入。

手绘

default

2:15:13

手动代码

1
import math
2
import torch
3
from torch import nn
4
from torch.nn import functional as F
5
from d2l import torch as d2l
6
7
batch_size, num_steps = 32, 35
8
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
9
10
# 获取初始参数
11
def get_params(vocab_size, num_hiddens, device):
12
num_inputs = num_outputs = vocab_size
13
def normal(shape):
14
return torch.randn(size=shape, device=device) * 0.01
15
# 隐藏层参数
145 collapsed lines
16
W_xh = normal((num_inputs, num_hiddens))
17
W_hh = normal((num_hiddens, num_hiddens))
18
b_h = torch.zeros(num_hiddens, device=device)
19
# 输出层参数
20
W_hq = normal((num_hiddens, num_outputs))
21
b_q = torch.zeros(num_outputs, device=device)
22
23
# 附加梯度
24
params = [W_xh, W_hh, b_h, W_hq, b_q]
25
for param in params:
26
param.requires_grad_(True)
27
return params
28
29
# 前向传播 forward
30
# 一次 forward 就将各个批量的所有时间步都生成完了
31
def rnn(inputs, state, params):
32
# inputs的形状:(时间步数量,批量大小,词表大小)
33
W_xh, W_hh, b_h, W_hq, b_q = params
34
H, = state
35
outputs = []
36
# 循环每个时间步
37
for X in inputs:
38
# X的形状:(批量大小,词表大小)
39
# 注意这里隐状态包含所有批量各自的隐状态
40
# H 形状: (批量大小,隐藏单元个数)
41
H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
42
43
# 本步输出不会影响下一步输出,只有隐状态才会
44
# Y 形状: (批量大小,词表大小),因为前面已经指定 num_outputs = vocab_size
45
Y = torch.mm(H, W_hq) + b_q
46
outputs.append(Y)
47
# 返回一个元组,即所有时间步的输出 y 和最后一个隐状态
48
return torch.cat(outputs, dim=0), (H,)
49
# torch.cat 输出为 (ns * n, n_vocab)
50
51
def init_rnn_state(batch_size, num_hiddens, device):
52
return (torch.zeros((batch_size, num_hiddens), device=device), )
53
54
# 模型封装
55
class RNNModelScratch:
56
def __init__(self, vocab_size, num_hiddens, device,
57
get_params, init_state, forward_fn):
58
self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
59
self.params = get_params(vocab_size, num_hiddens, device)
60
self.init_state, self.forward_fn = init_state, forward_fn
61
62
def __call__(self, X, state):
63
X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
64
return self.forward_fn(X, state, self.params)
65
66
def begin_state(self, batch_size, device):
67
return self.init_state(batch_size, self.num_hiddens, device)
68
69
num_hiddens = 512
70
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
71
init_rnn_state, rnn)
72
state = net.begin_state(X.shape[0], d2l.try_gpu())
73
74
# 预测
75
def predict_ch8(prefix, num_preds, net, vocab, device):
76
"""在prefix后面生成新字符"""
77
state = net.begin_state(batch_size=1, device=device)
78
outputs = [vocab[prefix[0]]]
79
get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))
80
for y in prefix[1:]: # 预热期
81
_, state = net(get_input(), state)
82
outputs.append(vocab[y])
83
for _ in range(num_preds): # 预测num_preds步
84
y, state = net(get_input(), state)
85
outputs.append(int(y.argmax(dim=1).reshape(1)))
86
return ''.join([vocab.idx_to_token[i] for i in outputs])
87
88
# 梯度裁剪
89
def grad_clipping(net, theta): #@save
90
"""裁剪梯度"""
91
if isinstance(net, nn.Module):
92
params = [p for p in net.parameters() if p.requires_grad]
93
else:
94
params = net.params
95
norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
96
if norm > theta:
97
for param in params:
98
param.grad[:] *= theta / norm
99
100
# 训练一个 epoch
101
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
102
"""训练网络一个迭代周期(定义见第8章)"""
103
state, timer = None, d2l.Timer()
104
metric = d2l.Accumulator(2) # 训练损失之和,词元数量
105
for X, Y in train_iter:
106
if state is None or use_random_iter:
107
# 在第一次迭代或使用随机抽样时初始化state
108
state = net.begin_state(batch_size=X.shape[0], device=device)
109
else:
110
if isinstance(net, nn.Module) and not isinstance(state, tuple):
111
# state对于nn.GRU是个张量
112
state.detach_()
113
else:
114
# state对于nn.LSTM或对于我们从零开始实现的模型是个张量
115
for s in state:
116
s.detach_()
117
y = Y.T.reshape(-1)
118
# y.shape: (ns * n)
119
X, y = X.to(device), y.to(device)
120
y_hat, state = net(X, state)
121
l = loss(y_hat, y.long()).mean()
122
if isinstance(updater, torch.optim.Optimizer):
123
updater.zero_grad()
124
l.backward()
125
grad_clipping(net, 1)
126
updater.step()
127
else:
128
l.backward()
129
grad_clipping(net, 1)
130
# 因为已经调用了mean函数
131
updater(batch_size=1)
132
metric.add(l * y.numel(), y.numel())
133
return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
134
135
# 训练多个 epoch
136
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
137
use_random_iter=False):
138
"""训练模型(定义见第8章)"""
139
loss = nn.CrossEntropyLoss()
140
animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',
141
legend=['train'], xlim=[10, num_epochs])
142
# 初始化
143
if isinstance(net, nn.Module):
144
updater = torch.optim.SGD(net.parameters(), lr)
145
else:
146
updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)
147
predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)
148
# 训练和预测
149
for epoch in range(num_epochs):
150
ppl, speed = train_epoch_ch8(
151
net, train_iter, loss, updater, device, use_random_iter)
152
if (epoch + 1) % 10 == 0:
153
print(predict('time traveller'))
154
animator.add(epoch + 1, [ppl])
155
print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')
156
print(predict('time traveller'))
157
print(predict('traveller'))
158
159
num_epochs, lr = 500, 1
160
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
Article title:获取初始参数
Article author:Julyfun
Release time:Aug 16, 2024
Copyright 2025
Sitemap