女朋友一定能看懂的LSTM编程详解

本文目标

使用字符串iloveu.作为训练集训练100轮,使模型过拟合,当输入i之后能够依次输出love.五个字符。

不对! 明明是让女朋友彻底掌握如何编写一个LSTM。亲爱的,深度学习其实很简单,维度对上就能解决80%的问题,对不上就用全连接层暴力解决(bushi,剩下的就交给显卡^^

请牢记:

  1. 矩阵相乘@
    • M1 @ M2M1.size() 须是 (a, m)M2.size()须是(m, b),其结果就是(a,b)
    • 若有batch_size,则它们的尺寸须分别为:(batch_size, m, k)(batch_size, k, n)
  2. 矩阵逐项乘 (*)
    • 二者维度保持一致

定义训练集

让我们来定义一个字典,共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 ̄)づ╭❤~

相关推荐
programmergo3 分钟前
CMS、G1、ZGC
java·jvm·算法
爱棋笑谦9 分钟前
e冒泡排序---复杂度O(X^2)
java·算法·排序算法
凭君语未可26 分钟前
详解贪心算法
算法·贪心算法
NeVeRMoRE_202433 分钟前
【数据结构和算法实践-树-LeetCode110-平衡二叉树】
数据结构·b树·算法·leetcode
夜清寒风43 分钟前
opencv学习:calcHist 函数绘制图像直方图及代码实现
人工智能·opencv·学习·算法·计算机视觉
Miss Stone1 小时前
对数器验证小和算法是否正确
算法
闲人编程1 小时前
Python实现优化的分水岭算法
python·算法·计算机视觉·优化·分水岭
一只邪恶大泡1 小时前
力扣14.最长公共前缀
数据结构·算法·leetcode
糖炒栗子要加糖2 小时前
论文阅读《Robust Steganography for High Quality Images》高质量因子图片的鲁棒隐写
图像处理·算法·安全
Java Fans2 小时前
Java 算法:随机抽题
java·python·算法