-
深度学习7–循环神经网络实现语言模型
2021-01-06 18:02:00循环神经网络实现语言模型循环神经网络裁剪梯度困惑度实现 循环神经网络 目的是基于当前的输入与过去的输入序列,预测序列的下一个字符。循环神经网络中引入一个隐含层HHH,用HtH_tHt表示HHH在时间步ttt的值。HtH_... -
【NLP理论到实战】11 循环神经网络实现文本情感分类
2020-12-19 23:54:04文章目录循环神经网络实现文本情感分类目标1. Pytorch中LSTM和GRU模块使用1.1 LSTM介绍1.2 LSTM使用示例1.3 GRU的使用示例1.4 双向LSTM1.4 LSTM和GRU的使用注意点2. 使用LSTM完成文本情感分类2.1 修改模型2.2 完成...文章目录
循环神经网络实现文本情感分类
目标
- 知道LSTM和GRU的使用方法及输入输出的格式
- 能够应用LSTM和GRU实现文本情感分类
1. Pytorch中LSTM和GRU模块使用
1.1 LSTM介绍
LSTM和GRU都是由
torch.nn
提供
一、通过观察文档,可知LSMT的参数,torch.nn.LSTM(input_size,hidden_size,num_layers,batch_first,dropout,bidirectional)
input_size
:输入数据的形状,即embedding_dimhidden_size
:隐藏层神经元的数量,即每一层有多少个LSTM单元num_layer
:即RNN的中LSTM单元的层数batch_first
:默认值为False,输入的数据需要[seq_len,batch_size,feature]
,如果为True,则为[batch_size,seq_len,feature]
dropout
:dropout的比例,默认值为0。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropoutbidirectional
:是否使用双向LSTM,默认是False
二、实例化LSTM对象之后,不仅需要传入数据,还需要前一次的h_0(前一次的隐藏状态)和c_0(前一次memory*),若不传则默认初始化全为0
即:
lstm(input,(h_0,c_0))
三、LSTM的默认输出为
output, (h_n, c_n)
output
:(batch_size, seq_len, hidden_size * num_directions)
—>batch_first=Falseh_n
:(num_layers * num_directions, batch_size, hidden_size)
c_n
:(num_layers * num_directions, batch_size, hidden_size)
如下图解释上面输入输出形状变化:
- output形状——把每个时间步上的结果在seq_len这一维度上进行了拼接
- h_n形状——隐藏状态相当于在一个隐藏层中从第一个h_0一直穿到最后一个h_n,把不同层的隐藏状态在第0个维度上进行了拼接
- c_n形状——同h_n。
1.2 LSTM使用示例
假设数据输入为 input ,形状是
[10,20]
,假设embedding的形状是[100,30]
则LSTM使用示例如下:batch_size = 10 # 输入一个batch的大小,即包含多少个text seq_len = 20 # batch中一个text的长度 vocab_size = 100 # 词典总数 embedding_dim = 30 # 每个word嵌入成表征向量的维度 hidden_size = 18 # 隐藏层神经元数,即一层有多少个LSTM单元 num_layers = 2 # 隐藏层数量,即LSTM单元层数 num_directions = 1 # 单向LSTM为1,双向LSTM为2 # 构造一个batch数据 input = torch.randint(low=0, high=100, size=(batch_size, seq_len)) print(input.size()) # [10, 20] # 数据经过embedding处理 embedding = nn.Embedding(vocab_size, embedding_dim) input_embeded = embedding(input) print(input_embeded.size()) # [10, 20, 30] # 初始化隐藏和记忆cell状态,否则待会也可以不传默认初始值全为0 h_0 = torch.rand(num_layer * num_directions, batch_size, hidden_size) c_0 = torch.rand(num_layer * num_directions, batch_size, hidden_size) print(h_0.size()) # [2, 10, 18] print(c_0.size()) # [2, 10, 18] # 把embedding处理后的数据传入LSTM lstm = nn.LSTM(input_size=embedding_dim, hidden_size=hidden_size, num_layers=num_layers, batch_first=True) output, (h_n, c_n) = lstm(input_embeded) print(output.size()) # [10, 20, 18] print(h_n.size()) # [2, 10, 18] print(c_n.size()) # [2, 10, 18]
通过前面的学习,我们知道,最后一次的h_n应该和output的最后一个time step的输出是一样的
通过下面的代码,我们来验证一下:last_output = output[:,-1,:] # 获取最后一个时间步上的输出(取最后一个text) last_hidden_state = h_n[-1,:,:] # 获取最后一次的hidden_state(取最后一层) print(last_output==last_hidden_state) """ tensor([[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]]) """
1.3 GRU的使用示例
GRU模块
torch.nn.GRU
,和LSTM的参数相同,含义相同,具体可参考文档
但是输入只剩下gru(input,h_0)
,输出为output, h_n
其形状为:output
:(seq_len, batch_size, num_directions * hidden_size)
h_n
:(num_layers * num_directions, batch_size, hidden_size)
则GRU使用示例如下:batch_size = 10 # 输入一个batch的大小,即包含多少个text seq_len = 20 # batch中一个text的长度 vocab_size = 100 # 词典总数 embedding_dim = 30 # 每个word嵌入成表征向量的维度 hidden_size = 18 # 隐藏层神经元数,即一层有多少个GRU单元 num_layers = 2 # 隐藏层数量,即GRU单元层数 num_directions = 1 # 单向GRU为1,双向GRU为2 # 构造一个batch数据 input = torch.randint(low=0, high=100, size=(batch_size, seq_len)) print(input.size()) # [10, 20] # 数据经过embedding处理 embedding = nn.Embedding(vocab_size, embedding_dim) input_embeded = embedding(input) print(input_embeded.size()) # [10, 20, 30] # 初始化隐藏和记忆cell状态,否则待会也可以不传默认初始值全为0 h_0 = torch.rand(num_layer * num_directions, batch_size, hidden_size) print(h_0.size()) # [2, 10, 18] # 把embedding处理后的数据传入GRU gru = nn.GRU(input_size=embedding_dim, hidden_size=hidden_size, num_layers=num_layers, batch_first=True) output, h_n = gru(input_embeded) print(output.size()) # [10, 20, 18] print(h_n.size()) # [2, 10, 18]
1.4 双向LSTM
如果需要使用双向LSTM,则在实例化LSTM的过程中,需要把LSTM中的参数
bidriectional
设置为True
,同时h_0
和c_0
使用num_layer*2
则双向LSTM使用示例如下:# 双向LSTM同前,主要是nn.LSTM中参数bidirectional需要设置为True,同时h_0和c_0使用num_layer*2 batch_size = 10 # 输入一个batch的大小 seq_len = 20 # batch中一个text的长度 vocab_size = 100 # 词典总数 embedding_dim = 30 # 每个word嵌入成表征向量的维度 hidden_size = 18 # 隐藏层神经元数,即一层有多少个LSTM单元 num_layers = 2 # 隐藏层数量,即LSTM单元层数 num_directions = 2 # 单向LSTM为1,双向LSTM为2 # 构造一个batch数据 input = torch.randint(low=0, high=100, size=(batch_size, seq_len)) print(input.size()) # [10, 20] # 数据经过embedding处理 embedding = nn.Embedding(vocab_size, embedding_dim) input_embeded = embedding(input) print(input_embeded.size()) # [10, 20, 30] # 初始化隐藏和记忆cell状态,否则待会也可以不传默认初始值全为0 h_0 = torch.rand(num_layer * num_directions, batch_size, hidden_size) c_0 = torch.rand(num_layer * num_directions, batch_size, hidden_size) print(h_0.size()) # [2 * 2, 10, 18], 双向包括反向需要*2 print(c_0.size()) # [2 * 2, 10, 18] # 把embedding处理后的数据传入LSTM lstm = nn.LSTM(input_size=embedding_dim, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, bidirectional=True) output, (h_n, c_n) = lstm(input_embeded) print(output.size()) # [10, 20, 18 * 2] print(h_n.size()) # [2 * 2, 10, 18] print(c_n.size()) # [2 * 2, 10, 18]
在单向LSTM中,最后一个time step的输出的前hidden_size个和最后一层隐藏状态h_n的输出相同,那么双向LSTM呢?
双向LSTM中:- output:按照正反计算的结果顺序在最后一个维度进行拼接,正向第一个拼接反向的最后一个输出
- hidden state:按照得到的结果在第0个维度进行拼接,正向第一个之后接着是反向第一个(从上往下顺序是:第一层正向,第一层反向,第二层正向,第二层反向…)
- 如下图:
- 前向的LSTM中,最后一个time step的输出的前hidden_size个和最后一层向前传播h_n的输出相同
示例:
last_output = output[:,-1,:18] # 获取双向LSTM中正向的最后一个时间步的output last_hidden_state = h_n[-2,:,:] # 获取双向LSTM中正向的最后一个hidden_state(倒数第二个) print(last_output.size()) print(last_hidden_state.size()) print(last_output==last_hidden_state)
结果如下:
- 后向LSTM中,最后一个time step的输出的后hidden_size个和最后一层后向传播的h_n的输出相同
示例
last_output = output[:,-1,18:] # 获取双向LSTM中反向的最后一个时间步的output >last_hidden_state = h_n[-1,:,:] # 获取双向LSTM中反向的最后一个hidden_state(倒数第一个) print(last_output.size()) print(last_hidden_state.size()) print(last_output==last_hidden_state)
结果如下:
1.4 LSTM和GRU的使用注意点
- 第一次调用之前,需要初始化隐藏状态,如果不初始化,默认创建全为0的隐藏状态
- 往往会使用LSTM or GRU 的输出的最后一维的结果,来代表LSTM、GRU对文本处理的结果,其形状为
[batch_size, num_directions * hidden_size]
。
- 并不是所有模型都会使用最后一维的结果
- 如果实例化LSTM的过程中,batch_first=False,则
output[-1] or output[-1,:,:]
可以获取最后一维 - 如果实例化LSTM的过程中,batch_first=True,则
output[:,-1,:]
可以获取最后一维
- 如果结果是
(seq_len, batch_size, num_directions * hidden_size)
,需要把它转化为(batch_size, seq_len, num_directions * hidden_size)
的形状,不能够使用view等变形的方法,需要使用output.permute(1,0,2)
,即交换0和1轴,实现上述效果 - 使用双向LSTM的时候,往往会分别使用每个方向最后一次的output,作为当前数据经过双向LSTM的结果
- 即:
torch.cat([h_n[-2,:,:],h_n[-1,:,:]],dim=-1)
- 最后的表示的size是
[batch_size,hidden_size * 2]
- 上述内容在GRU中同理
2. 使用LSTM完成文本情感分类
在前面,我们使用了word embedding去实现了toy级别的文本情感分类,那么现在我们在这个模型中添加上LSTM层,观察分类效果。
为了达到更好的效果,对之前的模型做如下修改- MAX_LEN = 200
- 构建dataset的过程,把数据转化为2分类的问题,pos为1,neg为0,否则25000个样本完成10个类别的划分数据量是不够的
- 在实例化LSTM的时候,使用dropout=0.5,在model.eval()的过程中,dropout自动会为0
2.1 修改模型
""" 一、重写数据集类和准备数据加载类对象(dataset.py) """ import torch from torch.utils.data import Dataset,DataLoader import os from utils import tokenize import config class ImdbDataset(Dataset): # 1.5重写Imdb数据集类,包括(init方法:获取所有文件路径列表)、(getitem方法:获取索引文件内容)、(len方法:计算文件总数) def __init__(self,train=True): root_path = '.\\data\\aclImdb' root_path = os.path.join(root_path,'train') if train else os.path.join(root_path,'test') all_father_path = [os.path.join(root_path,'pos'),os.path.join(root_path,'neg')] self.all_file_path = [] for father_path in all_father_path: file_paths = [os.path.join(father_path,file_name) for file_name in os.listdir(father_path) if file_name.endswith('.txt')] self.all_file_path.extend(file_paths) def __getitem__(self,index): file_path = self.all_file_path[index] content = tokenize(open(file_path,encoding='UTF-8').read()) # 1.6获取当前索引文件内容时,需要调用工具包分词过滤函数处理 label = 1 if file_path.split('\\')[-2] == 'pos' else 0 return content,label def __len__(self): return len(self.all_file_path) def collate_fn(batch): # 1.8重写collate_fn方法(zip操作+转换为LongTensor类型操作) contents,labels = zip(*batch) contents = torch.LongTensor([config.ws.transform(content,max_len=config.max_len) for content in contents]) labels = torch.LongTensor(labels) return contents,labels def get_dataloader(train=True): # 1.3定义获取dataset和dataloader的函数 Imdb_dataset = ImdbDataset(train=True) # 1.4 调用重写的Imdb数据集类 batch_size = config.train_batch_size if train else config.test_batch_size # 1.7 划分batch大小需要根据训练集还是测试集划分,就对应数字单独写到一个配置包中需要引入 Imdb_dataloader = DataLoader(Imdb_dataset,batch_size=batch_size,shuffle=True,collate_fn=collate_fn) # 1.8获取数据集加载类,并重写参数collate_fn方法 return Imdb_dataloader if __name__=='__main__': # 1.1测试入口,打印第一个batch结果 for idx,(x,y_true) in enumerate(get_dataloader()): # 1.2调用函数,获取dateloader,取到数据集 print('idx: ',idx) print('text: ',x) print('label: ',y_true) break
""" 工具包:定义文本过滤及分词方法函数(utils.py) """ import re def tokenize(text): # 1.6.1 定义文本过滤及分词函数 filters = ['!', '"', '#', '$', '%', '&', '\(', '\)', '\*', '\+', ',', '-', '\.', '/', ':', ';', '<', '=', '>', '\?', '@', '\[', '\\', '\]', '^', '_', '`', '\{', '\|', '\}', '~', '\t', '\n', '\x97', '\x96', '”', '“', '<.*?>'] text = re.sub("|".join(filters), " ", text, flags=re.S) return [word.lower() for word in text.split()]
""" 配置包:用于配置保存常用的常量及模型(config.py) """ import pickle train_batch_size = 512 test_batch_size = 500 max_len = 200 ws = pickle.load(open('./model/TextSentiment/ws_lstm.pkl', 'rb')) hidden_size = 128 num_layers = 2 bidriectional = True dropout = 0.4
""" 二、文本序列化(word2sequence.py) """ class Word2Sequence: # 1.定义文本转序列类,包含六个方法:(init方法:初始化词-序列字典和词频字典)、(fit方法:统计词频得到词频字典)、(build_vocab方法:由全部文本和条件构造词-序列字典和序列-词字典)、(transform方法:将一个文本转化为数字序列)、(inverse_transform方法:将一个数字序列转化为文本)、(len方法:统计词-序列字典的长度) UNK_TAG = '<UNK>' # 表示未知字符 PAD_TAG = '<PAD>' # 表示填充符 UNK = 0 # 未知字符对应数字序列中的数字 PAD = 1 # 填充字符对应数字序列中的数字 def __init__(self): # 1.1 init方法:初始化词-序列字典和词频字典 self.wordToSequence_dict = { # 初始化词—序列字典 self.UNK_TAG:self.UNK, self.PAD_TAG:self.PAD } self.count_dict = {} # 初始化词频字典 def fit(self,text): # 1.2 fit方法:统计所有文本的词频得到词频字典 for word in text: # 构造词频字典 self.count_dict[word] = self.count_dict.get(word,0)+1 def build_vocab(self,min_count=None,max_count=None,max_features=None): # 1.3 build_vocab方法:由全部文本和条件构造词-序列字典和序列-词字典 if min_count is not None: self.count_dict = {word:count for word,count in self.count_dict.items() if count>=min_count} if max_count is not None: self.count_dict = {word:count for word,count in self.count_dict.items() if count<=max_count} if max_features is not None: # key=lambda x: x[-1] 为对前面对象中最后一维数据(即value)的值进行排序。 self.count_dict = dict(sorted(self.count_dict.items(),key=lambda x: x[-1],reverse=True)[:max_features]) for word in self.count_dict: # 将词频字典中的每一个词依次递增转为数字,形成所有文本词的词-序列字典 self.wordToSequence_dict[word] = len(self.wordToSequence_dict) self.sequenceToWord_dict= dict(zip(self.wordToSequence_dict.values(),self.wordToSequence_dict.keys())) # 反转得到所有词文本的序列-词字典 def transform(self,text,max_len=None): # 1.4 transform方法:将一个文本转化为数字序列 if max_len is not None: if len(text)>max_len: text = text[:max_len] else: text = text + [self.PAD_TAG] * (max_len-len(text)) return [self.wordToSequence_dict.get(word,self.UNK) for word in text] def inverse_transform(self,sequence): # 1.5 inverse_transform方法:将一个数字序列转化为文本 return [self.sequenceToWord_dict.get(num,self.UNK_TAG) for num in sequence] def __len__(self): # 1.6 len方法:统计词-序列字典的长度) return len(self.wordToSequence_dict) if __name__=='__main__': # 测试入口,模拟字典的构建及转换效果 one_batch_text = (['今天','菜','很','好'],['今天','去','吃','什么']) # 模拟一个batch的text ws = Word2Sequence() # 初始化文本转序列类示例 for text in one_batch_text: # 遍历所有文本构建词频字典 ws.fit(text) ws.build_vocab(max_features=6) # 利用传入限制条件的词频字典构建所有词文本的词-序列字典 print(ws.wordToSequence_dict) new_text = ['去','吃','什么','菜','好','不','好','呀'] result1 = ws.transform(new_text,max_len=10) result2 = ws.inverse_transform(result1) print(result1) print(result2)
""" 三、主函数,即整合前两大步骤:分别构建训练集和测试集dataloader中所有batch的text的字典,并保存为模型(main.py) """ from dataset import get_dataloader from word2sequence import Word2Sequence import pickle from tqdm import tqdm if __name__ == '__main__': ws = Word2Sequence() train_dataloader = get_dataloader(train=True) test_dataloader = get_dataloader(train=False) for one_batch_text, labels in tqdm(train_dataloader): for text in one_batch_text: ws.fit(text) for one_batch_text, labels in tqdm(test_dataloader): for text in one_batch_text: ws.fit(text) ws.build_vocab() print(len(ws)) # 构建完整个字典后,保存实例化对象成文件 pickle.dump(ws, open('.\\model\\TextSentiment\\ws_lstm.pkl', 'wb'))
""" 四、构建模型(model.py) """ import torch import torch.nn as nn import torch.nn.functional as F import config class LstmImdbModel(nn.Module): def __init__(self): super(LstmImdbModel, self).__init__() # word embedding操作,将每次词随机初始化嵌入为词向量 self.emb = nn.Embedding( num_embeddings=len(config.ws), embedding_dim=300) self.lstm = nn.LSTM(input_size=300, hidden_size=config.hidden_size, num_layers=config.num_layers, batch_first=True, bidirectional=config.bidriectional, dropout=config.dropout) # 加入一个双向LSTM神经网络 self.fc = nn.Linear(2*config.hidden_size, 2) # 通过一个简单的全连接层进行学习 def forward(self, input): # input.size():[512, 200] x = self.emb(input) # x.size():[512, 200, 300] # x.size():[512, 200, 2*config.hidden_size] x, (h_n, c_n) = self.lstm(x) # 获取正反两个方向最后一次的output,进行concat操作 output_forward = h_n[-2, :, :] output_backward = h_n[-1, :, :] output = torch.cat([output_forward, output_forward], dim=-1) # [batch_size, 2*hidden_size] out = self.fc(output) # out.size():[512,2] return F.log_softmax(out, dim=-1)
""" 五、模型的训练和评估(train_test.py) """ from model import LstmImdbModel import torch import torch.nn.functional as F from dataset import get_dataloader import os import numpy as np Imdb_model = LstmImdbModel() optimizer = torch.optim.Adam(Imdb_model.parameters(),lr=1e-3) if os.path.exists('./model/TextSentiment/imdb_lstm_model.pkl'): Imdb_model.load_state_dict(torch.load('./model/TextSentiment/imdb_lstm_model.pkl')) optimizer.load_state_dict(torch.load('./model/TextSentiment/imdb_lstm_optimizer.pkl')) def train(epoch): train_dataloader = get_dataloader(train=True) for idx,(x,y_true) in enumerate(train_dataloader): y_predict = Imdb_model(x) loss = F.nll_loss(y_predict,y_true) optimizer.zero_grad() loss.backward() optimizer.step() if not(idx % 10): print('Train epoch:{} \t idx:{:>3} \t loss:{}'.format(epoch,idx,loss.item())) if not(idx % 20): torch.save(Imdb_model.state_dict(), './model/TextSentiment/imdb_lstm_model.pkl') torch.save(optimizer.state_dict(), './model/TextSentiment/imdb_lstm_optimizer.pkl') def test(): loss_list = [] acc_list = [] Imdb_model.eval() test_dataloader = get_dataloader(train=False) for idx, (x, y_true) in enumerate(test_dataloader): with torch.no_grad(): y_predict = Imdb_model(x) cur_loss = F.nll_loss(y_predict, y_true) pred = y_predict.max(dim=-1)[-1] cur_acc = pred.eq(y_true).float().mean() loss_list.append(cur_loss) acc_list.append(cur_acc) print(np.mean(acc_list), np.mean(loss_list)) if __name__=='__main__': test() for i in range(5): train(i) test()
2.2 完成训练和测试代码
为了提高程序的运行速度,可以考虑把模型放在gup上运行,那么此时需要处理一下几点:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
- 除了上述修改外,涉及计算的所有tensor都需要转化为CUDA的tensor
- 初始化的
h_0,c_0
- 训练集和测试集的
input,traget
- 在最后可以通过
tensor.cpu()
转化为torch的普通tensor
""" 配置包:用于配置保存常用的常量及模型(config.py) """ import pickle import torch train_batch_size = 512 test_batch_size = 500 max_len = 200 ws = pickle.load(open('./model/TextSentiment/ws_lstm.pkl', 'rb')) hidden_size = 128 num_layers = 2 bidriectional = True dropout = 0.4 device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
""" 五、模型的训练和评估(train_test.py) """ from model import LstmImdbModel import torch import torch.nn.functional as F from dataset import get_dataloader import os import numpy as np import config Imdb_model = LstmImdbModel().to(config.device) optimizer = torch.optim.Adam(Imdb_model.parameters(),lr=1e-3) if os.path.exists('./model/TextSentiment/imdb_lstm_model.pkl'): Imdb_model.load_state_dict(torch.load('./model/TextSentiment/imdb_lstm_model.pkl')) optimizer.load_state_dict(torch.load('./model/TextSentiment/imdb_lstm_optimizer.pkl')) def train(epoch): train_dataloader = get_dataloader(train=True) for idx,(x,y_true) in enumerate(train_dataloader): x,y_true = x.to(config.device),y_true.to(config.device) y_predict = Imdb_model(x) loss = F.nll_loss(y_predict,y_true) optimizer.zero_grad() loss.backward() optimizer.step() if not(idx % 10): print('Train epoch:{} \t idx:{:>3} \t loss:{}'.format(epoch,idx,loss.item())) if not(idx % 20): torch.save(Imdb_model.state_dict(), './model/TextSentiment/imdb_lstm_model.pkl') torch.save(optimizer.state_dict(), './model/TextSentiment/imdb_lstm_optimizer.pkl') def test(): loss_list = [] acc_list = [] Imdb_model.eval() test_dataloader = get_dataloader(train=False) for idx, (x, y_true) in enumerate(test_dataloader): with torch.no_grad(): x,y_true = x.to(config.device),y_true.to(config.device) y_predict = Imdb_model(x) cur_loss = F.nll_loss(y_predict, y_true) pred = y_predict.max(dim=-1)[-1] cur_acc = pred.eq(y_true).float().mean() loss_list.append(cur_loss) acc_list.append(cur_acc) print(np.mean(acc_list), np.mean(loss_list)) if __name__=='__main__': test() for i in range(2): train(i) test()
2.3 模型训练的最终输出
输出结果如下:(可以看见经过5个epoch训练,准确度达到91.29%左右)
- 注:大家可以把上述代码改为GRU,或者多层LSTM继续尝试,观察效果
0.50008 0.6938739 Train epoch:0 idx: 0 loss:0.6952251195907593 Train epoch:0 idx: 10 loss:0.6912798285484314 Train epoch:0 idx: 20 loss:0.6922942996025085 Train epoch:0 idx: 30 loss:0.6936517953872681 Train epoch:0 idx: 40 loss:0.6920375823974609 0.54528 0.6877126 Train epoch:1 idx: 0 loss:0.6881553530693054 Train epoch:1 idx: 10 loss:0.6865140795707703 Train epoch:1 idx: 20 loss:0.6727651953697205 Train epoch:1 idx: 30 loss:0.6800484657287598 Train epoch:1 idx: 40 loss:0.6654863357543945 0.57739997 0.67385924 Train epoch:2 idx: 0 loss:0.6788107752799988 Train epoch:2 idx: 10 loss:0.6566324830055237 Train epoch:2 idx: 30 loss:0.6512351632118225 Train epoch:2 idx: 40 loss:0.6584490537643433 0.63084006 0.5930069 Train epoch:3 idx: 0 loss:0.6028856635093689 Train epoch:3 idx: 10 loss:0.580773115158081 Train epoch:3 idx: 20 loss:0.5621671676635742 Train epoch:3 idx: 30 loss:0.49137082695961 Train epoch:3 idx: 40 loss:0.42168936133384705 0.84552 0.37732562 Train epoch:4 idx: 0 loss:0.35117626190185547 Train epoch:4 idx: 10 loss:0.34060919284820557 Train epoch:4 idx: 20 loss:0.34198158979415894 Train epoch:4 idx: 30 loss:0.3405914306640625 Train epoch:4 idx: 40 loss:0.31769031286239624 0.91291994 0.2382923
-
双向循环神经网络实现MNIST
2020-06-05 17:30:59使用Tensorflow 2.2 中的keras实现双向循环神经网络。训练速度非常慢。 import tensorflow as tf import numpy as np from tensorflow import keras import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1' #读取本地...使用Tensorflow 2.2 中的keras实现双向循环神经网络。训练速度非常慢。第一层的单元是64,第二层是32。通过model.summary()可以发现,实际上每一层都是乘以2的。
import tensorflow as tf import numpy as np from tensorflow import keras import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1' #读取本地mnist数据 def my_load_data(path='mnist.npz'): origin_folder = 'https://storage.googleapis.com/tensorflow/tf-keras-datasets/' path = tf.keras.utils.get_file( path, origin=origin_folder + 'mnist.npz', cache_dir='DataSet/', cache_subdir="" ) with np.load(path, allow_pickle=True) as f: x_train, y_train = f['x_train'], f['y_train'] x_test, y_test = f['x_test'], f['y_test'] return (x_train, y_train), (x_test, y_test) (x_train, y_train), (x_test, y_test) = my_load_data(path='mnist.npz') x_train=x_train/255. x_test=x_test/255. #转换为独热模式 y_train = keras.utils.to_categorical(y_train, num_classes=10) y_test = keras.utils.to_categorical(y_test, num_classes=10) # 数据长度 一行有28个像素 input_size = 28 # 序列的长度 time_steps = 28 # 隐藏层block的个数 cell_size = 64 b_size = 32 model = keras.Sequential() # 循环神经网络 model.add(keras.layers.Bidirectional( keras.layers.SimpleRNN(cell_size, return_sequences=True), input_shape = (time_steps, input_size) )) model.add(keras.layers.Bidirectional(keras.layers.SimpleRNN(b_size))) # 输出层 model.add(keras.layers.Dense(10, activation='softmax')) # 定义优化器 adam = keras.optimizers.Adam(lr=1e-4) # 定义优化器、loss function, 训练过程中计算准确率 model.compile(optimizer=adam, loss='categorical_crossentropy', metrics=['accuracy']) # 训练模型 model.fit(x_train, y_train, batch_size=64, epochs=3) # 评估模型 loss, accuracy = model.evaluate(x_test, y_test) print('test loss', loss) print('test accuracy', accuracy)
-
PyTorch-RNN循环神经网络实现分类-回归
2020-11-26 19:34:22一、RNN ...对循环神经网络的研究始于二十世纪80-90年代,并在二十一世纪初发展为深度学习(deep learning)算法之一 [2] ,其中双向循环神经网络(Bidirectional RNN, Bi-RNN)和长短期记忆网络一、RNN
1.1 简介
循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的递归神经网络(recursive neural network)
对循环神经网络的研究始于二十世纪80-90年代,并在二十一世纪初发展为深度学习(deep learning)算法之一 [2] ,其中双向循环神经网络(Bidirectional RNN, Bi-RNN)和长短期记忆网络(Long Short-Term Memory networks,LSTM)是常见的循环神经网络 。
循环神经网络具有记忆性、参数共享并且图灵完备(Turing completeness),因此在对序列的非线性特征进行学习时具有一定优势 。循环神经网络在自然语言处理(Natural Language Processing, NLP),例如语音识别、语言建模、机器翻译等领域有应用,也被用于各类时间序列预报。引入了卷积神经网络(Convoutional Neural Network,CNN)构筑的循环神经网络可以处理包含序列输入的计算机视觉问题1.2 序列数据
我们想象现在有一组序列数据 data 0,1,2,3. 在当预测 result0 的时候,我们基于的是 data0, 同样在预测其他数据的时候, 我们也都只单单基于单个的数据. 每次使用的神经网络都是同一个 NN. 不过这些数据是有关联 顺序的 , 就像在厨房做菜, 酱料 A要比酱料 B 早放, 不然就串味了. 所以普通的神经网络结构并不能让 NN 了解这些数据之间的关联.
1.3 处理序列数据的神经网络
那我们如何让数据间的关联也被 NN 加以分析呢? 想想我们人类是怎么分析各种事物的关联吧, 最基本的方式,就是记住之前发生的事情. 那我们让神经网络也具备这种记住之前发生的事的能力. 再分析 Data0 的时候, 我们把分析结果存入记忆. 然后当分析 data1的时候, NN会产生新的记忆, 但是新记忆和老记忆是没有联系的. 我们就简单的把老记忆调用过来, 一起分析. 如果继续分析更多的有序数据 , RNN就会把之前的记忆都累积起来, 一起分析.
我们再重复一遍刚才的流程, 不过这次是以加入一些数学方面的东西. 每次 RNN 运算完之后都会产生一个对于当前状态的描述 , state. 我们用简写 S( t) 代替, 然后这个 RNN开始分析 x(t+1) , 他会根据 x(t+1)产生s(t+1), 不过此时 y(t+1) 是由 s(t) 和 s(t+1) 共同创造的. 所以我们通常看到的 RNN 也可以表达成这种样子.二、RNN实现手写数字图片分类
2.1 MNIST手写数据
import torch from torch import nn import torchvision.datasets as dsets import torchvision.transforms as transforms from torch.autograd import Variable import matplotlib.pyplot as plt torch.manual_seed(1) # reproducible # Hyper Parameters EPOCH = 1 # 训练整批数据多少次, 为了节约时间, 我们只训练一次 BATCH_SIZE = 64 TIME_STEP = 28 # rnn 时间步数 / 图片高度 INPUT_SIZE = 28 # rnn 每步输入值 / 图片每行像素 LR = 0.01 # learning rate DOWNLOAD_MNIST = False # 如果你已经下载好了mnist数据就写上 Fasle # Mnist 手写数字 train_data = dsets.MNIST( root='./mnist/', # 保存或者提取位置 train=True, # this is training data transform=transforms.ToTensor(), # 转换 PIL.Image or numpy.ndarray 成 # torch.FloatTensor (C x H x W), 训练的时候 normalize 成 [0.0, 1.0] 区间 download=DOWNLOAD_MNIST, # 没下载就下载, 下载了就不用再下了 ) # 批训练 50samples, 1 channel, 28x28 (50, 1, 28, 28) train_loader = torch.utils.data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True) test_data = dsets.MNIST(root='./mnist/', train=False,transform=transforms.ToTensor()) # 为了节约时间, 我们测试时只测试前2000个 test_x = torch.unsqueeze(test_data.test_data, dim=1).type(torch.FloatTensor)[:2000]/255. # shape from (2000, 28, 28) to (2000, 1, 28, 28), value in range(0,1) test_y = test_data.test_labels.numpy().squeeze()[:2000]
2.2 搭建RNN网络并训练
# 搭建RNN网络 class RNN(nn.Module): def __init__(self): super(RNN, self).__init__() self.rnn = nn.LSTM( # LSTM 效果要比 nn.RNN() 好多了 input_size=28, # 图片每行的数据28像素点 hidden_size=64, # rnn hidden unit num_layers=1, # 有几层 RNN layers batch_first=True, # input & output 会是以 batch size 为第一维度的特征集 e.g. (batch, time_step, input_size) ) self.out = nn.Linear(64, 10) # 输出层 def forward(self, x): # x shape (batch, time_step, input_size) # r_out shape (batch, time_step, output_size) # h_n shape (n_layers, batch, hidden_size) LSTM 有两个 hidden states, h_n 是分线, h_c 是主线 # h_c shape (n_layers, batch, hidden_size) r_out, (h_n, h_c) = self.rnn(x, None) # None 表示 hidden state 会用全0的 state # 选取最后一个时间点的 r_out 输出 # 这里 r_out[:, -1, :] 的值也是 h_n 的值 out = self.out(r_out[:, -1, :]) return out rnn = RNN() print(rnn) optimizer = torch.optim.Adam(rnn.parameters(), lr=LR) # optimize all parameters loss_func = nn.CrossEntropyLoss() # the target label is not one-hotted # training and testing for epoch in range(EPOCH): for step, (x, b_y) in enumerate(train_loader): # gives batch data b_x = x.view(-1, 28, 28) # reshape x to (batch, time_step, input_size) output = rnn(b_x) # rnn output loss = loss_func(output, b_y) # cross entropy loss optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients test_output = rnn(test_x[:10].view(-1, 28, 28)) pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze() print(pred_y, 'prediction number') print(test_y[:10], 'real number')
2.3 结果
三、RNN实现回归-Sin曲线预测Cos曲线
循环神经网络让神经网络有了记忆, 对于序列话的数据,循环神经网络能达到更好的效果.
3.1 数据
import torch from torch import nn import numpy as np import matplotlib.pyplot as plt torch.manual_seed(1) # reproducible # Hyper Parameters TIME_STEP = 10 # rnn time step / image height INPUT_SIZE = 1 # rnn input size / image width LR = 0.02 # learning rate DOWNLOAD_MNIST = False # set to True if haven't download the data # show data steps = np.linspace(0, np.pi*2, 100, dtype=np.float32) x_np = np.sin(steps) # float32 for converting torch FloatTensor y_np = np.cos(steps) plt.plot(steps, y_np,'r-',label= 'target (cos)') plt.plot(steps, x_np,'b-',label= 'input (sin)') plt.legend(loc= 'best') plt.show()
3.2 搭建RNN网络并训练
这一次的 RNN, 我们对每一个 r_out 都得放到 Linear 中去计算出预测的 output, 所以我们能用一个 for loop 来循环计算. 这点是 Tensorflow 望尘莫及的!
其实熟悉 RNN 的朋友应该知道, forward 过程中的对每个时间点求输出还有一招使得计算量比较小的. 不过上面的内容主要是为了呈现 PyTorch 在动态构图上的优势, 所以我用了一个 for loop 来搭建那套输出系统. 下面介绍一个替换方式. 使用 reshape 的方式整批计算.class RNN(nn.Module): def __init__(self): super(RNN, self).__init__() self.rnn = nn.RNN( # 这回一个普通的 RNN 就能胜任 input_size=1, hidden_size=32, # rnn hidden unit num_layers=1, # 有几层 RNN layers batch_first=True, # input & output 会是以 batch size 为第一维度的特征集 e.g. (batch, time_step, input_size) ) self.out = nn.Linear(32, 1) def forward(self, x, h_state): # 因为 hidden state 是连续的, 所以我们要一直传递这一个 state # x (batch, time_step, input_size) # h_state (n_layers, batch, hidden_size) # r_out (batch, time_step, output_size) r_out, h_state = self.rnn(x, h_state) # h_state 也要作为 RNN 的一个输入 outs = [] # 保存所有时间点的预测值 for time_step in range(r_out.size(1)): # 对每一个时间点计算 output outs.append(self.out(r_out[:, time_step, :])) return torch.stack(outs, dim=1), h_state rnn = RNN() print(rnn) def forward(self, x, h_state): r_out, h_state = self.rnn(x, h_state) r_out = r_out.view(-1, 32) outs = self.out(r_out) return outs.view(-1, 32, TIME_STEP), h_state # 训练 optimizer = torch.optim.Adam(rnn.parameters(), lr=LR) # optimize all rnn parameters loss_func = nn.MSELoss() h_state = None # 要使用初始 hidden state, 可以设成 None for step in range(100): start, end = step * np.pi, (step+1)*np.pi # time steps # sin 预测 cos steps = np.linspace(start, end, 10, dtype=np.float32) x_np = np.sin(steps) # float32 for converting torch FloatTensor y_np = np.cos(steps) x = torch.from_numpy(x_np[np.newaxis, :, np.newaxis]) # shape (batch, time_step, input_size) y = torch.from_numpy(y_np[np.newaxis, :, np.newaxis]) prediction, h_state = rnn(x, h_state) # rnn 对于每个 step 的 prediction, 还有最后一个 step 的 h_state # !! 下一步十分重要 !! h_state = h_state.data # 要把 h_state 重新包装一下才能放入下一个 iteration, 不然会报错 loss = loss_func(prediction, y) # cross entropy loss optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients # plotting plt.plot(steps, y_np.flatten(), 'r-') plt.plot(steps, prediction.data.numpy().flatten(), 'b-') plt.draw(); plt.pause(0.05) plt.ioff() plt.show()
3.3 结果
-
机器学习004:循环神经网络实现与文本分类问题
2018-07-03 02:46:03本周主要目标在于实现一个可用的循环神经网络。主要内容包括:循环神经网络以及求导(BPTT)、多层循环神经网络、文本向量化以及一个小的文本分类实践。实现过程中使用了 Numpy 用于矩阵运算。文章目的在于脱离编程...本周主要目标在于实现一个可用的循环神经网络。主要内容包括:循环神经网络以及求导(BPTT)、多层循环神经网络、文本向量化以及一个小的文本分类实践。实现过程中使用了 Numpy 用于矩阵运算。文章目的在于脱离编程语言束缚去学习算法,因此可以使用其他编程语言,同时语言中应该包含以下组件:矩阵乘法、矩阵分片。
正文没有使用机器学习库,但由于 Python 语言的速度原因,为了更快获得结果,使用了 Tensorflow 进行了预训练,这个过程不是必须的。文末会附这部分代码。
本周推荐阅读时间:15min。
1. 阅读建议
- 推荐阅读时间:15min
- 推荐阅读文章:Hochreiter S, Schmidhuber J. Long short-term memory[J]. Neural Computation, 1997, 9(8):1735-1780.
2. 软件环境
- Python3
- Numpy
3. 前置基础
- 链式求导
4. 数据描述
- 数据来源:谭松波整理语料
- 数据地址:ChnSentiCorp
- 数据简介:语料之中包含:酒店、电脑(笔记本)与书籍等领域。是包含正负样本的平衡数据集。
5. 理论部分
5.1 循环神经网络正向传播过程
循环神经网络的输入是时序相关的,因此其输入与输入可以描述为 $h_1,\cdots,h_T$、$y_1,\cdots,y_T$,为了保持时序信息,最简单的 RNN 函数形式为:
$$\begin{matrix}h_t=f(x_t,h_{t-1})\\\rightarrow h_t=tanh(concat[x_t, h_{t-1}]\cdot W+b)\\\rightarrow tanh(x_t\cdot W1+h_{t-1}\cdot W2+b)\end{matrix}$$(1.1)
其中 $x_t$ 的形式为 [BATCHSIZE, Features1],$h_t$ 的形式为 [BatchSize, Features2]。多层 RNN 网络可以在输出的基础上继续加入 RNN 函数:
$$\begin{matrix}h^l_t=f(x_t,h^l_{t-1})\\h^{l+1}_t=f(h^l_t, h^{l+1}_{t-1})\end{matrix}$$(1.2)
对于简单的 RNN 来说,状态向量与输出相同。
5.2 文本向量化正向传播
循环神经网络(RNN)产生之初就是为了处理文本问题。但是神经网络本身只能处理浮点型数据,因此需要将文本转化为适合于神经网络处理的向量。对于 1.1 式之中的 $x_t$ 为向量化后的文本,其是固定长度的(本文设定为 128)。下面对文本向量化过程进行叙述:
对文本每个字符进行单独编号 -> 整形数字 -> OneHot 向量 -> 乘以降维矩阵 $W_{N, 128}$(N 为字符个数)
在处理文本过程之中我们需要对所有文本之中不重复的字符(或词)进行单独编号(整形数字),编号是从 0 开始连续的。处理文本过程之中将文本转化为相应的整形数字表示。处理完成后将整形数字使用 OneHot 向量的方式进行表示。此时向量的维度很高(中文几千个字符,OneHot 向量长度也是如此)。为了将输入转化为适合处理的向量长度,需要乘以一个降维矩阵 $W_{N, 128}$,从而形成 $x_t$。这是对于向量一种线性降维的方式。
5.2 反向传播过程
RNN 函数反向传播过程与全链接网络类似:
$$\begin{matrix}e^l_{t-1}=\frac{\partial loss}{\partial h^l_{t-1}}=\frac{\partial loss}{\partial h^l_{t}}\circ f'(x_t,h^l_{t-1})\frac{\partial(x_t\cdot W1+h_{t-1}\cdot W2+b)}{\partial h_{t-1}}=e^l_t f' W2&(a)\\\Delta W1=\sum_t (x_t)^T\cdot (e_t^lf')&(b)\\\Delta W2=\sum_t (h_{t-1})^T\cdot (e_t^lf')&(c)\\e^{l}_{t}=\frac{\partial loss}{\partial h^{l}_{t}}=\frac{\partial loss}{\partial h^{l+1}_{t}}\circ f'(h_t^l,h^{l+1}_{t-1})\frac{\partial(h_t^l\cdot W1+h_{t-1}\cdot W2+b)}{\partial h_{t}^l}=e^{l+1}_t f' W1&(d)\end{matrix}$$(1.3)
1.3-a 称之为时间反向传播算法 BPTT,1.3-c 为层间传播。可训练参数为 1.3-bc。实际上传统的 RNN 网络与全链接网络并无不同。只是添加了时间反向传播项。
6. 代码部分
6.1 循环神经网络层
import numpy as npclass NN(): def __init__(self): """ 定义可训练参数 """ self.value = [] self.d_value = [] self.outputs = [] self.layer = [] self.layer_name = [] def tanh(self, x, n_layer=None, layer_par=None): epx = np.exp(x) enx = np.exp(-x) return (epx-enx)/(epx+enx) def d_tanh(self, x, n_layer=None, layer_par=None): e2x = np.exp(2 * x) return 4 * e2x / (1 + e2x) ** 2 def _rnncell(self, X, n_layer, layer_par): """ RNN正向传播层 """ W = self.value[n_layer][0] bias = self.value[n_layer][1] b, h, c = np.shape(X) _, h2 = np.shape(W) outs = [] stats = [] s = np.zeros([b, h2]) for itr in range(h): x = X[:, itr, :] stats.append(s) inx = np.concatenate([x, s], axis=1) out = np.dot(inx, W) + bias out = self.tanh(out) s = out outs.append(out) outs = np.transpose(outs, (1, 0, 2)) stats= np.transpose(stats, (1, 0, 2)) return [outs, stats] def _d_rnncell(self, error, n_layer, layer_par): """ BPTT层,此层使用上一层产生的Error产生向前一层传播的error """ inputs = self.outputs[n_layer][0] states = self.outputs[n_layer + 1][1] b, h, insize = np.shape(inputs) back_error = [np.zeros([b, insize]) for itr in range(h)] W = self.value[n_layer][0] bias = self.value[n_layer][1] dw = np.zeros_like(W) db = np.zeros_like(bias) w1 = W[:insize, :] w2 = W[insize:, :] for itrs in range(h - 1, -1, -1): # 每一个时间步都要进行误差传播 if len(error[itrs]) == 0: continue else: err = error[itrs] for itr in range(itrs, -1, -1): h = states[:, itr, :] x = inputs[:, itr, :] inx = np.concatenate([x, h], axis=1) h1 = np.dot(inx, W) + bias d_fe = self.d_tanh(h1) err = d_fe * err # 计算可训练参数导数 dw[:insize, :] += np.dot(x.T, err) dw[insize:, :] += np.dot(h.T, err) db += np.sum(err, axis=0) # 计算传递误差 back_error[itr] += np.dot(err, w1.T) err = np.dot(err, w2.T) self.d_value[n_layer][0] = dw self.d_value[n_layer][1] = db return back_error def basic_rnn(self, w, b): self.value.append([w, b]) self.d_value.append([np.zeros_like(w), np.zeros_like(b)]) self.layer.append((self._rnncell, None, self._d_rnncell, None)) self.layer_name.append("rnn")
6.2 文本向量化层
def _embedding(self, inputs, n_layer, layer_par): W = self.value[n_layer][0] F, E = np.shape(W) B, L = np.shape(inputs) # 转换成one-hot向量 inx = np.zeros([B * L, F]) inx[np.arange(B * L), inputs.reshape(-1)] = 1 inx = inx.reshape([B, L, F]) # 乘以降维矩阵 embed = np.dot(inx, W) return [embed] def _d_embedding(self, in_error, n_layer, layer_par): inputs = self.outputs[n_layer][0] W = self.value[n_layer][0] F, E = np.shape(W) B, L = np.shape(inputs) inx = np.zeros([B * L, F]) inx[np.arange(B * L), inputs.reshape(-1)] = 1 error = np.transpose(in_error, (1, 0, 2)) _, _, C = np.shape(error) error = error.reshape([-1, C]) # 计算降维矩阵的导数 self.d_value[n_layer][0] = np.dot(inx.T, error) return [] def embedding(self, w): self.value.append([w]) self.d_value.append([np.zeros_like(w)]) self.layer.append((self._embedding, None, self._d_embedding, None)) self.layer_name.append("embedding")
6.3 使用 RNN 网络最后一层输出用于后续处理
def _last_out(self, inputs, n_layer, layer_par): return [inputs[:, -1, :]] def _d_last_out(self, in_error, n_layer, layer_par): X = self.outputs[n_layer][0] b, h, c = np.shape(X) error = [[] for itr in range(h)] error[-1] = in_error return error def last_out(self): self.value.append([]) self.d_value.append([]) self.layer.append((self._last_out, None, self._d_last_out, None)) self.layer_name.append("text_error")
6.4 网络其他部分
其他部分与前面所讲全链接网络、卷积神经网络类似:
def _matmul(self, inputs, n_layer, layer_par): W = self.value[n_layer][0] return [np.dot(inputs, W)] def _d_matmul(self, in_error, n_layer, layer_par): W = self.value[n_layer][0] inputs = self.outputs[n_layer][0] self.d_value[n_layer][0] = np.dot(inputs.T, in_error) error = np.dot(in_error, W.T) return error def matmul(self, filters): self.value.append([filters]) self.d_value.append([np.zeros_like(filters)]) self.layer.append((self._matmul, None, self._d_matmul, None)) self.layer_name.append("matmul") def _sigmoid(self, X, n_layer=None, layer_par=None): return [1/(1+np.exp(-X))] def _d_sigmoid(self, in_error, n_layer=None, layer_par=None): X = self.outputs[n_layer][0] return in_error * np.exp(-X)/(1 + np.exp(-X)) ** 2 def sigmoid(self): self.value.append([]) self.d_value.append([]) self.layer.append((self._sigmoid, None, self._d_sigmoid, None)) self.layer_name.append("sigmoid") def _relu(self, X, *args, **kw): return [(X + np.abs(X))/2.] def _d_relu(self, in_error, n_layer, layer_par): X = self.outputs[n_layer][0] drelu = np.zeros_like(X) drelu[X>0] = 1 return in_error * drelu def relu(self): self.value.append([]) self.d_value.append([]) self.layer.append((self._relu, None, self._d_relu, None)) self.layer_name.append("relu") def forward(self, X): self.outputs.append([X]) net = [X] for idx, lay in enumerate(self.layer): method, layer_par, _, _ = lay net = method(net[0], idx, layer_par) self.outputs.append(net) return self.outputs[-2][0] def backward(self, Y): error = self.layer[-1][2](Y, None, None) self.n_layer = len(self.value) for itr in range(self.n_layer-2, -1, -1): _, _, method, layer_par = self.layer[itr] #print("++++-", np.shape(error), np.shape(Y)) error = method(error, itr, layer_par) return error def apply_gradient(self, eta): for idx, itr in enumerate(self.d_value): if len(itr) == 0: continue for idy, val in enumerate(itr): self.value[idx][idy] -= val * eta def fit(self, X, Y, eta=0.1): self.forward(X) self.backward(Y) self.apply_gradient(eta) def predict(self, X): self.forward(X) return self.outputs[-2]
7. 程序运行
7.1 文本分类网络模型
搭建文本分类网络时,网络模型为两层 RNN 网络,网络输出的最后一个时间步携带了整个文本的信息,因此使用最后一个输出搭建多层全链接网络用以后续处理,最终使用向量距离作为 loss 函数:
...#搭建网络模型method = NN()method.embedding(ew)method.basic_rnn(w1, b1)method.basic_rnn(w2, b2)method.last_out()method.matmul(w3)method.bias_add(b3)method.relu()method.matmul(w4)method.bias_add(b4)method.sigmoid()method.loss_square()for itr in range(1000): # 获取数据 inx, iny = ... pred = method.forward(inx) method.backward(iny) method.apply_gradient(0.001) if itr% 20 == 0: # 获取测试数据 inx, iny = ... pred = method.forward(inx) prd1 = np.argmax(pred, axis=1) prd2 = np.argmax(iny, axis=1) print(np.sum(prd1==prd2)/len(idx))
8. 附录
8.1 使用 TensorFlow 验证程序正确性
使用 TensorFlow 作为验证程序,验证方法为输出计算导数:
# 搭建多层神经网络batch_size = 1max_time = 10indata = tf.placeholder(dtype=tf.float64, shape=[batch_size, 10, 3])# 两层RNN网络cell = rnn.MultiRNNCell([rnn.BasicRNNCell(3) for itr in range(2)], state_is_tuple=True)state = cell.zero_state(batch_size, tf.float64)outputs = []states = []# 获取每一步输出,与状态for time_step in range(max_time): (cell_output, state) = cell(indata[:, time_step, :], state) outputs.append(cell_output) states.append(state)y = tf.placeholder(tf.float64, shape=[batch_size, 3])# 定义loss函数loss = tf.square(outputs[-1]-y)opt = tf.train.GradientDescentOptimizer(1)# 获取可训练参数weights = tf.trainable_variables()# 计算梯度grad = opt.compute_gradients(loss, weights)sess = tf.Session()sess.run(tf.global_variables_initializer())# 获取变量值与梯度w1, b1, w2, b2 = sess.run(weights)dw1, db1, dw2, db2 = sess.run(grad, feed_dict={indata:np.ones([batch_size, 10, 3]), y:np.ones([batch_size, 3])})dw1 = dw1[0]db1 = db1[0]dw2 = dw2[0]db2 = db2[0]method = NN()method.basic_rnn(w1, b1)method.basic_rnn(w2, b2)method.last_out()method.loss_square()method.forward(np.ones([batch_size, 10, 3]))method.backward(np.ones([batch_size, 3]))print("TF Gradients", np.mean(dw1), np.mean(db1), np.mean(dw2), np.mean(db2))rnn.loss(np.ones([batch_size, 3]))for itr in method.d_value: if len(itr) == 0:continue print("NP Gradinets", np.mean(dw1, np.mean(db1), np.mean(dw2), np.mean(db2))
验证分程序仅用于演示思路。
8.2 TensorFlow 预训练网络
import tensorflow as tfinput_x = tf.placeholder(tf.int32, [None, 30], name='input_x')input_y = tf.placeholder(tf.float32, [None, 2], name='input_y')embedding = tf.get_variable('embedding', [vocab_size, 128])embedding_inputs = tf.nn.embedding_lookup(embedding, input_x)# 多层rnn网络cells = [tf.contrib.rnn.BasicRNNCell(128) for _ in range(2)]rnn_cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple=True)_outputs, _ = tf.nn.dynamic_rnn(cell=rnn_cell, inputs=embedding_inputs, dtype=tf.float32)last = _outputs[:, -1, :] # 取最后一个时序输出作为结果net = tf.layers.dense(last, self.config.hidden_dim, name='fc1')net = tf.nn.relu(net)# 分类器logits = tf.layers.dense(net, 2, name='fc2')loss = tf.square(logits - input_y)....训练过程....# 获取变量并保存name = []array = []for itra, itrb in zip(tf.global_variables(), session.run(tf.global_variables())): name.append(itra.name) array.append(itrb)np.savez("par.npz", name=name, data=array)
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
阅读全文: http://gitbook.cn/gitchat/activity/5b0eb962d0b199202f42912b
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。
-
循环神经网络实现创作歌词
2020-02-14 21:01:15,上式的计算是循环的,使用循环计算的网络即循环神经网络(recurrent neural network)。 在时间步 t ,输出层的输出为: O t = H t W h q + b q O_t=H_tW_{hq}+b_q O t = H t W h q + b q 其中 ... -
循环神经网络实现变长度输入
2020-10-05 16:18:35推荐阅读: Pytorch 中如何处理 RNN 输入变长序列 padding ...而在使用keras搭建模型时,如果直接使用LSTM层作为网络输入的第一层,需要指定输入的大小。如果需要使用变长序列,那么,只需要在LSTM层前加一个Masking -
-
使用循环神经网络实现语言模型
2018-07-27 17:29:31#定义使用LSTM结构为循环体结构且使用dropout的深层循环神经网络 lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE) if is_training: lstm_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_cell, output_keep_... -
84、循环神经网络实现语言模型
2019-09-30 05:47:16通过一个PTBModel类来描述模型,这样方便维护循环神经网络中的状态 class PTBModel(object): def __init__ (self, is_training, batch_size, num_steps): # 记录使用的batch大小和截断长度 self.batch_... -
RNN循环神经网络实现预测比特币价格过程详解
2019-08-02 06:47:52这是一个鼓励用户尝试 seq2seq 神经网络架构的项目: 在上述 GitHub 库基础上,考虑使用以下能够下载和标准化比特币历史值(美元或欧元)数据的函数,这些函数在 dataset.py 中定义。训练集和测试集根据 80... -
第七章:Tensorflow2.0 RNN循环神经网络实现IMDB数据集训练(理论+实践)
2019-09-03 23:21:11一、时序数据 卷积神经网络是针对二维位置...对于按时间轴不停产生的信号,神经网络中,我们称其为temporal signals,而循环神经网络处理的就是拥有时序相关或者前后相关的数据类型(Sequence)。 二、embeddin...
-
python列表嵌套字典取值赋值
-
深究字符编码的奥秘,与乱码说再见
-
使用vue搭建微信H5公众号项目
-
Feign 基本使用
-
MySQL 多实例安装 及配置主从复制实验环境
-
封装app分发源码+支持免签绿标.zip
-
Ant Design of Vue DatePicker 日期选择框只选择年份
-
1996年scarlet.github.io-源码
-
华为1+X——网络系统建设与运维(中级)
-
plugins的介绍使用
-
logdna-rails-example:logdna rails记录器整数示例-源码
-
Java核心技术面试精讲(第十三讲)| 谈谈接口和抽象类有什么区别?
-
【Python-随到随学】 FLask第一周
-
MySQL 管理利器 mysql-utilities
-
Android 开发后台
-
AcWing 1236. 递增三元组
-
MySQL NDB Cluster 负载均衡和高可用集群
-
面试套路问题总结5
-
智能停车场云平台(附vue+SpringBoot前后端项目源码)
-
【Python-随到随学】FLask第二周