Posts with tag d2l

10.4.bahdanau-attention

2024-12-22
d2ljulyfunnotes技术学习

class Seq2SeqAttentionDecoder(AttentionDecoder): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs): super(Seq2SeqAttentionDecoder, self).__init__(**kwargs) self.attention = d2l.AdditiveAttention( num_hiddens, num_hiddens, num_hiddens, dropout) self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = nn.GRU( embed_size + num_hiddens, num_hiddens, num_layers, dropout=dropout) self.dense = nn.Linear(num_hiddens, vocab_size) def init_state(self, enc_outputs, enc_valid_lens, *args): # outputs的形状为(batch_size,num_steps,num_hiddens). # hidden_state的形状为(num_layers,batch_size,num_hiddens) outputs, hidden_state = enc_outputs return (outputs.permute(1, 0, 2), hidden_state, enc_valid_lens) def forward(self, X, state): # enc_outputs的形状为(batch_size,num_steps,num_hiddens). # hidden_state的形状为(num_layers,batch_size, # num_hiddens) enc_outputs, hidden_state, enc_valid_lens = state # 输出X的形状为(num_steps,batch_size,embed_size) X = self.embedding(X).permute(1, 0, 2) outputs, self._attention_weights = [], [] for x in X: # query的形状为(batch_size,1,num_hiddens) query = torch.unsqueeze(hidden_state[-1], dim=1) # context的形状为(batch_size,1,num_hiddens) context = self.attention( query, enc_outputs, enc_outputs, enc_valid_lens) # 在特征维度上连结 x = torch.cat((context, torch.unsqueeze(x, dim=1)), dim=-1) # 将x变形为(1,batch_size,embed_size+num_hiddens) out, hidden_state = self.rnn(x.permute(1, 0, 2), hidden_state) outputs.append(out) self._attention_weights.append(self.attention.attention_weights) # 全连接层变换后,outputs的形状为 # (num_steps,batch_size,vocab_size) outputs = self.dense(torch.cat(outputs, dim=0)) return outputs.permute(1, 0, 2), [enc_outputs, hidden_state, enc_valid_lens] @property def attention_weights(self): return self._attention_weight

对于第一个 batch,每个 num_keys 保留 2

2024-11-06
d2ljulyfunnotes技术学习

$$ f(x) = sum_(i = 1)^n "softmax"(-1 / 2 ((x - x_i)w)^2)y_i $$该公式括号内部可称为”评分函数“masked_softmaxdef masked_softmax(X, valid_lens): """在最后一个轴上掩蔽元素后,执行softmax操作""" # X:3D张量,valid_lens:1D或2D张量 # 使用时,X.shape = (batch_size, num_queries, num_keys),表示注意力权重。此函数掩盖部分 keys 的注意力权重 if valid_lens is None: return nn.functional.softmax(X, dim=-1) else: shape = X.shape if valid_lens.dim() == 1: valid_lens = torch.repeat_interleave(valid_lens, shape[1]) else: valid_lens = valid_lens.reshape(-1) # 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0 X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6) return nn.functional.softmax(X.reshape(shape), dim=-1) # 对于第一个 batch,每个 num_keys 保留 2 # 对于第二个 batch,每个 num_keys 保留 3 masked_softmax(torch.rand(2, 2, 4), torch.tensor([2, 3])) >>> tensor([[[0.5980, 0.4020, 0.0000, 0.0000], [0.5548, 0.4452, 0.0000, 0.0000]], [[0.3716, 0.3926, 0.2358, 0.0000], [0.3455, 0.3337, 0.3208, 0.0000]]])masked_softmax(torch.rand(2, 2, 4), torch.tensor([[1, 3], [2, 4]])) >>> tensor([[[1.0000, 0.0000, 0.0000, 0.0000], [0.4125, 0.3273, 0.2602, 0.0000]], [[0.5254, 0.4746, 0.0000, 0.0000], [0.3117, 0.2130, 0.1801, 0.2952]]])加性注意力学习不同维度的查询与键的注意力。下面输出为实数. q 的大小为 query_size,k 大小为 key_sizeimage.pngimage.pngclass AdditiveAttention(nn.Module): """加性注意力""" def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs): super(AdditiveAttention, self).__init__(**kwargs) self.W_k = nn.Linear(key_size, num_hiddens, bias=False) self.W_q = nn.Linear(query_size, num_hiddens, bias=False) self.w_v = nn.Linear(num_hiddens, 1, bias=False) self.dropout = nn.Dropout(dropout) def forward(self, queries, keys, values, valid_lens): # 输入的 queries 的形状: (batch_size, num_queries, query_size) # 输入的 queries 的形状: (batch_size, num_keys, key_size) queries, keys = self.W_q(queries), self.W_k(keys) # - 在维度扩展 unsqueeze 后, # queries的形状:(batch_size,查询的个数,1,num_hidden) # key的形状:(batch_size,1,“键-值”对的个数,num_hiddens) # 使用广播方式进行求和 # - unsqueeze(n) 在第 n 维添加一维,长度为 1 features = queries.unsqueeze(2) + keys.unsqueeze(1) features = torch.tanh(features) # self.w_v仅有一个输出,因此从形状中移除最后那个维度。 # scores的形状:(batch_size,查询的个数,“键-值”对的个数) scores = self.w_v(features).squeeze(-1) self.attention_weights = masked_softmax(scores, valid_lens) # attension_weights.shape = (batch_size, num_queries, num_keys) # values的形状:(batch_size,“键-值”对的个数,值的维度) return torch.bmm(self.dropout(self.attention_weights), values) # return 的形状: (batch_size, num_queries, value_dim)# - queries: 2 个批量,分别 1 个 query,分别长度为 20 # - 这个 query 查询对所有 key 的权重,都是一样的(虽然参数随机生成,但过程中用到的参数一样) # - mask 仅保留前两个 key,所以其权重都是 1 / 2,结果 value 为前两个 key 所存 value 的均值 queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2)) # values的小批量,两个值矩阵是相同的 values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat( 2, 1, 1) valid_lens = torch.tensor([2, 6]) attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8, dropout=0.3) attention.eval() attention(queries, keys, values, valid_lens), values >>> (tensor([[[ 2.0000, 3.0000, 4.0000, 5.0000]], [[10.0000, 11.0000, 12.0000, 13.0000]]], grad_fn=<BmmBackward0>), tensor([[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], [16., 17., 18., 19.], [20., 21., 22., 23.], [24., 25., 26., 27.], [28., 29., 30., 31.], [32., 33., 34., 35.], [36., 37., 38., 39.]], [[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], [16., 17., 18., 19.], [20., 21., 22., 23.], [24., 25., 26., 27.], [28., 29., 30., 31.], [32., 33., 34., 35.], [36., 37., 38., 39.]]]))这两个 batch 对 10 个 key 的注意力分配:image.png缩放点积注意力原文很简洁:image.pngclass DotProductAttention(nn.Module): """缩放点积注意力""" def __init__(self, dropout, **kwargs): super(DotProductAttention, self).__init__(**kwargs) self.dropout = nn.Dropout(dropout) # queries的形状:(batch_size,查询的个数,d: 即 query_size) # keys的形状:(batch_size,“键-值”对的个数,d) # values的形状:(batch_size,“键-值”对的个数,值的维度) # valid_lens的形状:(batch_size,)或者(batch_size,查询的个数) def forward(self, queries, keys, values, valid_lens=None): d = queries.shape[-1] # 设置transpose_b=True为了交换keys的最后两个维度 scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d) self.attention_weights = masked_softmax(scores, valid_lens) return torch.bmm(self.dropout(self.attention_weights), values)[done

