文章目录
循环神经网络
这个网络主要用来处理序列信息,之前处理图片时大部分是分析图片的结构信息,
什么是序列信息,比如文本,音乐,视频,DNA都可以看做序列信息,这些前后都是有关联的,循环神经网络将会在这些数据中分析他们前后的逻辑,找出关联的部分,从而模拟它们,推导它们的含义和作用,从这个角度看,RNN相比CNN是一次飞跃式进步,这意味着RNN将会产生类似逻辑思维的东西,比如新出的ChatGPT,它已经具备了一定程度的逻辑思维能力,但是从来没有人教给他如何推导,它从那些序列信息中学习到了逻辑思维,模仿了这种逻辑思维,人工智能的潜力在RNN网络中才真正的释放出来!
已经有人在用RNN推导DNA序列和蛋白质结构信息了,人工智能真的能给我们带来不同的视角来观察我们自己!
而文本信息和图片信息的不同之处在于其前后连贯,具备逻辑性,其含义和时间和记忆相关,不再是分类和识别问题,而是涉及到了文本的理解问题
这就让语义分析变得虚无缥缈起来
比如举例,
1.我说汉语,今天我坐飞机到了美国.
2.我说意大利语,今天我坐飞机到了美国.那么我可能是哪个国家的人?
这涉及到语言的逻辑分析,文本的理解,如何让神经网络在接受大量的文本后理解那些语言的含义是一个巨大的问题
有人提出了循环神经网络的概念
即当前的输入输出受到之前"记忆"的影响,即使输入相同,但是因为记忆不同,所以输出也会不同
如果把文本看做一个序列,那么对文本的处理,就可以循环进行,总是如此,对于每个数据输入点都施加记忆的影响,这样循环下去,这就是循环神经网络
当然,上述中的数据流看似每个输入点都被添加了不同的记忆,实际上他们使用了参数共享,施加的是同一记忆,这样就保证不管序列的长短,他们的向其施加的参数是类似的,保证了序列处理的连续性
这种结构为每个词语建立了相应的逻辑映射关系,有助于发现隐含在语言中的逻辑,但是比较明显的问题是如果词语相距甚远而又存在逻辑关系,就会出现强记忆(类似于人类总是对最近的事情记忆尤深)和强遗忘(对相距甚远的记忆进行遗忘)情况,导致逻辑关系不连贯,错误
比如我生在中国,...(差距1000字,介绍中国风景地貌和外国地貌语言,不涉及中国的语言文化),我说汉语,那么就很难把中国和汉语联系起来,这个问题被称为长时依赖问题
于是有人提出了LSTM和GRU来解决这个问题
LSTM
标准RNN:
对输入xt和输出ht公式为
即当前输出和上一步ht-1的输出相关,代表了循环网络记忆的特点
Lstm网络:
Long Short Term Memory networks
网络结构为:
输入输出门控制着结构的输入和输出,而遗忘门控制着记忆的保留度
即决定哪些是相关的,应该保留哪些记忆,这个筛选过程是自动进行的
内部结构为:
这是三序列LSTM结构,A结构相等,对于当前输入xt,输出ht有如下控制:
通过sigmoid先计算出衰减系数Ft,即对于过去的信息Ht-1保留多少
然后根据Ht-1和xt计算出需要保留新信息的多少,即系数It,以及新接受的信息Ct
然后根据两者的衰减系数Ft和It,乘过去的信息和新信息来得到t时刻的真实记忆状态Ct
当然,对于当前的记忆状态Ct,它融合了之前的输入Ht-1和当前的输入Xt,但是它是不必要全部输出的,它只有一部分是有用的
我们使用Ht-1和Xt计算一个系数决定t时刻要从Ct中输出多少记忆状态到Ht
GRU网络结构:
gated Recurrent Unit
gru提出了更新门的概念,它由遗忘门和输入门合成,同时抛弃了记忆状态Ct,而是将结果直接作为记忆状态向后传播
词嵌入
在进行图像识别时使用的是one-hot编码,对于某一类别用1表示,其他则为0,(0,0,0,1,0),表示第四类
然而对于词语来讲,单词动辄上万,而且大量的单词实际上是有联系的,可以进行归类,比如love,like
新的名词层出不穷,如何让神经网络自学习它们的联系,并且对其预测就需要对这些单词使用一种新的编码方式
(其实我认为作为汉语言来讲,3000个词就足以应对生活和各领域所有的场景,在训练LSTM方面优势是显著的,更加便于AI理解,真正恐怖的是英文单词动辄上万,新创造的单词拼写和其他单词几乎毫无关联)
词嵌入的概念是:
对每一个词都进行向量化,含义相近的词语向量相近,这样便于归类
比如有如下四句话,思考主体对象词语之间的关系
猫喜欢玩线团
kitty喜欢玩线团
狗喜欢玩球
人喜欢玩球
然后思考猫,kitty,狗,人这几个词之间的关系,假设这几个点生成在原点附近,哪几个词距离更近呢?
我们把喜欢线团设置为1,可以有猫(1,1),kitty(1,2),把喜欢球设置为-1,狗(-1,1),人(-1,2)
可以观察出猫和kitty距离更近,狗和人距离很近,但是这只是单语句所在的情况,当语句变多时分类有更加复杂的向量和更准确的关系
skip-Gram模型
它的思想是通过一个词周围的词来预测这个词,当然这实际上就在训练词嵌入模型
我们来假设一个滑动窗口,这和图像处理中的滑动窗口概念类似,它在语句序列上移动,它和周围的词相关
我们设定一个滑动窗口移动步长和关联范围,假设关联范围为2,可以得到如下映射关系
核心词和它周围词组成初始向量来表示他们的关系
N-Gram模型
前面我们讲了词嵌入如何得到,接下来我们讲述词嵌入如何来训练自然语言模型
我们的目的是根据已有的单词,去推测未知的单词
比如,他受伤了,他想去__我们很容易想到医院,这就是单词内部的逻辑,而我们希望语言模型能够把受伤和医院通过逻辑推导出来,让语言更加自然,所谓的聊天机器人也是根据我们的问题启发词推测后面我们未知的内容
明白了这一点,那么训练模型,就是如何找到这些单词之间的规律和联系
那么对于一句话来讲,它内部单词的顺序和构成就是以概率存在的,比如,他生病了,他想吃(),后面无形中就已经把房子,汽车,摄像头等一大堆无关紧要的单词排除掉了,而高概率的词,药和西瓜等等会以概率,出现,这就是传统概率学模型
假设它有n个单词,就可以描述为如下公式
这个模型也可以进一步简化,一个词不必与已知的所有词相关,它和前面的部分词相关即可 ,这就是马尔可夫假设
对于这里的条件概率计算,传统方式是统计语料中每个词的概率,然后用贝叶斯公式估计该单词的条件概率,
但是在这里我们可以用词嵌入向量进行平替,然后用RNN进行条件概率计算,然后最大化这个条件概率,更新词嵌入向量,同时用模型来预测某个单词
词性预测
一个单词有不同的词性,比如书,可以是书写,也可以是图书,在英文中,book同理
当然在英文中,是以连续的字符串进行确立的
比如apple,使用lstm逐个字符输入a p p l e得到字符串apple,在最后输出,在这个过程中,单词也能形成一种记忆特性,
然后再根据前面的单词对后面的单词词性进行预测
依次输入 i like book,在最后的输出那里根据前面的单词对后面的单词book进行词性推测,可以推导出这个单词为名词图书,然后可以进一步推断这句话的含义
RNN循环神经网络的基础模块实现
首先看RNN的基础模块,然后再介绍LSTM和GRU如何实现
RNN模块:
pytroch提供了两个函数来实现RNN模块
RNNCell() 函数表示单步模块,只接受序列中的一步,必须传入上一步隐含模块的输出Ht才可进行运算
RNN() 函数可以接受整个序列,隐含上一步的输入默认全0,也可以指定上一步的输出Ht
RNN() 有如下参数:
(输入大小,输出大小,网络层数,激活函数,偏置,数据的格式,输出层是否启用Dropout,是否使用双向RNN默认false)
数据格式:,默认false,格式为(序列长度,批大小,特征维度),true为(批大小,序列长度,特征维度),控制着输入输出数据的格式,一般为true,因为pytroch大部分数据格式都是如此
双向RNN:相比单向RNN可以从前后同时处理序列,可以更好的理解文本上下文信息,但是训练和生成时间更久
(input_size,hidden_size,num_layers,nonlinearty,bias,batch_first,dropout,bidirectional)
RNNcell()只有三个参数,因为其只处理一步:
(input_size,hidden_size,bias,nonlinearty)很少使用它
rnn_single = nn.RNNCell(input_size=100, hidden_size=200)
print(rnn_single.weight_hh)
# 构造一个序列,长为 6,batch 是 5, 特征是 100
x = Variable(torch.randn(6, 5, 100)) # 这是 rnn 的输入格式
# 定义初始的记忆状态,假设上一步200个输出全0
h_t = Variable(torch.zeros(5, 200))
# 传入 rnn
out = []
for i in range(6): # 通过循环 6 次作用在整个序列上
h_t = rnn_single(x[i], h_t)
out.append(h_t)
print(out[0].shape)
print(len(out))
可以看到结果为5个批次,每个批次长度为6的200特征维度的输出,这样并不符合描述习惯,所以RNN()设置Batch_first为true更符合习惯
rnn_seq = nn.RNN(100, 200,batch_first=True)
# 访问其中的参数
print(rnn_seq.weight_hh_l0)
# 构造一个序列,batch 是 5, 长为 6,特征是 100
x = Variable(torch.randn(5,6, 100)) # 这是 rnn 的输入格式
out, h_t = rnn_seq(x) # 使用默认的全 0 隐藏状态
h_0 = Variable(torch.randn(1, 5, 200))# 自定义隐藏状态
out, h_t = rnn_seq(x, h_0)
print(out.shape)
不是特殊要求,初始隐藏状态我们也会选择全0,即使要求,我们也可以初始化隐藏状态,所以RNN()比RNNCell()使用更多,并且更方便
LSTM:
LSTM具备和RNN类似的使用方式,LSTMCell()和LSTM()
lstm_seq = nn.LSTM(50, 100, num_layers=2,batch_first=True) # 输入维度 50,输出 100,两层
print(lstm_seq.weight_hh_l0)
lstm_input = Variable(torch.randn(3,10, 50)) # bitch为3,序列长度为10,输入维度 50
out, (h, c) = lstm_seq(lstm_input) # 使用默认的全 0 隐藏状态
print(out.shape)
print(h.shape)
print(c.shape)
h_init = Variable(torch.randn(2, 3, 100))
c_init = Variable(torch.randn(2, 3, 100))
out, (h, c) = lstm_seq(lstm_input, (h_init, c_init))
out是最终输出,h代表层输出状态,c代表层记忆状态
GRU模块:
与之类似
gru_seq = nn.GRU(10, 20,batch_first=True)
gru_input = Variable(torch.randn(32, 3,10))
out, h = gru_seq(gru_input)
print(out.shape)
print(h.shape)
思考:为什么第一,二层的权重参数量为(400,100)?
lstm_seq = nn.LSTM(50, 100, num_layers=2,batch_first=True) # 输入维度 50,输出 100,两层
print(lstm_seq.weight_hh_l0.size())
print(lstm_seq.weight_hh_l1.size())
RNN处理序列的方式为我们提供了神经网络的新思路,当为神经网络添加记忆的特性时,很多显而易见的结果才变的显而易见,很容易想到,我们大脑处理信息时也不是单独的一次逻辑推导,而是伴随着我们的见识和阅历来解读新的信息,就像我们看到一张图片一段文字不同的理解一样
RNN识别图片
Normalize()将输入数据标准化,均值为0,标准差为1
tfs.Normalize([0.5], [0.5])含义为将输入数据的每个通道减去均值0.5,除以标准差0.5
我们继续对之前的minist十分类进行训练,查看rnn表现
对数据预处理
# 定义数据
data_tf = tfs.Compose([
tfs.ToTensor(),
tfs.Normalize([0.5], [0.5]) # 标准化
])
# # 指定文件夹
print(os.listdir(sys.path[0]))
folder_path =r"D:\PGMCode\Mycode\pythonCode\jupyterNoteBook\data"
train_set = MNIST(folder_path, train=True, transform=data_tf)
test_set = MNIST(folder_path, train=False, transform=data_tf)
train_data = DataLoader(train_set, 64, True, num_workers=4)
test_data = DataLoader(test_set, 128, False, num_workers=4)
定义rnn模型
# 定义模型
class rnn_classify(nn.Module):
def __init__(self, in_feature=28, hidden_feature=100, num_class=10, num_layers=2):
super(rnn_classify, self).__init__()
self.rnn = nn.LSTM(in_feature, hidden_feature, num_layers) # 使用两层 lstm
# 将最后一个 rnn 的输出使用全连接得到最后的分类结果
self.classifier = nn.Linear(hidden_feature, num_class)
def forward(self, x):
"""
x 大小为 (batch, 1, 28, 28),所以我们需要将其转换成 RNN 的输入形式,即 (28, batch, 28)
"""
x = x.squeeze() # 去掉 (batch, 1, 28, 28) 中的 1,变成 (batch, 28, 28)
x = x.permute(2, 0, 1) # 将最后一维放到第一维,变成 (28, batch, 28)
out, _ = self.rnn(x) # 使用默认的隐藏状态,得到的 out 是 (28, batch, hidden_feature)
out = out[-1, :, :] # 取序列中的最后一个,大小是 (batch, hidden_feature)
out = self.classifier(out) # 得到分类结果
return out
进行训练
net = rnn_classify()
criterion = nn.CrossEntropyLoss()
optimzier = torch.optim.Adadelta(net.parameters(), 1e-1)
# 开始训练
from utils import train
train(net, train_data, test_data, 10, optimzier, criterion)
十次训练结果就达到了97%,效果显著,然而图像上用rnn效果和cnn差距不大
RNN表现最强势的地方在于文字序列的处理
接下来我们用RNN来预测飞机人次数据
RNN时间序列预测
以下是10年来每个月,飞机人次登记数据,因为是序列数据,我们用前两个月的流量人次作为x,后一个月流量人次作为y进行训练
先查看数据:
folder_path =r"D:\PGMCode\Mycode\pythonCode\jupyterNoteBook\data.csv"
data_csv = pd.read_csv(folder_path, usecols=[1])
print(data_csv)
plt.plot(data_csv)
可以观察到csv中为一维的流量人次数据,10年120个月
数据预处理,清除nan数据,将所有值缩放到[0,1]区间,将前两个月的数据作为x,后一个月作为y处理序列数据
# 数据预处理
data_csv = data_csv.dropna() # 去掉 na
dataset = data_csv.values
dataset = dataset.astype('float32')
max_value = np.max(dataset)
min_value = np.min(dataset)
scalar = max_value - min_value
dataset = list(map(lambda x: x / scalar, dataset))
def create_dataset(dataset, look_back=2):
dataX, dataY = [], []
for i in range(len(dataset) - look_back):
a = dataset[i:(i + look_back)]
dataX.append(a)
dataY.append(dataset[i + look_back])
return np.array(dataX), np.array(dataY)
# 创建好输入输出
data_X, data_Y = create_dataset(dataset)
划分测试集和训练集,调整数据输入格式
numpy中的reshape()函数,(-1,1,2),-1表示该维度大小由其他维度控制,这里-1表示3,
# 划分训练集和测试集,70% 作为训练集
train_size = int(len(data_X) * 0.7)
test_size = len(data_X) - train_size
train_X = data_X[:train_size]
train_Y = data_Y[:train_size]
test_X = data_X[train_size:]
test_Y = data_Y[train_size:]
import torch
train_X = train_X.reshape(-1, 1, 2)# RNN 读入的数据维度是 (seq, batch, feature)
train_Y = train_Y.reshape(-1, 1, 1)
test_X = test_X.reshape(-1, 1, 2)
train_x = torch.from_numpy(train_X)
train_y = torch.from_numpy(train_Y)
test_x = torch.from_numpy(test_X)
构建rnn网络进行训练
from torch import nn
from torch.autograd import Variable
# 定义模型
class lstm_reg(nn.Module):
def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
super(lstm_reg, self).__init__()
self.rnn = nn.LSTM(input_size, hidden_size, num_layers) # rnn
self.reg = nn.Linear(hidden_size, output_size) # 回归
def forward(self, x):
x, _ = self.rnn(x) # (seq, batch, hidden)
s, b, h = x.shape
x = x.view(s*b, h) # 转换成线性层的输入格式
x = self.reg(x)
x = x.view(s, b, -1)
return x
net = lstm_reg(2, 4)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)
# 开始训练
for e in range(1000):
var_x = Variable(train_x)
var_y = Variable(train_y)
# 前向传播
out = net(var_x)
loss = criterion(out, var_y)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (e + 1) % 100 == 0: # 每 100 次输出结果
print('Epoch: {}, Loss: {:.5f}'.format(e + 1, loss.item()))
将整个数据集的data_X输入网络进行训练,查看模拟情况
net = net.eval() # 转换成测试模式
data_X = data_X.reshape(-1, 1, 2)
data_X = torch.from_numpy(data_X)
var_data = Variable(data_X)
pred_test = net(var_data) # 测试集的预测结果
# 改变输出的格式
pred_test = pred_test.view(-1).data.numpy()
# 画出实际结果和预测的结果
plt.plot(pred_test, 'r', label='prediction')
plt.plot(dataset, 'b', label='real')
plt.legend(loc='best')
如果使用线性回归模拟效果并不如RNN找到的规律有用,RNN对之前规律的记忆对趋势的把握更加准确
词向量模块
我们知道词向量是用来在神经网网络训练中表示词之间的关系的
这个关系也是神经网络经过训练之后生成的
在pytorch中初始化词向量操作如下
torch.nn.Embedding(m, n)创造了一个单词矩阵,每个单词都被一个n维度的向量标定
生成此矩阵时会默认随机赋予权重
也可以使用规定的权重代替
如果要查看某个单词的权重可以使用 LongTensor类型的坐标调用
# 定义词嵌入
embeds = nn.Embedding(2, 5) # 2 个单词,维度 5
embeds.weight.data = torch.ones(2, 5)
print(embeds.weight)
single_word_embed = embeds(Variable(torch.LongTensor([1])))
print(single_word_embed)
词向量运用N-Gram模型
前面讲述了词向量如何被初始化,现在我们给出一段文字,要求在已知前两个词的前提下预测第三个词
CONTEXT_SIZE = 2 # 依据的单词数
EMBEDDING_DIM = 10 # 词向量的维度
test_sentence = """北 国 风 光 千 里 冰 封 万 里 雪 飘
望 长 城 内 外 惟 余 莽 莽 ; 大 河 上 下 顿 失 滔 滔
山 舞 银 蛇 原 驰 蜡 象 欲 与 天 公 试 比 高
须 晴 日 看 红 装 素 裹 分 外 妖 娆
江 山 如 此 多 娇 引 无 数 英 雄 竞 折 腰
惜 秦 皇 汉 武 略 输 文 采 ; 唐 宗 宋 祖 稍 逊 风 骚
一 代 天 骄 成 吉 思 汗 只 识 弯 弓 射 大 雕
俱 往 矣 数 风 流 人 物 还 看 今 朝
""".split()
接下来我们为每个字构建一个坐标,来标识这个字
trigram = [((test_sentence[i], test_sentence[i+1]), test_sentence[i+2])
for i in range(len(test_sentence)-2)]
print(trigram[0])
# 建立每个词与数字的编码,据此构建词嵌入
vocb = set(test_sentence) # 使用 set 将重复的元素去掉
word_to_idx = {word: i for i, word in enumerate(vocb)}
print(word_to_idx)
创建一个模型,输入词104,标识维度为10,输出维度为104,相当于一个矩阵
# 定义模型
class n_gram(nn.Module):
def __init__(self, vocab_size, context_size=CONTEXT_SIZE, n_dim=EMBEDDING_DIM):
super(n_gram, self).__init__()
self.embed = nn.Embedding(vocab_size, n_dim)
self.classify = nn.Sequential(
nn.Linear(context_size * n_dim, 128),
nn.ReLU(True),
nn.Linear(128, vocab_size)
)
def forward(self, x):
voc_embed = self.embed(x) # 得到词嵌入
voc_embed = voc_embed.view(1, -1) # 将两个词向量拼在一起
out = self.classify(voc_embed)
return out
net = n_gram(len(word_to_idx))
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2, weight_decay=1e-5)
print(net)
开始训练,主要是对特征维度进行更新
for e in range(100):
train_loss = 0
for word, label in trigram:
word = Variable(torch.LongTensor([word_to_idx[i] for i in word])) # 将两个词作为输入
print(word)
label = Variable(torch.LongTensor([word_to_idx[label]]))
print(label)
# 前向传播
out = net(word)
print(out.size())
loss = criterion(out, label)
train_loss += loss.item()
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# if (e + 1) % 20 == 0:
print('epoch: {}, Loss: {:.6f}'.format(e + 1, train_loss / len(trigram)))
查看词嵌入预测效果如何:
查看更新后的权重和预测效果
a=net.embed
print(a.weight.size())
idx_to_word = {word_to_idx[word]: word for word in word_to_idx}
net = net.eval()
# 测试一下结果
word, label = trigram[3]
print('input: {}'.format(word))
print('label: {}'.format(label))
print()
word = Variable(torch.LongTensor([word_to_idx[i] for i in word]))
out = net(word)
pred_label_idx = out.max(1)[1].data[0]
predict_word = idx_to_word[int(pred_label_idx)]
print('real word is {}, predicted word is {}'.format(label, predict_word))
lstm词性预测
掌握一句话中每个字的词性对于推导这句话的含义具有重大帮助
从之前的词性预测理论中我们设想了这样一种方式
将每个单词拆分成一个个字母,分析它内在的结构对词性的影响,比如look和looking
然后再通过整个句子对这个单词进行词性推测
我们对两句话进行训练和学习
training_data = [("The dog ate the apple".split(),
["DET", "NN", "V", "DET", "NN"]),
("Everybody read that book".split(),
["NN", "V", "DET", "NN"])]
word_to_idx = {}
tag_to_idx = {}
for context, tag in training_data:
for word in context:
if word.lower() not in word_to_idx:
word_to_idx[word.lower()] = len(word_to_idx)
for label in tag:
if label.lower() not in tag_to_idx:
tag_to_idx[label.lower()] = len(tag_to_idx)
print(word_to_idx)
print(tag_to_idx)
然后对单词的字母进行编码,为组字母成单词,单词成句做基础工作
alphabet = 'abcdefghijklmnopqrstuvwxyz'
char_to_idx = {}
for i in range(len(alphabet)):
char_to_idx[alphabet[i]] = i
print(char_to_idx)
我们查看一下被编码后原来的句子和单词都如何被机器识别,它们都被一个个数字所标记
def make_sequence(x, dic): # 字符编码
idx = [dic[i.lower()] for i in x]
idx = torch.LongTensor(idx)
return idx
a=make_sequence('apple', char_to_idx)
print(a)
print(training_data[1][0])
print(make_sequence(training_data[1][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_embed = nn.Embedding(n_char, char_dim)
self.lstm = nn.LSTM(char_dim, char_hidden)
def forward(self, x):
x = self.char_embed(x)
out, _ = self.lstm(x)
return out[-1] # (batch, hidden)
构建词性分类的模型,将单个词性的lstm输出作为结果当词性模型的输入
class lstm_tagger(nn.Module):
def __init__(self, n_word, n_char, char_dim, word_dim,
char_hidden, word_hidden, n_tag):
super(lstm_tagger, self).__init__()
self.word_embed = nn.Embedding(n_word, word_dim)
self.char_lstm = char_lstm(n_char, char_dim, char_hidden)
self.word_lstm = nn.LSTM(word_dim + char_hidden, word_hidden)
self.classify = nn.Linear(word_hidden, n_tag)
def forward(self, x, word):
char = []
for w in word: # 对于每个单词做字符的 lstm
char_list = make_sequence(w, char_to_idx)
char_list = char_list.unsqueeze(1) # (seq, batch, feature) 满足 lstm 输入条件
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_embed(x) # (batch, seq, word_dim)
x = x.permute(1, 0, 2) # 改变顺序
x = torch.cat((x, char), dim=2) # 沿着特征通道将每个词的词嵌入和字符 lstm 输出的结果拼接在一起
x, _ = self.word_lstm(x)
s, b, h = x.shape
x = x.view(-1, h) # 重新 reshape 进行分类线性层
out = self.classify(x)
return out
开始训练
net = lstm_tagger(len(word_to_idx), len(char_to_idx), 10, 100, 50, 128, len(tag_to_idx))
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=1e-2)
# 开始训练
for e in range(300):
train_loss = 0
for word, tag in training_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)
print(tag)
# 前向传播
out = net(word_list, word)
print(out)
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(training_data)))
测试预测效果
net = net.eval()
test_sent = 'The dog ate the apple'
test = make_sequence(test_sent.split(), word_to_idx).unsqueeze(0)
print(test)
out = net(Variable(test), test_sent.split())
print(out)
查看预测效果如何
import numpy as np
keys=tag_to_idx.keys()
speechList=[]
for i in out:
temp=i.detach().numpy()
for j in keys:
if tag_to_idx.get(j)==temp.argmax():
speechList.append(j)
break
test_sent = 'The dog ate the apple'
print(test_sent)
print(speechList)
可以看到效果不错