(自用笔记)RNN原理,Pytorch实现和使用RNN实现IMDB英文电影评价二分类

循环神经网络

记忆单元分类

RNN,GRU,LSTM

类别

单向循环,

双向循环

多层单或双向叠加

delay:n帧输入输入网络,记忆单元先更新n步,这n步的输出先不要

使得预测第一帧输出的时候不只看到了第一帧的输入(看到的上下文更宽)

优点/缺点:

优点:每个时刻的权重共享,可以处理变长序列,模型大小与序列长度无关,计算量与序列长度呈线性增长,考虑历史信息,便于流式输出
缺点:串行计算较慢,无法获取很长的历史信息

transformer计算复杂度和序列是平方关系
生成任务:image
分类:image
词法识别image

RNN(Recurrent Neural Network)

x<1>输入神经网络,输出判断这个单词是否是人名的一部分,

读到x<2>时,不仅仅使用x<2>来预测,还会使用time-step 1的信息作为输入

a<1>代表time step1的激活值

image

黑色块表示延迟一个time-step

RNN每个time-step的参数是共享的,数据从左到右读入

缺点:只利用了之前的信息而没有利用之后的信息

根据y hat输出选择激活函数:

比如对于ner任务,输出为0,1则可以选择sigmoid

简化符号:

image

输出中的一个分叉将成为其自身的输入

image

image

与前馈神经网络不同:多个RNN层都是同一个层

各个时刻的 RNN 层接收传给该层的输入和前一个 RNN 层的输出,然后据此计算当前时刻的输出

image

RNN的权重:

(1)输入x转化为输出h的权重Wx

(2)将前一个RNN层输出ht-1转化为当前层输出的Wh

ht−1 和 xt 都是行向量

RNN 的 h 存储“状态”,时间每前进一步(一个单位),它就以式 (5.9) 的形式被更新

Backpropagation Through Time

基于时间的反向传播

image

Truncated BPTT

按适当长度截断的误差反向传播法

如果序列太长,就会出现计算量或者内存使 用量方面的问题。此外,随着层变长,梯度逐渐变小,梯度将无法向前一层 传递

以各个块为单位(和其他块没有关联)完成误差反向传播法

正向传播之间是有关联的,这意味着必须按顺序输入数据

神经网络在进行 mini-batch 学习时,数据都是 随机选择的。但是,在 RNN 执行 Truncated BPTT 时,数据需 要按顺序输入

正向传播的计算需要前一个块最后的隐藏状态 h9,这样可以维 持正向传播的连接。

按顺序输入

image

两个批次:一个批次500个时序数据,由于按长度10截断,则一个批次的元素量为50

批次的第一个元素:0-9,

第 1 笔样本数据从头开始按顺序输入,第 2 笔数据从第 500 个数据开始按顺 序输入。

一是要按顺序输入数据,二是 要平移各批次(各样本)输入数据的开始位置

image

实现RNN

基于RNN的神经网络:在水平方向上长度固定

在水平方向创建长度固定的网络序列

image

将进 行 Time RNN 层中的单步处理的层称为“RNN 层”,将一次处理 T 步的层 称为“Time RNN 层”

RNN单步 time-step处理

ht = tanh(ht−1Wh + xtWx + b) :RNN正向传播

image

image

HxD * DxN + HxH*HxN = HxN

class RNN:
    def __init__(self,Wx,Wh,b):
        self.params = [Wx,Wh,b]#初始化参数
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh),np.zeros_like(b)]#计算梯度
        self.cahe = None#缓存权重,激活值等用于反向传播
        #正向传播
        def forward(self,x,h_prev):
            #用公式计算
            Wx,Wh,b = self.params
            t = np.dot(h_prev,Wh)+np.dot(x,Wx)+b#此处使用本书的公式,和吴恩达课程不同之处只是矩阵转置了
            h_next = np.tanh(t)
            self,cache = (x,h_prev,h_next)
            
            return h_next

从前一个 RNN 层接收的输入是 h_prev,当前时刻的 RNN 层的输出(= 下 一时刻的 RNN 层的输入)是 h_next

image

db = ∂ h_next/∂ t x ∂ t/∂ b = dt x 1

则需要每个时间步求和:db 是偏置 b 的梯度,是 dt 沿着每个时间步的总和,计算方式如下:

image

这是因为偏置 b 对每个时间步的隐藏状态都有相同的梯度,因此需要将 dt 沿时间步求和以得到 db

