makemore: bigram
似然:使概率的乘积最大 对数似然:使log(概率)的和最大 负对数似然:使-log(概率)的和最小 取负对数似然作为loss函数
对各种输出计数 -> 计算各种输出的概率 -> 按照概率抽样 输入给softmax的logits是对各种输出的计数的对数(倒推出来的意义) -> exp(logit)就是计数 -> softmax就是各种输出的概率 -> 按照概率抽样
广播机制:维度右对齐,然后左边补1
makemore: mlp
loss = F.cross_entropy(logits, Y) <=等价于=>
counts = logits.exp()
prob = counts / counts.sum(1, keepdims=True)
loss = -prob[torch.arange(N), Y].log().mean()makemore: batchnorm
权重矩阵不初始化为0,初始化为很小的数字,用于对称性破坏
论文Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification中初始化的缩放因子torch.nn.init.kaiming_normal_()实现了论文中的初始化,是常见的神经网络初始化方法,ReLu对应的gain是
将隐藏层在激活之前进行标准化:
hpreact = embcat @ @1 + b1 # hidden layer pre-activation
bnmean_i = hpreact.mean(0, keepdim=True) # 此批次的
bnstd_i = hpreact.std(0, keepdim=True)
bngain, bnbias = torch.ones((1, n_hidden)), torch.zeros((1, n_hidden))
hpreact = bngain * (hpreact - bnmean_i) / bnstd_i + bnbias # 标准化
# <- 这儿更新 bnmean_running 和 bnstd_running
h = torch.tanh(hpreact) # hidden layer这就是BatchNorm
推断时用在整个训练集计算的bn_mean和bn_std。
或者用指数移动平均作近似 bn_mean_running和bn_std_running
# 初始化
bnmean_running = torch.zeros((1, n_hidden))
bnstd_running = torch.ones((1, n_hidden)) # 更新
with torch.no_grad():
# 这里momentum为0.001
bnmean_runnng = 0.999 * bnmean_running + 0.001 * bnmean_i
bnstd_runnng = 0.999 * bnstd_running + 0.001 * bnstd_i线性层或卷积层 => BatchNorm => 非线性激活 这里线性 层或卷积层可以省略bias,因为假使加上bias,也会在BatchNorm中归一化时被减掉。
BatchNorm缺点:在前向传播时跟批次样本耦合
makemore: wavnet
卷积基本上可以认为是一个for循环,在某些输入序列的空间上应用一个小的线性滤波器
build GPT
transformer的输入
- 从训练文本中随机选
batch_size个block,block长block_size+1。block可重叠,互相独立。 - 每个block贡献
block_size个示例:从长1的序列推下一个、从长2的序列推下一个、……、从长block_size的序列推下一个。
def get_batch(split):
data = train_data if split == 'train' else val_data
ix = torch.randint(len(data) - block_size, (batch_size,))
x = torch.stack([data[i:i+block_size] for i in ix])
y = torch.stack([data[i+1:i+1+block_size] for i in ix])
return x, y注解:
ix = torch.randint(len(data) - block_size, (batch_size,))其中 max(ix) = len(data)-block_size-1,块[max(ix), len(data))长block_size+1
x = torch.stack([data[i:i+block_size] for i in ix])
y = torch.stack([data[i+1:i+1+block_size] for i in ix])一个block贡献block_size个示例:
data[i:i+1] => data[i+1]
data[i:i+2] => data[i+2]
...
data[i:i+block_size] => data[i+block_size]
x和y的形状都是
batch_size * block_sizenn.Embedding(vocab_size, n_embd)- 输入是任意形状的、值为
[0,vocab_size)的张量,比如形状为(a,b) - 输出会添加一维
(a,b,n_embd)
nn.Linear(n_embd, vocab_size)- 输入的最后一维为n_embd,比如形状为
(a,b,n_embd) - 输出的最后一维替换为vocab_size,比如变为
(a,b,vocab_size)
self-attention
B,T,C 指 batch, time, channel
用下三角矩阵,计算矩阵行的累加均值
# x形状是(B,T,C),计算x行的累加均值
wei = torch.tril(torch.ones(T,T))
wei = wei / wei.sum(1, keepdim=True) # --- (1)
out = wei @ x # (T,T) @ (B,T,C) --右对齐广播--> (B,T,T) @ (B,T,C) --> (B,T,C)扩展成用softmax
import torch.nn.functional as F
tril = torch.tril(torch.ones(T,T))
wei = torch.zeros((T,T))
wei = wei.masked_fill(tril == 0, float('-inf')) # 不要的标为-inf
wei = F.softmax(wei, dim=-1) # --- (2),这4行等同于(1)的2行
out = wei @ x扩展成加权和,就是self-attention
# let's see a single Head perform self-attention
head_size = 16
key = nn.Linear(C, head_size, bias=False)
query = nn.Linear(C, head_size, bias=False)
k = key(x) # (B,T,head_size)
q = query(x) # (B,T,head_size)
wei = q @ k.transpose(-2,-1) # (B,T,head_size) @ (B,head_size,T) ---> (B,T,T)
wei *= head_size**-0.5 # 保持wei方差为1
tril = torch.tril(torch.ones(T,T))
# wei = torch.zeros((T,T))
wei = wei.masked_fill(tril == 0, float('-inf'))
wei = F.softmax(wei, dim=-1)
# out = wei @ x
# 不聚合x,聚合v(x通过线性层后的值)
value = nn.Linear(C, head_size, bias=False)
v = value(x) # (B,T,head_size)
out = wei @ v # (B,T,head_size)- attention是节点间的沟通机制,每个节点加权聚合所有指向它的节点的信息
- attention的节点没有空间的概念,所以要加上位置编码
- batch中的block互相独立不沟通,block中的节点互相沟通
- 在encoder注意力块中删除
masked_fill()那行代码,允许所有节点沟通;在decoder注意力块中需要masked_fill(),以保证自回归时不使用未来信息 - self-attention指query都来自x,key和value也来自x;cross-attention指query来自x,而key和value来自他处(比如来自encoder块)
- 缩放点积attention,还需要
wei / sqrt(head_size),以保证wei方差为1
transformer的优化
def forward(self, x):
x = x + self.sa(self.ln1(x))
x = x + self.ffwd(self.ln2(x))
return ×残差网络
x = x + XXXLayer(x),XXXLayer(x)可以认为是计算分支
LayerNorm
pre-norm,在转换层之前使用LayerNorm(原论文在之后使用)。
LayerNorm在特征维度做规范化(pytorch中特征维度在dim=1)。
Dropout
Dropout训练了一堆子网络的ensemble,可以认为是一种正则化技术
- 对残差网络的计算分支Dropout
- 对self-attention的权重
weiDropout
build GPT Tokenizer
BPE, Byte Pair Encoding
递归地将最常见的两个token合成一个token,wiki