精华内容
下载资源
问答
  • 循环神经网络实现语言模型循环神经网络裁剪梯度困惑度实现 循环神经网络 目的是基于当前的输入与过去的输入序列,预测序列的下一个字符。循环神经网络中引入一个隐含层HHH,用HtH_tHt​表示HHH在时间步ttt的值。HtH_...
  • 文章目录循环神经网络实现文本情感分类目标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 完成...

    循环神经网络实现文本情感分类

    目标

    1. 知道LSTM和GRU的使用方法及输入输出的格式
    2. 能够应用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)

    1. input_size:输入数据的形状,即embedding_dim
    2. hidden_size:隐藏层神经元的数量,即每一层有多少个LSTM单元
    3. num_layer :即RNN的中LSTM单元的层数
    4. batch_first:默认值为False,输入的数据需要[seq_len,batch_size,feature],如果为True,则为[batch_size,seq_len,feature]
    5. dropout:dropout的比例,默认值为0。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropout
    6. bidirectional:是否使用双向LSTM,默认是False

    二、实例化LSTM对象之后,不仅需要传入数据,还需要前一次的h_0(前一次的隐藏状态)和c_0(前一次memory*),若不传则默认初始化全为0

    即:lstm(input,(h_0,c_0))


    三、LSTM的默认输出为output, (h_n, c_n)

    1. output(batch_size, seq_len, hidden_size * num_directions)—>batch_first=False
    2. h_n:(num_layers * num_directions, batch_size, hidden_size)
    3. 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
    其形状为:

    1. output:(seq_len, batch_size, num_directions * hidden_size)
    2. 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_0c_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个维度进行拼接,正向第一个之后接着是反向第一个(从上往下顺序是:第一层正向,第一层反向,第二层正向,第二层反向…)
    • 如下图:
      在这里插入图片描述
    1. 前向的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)
    

    结果如下:在这里插入图片描述

    1. 后向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的使用注意点

    1. 第一次调用之前,需要初始化隐藏状态,如果不初始化,默认创建全为0的隐藏状态
    2. 往往会使用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,:]可以获取最后一维
    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轴,实现上述效果
    2. 使用双向LSTM的时候,往往会分别使用每个方向最后一次的output,作为当前数据经过双向LSTM的结果
    • 即:torch.cat([h_n[-2,:,:],h_n[-1,:,:]],dim=-1)
    • 最后的表示的size是[batch_size,hidden_size * 2]
    1. 上述内容在GRU中同理

    2. 使用LSTM完成文本情感分类

    在前面,我们使用了word embedding去实现了toy级别的文本情感分类,那么现在我们在这个模型中添加上LSTM层,观察分类效果。


    为了达到更好的效果,对之前的模型做如下修改

    1. MAX_LEN = 200
    2. 构建dataset的过程,把数据转化为2分类的问题,pos为1,neg为0,否则25000个样本完成10个类别的划分数据量是不够的
    3. 在实例化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上运行,那么此时需要处理一下几点:

    1. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    2. model.to(device)
    3. 除了上述修改外,涉及计算的所有tensor都需要转化为CUDA的tensor
    • 初始化的h_0,c_0
    • 训练集和测试集的input,traget
    1. 在最后可以通过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
    
    展开全文
  • 使用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)
    
    
    
    展开全文
  • 一、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 结果

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • 本周主要目标在于实现一个可用的循环神经网络。主要内容包括:循环神经网络以及求导(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 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • ,上式的计算是循环的,使用循环计算的网络即循环神经网络(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 ​ 其中 ...
  • 推荐阅读: Pytorch 中如何处理 RNN 输入变长序列 padding ...而在使用keras搭建模型时,如果直接使用LSTM层作为网络输入的第一层,需要指定输入的大小。如果需要使用变长序列,那么,只需要在LSTM层前加一个Masking
  • #定义使用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_...
  • 通过一个PTBModel类来描述模型,这样方便维护循环神经网络中的状态 class PTBModel(object): def __init__ (self, is_training, batch_size, num_steps): # 记录使用的batch大小和截断长度 self.batch_...
  • 这是一个鼓励用户尝试 seq2seq 神经网络架构的项目:   在上述 GitHub 库基础上,考虑使用以下能够下载和标准化比特币历史值(美元或欧元)数据的函数,这些函数在 dataset.py 中定义。训练集和测试集根据 80...
  • 一、时序数据 卷积神经网络是针对二维位置...对于按时间轴不停产生的信号,神经网络中,我们称其为temporal signals,而循环神经网络处理的就是拥有时序相关或者前后相关的数据类型(Sequence)。 二、embeddin...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,709
精华内容 683
关键字:

循环神经网络实现