RNN原理
-
循环神经网络:处理序列模型,权值共享。
h[t] = fw(h[t-1], x[t]) #fw is some function with parameters W h[t] = tanh(W[h,h]*h[t-1] + W[x,h]*x[t]) #to be specific y[t] = W[h,y]*h[t]
-
Sequence to Sequence 模型示意图
-
语言模型示意图
RNN实战
-
3个句子,一个句子10个单词,一个单词100维
-
基础用法
rnn = nn.RNN(100,10) #input_size(feature_len)=100,hidden_len = 10 rnn._parameters.keys() #how many tensor(4个l0层的w和b) nn.RNN(input_size,hidden_size, num_layers = 1) #h0 : [layer, batchsz, hidden] x : [seq, batchsz, input ] #ht : [layer, batchsz, hidden] out: [seq, batchsz, hidden] out, ht = forward(x, h0) #对于多层rnn,out不变(所有时间最后mem状态),ht变为[2,~,~](所有层最后时间状态) cell = nn.RNNcell() #参数与nn.RNN()相同,但是每个时间戳都要输入xt for xt in x: h1 = cell(xt,h1) #如果你想在输入x时按这个格式:[batchsz, seq_len, input] #需要在nn.RNN()中加参数: batch_first = True
-
建一个非常简单的RNN模型(以一个简单时间序列预测为例)
class Net(nn.Module): def __init__(self,input_size,hidden_size): super(net, self).__init__() self.rnn = nn.RNN( input_size=input_size, hidden_size=hidden_size, num_layers=1, batch_first=True, ) for p in self.rnn.parameters():#正态分布的权值初始化 nn.init.normal_(p, mean=0.0, std=0.001) self.linear = nn.Linear(hidden_size, output_size) def forward(self,x,h0): out, ht = self.rnn(x, h0) #out:[batch_sz,seq,hidden_sz] out = out.view(-1, hidden_size)#out:[seq,hidden_size](b=1) out = self.linear(out) #out:[seq,output_size] out = out.unsqueeze(dim=0) #out:[1,seq,output_size] return out, ht
-
由于我们在对RNN进行反向传播求梯度的时候,最后的求导项里面有一个Whhk,会导致梯度爆炸或梯度消失。
# 解决梯度爆炸 for p in net.parameters(): torch.nn.utils.clip_grad_norm_(p, 10) # 保证梯度的绝对值小于10 # 解决梯度离散:LSTM
-
LSTM:Long Short-Term Memory(结构如下图)
有三个门:分别是遗忘门—f,输入门—i,输出门—o。对于输入输出变量:ct−1是输入的memory(新增的,为了解决梯度离散增强记忆能力),xt是输入, ht−1是前一个时间单元的输出,ct是传到下一个时间单元的memory。-
遗忘门:ft=σ(Wf×[ht−1,xt]+bf)
-
输入门:it=σ(Wi×[ht−1,xt]+bi)
-
输出门:ot=σ(Wo×[ht−1,xt]+bo)
-
过滤输入:ct=tanh(Wc×[ht−1,xt]+bc),得到的结果是过滤后的输入
那么,新的memory就等于 “遗忘门作用后保留的之前的memory” + “输入门作用后保留的新增的过滤后的输入”, 即 ct=ft×ct−1+it×ct。而新的输出(h)等于输出门作用后保留的"经tanh处理过的新的memory",即ht=ot×tanh(ct)。
-
-
LSTM layer
# initial nn.LSTM(input_size,hidden_size, num_layers = 1) # forward # x : [seq, batchsz, input] out : [seq, batchsz, hidden] # h/c : [layer, batchsz, hidden] out, (ht, ct) = lstm(x, [h0, c0]) # silimar to LSTMcell cell = nn.LSTMcell(~) for xt in x: h, c = cell(xt, [h, c])
情感分类问题实战
-
例如淘宝,对好评差评分类。模型如下。分别对每个词embedding后送入RNN,对所有输出综合出情感类别。
-
加载数据集(很重要的包__torchtext__)
from torchtext import data, datasets # data.Field() : 默认在空格上拆分字符串,token设置为spacy表示英语分词 # 用来定义字段的处理方法 TEXT = data.Field(tokenize='spacy') # LabelField是Field的子类,专门用于处理标签 LABEL = data.LabelField(dtype=torch.float) # 加载IMDB电影评论数据集 train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
-
使用Glove词向量模型构建语料库,并将处理后的数据进行batch操作。BucketIterator的作用是按相似长度分为若干批,每一批进行对应长度的补齐操作。
TEXT.build_vocab(train_data,max_size=10000,vectors='glove.6B.100d') LABEL.build_vocab(train_data) train_iterator, test_iterator = data.BucketIterator.splits( (train_data, test_data), batch_size = batchsz, device=device )
-
网络结构
class LSTM_Net(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim): super(LSTM_Net, self).__init__() # [0-10001] => [100] [vb -> embedding] self.embedding = nn.Embedding(vocab_size, embedding_dim) # [100] => [256] [embedding -> hidden] self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=2, bidirectional=True, dropout=0.5) # [256*2] => [1] self.fc = nn.Linear(hidden_dim*2, 1) self.dropout = nn.Dropout(0.5) def forward(self, x): # [seq, batchsz, 1(字符串)] => [seq, batchsz, 100] embedding = self.dropout(self.embedding(x)) # output: [seq, batchsz, hidden*2] 因为是双层 # hidden/cell: [layers, batchsz, hidden] # hidden 是每个时间戳的输出 output, (hidden, cell) = self.lstm(embedding) # [layers*2, batchsz, hidden] => [batchsz, hidden*2] # torch.cat():把前面两个torch按维度1拼接起来 hidden = torch.cat([hidden[-2], hidden[-1]], dim=1) # [batchsz, hidden*2] => [b, 1] hidden = self.dropout(hidden) out = self.fc(hidden) return out
-
Embedding初始化
rnn = LSTM_Net(len(TEXT.vocab), 100, 256) pretrained_embedding = TEXT.vocab.vectors # 指定初始权重(由glove得到) rnn.embedding.weight.data.copy_(pretrained_embedding)# 将初始化权重导入
-
定义优化器及损失函数
optimizer = optim.Adam(rnn.parameters(), lr=1e-3) criteon = nn.BCEWithLogitsLoss() # 二分类交叉熵损失函数
-
训练与测试
def binary_acc(preds, y): preds = torch.round(torch.sigmoid(preds)) correct = torch.eq(preds, y).float() acc = correct.sum() / len(correct) return acc def train(rnn, iterator, optimizer, criteon): avg_acc = [] lstm.train() for i, batch in enumerate(iterator): # [seq, b] => [b, 1] => [b] pred = lstm(batch.text).squeeze(1) loss = criteon(pred, batch.label) acc = binary_acc(pred, batch.label).item() avg_acc.append(acc) optimizer.zero_grad() loss.backward() optimizer.step() avg_acc = np.array(avg_acc).mean() print('train acc:', avg_acc) def eval(rnn, iterator, criteon): avg_acc = [] lstm.eval() with torch.no_grad(): for batch in iterator: # [b, 1] => [b] pred = lstm(batch.text).squeeze(1) loss = criteon(pred, batch.label) acc = binary_acc(pred, batch.label).item() avg_acc.append(acc) avg_acc = np.array(avg_acc).mean() print('test acc:', avg_acc)