利用LSTM判断词性
给出一句话,判断里面的每个单词是什么类型。名词、代词、副词等等。
import torch
from torch import nn
from torch.autograd import Variable
# 给出两句话作为训练集,每个单词给出词性
train_data = [
("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]),
("Everybody read that book".split(), ["NN", "V", "DET", "NN"])
]
print('======train_data=====')
print(train_data)
运行结果
# 对单词给出数字编码,以便传入Embedding中转化成词向量
word_to_idx = {}
tag_to_idx = {}
for context, tag in train_data: # context时句子,tag是后面的词性[]里面的一长串
for word in context: # 给每个单词编号
if word.lower() not in word_to_idx:
word_to_idx[word.lower()] = len(word_to_idx) # lower()函数,将字符串中的所有大写字母转换为小写字母。
for label in tag: # 给每个词性标label,以及编号
if label.lower() not in tag_to_idx:
tag_to_idx[label.lower()] = len(tag_to_idx)
# 定义数字和tag的字典,方便测试时使用,能够通过查找数字找到tag
idx_to_tag = {tag_to_idx[tag.lower()]: tag for tag in tag_to_idx}
# 对a-z的字符进行数字编码
alphabet = 'abcdefghijklmnopqrstuvwxyz'
character_to_idx = {}
for i in range(len(alphabet)):
character_to_idx[alphabet[i]] = i
# 这三个编码之后,用字典这种容器存储,每个元素对应一个数字编号
print('=====字符对应数字=====')
print(tag_to_idx)
print(idx_to_tag)
print(word_to_idx)
print(character_to_idx)
运行结果
# 字符编码,将传入字符x中对应的编码,转化成LongTensor类型
def make_sequence(x, dic):
idx = [dic[i.lower()] for i in x]
idx = torch.LongTensor(idx)
return idx
print('=====''make_sequence()函数输出结果查看''=====')
print(make_sequence('abcdef', character_to_idx).type())
print(make_sequence('abcdef', character_to_idx).size())
print(make_sequence('abcdef', character_to_idx))
print(make_sequence(train_data[0][0], word_to_idx))
运行结果
# 定义字母字符的LSTM
class char_lstm(nn.Module):
def __init__(self, n_char, char_dim, char_hidden):
super(char_lstm, self).__init__()
self.char_embedding = nn.Embedding(n_char, char_dim)
self.char_lstm = nn.LSTM(char_dim, char_hidden)
def forward(self, x):
x = self.char_embedding(x)
out, _ = self.char_lstm(x)
return out[-1] # (batch, hidden_size)
# 定义词性分析的LSTM
class lstm_tagger(nn.Module):
# n_word:单词的数目,n_dim:单词向量维度,n_char和char_dim同理,char_hidden:字母LSTM的输出维度,
# n_hidden:单词词性预测LSTM的输出维度,n_tag:输出的词性分类
def __init__(self, n_word, n_char, char_dim, n_dim, char_hidden, n_hidden, n_tag):
super(lstm_tagger, self).__init__()
self.word_embedding = nn.Embedding(n_word, n_dim)
self.char_lstm = char_lstm(n_char, char_dim, char_hidden)
self.lstm = nn.LSTM(n_dim + char_hidden, n_hidden) # 词性分析LSTM输入:词向量维度数+字符LSTM输出维度数
self.classify = nn.Linear(n_hidden, n_tag)
# 字符增强,传入句子的同时作为序列的同时,还要传入句子中的单词,用word表示
def forward(self, x, word):
char = []
for w in word: # 对于每个单词,做字母字符的lstm
char_list = make_sequence(w, character_to_idx)
char_list = char_list.unsqueeze(1) # (seq, batch, feature) 满足 lstm 输入条件
# unsqueeze(1)在第二维度上增加一个维度
char_infor = self.char_lstm(Variable(char_list)) # (batch, char_hidden)
char.append(char_infor)
char = torch.stack(char, dim=0) # (seq, batch, feature)
x = self.word_embedding(x) # (batch, seq, word_dim)
x = x.permute(1, 0, 2) # 改变顺序,变成(seq, batch, word_dim)
x = torch.cat((x, char), dim=2) # 沿着特征通道将每个词的词嵌入和字符 lstm 输出的结果拼接在一起
x, _ = self.lstm(x)
seq, batch, h = x.shape
x = x.view(-1, h) # 重新 reshape 进行分类线性层
out = self.classify(x)
return out
net = lstm_tagger(len(word_to_idx), len(character_to_idx), 10, 100, 50, 128, len(tag_to_idx))
# (n_word, n_char, char_dim, n_dim, char_hidden, n_hidden, n_tag)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2)
print('=====开始训练=====')
# 开始训练
for e in range(500):
train_loss = 0
for word, tag in train_data:
word_list = make_sequence(word, word_to_idx).unsqueeze(0) # 在第一维度上,添加第一维 batch
tag = make_sequence(tag, tag_to_idx)
word_list = Variable(word_list)
tag = Variable(tag)
# 前向传播
out = net(word_list, word)
loss = criterion(out, tag)
train_loss += loss.item()
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (e + 1) % 50 == 0:
print('Epoch: {}, Loss: {:.5f}'.format(e + 1, train_loss / len(train_data)))
运行结果
# 看看预测的结果
print('=====测试阶段=====')
net = net.eval()
# test_sent = 'Everybody ate the apple'
test_sent = 'Everybody ate the apple read the book'
test = make_sequence(test_sent.split(), word_to_idx).unsqueeze(0)
test_set = test_sent.split()
out = net(Variable(test), test_set)
print('=====out的tensor类型输出======')
print(out)
print('out的size是: ', out.size()) # 输入的test_sent是7个单词,单词词有三种:det,nn,v,所以结果是torch.Size([7, 3])
print('每一行tensor的三个值代表着:', tag_to_idx)
# 最后可以得到一个7x3的tensor,因为最后一层的线性层没有使用 softmax,所以数值不太像一个概率,
# 但是每一行数值最大的就表示属于该类,可以看到第一个单词 'Everybody' 属于 nn,
# 第二个单词 'ate' 属于 v,第三个单词 'the' 属于det,第四个单词 'apple' 属于 nn,
# 所以得到的这个预测结果是正确的
运行结果
# pred_tag_idx = out[0].argmax().item()
# pred_word = idx_to_tag[pred_tag_idx]
# print('The word is: ', test_set[0], '. And the predicted word is: ', idx_to_tag[pred_tag_idx])
print('=====.item()方法的作用=====')
pred_tag = out[0].argmax()
print('*****不使用.item()时*****')
print('变量类型type:', type(pred_tag))
print('变量值:', pred_tag)
print('*****使用.item()后*****')
print('变量类型type:',type(pred_tag.item()))
print('变量值:', pred_tag.item())
运行结果
print('=====测试结果=====')
for i in range(len(test_set)):
pred_tag_idx = out[i].argmax().item()
# out[i]表示out这个tensor的第i行,
# argmax()找出这一行最大值所在的位置,
# .item()方法将tensor类型的pred_tag_idx变为int类型,才可以用于字典查询索引
pred_word = idx_to_tag[pred_tag_idx]
print('The word is: ', test_set[i], '. And the predicted word is: ', idx_to_tag[pred_tag_idx])
运行结果
tips:pytorch动态图,目前神经网络框架分为静态图框架和动态图框架, TensorFlow 使用静态图,这意味着我们先定义计算图,然后不断使用它,而在 PyTorch 中,每次都会重新构建一个新的计算图,称之为动态图结构。
pytorch中函数总结:
unsqueeze()函数
增加tensor维度
unsqueeze(0)就是在第一维度增加一个维度
unsqueeze(1)就是在第二维度增加一个维度
squeeze()函数
减少tensor维度
squeeze(0)就是在第一维度减少一个维度
squeeze(1)就是在第二维度减少一个维度