torch.Size([2, 1, 6])

2024-09-26
d2ljulyfunnotes技术学习

语法n 批量矩阵乘法X = torch.ones((2, 1, 4)) Y = torch.ones((2, 4, 6)) torch.bmm(X, Y).shape # torch.Size([2, 1, 6])Nadaraya-Watson 核回归思想若训练数据包含若干 $x_i, y_i$ 对,则测试输入为 $x$ 时,将对每个训练数据分配一个权重(注意力权重),最终测试输出为 $f(x) = sum_(i - 1)^n alpha (x, x_i) y_i$。Nadaraya 的想法如下: $$alpha(x, x_i) = K(x - x_i)$$$x$ 被称为查询,$x_i, y_i$ 为键值对,这就是在查询 $x$ 和 $x_i$ 键之间的注意力权重其中 $K$ 可设计。例如,设计为 $K(u) = 1 / (sqrt(2 pi)) exp(- u^2 / 2)$,则推导出 $$f(x) = sum_(i = 1)^n "softmax"(-1 / 2 (x - x_i)^2)y_i$$这对 $x$ 的分布有要求。因此改进为带参数的注意力汇聚模型,额外学习一个 $w$ 参数使得:$$ f(x) = sum_(i = 1)^n "softmax"(-1 / 2 ((x - x_i)w)^2)y_i $$使用平方损失函数 nn.MESLoss 和随机梯度下降 nn.optim.SGD(lr=0.5) 进行训练。得到的注意力热图为:image.png(此图中任意一条横线表示一个测试输入 $x$ 下对整条 $x$ 轴的权重分配)学到的 $w$ 很小,使注意力更多分配到最近的 $x$。done

4.5.权重衰减

2024-08-23
d2ljulyfunnotes技术学习

下面是手动实现,写起来符合直觉,如下:def l2_penalty(w): return torch.sum(w.pow(2)) / 2 def train(lambd): w, b = init_params() net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss num_epochs, lr = 100, 0.003 animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) for epoch in range(num_epochs): for X, y in train_iter: # 增加了L2范数惩罚项, # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量 l = loss(net(X), y) + lambd * l2_penalty(w) l.sum().backward() d2l.sgd([w, b], lr, batch_size) if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数是:', torch.norm(w).item())集成实现:def train_concise(wd): net = nn.Sequential(nn.Linear(num_inputs, 1)) for param in net.parameters(): param.data.normal_() loss = nn.MSELoss(reduction='none') num_epochs, lr = 100, 0.003 # 偏置参数没有衰减 trainer = torch.optim.SGD([ {"params":net[0].weight,'weight_decay': wd}, {"params":net[0].bias}], lr=lr) animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) for epoch in range(num_epochs): for X, y in train_iter: trainer.zero_grad() # loss 上面 loss = nn.MSELoss l = loss(net(X), y) l.mean().backward() trainer.step() if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数:', net[0].weight.norm().item()

输入必须为 long 类型

2024-08-22
d2ljulyfunnotes技术学习