image

image

使用 Time RNN 层管理 RNN 层的隐藏状态

Time RNN 层是 T 个 RNN 层连接起来的网络

隐藏状态向量即为h0-ht

双向RNN

image

单向RNN没有完整的上下文信息

x1开始正向流动,x4开始反向流动,最后的输出由正向和反向拼接起来(可以直接使用矩阵运算一次计算)

image

缺点:需要完整的序列(完整的句子,完整的语音等)

Deep RNN

image

RNN PyTorch

https://pytorch.org/docs/stable/generated/torch.nn.RNN.htmlimage

test_acc_list = []
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
    for data, target in test_loader:
        data, target = data.to(device), target.to(device)
        output = model(data)
        pred = output.max(1, keepdim=True)[1]                           # 找到概率最大的下标
        correct += pred.eq(target.view_as(pred)).sum().item()

# test_loss /= len(test_loader.dataset)
# test_loss_list.append(test_loss)
test_acc_list.append(100. * correct / len(test_loader.dataset))
print('Accuracy: {}/{} ({:.0f}%)\n'.format(correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))
import torch
import torch.nn as nn
single_rnn = nn.RNN(4,3,1,batch_first=True)#特征数量,hidden size,RNN层数
input = torch.randn(1,2,4)#bs*sl*fs(x feature size),这里RNN的时间步为sl
output,h_n = single_rnn(input)
output #输出
#维度见上面图片


#双向、双层RNN
bi_rnn = nn.RNN(4,3,1,batch_first=True,bidirectional = True)#特征数量,hidden size,RNN层数
bi_output,bi_h_n = bi_rnn(input)
bi_output
#双向将b,f拼接所以3变成6

隐藏大小(hidden size)指的是RNN单元中隐藏层的神经元数量。在RNN中,隐藏状态是通过隐藏层中的神经元来表示的,隐藏层的大小决定了模型可以学习和存储的信息的容量。因此,隐藏大小直接影响了模型的表示能力和学习能力。

在PyTorch中,隐藏大小通常是在创建RNN模型时指定的一个参数。例如,对于PyTorch的nn.RNN模块,你可以通过指定参数hidden_size来设置隐藏大小。

RNN实现文本分类

多对一模型

import  torch
from    torch import nn
from    torch.nn import functional as F
rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=2)
# 可理解为一个字串长度为5(RNN训练时每个字符一个个传进去,一个字符代表一个时刻t), batch size为3, 字符维度为10的输入
input_tensor  = torch.randn(5, 3, 10)
# 两层RNN的初始H参数,维度[layers, batch, hidden_len]
h0 = torch.randn(2, 3, 20)
# output_tensor最后一层所有状态!!!!!的输出(看上面那个多层的RNN图最后一层会输出h1,h2....hn)
#hn(也即Cn)表示两层最后一个时序的状态输出
output_tensor, hn =rnn(input_tensor, h0)
print(output_tensor.shape, hn.shape)

使用imdb英文电影影评数据集(二分类:消极,积极)

#查看数据集
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
df.head(5)

image

from nltk.corpus import stopwords
import nltk
nltk.download('punkt')#分词模块
nltk.download('stopwords')#停用词
nltk.download('averaged_perceptron_tagger')#词性分析

#首先对数据集进行处理,对每条文本进行英文分词,使用NLTK去除数据集中的停用词,去除动词,助词等
def process_sentence(sentence):
    disease_List = nltk.word_tokenize(sentence)
    #     print(disease_List)
    # 停用词通常是一些常见的、在句子中频繁出现但并不携带实际语义信息的词语,例如“the”、“is”、“and”等
    filtered = [w for w in disease_List if(w not in stopwords.words('english'))]
#     print(len(filtered))
    # 使用nltk.pos_tag()对剩余的词语进行词性标注,得到每个词和它的词性的元组
    Rfiltered =nltk.pos_tag(filtered)
#     print(Rfiltered)#返回元组('One', 'CD')的列表
#     nouns = [word for word, pos in Rfiltered if pos.startswith('NN')]

    filter_word = [i[0] for i in Rfiltered if i[1].startswith('NN')]
#     print(filter_word)
    return " ".join(filter_word)

df['sep_review'] = df['review'].apply(lambda x:process_sentence(x))
df.to_csv('imdb_processed.csv')
df

image

