本文目标
使用字符串iloveu.
作为训练集训练100轮,使模型过拟合,当输入i
之后能够依次输出l
、o
、v
、e
、.
五个字符。
不对! 明明是让女朋友彻底掌握如何编写一个LSTM
。亲爱的,深度学习其实很简单,维度对上就能解决80%的问题,对不上就用全连接层暴力解决(bushi,剩下的就交给显卡^^
请牢记:
- 矩阵相乘 (
@
)- 若
M1 @ M2
,M1.size()
须是(a, m)
,M2.size()
须是(m, b)
,其结果就是(a,b)
- 若有
batch_size
,则它们的尺寸须分别为:(batch_size, m, k)
和(batch_size, k, n)
。
- 若
- 矩阵逐项乘 (
*
)- 二者维度保持一致
定义训练集
让我们来定义一个字典,共7个字符,直接使用字符串及其下标作为映射关系。
python
dict = {'i': 0, 'l': 1, 'o': 2, 'v': 3, 'e': 4, 'u': 5, '.': 6}
所以训练集应该是[0, 1, 2, 3, 4, 5, 6]
对应的独热向量或者 embedding
使用 nn.LSTM
万事开头难,首先简单点,让我们使用pytorch
自带的nn.LSTM
类。
python
import torch
import torch.nn as nn
import tqdm # 这个是进度条
# 定义 LSTM 模型
# 所有的模型都需要override init 和 forward 两个函数
# init里放一些初始化超参数、embedding、全连接和 attention 等我们在自己的模型中要用到的其他模型
class LSTMModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(LSTMModel, self).__init__()
self.hidden_size = hidden_size
# nn.Embedding() 的第一个参数应该是字典大小,这里由于字典大小等于input_size,直接用,实际上并不严谨
self.embedding = nn.Embedding(input_size, hidden_size)
self.lstm = nn.LSTM(hidden_size, hidden_size)
# 全连接层,将隐藏维度映射到输出维度(字典大小)
self.linear = nn.Linear(hidden_size, output_size)
def forward(self, input):
embedded = self.embedding(input)
output, _ = self.lstm(embedded.view(len(input), 1, -1))
output = self.linear(output.view(len(input), -1))
return output
# 定义训练数据和目标数据
string = "iloveu."
input_data = torch.tensor([0, 1, 2, 3, 4, 5, 6])
target_data = torch.tensor([1, 2, 3, 4, 5, 6, 0])
# 注意这里的训练集是 iloveu. 其中 iloveu 与 loveu. 计算损失,
# 因为每个位置都应该预测下一个单词,最后一个输入 '.' 不需要预测了,所以写什么都可以
# 在正式的 NLP 任务中可能是一个自定义的终止符号
# 我们一般会让 nn.Embedding 的第一个参数为字典大小 +1,即 8
# 这样就可以定义 target_data 为 torch.tensor([1, 2, 3, 4, 5, 6, 7])
# 这里的 7 不参与训练且无意义。
# 定义模型和损失函数
input_size = 7 # 字典大小
hidden_size = 128 # 隐藏状态的大小,亲测同样是一百轮,128 比 64 的 loss 少很多,其他模型不一定,需要调参
output_size = 7 # 输出大小,与字典大小相同
# 加载模型
model = LSTMModel(input_size, hidden_size, output_size)
# 加载损失函数,使用交叉熵
criterion = nn.CrossEntropyLoss()
# 使用优化器,这个对此模型影响不大
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# 训练模型
for epoch in tqdm.tqdm(range(100)):
optimizer.zero_grad()
output = model(input_data)
loss = criterion(output, target_data)
# 这里应该不取最后一个↑,但是 case 太简单,无所谓
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f'Epoch {epoch}, Loss: {loss.item()}')
test_input = torch.tensor([0,6,6,6,6,6,6])
# 长度保持为7 后面这么多 6 是占位符
print("结果为:")
print(string[test_input[0]],end="") # 输入的是i
for i in range(7):
test_output = model(test_input)
next_word = string[torch.argmax(test_output[i])]
print(next_word,end="")
if(next_word=='.'): # 相当于end,结束
break
# 替换下一个输入为我们的预测单词
test_input[i+1] = torch.argmax(test_output[i])
看看结果~ 爱你哦~
不使用 nn.LSTM
调库实在太简单了,那我们来试试手动实现一个LSTM
吧^^
让我们照着图和公式来。
由以上两个公式可知我们需要 8 个权重和 4 个 bias,我们不妨直接用全连接层,一个有 bias 另一个没有就行了,还能省去矩阵乘法的心智负担:
python
self.Wxi = nn.Linear(hidden_size, hidden_size,bias=False)
self.Whi = nn.Linear(hidden_size, hidden_size,bias=True)
self.Wxf = nn.Linear(hidden_size, hidden_size,bias=False)
self.Whf = nn.Linear(hidden_size, hidden_size,bias=True)
self.Wxo = nn.Linear(hidden_size, hidden_size,bias=False)
self.Who = nn.Linear(hidden_size, hidden_size,bias=True)
self.Whc = nn.Linear(hidden_size, hidden_size,bias=True)
self.Wxc = nn.Linear(hidden_size, hidden_size,bias=False)
接下来就是两个 element-wise 乘法和加法,不再赘述
修改一下原来的模型,去掉nn.LSTM
python
import torch
import torch.nn as nn
import tqdm
class LSTMModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(LSTMModel, self).__init__()
self.hidden_size = hidden_size
self.embedding = nn.Embedding(input_size, hidden_size)
# 注意这一行,我们改为了自己的 LSTM 类
self.lstm = MyLSTM(hidden_size, hidden_size)
self.linear = nn.Linear(hidden_size, output_size)
def forward(self, input):
embedded = self.embedding(input)
output, _ = self.lstm(embedded.view(len(input), 1, -1))
output = self.linear(output.view(len(input), -1))
return output
python
class MyLSTM(nn.Module):
def __init__(self, hidden_size):
super(MyLSTM, self).__init__()
self.hidden_size = hidden_size
## 下面可以用nn.Parameters,但是之前遇到过保存模型后再加载,Parameters参数还是随机初始化的问题,所以用Linear代替也一样
self.Wxi = nn.Linear(hidden_size, hidden_size,bias=False)
self.Whi = nn.Linear(hidden_size, hidden_size,bias=True)
self.Wxf = nn.Linear(hidden_size, hidden_size,bias=False)
self.Whf = nn.Linear(hidden_size, hidden_size,bias=True)
self.Wxo = nn.Linear(hidden_size, hidden_size,bias=False)
self.Who = nn.Linear(hidden_size, hidden_size,bias=True)
self.Whc = nn.Linear(hidden_size, hidden_size,bias=True)
self.Wxc = nn.Linear(hidden_size, hidden_size,bias=False)
def forward(self, input):
# 定义两个细胞状态,初始化为全零
h_t, c_t = (torch.zeros(self.hidden_size),torch.zeros(self.hidden_size))
result = []
for i in range(input.size(0)):
It = torch.sigmoid(self.Whi(h_t) + self.Wxi(input[i]))
Ft = torch.sigmoid(self.Whf(h_t) + self.Wxf(input[i]))
Ot = torch.sigmoid(self.Who(h_t) + self.Wxo(input[i]))
c_hat_t = torch.tanh(self.Whc(h_t) + self.Wxc(input[i]))
# 更新两个状态
c_t = Ft * c_t + It * c_hat_t
h_t = Ot * torch.tanh(c_t)
result.append(h_t.detach().numpy())
return torch.tensor(result)
饱饱爱你哦(づ ̄3 ̄)づ╭❤~