Embedding 层see: https://blog.csdn.net/zhaohongfei_358/article/details/122809709与独热编码不同,Embedding 可以将词表降维,且可进行学习。也就是做了 word2vecimport torch from torch import nn embedding = nn.Embedding(20, 5) # 输入必须为 long 类型 embedding(torch.LongTensor([0,1,2]))# 初始状态为随机编码,会随着梯度下降逐渐学习 tensor([[ 0.4471, 0.3875, -1.0195, -1.1125, 1.3481], [-1.7230, -0.1964, -0.0420, 0.5782, 0.4514], [-0.0310, -1.9674, -1.1344, -1.6752, 1.0801]], grad_fn=<EmbeddingBackward0>)类参数 padding_idx:指定填充的索引,这个索引的向量会被初始化为 0。关于形状:nl: num_layers ,GRU 的隐藏层个数ns: num_steps,时间步?n: batch_size各层Encoder.Embedding 学会将原始语言(en)词元转换为 vecEncoder.GRU 学会对 en vec 和上一时刻隐状态给出良好的神秘输出(无意义)和下一时刻 en 隐状态Decoder.Embedding 将学会将目标语言(fr)词元转换为 vecDecoder.GRU 学会对 fr vec 和上一时刻隐状态给出良好的神秘输出和下一时刻 fr 隐状态Decoder.Dense 学会将神秘输出转换为词元概率Li Mu:image.png训练时 Decoder 输入为正确答案:image.png推理时 Decoder 输入为上一时刻输出:image.png外层训练入口[train] 训练时,每对数据仅预测一步,获取损失optimizer = torch.optim.Adam(net.parameters(), lr=lr) loss = MaskedSoftmaxCELoss() for epoch in range(num_epochs): timer = d2l.Timer() for batch in data_iter: optimizer.zero_grad() X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch] # X 和 Y 的形状:(n, ns) bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0], device=device).reshape(-1, 1) # bos 的形状:(n, 1) dec_input = torch.cat([bos, Y[:, :-1]], 1) # 强制教学 # dec_input 的形状:(n, ns),第一个改为 <bos> # 强制教学把答案当做输入 # 使 embedding 学会正确编码法语, s.t. GPU 可以正确给出下一时刻的 output + state # s.t. 使得 Linear 可以根据 output 正确给出词元概率 && 这个 state 可以帮助下一时刻正确给出 output 和 state # enc_X=X, dec_X=dec_input, *args Y_hat, _ = net(X, dec_input, X_valid_len) l = loss(Y_hat, Y, Y_valid_len) l.sum().backward() # 损失函数的标量进行“反向传播” d2l.grad_clipping(net, 1) num_tokens = Y_valid_len.sum() optimizer.step()[train.net()] 其中 enc_X 输入为原始语言序列,dec_X 输入为目标语言序列答案。def forward(self, enc_X, dec_X, *args): enc_outputs = self.encoder(enc_X, *args) # enc_outputs: (output(ns, n, nh), state(nl, n, nh)) dec_state = self.decoder.init_state(enc_outputs, *args) # dec_state: 即 state(nl, n, nh) return self.decoder(dec_X, dec_state)损失函数就是预测出来概率与答案的交叉熵损失. 但是 <pad> 需要被排除在外.[train.loss] loss 传入解码器的预测概率(长度为词表大小)和答案序列计算损失,会对 valid_len 以外的 <pad> 进行 mask,使之不产生损失。外层预测入口首先将整个原始句子传入编码器,得到浓缩的所有信息 $bold(c)$传给解码器一步步预测。$bold(c)$ 作为第一步预测的隐状态,接下来每步隐状态会不断更新# def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps, # device, save_attention_weights=False): """ 此处设置解码器网路内部 num_steps = 1,解码器输入的时间长度也为 1,每次只预测一个单词. """ src_tokens = src_vocab[src_sentence.lower().split(' ')] + [ src_vocab['<eos>']] enc_valid_len = torch.tensor([len(src_tokens)], device=device) src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>']) # 添加批量轴 # 在 shape[0] 处加一维(长度为 1) # enc_X: shape = (1, len(src_tokens)) enc_X = torch.unsqueeze( torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0) # 现将整个 enc_X 传入编码器 # enc_outputs: 包含 output, state enc_outputs = net.encoder(enc_X, enc_valid_len) # 此处 [0] output: (ns, n, nh) # [1] state: (nl, n, nh) dec_state = net.decoder.init_state(enc_outputs, enc_valid_len) # dec_state shape: (nl, n, nh) 不包含每个时间步 dec_X = torch.unsqueeze(torch.tensor( [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0) # dec_X: shape (n, ns) ex (1, 1) output_seq, attention_weight_seq = [], [] for _ in range(num_steps): Y, dec_state = net.decoder(dec_X, dec_state) # Y shape = (1, 1, vocab_size) (n, ns, nvocab) # 我们使用具有预测最高可能性的词元,作为解码器在下一时间步的输入 dec_X = Y.argmax(dim=2) pred = dec_X.squeeze(dim=0).type(torch.int32).item() # 保存注意力权重(稍后讨论) if save_attention_weights: attention_weight_seq.append(net.decoder.attention_weights) # 一旦序列结束词元被预测,输出序列的生成就完成了 if pred == tgt_vocab['<eos>']: break output_seq.append(pred) return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq类class Seq2SeqEncoder(d2l.Encoder): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs): super(Seq2SeqEncoder, self).__init__(**kwargs) # 嵌入层 self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = rnn.GRU(num_hiddens, num_layers, dropout=dropout) def forward(self, X, *args): # [def] n 是批量 # 输入 X 形状: (n, ns) X = self.embedding(X) # 输出'X'的形状:(batch_size,num_steps,embed_size) # 在循环神经网络模型中,第一个轴对应于时间步 X = X.swapaxes(0, 1) state = self.rnn.begin_state(batch_size=X.shape[1], ctx=X.ctx) output, state = self.rnn(X, state) # output的形状:(num_steps,batch_size,num_hiddens) 被舍弃 # state的形状:(num_layers,batch_size,num_hiddens) 编码全部输入信息 return output, state class Seq2SeqDecoder(d2l.Decoder): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs): super(Seq2SeqDecoder, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers, dropout=dropout) self.dense = nn.Linear(num_hiddens, vocab_size) def init_state(self, enc_outputs, *args): return enc_outputs[1] # 也就是 state def forward(self, X, state): # X: shape (n, ns) # state: shape (nl, n, nh),就是编码器对所有输入信息的编码. X = self.embedding(X).permute(1, 0, 2) # X: (ns, n, n_embed) ex (7, 4, 8) # 此处 state[-1] 是深度循环神经网络最后一层,即最靠近 output 的一层. # 将这一层复制 n_timestep 次. [这合理吗?] context = state[-1].repeat(X.shape[0], 1, 1) # context: (ns, n, nh) ex (7, 4, 16) X_and_context = torch.cat((X, context), 2) # X_and_context: (ns, n, n_embed + nh) ex (7, 4, 24) # 也即,每一时间步每一批量的输入为 n_embed + nh,即 embedding + 刚刚 state 最后一层. # [qm] 这里 rnn() 怎么又把 state 输入进去了,这不纯信息冗余吗 # 注意 rnn 是一次性循环所有时间步. output, state = self.rnn(X_and_context, state) # 此时 output 形状为 (ns, n, nh) 即每时间步每批量输出一个神秘的 nh output = self.dense(output).permute(1, 0, 2) # 最终 output的形状:(n, ns, n_vocab) 这里 ns 中的有效长度和输入 X 不同 # state的形状: (num_layers,batch_size,num_hiddens) return output, state class EncoderDecoder(nn.Module): def __init__(self, encoder, decoder, **kwargs): super(EncoderDecoder, self).__init__(**kwargs) self.encoder = encoder self.decoder = decoder def forward(self, enc_X, dec_X, *args): enc_outputs = self.encoder(enc_X, *args) dec_state = self.decoder.init_state(enc_outputs, *args) return self.decoder(dec_X, dec_state)手绘流程图公式解码时隐状态: $$s_(t^prime) = g(y_(t^prime - 1), bold(c), s_(t^prime - 1))$$其中 $c$ 是编码器对整个输入序列 $x_1, ... x_T$ 的编码.这里 $t^prime$ 只是为了和输入序列时间区分.output, state = self.rnn(X_and_context, state) output = self.dense(output).permute(1, 0, 2)BLEU用于评估预测序列的好坏,值域 $0 tilde 1$。给出预测序列中的 $n$ 元语法有多少出现在标签序列(答案)中。越长的语法权重越大。若预测的太短,会有惩罚。go . => va !, bleu 1.000 i lost . => j'ai perdu ., bleu 1.000 he's calm . => il est bon ?, bleu 0.537 i'm home . => je suis chez moi debout ., bleu 0.80

9.6.encoderdecoder

2024-08-22
d2ljulyfunnotes技术学习

from torch import nn class Encoder(nn.Module): """编码器-解码器架构的基本编码器接口""" def __init__(self, **kwargs): super(Encoder, self).__init__(**kwargs) def forward(self, X, *args): raise NotImplementedError class Decoder(nn.Module): """编码器-解码器架构的基本解码器接口""" def __init__(self, **kwargs): super(Decoder, self).__init__(**kwargs) def init_state(self, enc_outputs, *args): raise NotImplementedError def forward(self, X, state): raise NotImplementedError class EncoderDecoder(nn.Module): """编码器-解码器架构的基类""" def __init__(self, encoder, decoder, **kwargs): super(EncoderDecoder, self).__init__(**kwargs) self.encoder = encoder self.decoder = decoder def forward(self, enc_X, dec_X, *args): enc_outputs = self.encoder(enc_X, *args) dec_state = self.decoder.init_state(enc_outputs, *args) return self.decoder(dec_X, dec_state)编码器有输入 env_X,同时解码器还会将编码器输出转化为 state,并和自己的额外输入 dec_X 一起输入到解码器中。这里并没有说输入会是独热编码

X: (2, 8) (n, ns)

2024-08-21
d2ljulyfunnotes技术学习