use_df = df[:1000]
use_df.head(10)
sentences = list(use_df['sep_review'])
labels = list(use_df['sentiment'])

max_seq_len = max(use_df['sep_review'].apply(lambda x: len(x.split())))
PAD = ' <PAD>'  # 未知字,padding符号用来填充长短不一的句子(用啥符号都行,到时在nn.embedding的参数设为padding_idx=word_to_id['<PAD>'])即可

#小于最大长度的补齐
for i in range(len(sentences)):
    sen2list = sentences[i].split()
    sentence_len = len(sen2list)
    if sentence_len<max_seq_len:
        sentences[i] += PAD*(max_seq_len-sentence_len)
        
use_df['sentiment'] = use_df['sentiment'].apply(lambda x: 1 if x == 'positive' else 0)
labels = list(use_df['sentiment'])
use_df

image

import torch
num_classes = len(set(labels))  # num_classes=2
# 所有句子连接成一个长字符串,然后使用空格分割成单词列表,目的是收集数据集中的所有单词
word_list = " ".join(sentences).split()
#词汇表
vocab = list(set(word_list))
# 构建单词到索引的映射字典,遍历词汇表中的每个单词,将其与对应的索引建立映射关系
word2idx = {w: i for i, w in enumerate(vocab)}
vocab_size = len(vocab)

#label转换为标签0,1

# inputs,用于存储句子的索引表示。然后,它遍历每个句子 sen,将句子拆分成单词,并使用预先创建的 word2idx 字典将每个单词转换为其在词汇表中的索引。这样就得到了一个句子的索引表示,然后将其添加到 inputs 列表中

def make_data(sentences, labels):
    inputs = []
    for sen in sentences:
        inputs.append([word2idx[n] for n in sen.split()])
 
    targets = []
    for out in labels:
        targets.append(out) # To using Torch Softmax Loss function
    return inputs, targets

input_batch, target_batch = make_data(sentences, labels)
# input_batch
# 返回两个列表 inputs 和 targets,它们分别包含了句子的索引表示和相应的标签。这些列表被转换为 PyTorch 的张量类型 torch.LongTensor
input_batch = torch.LongTensor(input_batch)
input_batch.shape
#每个样本有 390 个特征
# input_batch 的形状中的 390 表示词汇表的大小,即样本中的每个文本被表示为一个长度为 390 的向量
target_batch = torch.LongTensor(target_batch)
target_batch.shape

TensorDataset 是 PyTorch 中的一个数据集类,用于将张量数据和对应的目标数据打包在一起。它通常用于创建数据加载器(DataLoader),方便地加载训练数据和目标数据,从而进行模型训练和评估。

TensorDataset 的主要作用是将输入数据和对应的目标数据封装成一个数据集对象,方便后续的处理。它接受一系列的张量作为参数,每个张量对应一个特征(输入数据)或一个目标(标签)。在训练过程中,可以通过数据加载器迭代访问 TensorDataset 中的数据,每次迭代返回一个批次的输入数据和对应的目标数据。

import numpy as np
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
import torch.nn.functional as F
from sklearn.model_selection import train_test_split
# 划分训练集,测试集
x_train,x_test,y_train,y_test = train_test_split(input_batch,target_batch,test_size=0.2,random_state = 0)

train_dataset = Data.TensorDataset(torch.tensor(x_train), torch.tensor(y_train))
test_dataset = Data.TensorDataset(torch.tensor(x_test), torch.tensor(y_test))
# dataset = Data.TensorDataset(input_batch, target_batch)

batch_size = 16
train_loader = Data.DataLoader(
    dataset=train_dataset,      # 数据,封装进Data.TensorDataset()类的数据
    batch_size=batch_size,      # 每块的大小
    shuffle=True,               # 要不要打乱数据 (打乱比较好)
    num_workers=2,              # 多进程(multiprocess)来读数据
)
test_loader = Data.DataLoader(
    dataset=test_dataset,      # 数据,封装进Data.TensorDataset()类的数据
    batch_size=batch_size,      # 每块的大小
    shuffle=True,               # 要不要打乱数据 (打乱比较好)
    num_workers=2,              # 多进程(multiprocess)来读数据
)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device,' available')
class RNN(nn.Module):
    def __init__(self,vocab_size, embedding_dim, hidden_size, num_classes, num_layers,bidirectional):
        super(RNN, self).__init__()
        self.vocab_size = vocab_size
        self.embedding_dim = embedding_dim
        self.hidden_size = hidden_size
        self.num_classes = num_classes
        self.num_layers = num_layers
        self.bidirectional = bidirectional
