Pytorch 自学笔记(一):使用字符级特征来增强 LSTM 词性标注器

最近开始系统的学习Pytorch,准备写一个系列的blog(大概5篇左右),记录一下自己学习的进程。这第一篇笔记,写的是PyTorch官方教程序列模型和长短句记忆(LSTM)模型篇的课后练习题实现,网络很简单,但确实是我用Pytorch实现的第一个模型,值得写一篇blog来mark一下。

1. Pytorch中的LSTM

关于LSTM的基本概念和结构这里都不再过多介绍(想了解的来这里——一个非常有名的blog),主要说一下使用pytorch自带的lstm模型时一些需要注意的点:

  • Pytorch中自带的 LSTM 的输入形式是一个 3D 的Tensor,每一个维度都有重要的意义,第一个维度就是序列本身, 第二个维度是mini-batch中实例的索引,第三个维度是输入元素的索引
  • embedding层的输出一般是一个二维的向量,因此如果embedding层后直接接lstm层,需要利用tensor.view() 方法显式的修改embedding层的输出结构——embeds.view(len(sentence), 1, -1)
  • LSTM的输出格式为 output, (h_n, c_n),其中 output 保存了最后一层,每个time step的输出h,如果是双向LSTM,每个time step的输出h = [h正向, h逆向] (同一个time step的正向和逆向的h连接起来);而h_n实际上是每个layer最后一个状态(纵向)输出的拼接,c_n则为每个layer最后一个状态记忆单元中的值的拼接。

Note:这个地方先简单讲到这里,想更深入了解的推荐去看这篇,里面的图示很清晰。pytorch中LSTM的输入输出比想象中的复杂很多,其中还涉及到了输入序列的packed,后期会再开一篇来说一说这个。

2. 用LSTM来进行词性标注

教程中给出了 LSTM 网络来进行词性标注的代码,代码包括了三部分——数据准备,创建模型与训练模型。

2.1 数据准备

def prepare_sequence(seq, to_ix):
    idxs = [to_ix[w] for w in seq]
    return torch.tensor(idxs, dtype=torch.long)

training_data = [
    ("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]),
    ("Everybody read that book".split(), ["NN", "V", "DET", "NN"])
]
word_to_ix = {}
for sent, tags in training_data:
    for word in sent:
        if word not in word_to_ix:
            word_to_ix[word] = len(word_to_ix)
print(word_to_ix)
tag_to_ix = {"DET": 0, "NN": 1, "V": 2}

# 实际中通常使用更大的维度如32维, 64维.
# 这里我们使用小的维度, 为了方便查看训练过程中权重的变化.
EMBEDDING_DIM = 6
HIDDEN_DIM = 6

2.2 创建模型

class LSTMTagger(nn.Module):

    def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):
        super(LSTMTagger, self).__init__()
        self.hidden_dim = hidden_dim

        self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)

        # LSTM以word_embeddings作为输入, 输出维度为 hidden_dim 的隐藏状态值
        self.lstm = nn.LSTM(embedding_dim, hidden_dim)

        # 线性层将隐藏状态空间映射到标注空间
        self.hidden2tag = nn.Linear(hidden_dim, tagset_size)
        self.hidden = self.init_hidden()

    def init_hidden(self):
        # 一开始并没有隐藏状态所以我们要先初始化一个
        # 关于维度为什么这么设计请参考Pytoch相关文档
        # 各个维度的含义是 (num_layers, minibatch_size, hidden_dim)
        return (torch.zeros(1, 1, self.hidden_dim),
                torch.zeros(1, 1, self.hidden_dim))

    def forward(self, sentence):
        embeds = self.word_embeddings(sentence)
        lstm_out, self.hidden = self.lstm(
            embeds.view(len(sentence), 1, -1), self.hidden)
        tag_space = self.hidden2tag(lstm_out.view(len(sentence), -1))
        tag_scores = F.log_softmax(tag_space, dim=1)
        return tag_scores

2.3 模型训练

model = LSTMTagger(EMBEDDING_DIM, HIDDEN_DIM, len(word_to_ix), len(tag_to_ix))
loss_function = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 查看训练前的分数
# 注意: 输出的 i,j 元素的值表示单词 i 的 j 标签的得分
# 这里我们不需要训练不需要求导,所以使用torch.no_grad()
with torch.no_grad():
    inputs = prepare_sequence(training_data[0][0], word_to_ix)
    tag_scores = model(inputs)
    print(tag_scores)

for epoch in range(300):  # 实际情况下你不会训练300个周期, 此例中我们只是随便设了一个值
    for sentence, tags in training_data:
        # 第一步: 请记住Pytorch会累加梯度.
        # 我们需要在训练每个实例前清空梯度
        model.zero_grad()

        # 此外还需要清空 LSTM 的隐状态,
        # 将其从上个实例的历史中分离出来.
        model.hidden = model.init_hidden()

        # 准备网络输入, 将其变为词索引的 Tensor 类型数据
        sentence_in = prepare_sequence(sentence, word_to_ix)
        targets = prepare_sequence(tags, tag_to_ix)

        # 第三步: 前向传播.
        tag_scores = model(sentence_in)

        # 第四步: 计算损失和梯度值, 通过调用 optimizer.step() 来更新梯度
        loss = loss_function(tag_scores, targets)
        loss.backward()
        optimizer.step()

