LSTM&RNN——关于理论和coding的story
本周的任务主要是盘复这个学期接触到的比较基础的NLP的模型~🐸(#)
第一周任务汇总如下:
- RNN的理论以及手撕代码
- LSTM的理论以及手撕代码
1、RNN
1.1 RNN简介
单输出的RNN结构如图所示
此时输出层的神经元公式为:
y
=
f
(
U
x
3
+
W
h
3
+
b
)
y=f(Ux_3+Wh_3+b)
y=f(Ux3+Wh3+b)
h i = f ( U x i + W h i − 1 + b ) h_i=f(Ux_i+Wh_{i-1}+b) hi=f(Uxi+Whi−1+b)
RNN的特点在于整个结构共享1组(U,W,b),f是同一个激活函数,每一个神经元h的计算公式都是由当前的输入x与上一个隐层神经元的输出组成。
它的一些变式:
1、单输入,多输出:通常用于输入一个图像,输出描述该图像的一个文本
2、多输入,多输出:
- 如果输入输出等长(应用范围较小,一般是作诗~好闲😆)
- 输入输出不等长的话,Seq2Seq!
1.2 Seq2Seq
两种结构:
第一种格式:
第二种格式:
Seq2Seq的小瑕疵:解码器decoder的输入都是译码器encoder的同一个输出,也就是说无论输入的语句是什么,编码器encoder都会将它转化成同一个中间语意h’.
因为一句话是有重点的,因此注意力机制应运而生。
Seq2Seq的改进
上图为基于注意力机制的Seq2Seq:注意力机制下,输入不是直接的序列输入,而是经过编码器encoder转换的中间语意C,而这些输入C也不尽相同,每一个C都是由权重w和译码器的隐藏层输出h加权组成
举个栗子:下图如果C1的重点在‘中’这个字,那么中间语意可以表示为
C
1
=
0.6
h
1
+
0.2
h
2
+
0.3
h
3
+
0.1
h
4
C_1=0.6h_1+0.2h_2+0.3h_3+0.1h_4
C1=0.6h1+0.2h2+0.3h3+0.1h4
求中间语意C的权值w的表征可以看作是一个分类的问题,输出的是一个概率值,与hi越接近,输出的概率就越大
1.3 RNN的代码🍣
麻了,0.65是什么黑洞吖😅,怎么换成asgd还是这个局部最优解😓
max_test_acc_overall: 0.65
max_f1_overall: 0.26262626262626265
class MyRNN(nn.Module):
def __init__(self,embedding_matrix,opt):
super(MyRNN,self).__init__()
self.embed=nn.Embedding.from_pretrained(torch.tensor(embedding_matrix,dtype=torch.float))
'''RNN的调用'''
self.rnn=myrnn(opt.embed_dim,opt.hidden_dim)
self.dense=nn.Linear(opt.hidden_dim,opt.polarities_dim)
def forward(self,inputs):
text=inputs[0]
x=self.embed(text)
h,y=self.rnn(x)
#print(y.shape)
out=self.dense(y)
#print(out.shape)
return out
class myrnn(nn.Module):
def __init__(self,input_size,hidden_size):
super(myrnn,self).__init__()
self.input_size=input_size
self.hidden_size=hidden_size
# RNN参数
self.U=nn.Parameter(torch.Tensor(input_size,hidden_size))
self.W=nn.Parameter(torch.Tensor(hidden_size,hidden_size))
self.b=nn.Parameter(torch.Tensor(hidden_size))
self.init_weights()
# 这个函数捏,可以省略,但是收敛应该会差一些
def init_weights(self):
stdv = 1.0 / math.sqrt(self.hidden_size)
for weight in self.parameters():
weight.data.uniform_(-stdv, stdv)
# 前向传播的过程
def forward(self,inputs,init_state=None):
batch_size,seq_size,_=inputs.size()
# 初始化
if init_state is None:
h_t, c_t = (
torch.zeros(batch_size, self.hidden_size).to(inputs.device),
torch.zeros(batch_size, self.hidden_size).to(inputs.device)
)
else:
h_t, c_t = init_state
# 对于序列的时间步进行RNN step
for t in range(seq_size):
x_t = inputs[:, t, :]
#print(x_t.shape,self.U.shape,h_t.shape)
h_t=torch.sigmoid(x_t@self.U+h_t@self.W+self.b)
y_t = torch.sigmoid(x_t@self.U + h_t@self.W + self.b)
return h_t,y_t
2、LSTM
LSTM提出的契机: LSTM是基于RNN的缺陷提出的一种方法,因为RNN是共享一组(U,W,b)的结构,在(U,W,b)不变的情况下,梯度在反向传播的过程中,不断连乘,数值不是越来越大就是越来越小,这样就导致__梯度消失__或者__梯度爆炸__的情况
1.1 LSTM的总体结构
与RNN相比,LSTM的神经元加入了输入门i、遗忘门f、输出门o和内部记忆单元c。
遗忘门f:控制输入x和上一层隐藏层输出h被遗忘的程度大小
f
t
=
s
i
g
m
o
i
d
(
W
f
x
t
+
U
f
h
t
−
1
+
b
f
)
f_t=sigmoid(W_fx_t+U_fh_{t-1}+b_f)
ft=sigmoid(Wfxt+Ufht−1+bf)
输入门i:控制输入x和当前计算的状态更新到记忆单元程度的大小
i
t
=
s
i
g
m
o
i
d
(
W
i
x
t
+
U
i
h
t
−
1
+
b
i
)
i_t=sigmoid(W_ix_t+U_ih_{t-1}+b_i)
it=sigmoid(Wixt+Uiht−1+bi)
内部记忆单元:利用激活函数得到当前内部记忆单元表征c’,再结合前一个结点的内部记忆单元最终表征c得到当前记忆单元的最终表征
c
t
′
=
T
a
n
h
(
W
c
x
t
+
U
c
h
t
−
1
)
c'_t=Tanh(W_cx_t+U_ch_{t-1})
ct′=Tanh(Wcxt+Ucht−1)
c t = f t c t − 1 + i t c t ′ c_t=f_tc_{t-1}+i_tc'_t ct=ftct−1+itct′
输出门o:控制输入x和当前输出取决于记忆单元的程度大小
o
t
=
s
i
g
m
o
i
d
(
W
o
x
t
+
U
o
h
t
−
1
+
b
o
)
o_t=sigmoid(W_ox_t+U_oh_{t-1}+b_o)
ot=sigmoid(Woxt+Uoht−1+bo)
h t = o t T a n h ( c t ) h_t=o_tTanh(c_t) ht=otTanh(ct)
**注意:**LSTM的激活函数可以自己调节
1.2 一个利用LSTM实现情感预测的Demo
好叭,其实是论文复现工作的回炉~
Effective LSTMs for Target-Dependent Sentiment Classification
- 之前这个任务上用的用的是nn.LSTM版本~虽说暑假自己裸着写过莎士比亚诗词生成的LSTM的Demo,但是当时用的是NoteBook(😐加上期末的时候NoteBook环境被玩坏了一直没修,所以相当于重新修练了哇)
- 改了两天一直收敛不起来,后来调epoch从20到30就修好了,对比试验用nn.LSTM做的,确实20epoch可以收敛(基本上1-5就可以收敛比较完美了),但是换上自己的,可能初始化部分写的还是有问题叭,总之epoch在1-2的时候有loss下降,后来2-20都是在震荡(输出中间结果看了觉得逻辑没有问题🙄,但是损失一直不降)
- 离谱的是20之后就很快收敛了🤪
model.py
import torch
import torch.nn as nn
import math
class MyLSTM(nn.Module):
def __init__(self,embedding_matrix,opt):
super(MyLSTM,self).__init__()
self.embed=nn.Embedding.from_pretrained(torch.tensor(embedding_matrix,dtype=torch.float))
'''LSTM的调用'''
self.lstm=myLstm(opt.embed_dim,opt.hidden_dim)
self.dense=nn.Linear(opt.hidden_dim,opt.polarities_dim)
def forward(self,inputs):
text = inputs[0]
x = self.embed(text)
_, (h_n, _) = self.lstm(x)
#print("?",h_n.shape)
out = self.dense(h_n)
return out
class myLstm(nn.Module):
def __init__(self, input_sz, hidden_sz):
super(myLstm,self).__init__()
self.input_size = input_sz
self.hidden_size = hidden_sz
self.U_i = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
self.V_i = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
self.b_i = nn.Parameter(torch.Tensor(hidden_sz))
# f_t遗忘门
self.U_f = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
self.V_f = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
self.b_f = nn.Parameter(torch.Tensor(hidden_sz))
# c_t记忆门
self.U_c = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
self.V_c = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
self.b_c = nn.Parameter(torch.Tensor(hidden_sz))
# o_t 输出门
self.U_o = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
self.V_o = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
self.b_o = nn.Parameter(torch.Tensor(hidden_sz))
self.init_weights()
# 这个函数捏,可以省略,但是收敛应该会差一些
def init_weights(self):
stdv = 1.0 / math.sqrt(self.hidden_size)
for weight in self.parameters():
weight.data.uniform_(-stdv, stdv)
def forward(self, x, init_states=None):
bs, seq_sz, _ = x.size()
# 64 80 300
hidden_seq = []
if init_states is None:
h_t, c_t = (
torch.zeros(bs, self.hidden_size).to(x.device),
torch.zeros(bs, self.hidden_size).to(x.device)
)
else:
h_t, c_t = init_states
for t in range(seq_sz):
x_t = x[:, t, :]
i_t = torch.sigmoid(x_t @ self.U_i + h_t @ self.V_i + self.b_i)
f_t = torch.sigmoid(x_t @ self.U_f + h_t @ self.V_f + self.b_f)
g_t = torch.tanh(x_t @ self.U_c + h_t @ self.V_c + self.b_c)
o_t = torch.sigmoid(x_t @ self.U_o + h_t @ self.V_o + self.b_o)
c_t = f_t * c_t + i_t * g_t
h_t = o_t * torch.tanh(c_t)
hidden_seq.append(h_t.unsqueeze(0))
hidden_seq = torch.cat(hidden_seq, dim=0)
hidden_seq = hidden_seq.transpose(0, 1).contiguous()
return hidden_seq, (h_t, c_t)
搞心态的结果展示:
看结果的时候一度以为是跑错了模型,就2个epoch的差别太大了哇~🐸
batch_loss:很明显的断层哇哇哇,没见过这种的!!!😅
参考
可以忽视的碎碎念
暑假因为疫情阴差阳错困在了学校大半年,结果刚结束期末考试就飞扑回家啦😃~
然后默默吐槽一下自己一回家就是晚睡晚起的”社畜“状态,放假快一周的内心os居然是想快点回学校恢复正常作息🤣