#         self.vocab_size:词汇表的大小,即词汇表中不同单词的数量。
# embedding_dim:词嵌入的维度,即嵌入层的输出维度。
# padding_idx:指定用于填充的特殊单词的索引,这里指定为 <PAD> 单词的索引。在训练过程中,如果输入的序列长度不足,会用 <PAD> 进行填充,以保证输入序列的长度一致。
        self.embedding = nn.Embedding(self.vocab_size, embedding_dim, padding_idx=word2idx['<PAD>'])
        self.rnn = nn.RNN(input_size=self.embedding_dim, hidden_size=self.hidden_size,batch_first=True,num_layers=self.num_layers,bidirectional=self.bidirectional)
        if self.bidirectional:
            self.fc = nn.Linear(hidden_size*2, num_classes)
        else:
            self.fc = nn.Linear(hidden_size, num_classes)
        
    def forward(self, x):
        batch_size, seq_len = x.shape
        #初始化一个h0,也即c0,在RNN中一个Cell输出的ht和Ct是相同的,而LSTM的一个cell输出的ht和Ct是不同的
        #维度[layers, batch, hidden_len]
        if self.bidirectional:
            h0 = torch.randn(self.num_layers*2, batch_size, self.hidden_size).to(device)
        else:
            h0 = torch.randn(self.num_layers, batch_size, self.hidden_size).to(device)
        x = self.embedding(x)
        out,_ = self.rnn(x, h0)
        output = self.fc(out[:,-1,:]).squeeze(0) #因为有max_seq_len个时态,所以取最后一个时态即-1层
        return output   
# out[:, -1, :]:表示取 out 张量的最后一个时间步的输出。在 PyTorch 中,张量的第一个维度是 batch size,第二个维度是序列长度,第三个维度是隐藏状态的维度。因此,out[:, -1, :] 表示取所有样本的最后一个时间步的隐藏状态。
# self.fc(out[:, -1, :]):将最后一个时间步的隐藏状态通过全连接层 self.fc 进行分类预测。这一步将隐藏状态映射到分类空间,得到模型的输出。
# .squeeze(0):由于 out[:, -1, :] 的结果是形状为 (batch_size, hidden_size) 的张量,而全连接层需要的输入是形状为 (batch_size, ..., ...) 的张量,因此需要对第一个维度进行压缩,即去掉大小为 1 的维度,使得输出形状为 (batch_size, ...)。
model = RNN(vocab_size=vocab_size,embedding_dim=300,hidden_size=20,num_classes=2,num_layers=2,bidirectional=True).to(device)
criterion = nn.CrossEntropyLoss().to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
#  PyTorch 中,可以直接使用 torch.nn.CrossEntropyLoss() 来计算交叉熵损失。此函数内部会自动进行 softmax 计算,并且可以处理多分类问题,无需手动进行 softmax 转换

model.train()
for epoch in range(10):
    total_loss = 0.0  # 初始化总损失
    for batch_x, batch_y in train_loader:
        batch_x, batch_y = batch_x.to(device), batch_y.to(device)
        pred = model(batch_x)
        loss = criterion(pred, batch_y)  # batch_y 类标签就好,不用 one-hot 形式   
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        total_loss += loss.item()  # 累加每个 batch 的损失
        
    # 打印平均损失
    avg_loss = total_loss / len(train_loader)
    print('Epoch:', '%04d' % (epoch + 1), 'Avg Loss =', '{:.6f}'.format(avg_loss))

image

test_acc_list = []
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
    for data, target in test_loader:
        data, target = data.to(device), target.to(device)
        output = model(data)
        pred = output.max(1, keepdim=True)[1]                           # 找到概率最大的下标
        correct += pred.eq(target.view_as(pred)).sum().item()

# test_loss /= len(test_loader.dataset)
# test_loss_list.append(test_loss)
test_acc_list.append(100. * correct / len(test_loader.dataset))
print('Accuracy: {}/{} ({:.0f}%)\n'.format(correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))

RNN的缺陷

Consider trying to predict the last word in the text “I grew up in France… I speak fluent French.” Recent information suggests that the next word is probably the name of a language, but if we want to narrow down which language, we need the context of France, from further back. It’s entirely possible for the gap between the relevant information and the point where it is needed to become very large.