为了防止词表太大,将低频词元替换为 <unk> 最后生成的 iter 接口为: ```python train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8) for X, X_valid_len, Y, Y_valid_len in train_iter: print('X:', X.type(torch.int32)) print('X的有效长度:', X_valid_len) print('Y:', Y.type(torch.int32)) print('Y的有效长度:', Y_valid_len) break # X: (2, 8) (n, ns) # Y: 也是 (n, ns)# 小批量中训练数据的长度统一为 num_steps(上面有),不足的用 1 即 <pad> 填充 X: tensor([[62, 25, 4, 3, 1, 1, 1, 1], [99, 10, 4, 3, 1, 1, 1, 1]], dtype=torch.int32) # 可能代表的数据形如 [["I", "try", ".", <eos>, <pad>, <pad>, <pad>, <pad>], ["This", "is", ".", <eos>, <pad>, <pad>, <pad>, <pad>]] X的有效长度: tensor([4, 4]) Y: tensor([[186, 5, 3, 1, 1, 1, 1, 1], [ 0, 8, 4, 3, 1, 1, 1, 1]], dtype=torch.int32) Y的有效长度: tensor([3, 4]) # <bos> 在 train_seq2seq() 中再加入注意这并非要求网络输入层维数为 num_steps,比如 rnn 中也有 num_steps 但输入层维数就是词表大小(即一个单词的独热编码

iter 每次迭代 X([n, d] or [n, c, h, w]), y([n])

2024-08-19
d2ljulyfunnotes技术学习

import torch from torch import nn from d2l import torch as d2l net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10)) def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) batch_size, lr, num_epochs = 256, 0.1, 10 loss = nn.CrossEntropyLoss() trainer = torch.optim.SGD(net.parameters(), lr=lr) train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # iter 每次迭代 X([n, d] or [n, c, h, w]), y([n]) # 例如: (torch.Size([256, 1, 28, 28]), torch.Size([256])) class Accumulator: def __init__(self, n): self.data = [0.0] * n def add(self, *args): self.data = [a + float(b) for a, b in zip(self.data, args)] def reset(self): self.data = [0.0] * len(self.data) def __getitem__(self, idx): return self.data[idx] def evaluate_accuracy(net, data_iter): if isinstance(net, torch.nn.Module): net.eval() # 将模型设置为评估模式 metric = Accumulator(2) # 正确预测数、预测总数 with torch.no_grad(): for X, y in data_iter: metric.add(accuracy(net(X), y), y.numel()) return metric[0] / metric[1] def train_epoch_ch3(net, train_iter, loss, updater): # 将模型设置为训练模式 if isinstance(net, torch.nn.Module): net.train() # 训练损失总和、训练准确度总和、样本数 metric = Accumulator(3) for X, y in train_iter: # 计算梯度并更新参数 y_hat = net(X) l = loss(y_hat, y) if isinstance(updater, torch.optim.Optimizer): updater.zero_grad() l.mean().backward() updater.step() else: l.sum().backward() updater(X.shape[0]) metric.add(float(l.sum()), accuracy(y_hat, y), y.numel()) # 训练损失和训练精度 return metric[0] / metric[2], metric[1] / metric[2] def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save legend=['train loss', 'train acc', 'test acc']) for epoch in range(num_epochs): train_metrics = train_epoch_ch3(net, train_iter, loss, updater) test_acc = evaluate_accuracy(net, test_iter) train_loss, train_acc = train_metrics assert train_loss < 0.5, train_loss assert train_acc <= 1 and train_acc > 0.7, train_acc assert test_acc <= 1 and test_acc > 0.7, test_acc train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer

9.1.gru

2024-08-19
d2ljulyfunnotes技术学习

$$R_t^(in n times h) = sigma (X_t W_(x r) + H_(t - 1) W_(h r) + b_r)$$$$Z_t^(in n times h) = sigma (X_t W_(x z) + H_(t - 1) W_(h z) + b_z)$$以上都独占三组参数。候选隐状态,由重置门 $R_t$ 决定是否保留旧状态,若 $R_t$ 接近 $0$ 不保留,则倾向于捕获短期依赖关系。这里又占有三组参数:$$limits(H_t)^(tilde) = tanh(X_t W_(x h) + (R_t dot.circle H_(t - 1)) W_(h h) + b_h)$$更新门将决定是否忽略当前隐状态,最终生成真正的隐状态。若 $Z_t$ 接近 $1$ 则倾向于保留旧状态,捕获长期依赖关系:$$H_t = Z_t dot.circle H_(t - 1) + (1 - Z_t) dot.circle limits(H_t)^(tilde)$$从头实现def get_params(vocab_size, num_hiddens, device): num_inputs = num_outputs = vocab_size def normal(shape): return torch.randn(size=shape, device=device)*0.01 def three(): return (normal((num_inputs, num_hiddens)), normal((num_hiddens, num_hiddens)), torch.zeros(num_hiddens, device=device)) W_xz, W_hz, b_z = three() # 更新门参数 W_xr, W_hr, b_r = three() # 重置门参数 W_xh, W_hh, b_h = three() # 候选隐状态参数 # 输出层参数 W_hq = normal((num_hiddens, num_outputs)) b_q = torch.zeros(num_outputs, device=device) # 附加梯度 params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q] for param in params: param.requires_grad_(True) return params def init_gru_state(batch_size, num_hiddens, device): return (torch.zeros((batch_size, num_hiddens), device=device), ) def gru(inputs, state, params): W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] for X in inputs: Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z) R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r) H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h) H = Z * H + (1 - Z) * H_tilda Y = H @ W_hq + b_q outputs.append(Y) # [qm] 这里批量何在? return torch.cat(outputs, dim=0), (H,)高级 api 接口num_inputs = vocab_size gru_layer = nn.GRU(num_inputs, num_hiddens) model = d2l.RNNModel(gru_layer, len(vocab)) model = model.to(device) d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)问题训练好后的网络如何决定是否保留隐藏状态?是因为 R 和 Z 中的权重分布,导致输入 ahh 的无效单词就会导致 $R_t$ 重置?根据上面的 R 公式,似乎就是依赖当前输入和隐状态来决定生成重置权重

RNN() 内部含有 W, b 参数之类的

2024-08-16
d2ljulyfunnotes技术学习

nn.RNN接口很简单。注意隐状态 state 是在 RNN 对象之外另开对象进行存储的。batch_size, num_steps = 32, 35 train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps) num_hiddens = 256 # RNN() 内部含有 W, b 参数之类的 rnn_layer = nn.RNN(len(vocab), num_hiddens) # state 形状 [隐藏层数,批量大小,隐藏单元数] state = torch.zeros((1, batch_size, num_hiddens)) # X.shape = [时间步,批量,词典大小(独热)] X = torch.rand(size=(num_steps, batch_size, len(vocab))) # Y.shape = [时间步,批量,隐藏单元数], state 形状不变 Y, state = self.rnn(X, state)封装class RNNModel(nn.Module): """循环神经网络模型""" def __init__(self, rnn_layer, vocab_size, **kwargs): super(RNNModel, self).__init__(**kwargs) self.rnn = rnn_layer self.vocab_size = vocab_size self.num_hiddens = self.rnn.hidden_size # 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1 if not self.rnn.bidirectional: self.num_directions = 1 self.linear = nn.Linear(self.num_hiddens, self.vocab_size) else: self.num_directions = 2 self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size) def forward(self, inputs, state): X = F.one_hot(inputs.T.long(), self.vocab_size) X = X.to(torch.float32) Y, state = self.rnn(X, state) # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数) # 它的输出形状是(时间步数*批量大小,词表大小)。 output = self.linear(Y.reshape((-1, Y.shape[-1]))) return output, state def begin_state(self, device, batch_size=1): if not isinstance(self.rnn, nn.LSTM): # nn.GRU以张量作为隐状态 return torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device) else: # nn.LSTM以元组作为隐状态 return (torch.zeros(( self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device), torch.zeros(( self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device))32:0

获取初始参数

2024-08-16
d2ljulyfunnotes技术学习

梯度裁剪类似于球形投影,使梯度的 L2 范数不超过阈值。训练注意训练时候每个批量的隐状态是分别存储的,不会相互影响。多久一次 backward?要将一个批量内所有样本的所有时间步(在程序开始时设定,比如样本 size = 32,单次输入时间步 num_steps 为 35)forward 以后(每次 forward 仅进行一个时间步的预测),才进行 backward。因此权重参数矩阵会累乘,容易导致梯度爆炸,故进行梯度裁剪。d2l 代码中,梯度裁剪在 backward 后,updater 之前执行。注意对每个批量而言,每次输入仅有一个 token,并非多个时间步 token 同时输入。手绘2:15:13手动代码import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l batch_size, num_steps = 32, 35 train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps) # 获取初始参数 def get_params(vocab_size, num_hiddens, device): num_inputs = num_outputs = vocab_size def normal(shape): return torch.randn(size=shape, device=device) * 0.01 # 隐藏层参数 W_xh = normal((num_inputs, num_hiddens)) W_hh = normal((num_hiddens, num_hiddens)) b_h = torch.zeros(num_hiddens, device=device) # 输出层参数 W_hq = normal((num_hiddens, num_outputs)) b_q = torch.zeros(num_outputs, device=device) # 附加梯度 params = [W_xh, W_hh, b_h, W_hq, b_q] for param in params: param.requires_grad_(True) return params # 前向传播 forward # 一次 forward 就将各个批量的所有时间步都生成完了 def rnn(inputs, state, params): # inputs的形状:(时间步数量,批量大小,词表大小) W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] # 循环每个时间步 for X in inputs: # X的形状:(批量大小,词表大小) # 注意这里隐状态包含所有批量各自的隐状态 # H 形状: (批量大小,隐藏单元个数) H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h) # 本步输出不会影响下一步输出,只有隐状态才会 # Y 形状: (批量大小,词表大小),因为前面已经指定 num_outputs = vocab_size Y = torch.mm(H, W_hq) + b_q outputs.append(Y) # 返回一个元组,即所有时间步的输出 y 和最后一个隐状态 return torch.cat(outputs, dim=0), (H,) # torch.cat 输出为 (ns * n, n_vocab) def init_rnn_state(batch_size, num_hiddens, device): return (torch.zeros((batch_size, num_hiddens), device=device), ) # 模型封装 class RNNModelScratch: def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn): self.vocab_size, self.num_hiddens = vocab_size, num_hiddens self.params = get_params(vocab_size, num_hiddens, device) self.init_state, self.forward_fn = init_state, forward_fn def __call__(self, X, state): X = F.one_hot(X.T, self.vocab_size).type(torch.float32) return self.forward_fn(X, state, self.params) def begin_state(self, batch_size, device): return self.init_state(batch_size, self.num_hiddens, device) num_hiddens = 512 net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn) state = net.begin_state(X.shape[0], d2l.try_gpu()) # 预测 def predict_ch8(prefix, num_preds, net, vocab, device): """在prefix后面生成新字符""" state = net.begin_state(batch_size=1, device=device) outputs = [vocab[prefix[0]]] get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1)) for y in prefix[1:]: # 预热期 _, state = net(get_input(), state) outputs.append(vocab[y]) for _ in range(num_preds): # 预测num_preds步 y, state = net(get_input(), state) outputs.append(int(y.argmax(dim=1).reshape(1))) return ''.join([vocab.idx_to_token[i] for i in outputs]) # 梯度裁剪 def grad_clipping(net, theta): #@save """裁剪梯度""" if isinstance(net, nn.Module): params = [p for p in net.parameters() if p.requires_grad] else: params = net.params norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params)) if norm > theta: for param in params: param.grad[:] *= theta / norm # 训练一个 epoch def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): """训练网络一个迭代周期(定义见第8章)""" state, timer = None, d2l.Timer() metric = d2l.Accumulator(2) # 训练损失之和,词元数量 for X, Y in train_iter: if state is None or use_random_iter: # 在第一次迭代或使用随机抽样时初始化state state = net.begin_state(batch_size=X.shape[0], device=device) else: if isinstance(net, nn.Module) and not isinstance(state, tuple): # state对于nn.GRU是个张量 state.detach_() else: # state对于nn.LSTM或对于我们从零开始实现的模型是个张量 for s in state: s.detach_() y = Y.T.reshape(-1) # y.shape: (ns * n) X, y = X.to(device), y.to(device) y_hat, state = net(X, state) l = loss(y_hat, y.long()).mean() if isinstance(updater, torch.optim.Optimizer): updater.zero_grad() l.backward() grad_clipping(net, 1) updater.step() else: l.backward() grad_clipping(net, 1) # 因为已经调用了mean函数 updater(batch_size=1) metric.add(l * y.numel(), y.numel()) return math.exp(metric[0] / metric[1]), metric[1] / timer.stop() # 训练多个 epoch def train_ch8(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False): """训练模型(定义见第8章)""" loss = nn.CrossEntropyLoss() animator = d2l.Animator(xlabel='epoch', ylabel='perplexity', legend=['train'], xlim=[10, num_epochs]) # 初始化 if isinstance(net, nn.Module): updater = torch.optim.SGD(net.parameters(), lr) else: updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size) predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device) # 训练和预测 for epoch in range(num_epochs): ppl, speed = train_epoch_ch8( net, train_iter, loss, updater, device, use_random_iter) if (epoch + 1) % 10 == 0: print(predict('time traveller')) animator.add(epoch + 1, [ppl]) print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}') print(predict('time traveller')) print(predict('traveller')) num_epochs, lr = 500, 1 train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu()

8.4.RNN

2024-08-16
d2ljulyfunnotes技术学习

RNN 基础模型$$bold(H)^(in n times h)t = phi(bold(X^(in n times d "输入维度")) bold(W(x h)^(in d times h)) + bold(H)(t - 1)^(in h times h) bold(W)(h h)^(in h times h) b_h)$$$$O_t = H_t W_(h q "输出维度") + b_q$$这里 $W_(x h), W_(h h)$ 极其类似于单隐藏层感知机中的隐藏层,只是前一刻的隐藏层输出会成为下一刻隐藏层的输入的一部分。而 $H_t$ 隐状态则存储在网络之外。Perplexity 困惑度$$exp(- 1 / n sum_(t = 1)^(n) log P(x_t | x_(t - 1), ..., x_1))$$可以直接利用神经网络输出的概率,评估它有多自信。当每个输出的概率均为 $1$ 时,困惑度为 $1$,当概率为 $0$ 时困惑度正无穷,当概率为均匀分布时困惑度为唯一词元数(也是未压缩情况下存储序列最好的编码方式)。看到一个语言模型报告其 perplexity 为 $109$ 时,直观理解为它每次输出认为有 $109$ 个词作为下一个词的合理选择。ref: http://sentiment-mining.blogspot.com/2016/11/perplexity.htm

x shape: (n, ns)

2024-08-15
d2ljulyfunnotes技术学习

拉普拉斯平滑在统计单词 / 连续单词出现次数后,计算出现概率 $P$ 时,添加一个超参数小常量。效果:若常量趋于无穷大,则概率为 $1 / "单词总数"$(对于连续单词 AB,则是趋于 $1 / P("A")$)齐普夫定律分布满足对数坐标系上的下降直线。一元语法,n 元语法均遵守这个分布。构造的数据集形如for X, Y in seq_data_iter_sequential(my_seq, batch_size=2, num_steps=5): print('X: ', X, '\nY:', Y)# x shape: (n, ns) # y shape: (n, ns) X: tensor([[ 2, 3, 4, 5, 6], [18, 19, 20, 21, 22]]) Y: tensor([[ 3, 4, 5, 6, 7], [19, 20, 21, 22, 23]]) X: tensor([[ 7, 8, 9, 10, 11], [23, 24, 25, 26, 27]]) Y: tensor([[ 8, 9, 10, 11, 12], [24, 25, 26, 27, 28]]) X: tensor([[12, 13, 14, 15, 16], [28, 29, 30, 31, 32]]) Y: tensor([[13, 14, 15, 16, 17], [29, 30, 31, 32, 33]])44:2

7.7.densenet

2024-08-15
d2ljulyfunnotes技术学习

概述densenet 与 resnet 相比,拼接了连续多个 ConvBlock 构成一个 DenseBlock,并且使用的也不是相加而是 torch.cat。例如,对于一个 DenseBlock,若输入通道为 64,第一个卷积层输出通道数为 32,则会先拼接原始 64 通道和卷积输出的 32 通道,构成 96 通道输入到第二个卷积层,再输出通道数 32,则拼接为 96 + 32 = 128 通道。此处的标准 conv_block 变成了 BN + ReLU + Conv2d3x3为了保持通道数不爆炸,引入过渡块,直接 BN + Conv2d1x1 + AvgPool2x2 把通道数压下来(压一半)。最前面和最后面与 Googlenet 一致。训练时间 2m 36.6s (高宽已经 resize 到 96)loss 0.140, train acc 0.949, test acc 0.903练习为什么我们在过渡层使用平均汇聚层而不是最大汇聚层?不知道DenseNet的优点之一是其模型参数比ResNet小。为什么呢?DenseNet一个诟病的问题是内存或显存消耗过多。真的是这样吗?可以把输入形状换成$224 times 224$,来看看实际的显存消耗。有另一种方法来减少显存消耗吗?需要改变框架么?实现DenseNet论文 :cite:Huang.Liu.Van-Der-Maaten.ea.2017表1所示的不同DenseNet版本。应用DenseNet的思想设计一个基于多层感知机的模型。将其应用于 :numref:sec_kaggle_house中的房价预测任务

[解释] 输入通道 3,输出通道 96,输出大小 54 * 54,注意这里 kernel 没有刚好填到最右端,下取整为 54. 卷积核有 96 个(每个输出通道拥有一个卷积核,每个卷积核的形状是 3 * 11 * 11).

2024-08-15
d2ljulyfunnotes技术学习

四维度数据顺序为 n, c, h, w. ref: https://github.com/julyfun/numpytorch/blob/main/numpytorch/nn/modules/conv.pyFlatten 不会展开 batch 维度,(n, 512, 1, 1) 经过 Flatten 以后的形状为 (n, 512)卷积层中,输入通道到输出通道是全连接的。卷积核的形状是 out_channel * in_channel * k_h * k_w,这也是一层的参数量,每个输入-输出通道 pair 拥有仅一个 $k_h times k_w$ 的卷积核(或者说每个输出通道拥有一个 in_channel * k_h * k_w 的卷积核).torch.nn.Conv2d# [解释] 输入通道 3,输出通道 96,输出大小 54 * 54,注意这里 kernel 没有刚好填到最右端,下取整为 54. 卷积核有 96 个(每个输出通道拥有一个卷积核,每个卷积核的形状是 3 * 11 * 11). nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=1),ClassesConvTranspose2dself.upconv2 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)他会在卷积逆操作的同时插入 0,放大分辨率。输入张量: 1 2 3 4 插入零后的张量: 1 0 2 0 0 0 0 0 3 0 4 0 0 0 0 0 卷积核: 1 0 0 1 转置卷积操作的输出: 1 0 2 0 0 0 0 0 3 0 4 0 0 0 0

7.6.resnet

2024-08-15
d2ljulyfunnotes技术学习

回忆提纲resnet 块一条路径为 Conv + BN + ReLU + Conv + BN,保持形状不变,将输入直接加到输出,再跟一个 ReLU.为什么有用? 当网络深度加深时,可能出现网络退化(不是梯度爆炸)。事实上,可能中间的某一层已经是最优网络。resnet 非常容易学习到恒等映射,避免了网络退化。ref: https://blog.csdn.net/fengdu78/article/details/128451148手绘:截图:通道数不断增加(在每一 block 的第一个卷积层增加):conv5 output shape: (1, 64, 112, 112) batchnorm4 output shape: (1, 64, 112, 112) relu0 output shape: (1, 64, 112, 112) pool0 output shape: (1, 64, 56, 56) sequential1 output shape: (1, 64, 56, 56) sequential2 output shape: (1, 128, 28, 28) sequential3 output shape: (1, 256, 14, 14) sequential4 output shape: (1, 512, 7, 7) pool1 output shape: (1, 512, 1, 1) dense0 output shape: (1, 10)loss 0.016, train acc 0.996, test acc 0.919耗时 3m 0.1s,train acc 十分惊人练习:numref:fig_inception中的Inception块与残差块之间的主要区别是什么?在删除了Inception块中的一些路径之后,它们是如何相互关联的?参考ResNet论文 :cite:He.Zhang.Ren.ea.2016中的表1,以实现不同的变体。对于更深层次的网络,ResNet引入了“bottleneck”架构来降低模型复杂性。请试着去实现它。bottleneck 结构: 通过前后两个 Conv1x1,先降低通道,再恢复通道。ref: https://blog.csdn.net/DanTaWu/article/details/111468257 在ResNet的后续版本中,作者将“卷积层、批量规范化层和激活层”架构更改为“批量规范化层、激活层和卷积层”架构。请尝试做这个改进。详见 :cite:He.Zhang.Ren.ea.2016*1中的图1。改变后,需要使用 batch_size = 64, lr = 0.005,即缩小学习率和 batchsize为什么即使函数类是嵌套的,我们仍然要限制增加函数的复杂性呢?计算资源问题。复杂度越高,优化算法找到良好局部最小值的可能性也会下降。更简单的模型更容易解释。更简单的模型泛化能力更好,因为它不依赖特定训练集的特征

7.5.BN

2024-08-06
d2ljulyfunnotes技术学习

mean 用法ref: https://blog.csdn.net/qq_40714949/article/details/115485140dim 写第几维,第几维就会被求和,并消失。x = torch.Tensor([1, 2, 3, 4, 5, 6]).view(2, 3) y_0 = torch.mean(x, dim=0) y_1 = torch.mean(x, dim=1) print(x) print(y_0) print(y_1)tensor([[1., 2., 3.], [4., 5., 6.]]) tensor([2.5000, 3.5000, 4.5000]) tensor([2., 5.])BN 层回忆提纲\mathrm{BN}(\mathbf{x}) = \boldsymbol{\gamma} \odot \frac{\mathbf{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} + \boldsymbol{\beta}BN 层对上一层的输出进行规范化到 0 均值和 1 方差,同时学习一个缩放系数 gamma(初始为 1)和平移系数 beta(初始为 0)。符合直觉地,全连接层的每个元素对样本维求均值,mean.shape = (元素数) 。卷积层的每个通道对(样本,宽,高)求均值,mean.shape = (通道数)。下面 gamma 和 beta 是待学习参数,momentum 是上一次均值的权重(并不会直接采纳本次求得的均值,形同低通滤波)。非训练模式时,直接使用先前计算出来的 moving_mean, moving_var.import torch from torch import nn from d2l import torch as d2l def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式 if not torch.is_grad_enabled(): # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差 X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps) else: assert len(X.shape) in (2, 4) if len(X.shape) == 2: # 使用全连接层的情况,计算特征维上的均值和方差 mean = X.mean(dim=0) var = ((X - mean) ** 2).mean(dim=0) else: # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。 # 这里我们需要保持X的形状以便后面可以做广播运算 mean = X.mean(dim=(0, 2, 3), keepdim=True) var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # 训练模式下,用当前的均值和方差做标准化 X_hat = (X - mean) / torch.sqrt(var + eps) # 更新移动平均的均值和方差 moving_mean = momentum * moving_mean + (1.0 - momentum) * mean moving_var = momentum * moving_var + (1.0 - momentum) * var Y = gamma * X_hat + beta # 缩放和移位 return Y, moving_mean.data, moving_var.data class BatchNorm(nn.Module): # num_features:完全连接层的输出数量或卷积层的输出通道数。 # num_dims:2表示完全连接层,4表示卷积层 def __init__(self, num_features, num_dims): super().__init__() if num_dims == 2: shape = (1, num_features) else: shape = (1, num_features, 1, 1) # 参与求梯度,拉伸和偏移参数,分别初始化成1和0 self.gamma = nn.Parameter(torch.ones(shape)) self.beta = nn.Parameter(torch.zeros(shape)) # 非模型参数的变量初始化为0和1 self.moving_mean = torch.zeros(shape) self.moving_var = torch.ones(shape) def forward(self, X): # 如果X不在内存上,将moving_mean和moving_var # 复制到X所在显存上 if self.moving_mean.device != X.device: self.moving_mean = self.moving_mean.to(X.device) self.moving_var = self.moving_var.to(X.device) # 保存更新过的moving_mean和moving_var Y, self.moving_mean, self.moving_var = batch_norm( X, self.gamma, self.beta, self.moving_mean, self.moving_var, eps=1e-5, momentum=0.9) return Y net = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(), nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(), nn.Linear(84, 10))由上述示例知,BN 是放在激活函数和卷积或线性层之间。为什么只检查 moving_mean 所在的显存,而不需要检查 gamma, beta 所在的显存?chat: 在PyTorch中,nn.Parameter对象自动与模块(nn.Module)注册,并且会随模块一起被移动到指定的设备(CPU或GPU)上。当你调用.to(device)、.cuda()或.cpu()方法在一个nn.Module上时,它不仅会移动模块的参数(即由nn.Parameter定义的参数)到指定的设备,还会移动缓冲区(如批量归一化层的运行时均值和方差)。然而,这里的关键点是self.moving_mean和self.moving_var并不是通过nn.Parameter定义的,它们是作为普通的torch.Tensor对象存在的,不会自动随着模块的.to(device)调用而移动。因此,在BatchNorm类的forward方法中,需要手动检查self.moving_mean和self.moving_var是否与输入X在同一设备上。如果它们不在同一设备上,就需要手动将它们移动到X所在的设备,以保证计算能够正常进行。这是为了确保所有用于计算的张量都在同一设备上,从而避免因设备不匹配导致的错误。verified. loss 0.272, train acc 0.899, test acc 0.864学习到的参数:net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))(tensor([2.0412, 4.2506, 2.4837, 2.8996, 0.3911, 3.2350], device='cuda:0', grad_fn=<ViewBackward0>), tensor([-0.7424, 2.6919, 2.7948, -2.8892, -0.3490, 1.4538], device='cuda:0', grad_fn=<ViewBackward0>))pure pytorchnet = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(), nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(), nn.Linear(84, 10))loss 0.264, train acc 0.904, test acc 0.853炼金术从 BN 开始就很难解释网络层作用的原理了。Exercise在使用批量规范化之前,我们是否可以从全连接层或卷积层中删除偏置参数?为什么?可以,BN 层会消除前一层 $b$ 的影响比较LeNet在使用和不使用批量规范化情况下的学习率。绘制训练和测试准确度的提高。使用 (10 epochs):loss 0.264, train acc 0.904, test acc 0.853不使用 BN:loss 0.502, train acc 0.807, test acc 0.804学习率有多高?带 BN 学习率 1.0,不带则 0.9我们是否需要在每个层中进行批量规范化?尝试一下?不删除BN层,则loss 0.268, train acc 0.901, test acc 0.861 只保留第二个和第四个BN层,则loss 0.289, train acc 0.894, test acc 0.787 只保留第一个和第三个BN层,则loss 0.286, train acc 0.893, test acc 0.820 只删除第一个BN层,则loss 0.272, train acc 0.899, test acc 0.769 只删除第二个BN层,则loss 0.269, train acc 0.901, test acc 0.774 只删除第三个BN层,则loss 0.272, train acc 0.901, test acc 0.857 只删除第四个BN层,则loss 0.283, train acc 0.895, test acc 0.857 可以看出删除全连接层后面的批量规范化对结果影响不大,因此实际上是可以删除的可以通过批量规范化来替换暂退法吗?行为会如何改变?似乎是可以的,BN层兼具稳定训练过程和正则化的效果,将全连接阶段的BN层全部替换为弃置概率为0.5的Dropout层, 则loss 0.486, train acc 0.827, test acc 0.763,对比而言精度不如使用BN层的情况,而且train acc与test acc的差距也更大,说明BN反而能够起到比dropout更好更稳定的正则化效果确定参数beta和gamma,并观察和分析结果。通过实验观察模型中gamma和beta的情况。从结果来看,随着网络加深,beta的值逐渐稳定到0附近,而gamma的值则逐步稳定到1到2之间,说明BN层确实有助于稳定中间结果的数据分布查看高级API中有关BatchNorm的在线文档,以查看其他批量规范化的应用。研究思路:可以应用的其他“规范化”转换?可以应用概率积分变换吗?全秩协方差估计可以么?BatchNorm vs LayerNorm不错的图示解释: https://blog.csdn.net/Little_White_9/article/details/123345062稍正式的解释,同一个图: https://snailcoder.github.io/2024/05/01/batchnorm-and-layernorm.htm

4.5.weight-decay

2024-08-06
d2ljulyfunnotes技术学习

默认情况下,PyTorch 同时衰减权重和偏移可以手动设置不衰减偏置:def train_concise(wd): net = nn.Sequential(nn.Linear(num_inputs, 1)) for param in net.parameters(): param.data.normal_() loss = nn.MSELoss(reduction='none') num_epochs, lr = 100, 0.003 # 偏置参数没有衰减 trainer = torch.optim.SGD([ {"params":net[0].weight,'weight_decay': wd}, {"params":net[0].bias}], lr=lr) animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) for epoch in range(num_epochs): for X, y in train_iter: trainer.zero_grad() l = loss(net(X), y) l.mean().backward() trainer.step() if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数:', net[0].weight.norm().item()

先定义一个丢弃层

2024-08-06
d2ljulyfunnotes技术学习

Dropout 法就是训练中传播时随机丢弃一些节点的权重。仅在训练期间有效,每次前向传播均会重新生成 mask手动实现# 先定义一个丢弃层 def dropout_layer(X, dropout): assert 0 <= dropout <= 1 # 在本情况中,所有元素都被丢弃 if dropout == 1: return torch.zeros_like(X) # 在本情况中,所有元素都被保留 if dropout == 0: return X mask = (torch.rand(X.shape) > dropout).float() return mask * X / (1.0 - dropout) dropout1, dropout2 = 0.2, 0.5 class Net(nn.Module): def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training = True): super(Net, self).__init__() self.num_inputs = num_inputs self.training = is_training self.lin1 = nn.Linear(num_inputs, num_hiddens1) self.lin2 = nn.Linear(num_hiddens1, num_hiddens2) self.lin3 = nn.Linear(num_hiddens2, num_outputs) self.relu = nn.ReLU() def forward(self, X): H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs)))) # 只有在训练模型时才使用dropout if self.training == True: # 在第一个全连接层之后添加一个dropout层 H1 = dropout_layer(H1, dropout1) H2 = self.relu(self.lin2(H1)) if self.training == True: # 在第二个全连接层之后添加一个dropout层 H2 = dropout_layer(H2, dropout2) out = self.lin3(H2) return out net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)简洁实现net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), # 在第一个全连接层之后添加一个dropout层 # 这里 dropout1 是一个实数 nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Linear(256, 10)) def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights)

样本 * 通道 * h * w

2024-08-05
d2ljulyfunnotes技术学习

阅读网络结构的函数参数# 样本 * 通道 * h * w # 输入为 X = torch.randn(1, 1, 224, 224) net = nn.Sequential( # [解释] 输入通道 1,输出通道 96,输出大小 54 * 54,注意这里 kernel 没有刚好填到最右端,下取整为 54. 卷积核有 96 个(每个输入-输出通道组合拥有一个卷及核) nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数 nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # 使用三个连续的卷积层和较小的卷积窗口。 # 除了最后的卷积层,输出通道的数量进一步增加。 # 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度 nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Flatten(), # 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合 nn.Linear(6400, 4096), nn.ReLU(), nn.Dropout(p=0.5), nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(p=0.5), # 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000 nn.Linear(4096, 10)

7.3.nin

2024-08-05
d2ljulyfunnotes技术学习

关于卷积层的提示注意输入通道和输出通道是全连接的,即:若单层图像的核有 X 个(取决于图像长宽,kernel_size, padding 和 stride),输入通道 n,输出通道为 m,则核函数有 X * n * m 个核参数则有 X \times n \times m \times \texttt{kernel_size} \times \texttt{kernel_size} 个ref: https://zh.d2l.ai/chapter_convolutional-neural-networks/channels.html特色相比 vgg,nin 主要特色两个:block 内部是 Conv + ReLU + Conv(kernel_size = 1) + ReLU + Conv(kernel_size = 1) + ReLU + MaxPool,给每个像素做了通道到通道的全连接最后直接是 384 通道到分类数通道的 nin block,没有 Linear 层全局平均汇聚层将图像的大小压缩至 1 * 1,不改变 n 和 channels,也不改变维数。def nin_block(in_channels, out_channels, kernel_size, strides, padding): return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), nn.ReLU(), nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(), nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()) net = nn.Sequential( nin_block(1, 96, kernel_size=11, strides=4, padding=0), nn.MaxPool2d(3, stride=2), nin_block(96, 256, kernel_size=5, strides=1, padding=2), nn.MaxPool2d(3, stride=2), nin_block(256, 384, kernel_size=3, strides=1, padding=1), nn.MaxPool2d(3, stride=2), nn.Dropout(0.5), # 标签类别数是10 nin_block(384, 10, kernel_size=3, strides=1, padding=1), nn.AdaptiveAvgPool2d((1, 1)), # 将四维的输出转成二维的输出,其形状为(批量大小,10) nn.Flatten())X = torch.rand(size=(1, 1, 224, 224)) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape:\t', X.shape)Copy to clipboard Sequential output shape: torch.Size([1, 96, 54, 54]) # 包含整个 nin_block MaxPool2d output shape: torch.Size([1, 96, 26, 26]) Sequential output shape: torch.Size([1, 256, 26, 26]) MaxPool2d output shape: torch.Size([1, 256, 12, 12]) Sequential output shape: torch.Size([1, 384, 12, 12]) MaxPool2d output shape: torch.Size([1, 384, 5, 5]) Dropout output shape: torch.Size([1, 384, 5, 5]) Sequential output shape: torch.Size([1, 10, 5, 5]) AdaptiveAvgPool2d output shape: torch.Size([1, 10, 1, 1]) Flatten output shape: torch.Size([1, 10]

7.4.googlenet

2024-08-05
d2ljulyfunnotes技术学习

https://www.cv-foundation.org/openaccess/content_cvpr_2015/html/Szegedy_Going_Deeper_With_2015_CVPR_paper.html[ ] 不错的论文解读: https://www.cnblogs.com/harrymore/p/17263443.html相比于 nin,google net 的 inception 块有 4 条路径。注意在合并时是直接在 channels 维度拼接而不是相加。但 googlenet 的最后重新引入了 Linear 层。import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l class Inception(nn.Module): # c1--c4是每条路径的输出通道数 def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1,单1x1卷积层 self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # 在通道维度上连结输出 return torch.cat((p1, p2, p3, p4), dim=1) b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10)

linear

2024-01-15
d2llearn

典型线性回归训练过程我不知道哪句话是给 backward() 做准备的,估计是 loss() 吧。测了一下发现 zero_grad() 好像也是必要的。我猜想不 zero_grad() 则 loss() + backword() 不会改变权重。至少不会有效地改变权重。num_epochs = 3 for epoch in range(num_epochs): # 调用迭代器的一组数据 for x, y in data_iter: # remind: loss = nn.MSELoss() l = loss(net(x), y) # clears the gradients of all parameters in the model, # ensuring that the gradients are fresh and ready to # be computed for the current iteration. trainer.zero_grad() # 反向传播 l.backward() # trainer 中传入了 net 的参数的引用 trainer.step() l = loss(net(features), labels) print(f'epoch {epoch + 1}, loss {l:f}')Chat says:在 PyTorch 中,trainer.zero_grad()用于清除模型参数的梯度,以确保在每个迭代中梯度是新鲜的,并且可以在当前迭代中进行计算。如果你去掉了trainer.zero_grad(),会导致梯度累积,也就是说梯度不会在每个batch之间清零,而会累积到之前的梯度上。这可能会导致两个主要问题:梯度爆炸或梯度消失:梯度可能会变得非常大或非常小,使得优化算法不稳定,这可能导致模型无法收敛或收敛非常缓慢。内存占用问题:梯度不清零可能会导致内存占用增加,因为梯度会持续累积,占用更多的内存。这里的所有数据都是用一个假定的 w 和 b 生成的。让我们看看网络得到的 w 和 b 与真实的 w b 差多少。w = net[0].weight.data print('w的估计误差:', true_w - w.reshape(true_w.shape)) b = net[0].bias.data print('b的估计误差:', true_b - b

No more posts to load.