# 查看训练后的得分
with torch.no_grad():
    inputs = prepare_sequence(training_data[0][0], word_to_ix)
    tag_scores = model(inputs)

    # 句子是 "the dog ate the apple", i,j 表示对于单词 i, 标签 j 的得分.
    # 我们采用得分最高的标签作为预测的标签. 从下面的输出我们可以看到, 预测得
    # 到的结果是0 1 2 0 1. 因为 索引是从0开始的, 因此第一个值0表示第一行的
    # 最大值, 第二个值1表示第二行的最大值, 以此类推. 所以最后的结果是 DET
    # NOUN VERB DET NOUN, 整个序列都是正确的!
    print(tag_scores)

3. 使用字符级特征来增强 LSTM 词性标注器

教程给出的模型只使用词向量作为序列模型的输入,相当于只考虑词级别的特征,而像词缀这样的字符级信息对于词性有很大的影响。比如说, 像包含词缀 -ly 的单词基本上都是被标注为副词。因此,接下来我们会在刚刚代码的基础上考虑加入每个的单词的字符级别的特征来增强词嵌入。
教程中给出的思路为:

  • 新模型中需要两个 LSTM, 一个跟之前一样, 用来输出词性标注的得分, 另外一个新增加的用来获取每个单词的字符级别表达;
  • 为了在字符级别上运行序列模型,你需要用嵌入的字符来作为字符 LSTM 的输入。

因此,我们会在模型设置两个embedding层——character_embeddingsword_embeddings

 # 词嵌入
 self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)

 # 字符嵌入
 self.character_embeddings = nn.Embedding(character_size, character_embedding_dim)

两个LSTM层—— character-ltsmtag-lstm

# lstm_character以每个字符的character_embeddings作为输入, 输出即为该单词对应字符级别的特征,输出维度为 character_hidden_dim 的隐藏状态值
self.lstm_character = nn.LSTM(character_embedding_dim, character_hidden_dim)

# tag_lstm以word_embeddings和该词字符级别特征的拼接向量作为输入, 输出维度为 hidden_dim 的隐藏状态值
self.tag_lstm = nn.LSTM(embedding_dim + character_hidden_dim, hidden_dim)

其中,character_embeddings用来表示每个单词字符级别的embedding,这个embedding会输入到character-ltsm中获取每个单词字符级别的特征;随后,character-ltsm层的输出会和word_embeddings层输出的词向量进行拼接,拼接后的结果作为新的词向量输入到tag-lstm中进行序列的标注。

# 词嵌入
word_embed = self.word_embeddings(sentence_word)
# 获取单词字符级别的特征
word_character = words_characters[sentence_word.item()]
word_character_in = prepare_sequence(word_character, character_to_ix)
character_embeds = self.character_embeddings(word_character_in)
character_lstm_out, self.hidden_character = self.lstm_character(
    character_embeds.view(len(word_character_in), 1, -1), self.hidden_character)
# 拼接词向量与字符级别的特征
embed = torch.cat((word_embed, self.hidden_character[0].view(-1)))
# 拼接句子中每个词的词向量,拼接后的结果作为tag_lstm的输入
embeds = torch.cat(embeds).view(len(sentence), 1, -1)
lstm_out, self.hidden = self.tag_lstm(embeds, self.hidden)

模型训练时,需要清空清空 LSTM 的隐状态,将其从上个实例的历史中分离出来

# 此外还需要清空 LSTM 的隐状态,
# 将其从上个实例的历史中分离出来.
model.hidden_tag = model.init_hidden(HIDDEN_DIM)
model.hidden_character = model.init_hidden(CHARACTER_HIDDEN_DIM)

大概就是这些了,萌新一枚,欢迎交流指正。
完整代码链接 -.-

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
当然,我可以为您提供一个使用PyTorch框架的LSTM回归预测代码。以下是一个示例代码: ```python import torch import torch.nn as nn # 定义LSTM模型 class LSTMRegressor(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(LSTMRegressor, self).__init__() self.hidden_size = hidden_size self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) out = out[:, -1, :] out = self.fc(out) return out # 定义输入和输出的维度 input_size = 6 output_size = 1 # 定义模型超参数 hidden_size = 64 learning_rate = 0.001 num_epochs = 100 # 创建模型实例 model = LSTMRegressor(input_size, hidden_size, output_size) # 定义损失函数和优化 criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 创建输入数据和目标数据(示例数据) input_data = torch.randn(100, 10, input_size) # (batch_size, sequence_length, input_size) target_data = torch.randn(100, output_size) # (batch_size, output_size) # 训练模型 for epoch in range(num_epochs): # 前向传播 output = model(input_data) loss = criterion(output, target_data) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() # 打印训练信息 if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') # 使用模型进行预测 input_test = torch.randn(1, 10, input_size) # (batch_size, sequence_length, input_size) output_test = model(input_test) print(f'Prediction: {output_test.item()}') ``` 这个代码定义了一个包含一个LSTM层和一个全连接层的模型,用于回归预测任务。您可以根据自己的数据和需求进行相应的修改和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JimmyTotoro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值