Unfortunately, as that gap grows, RNNs become unable to learn to connect the information.

RNN梯度消失

例:

如果句子后期的单词对前期的单词有依赖(long-term),RNN不擅长捕获这种依赖(由于深度网络的梯度消失问题,反向传播困难,后期误差难以影响前期计算)

如果出现梯度爆炸则使用梯度修剪

image

RNNLM,BPTT:梯度将从正确解标签 Tom 出现的地方向过去的方向传播

通过将这些信息向过去传 递,RNN 层学习长期的依赖关系。但是,如果这个梯度在中途变弱(甚至 没有包含任何信息),则权重参数将不会被更新

原因

RNNLM的计算图中“+”的反向传播将上游传来的梯度原样传给下游

x是神经网络中的某一层的输入,它是由上一层的输出和权重矩阵相乘得到的。也就是说,x = Wf,其中 W 是权重矩阵,f 是上一层的激活函数。如果上一层也是 tanh 函数,那么 f 的值的范围是 (-1, 1)。如果 W 的元素的绝对值都大于 1,那么 x 的值就会越来越大,远离 0。这种情况下,tanh 函数的导数的值就会越来越小,接近于 0。

那么,为什么 W 的元素的绝对值会大于 1 呢?这就涉及到神经网络的初始化和更新的问题。如果我们随机初始化 W 的元素,那么有可能出现一些很大或很小的值。如果我们使用梯度下降法来更新 W 的元素,那么有可能出现梯度爆炸或梯度消失的问题。梯度爆炸是指梯度的值变得非常大,导致 W 的元素的更新幅度过大,使得 W 的元素的绝对值越来越大。梯度消失是指梯度的值变得非常小,导致 W 的元素的更新幅度过小,使得 W 的元素的绝对值越来越小。这两种问题都会影响神经网络的收敛和性能。

改进:使用ReLU函数

当 x 大于 0 时,反向 传播将上游的梯度原样传递到下游,梯度不会“退化”

论文:Improving performance of recurrent neural network with relu nonlinearity

如果奇异值的最大值大于 1,则可以预测梯度很有可能会呈指数 级增加;而如果奇异值的最大值小于 1,则可以判断梯度会呈指 数级减小。但是,并不是说奇异值比 1 大就一定会出现梯度爆炸。 也就是说,这是必要条件,并非充分条件

image

#测试梯度爆炸/消失
import numpy as np
import matplotlib.pyplot as plt
N = 2 # mini-batch的大小
H = 3 # 隐藏状态向量的维数
T = 20 # 时序数据的长度
dh = np.ones((N, H))
np.random.seed(3) # 为了复现,固定随机数种子
Wh = np.random.randn(H, H)
norm_list = []
for t in range(T):
    dh = np.dot(dh, Wh.T)
    norm = np.sqrt(np.sum(dh**2)) / N
    norm_list.append(norm)
print(norm_list)
plt.plot(norm_list)

image

  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IMDb是一个全球知名的电影资料库网站,其中包含了大量的电影资讯和用户评论。而RNN是一种神经网络模型,可以用于序列数据的处理与分类PyTorch则是一个深度学习库,提供了对神经网络的丰富支持。因此,IMDb RNN分类 PyTorch指的是使用PyTorch框架对IMDb数据集进行情感分类任务。 IMDb数据集包含了50,000条来自影评网站IMDb的评论数据,其中25,000条作为训练集,25,000条作为测试集。每条评论标记为正面或负面两类。 在使用PyTorch框架进行情感分类任务时,我们通常需要对数据进行以下几个处理步骤: 1. 数据预处理:包括对原始文本进行分词、去除停用词、生成词表等操作。 2. 数据编码:将预处理后的文本数据转换为数字化的向量,便于神经网络处理。 3. 模型设计:选择RNN网络结构,并根据数据特点进行双向LSTM、dropout等技巧的应用,构建一个有效的情感分类模型。 4. 模型训练:利用优化算法对模型进行训练,并监控训练过程中的精度、损失等指标,不断调整超参数,达到最佳效果。 5. 模型评估:在测试集上对模型进行评估,并计算出准确率、召回率、F1值等指标,评估模型性能。 在使用PyTorch进行IMDb RNN分类时,需要深入理解神经网络原理,熟悉PyTorch框架的使用方法,具备较好的编程能力,还需要对自然语言处理有一定了解和实践经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值