Pytorch:循环神经网络与文本预处理

Pytorch: 循环神经网络与文本数据预处理

Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, School of Artificial and Intelligence, Huazhong University of Science and Technology

Pytorch教程专栏链接


文章目录

      • Pytorch: 循环神经网络与文本数据预处理
    • @[toc]
        • Reference
        • RNN
        • LSTM
        • GRU
        • 应用方式
        • 文本数据预处理
          • 文本预处理与探索
          • 可视化分析

本教程不商用,仅供学习和参考交流使用,如需转载,请联系本人。

Reference

LSTM 论文链接

GRU 论文链接

博客参考1

博客参考2

循环神经网络的基本思想是,网络中不同时间的输入之间会存在顺序关系,每个输入和它之前或者之后的输入存在关联,希望通过循环神经网络在时序上找到样本之间的序列相关性。

最常见的循环神经网络有 RNN、LSTM(长短期记忆) 和 GRU(循环门控单元)等,GRU 可以看成是 LSTM 的简化版本。

RNN

RNN可用torch.nn.RNN() 来创建。其基础链接结构,针对 t t t 时刻的隐状态 h t h_t ht ,可以由下面的公式计算:

h t = σ ( W i h x t + b i h + W h h h t − 1 + b h h ) h_t = \sigma(W_{ih}x_t+b_{ih}+W_{hh}h_{t-1}+b_{hh}) ht=σ(Wihxt+bih+Whhht1+bhh)

公式中:
h t h_t ht t t t 时刻的隐藏状态。

x t x_t xt t t t 时刻的输入。

h t − 1 h_{t-1} ht1 t − 1 t-1 t1 时刻的隐藏状态。

W i h W_{ih} Wih 是隐藏层到隐藏层的权重。

b i h b_{ih} bih 是输入到隐藏层的偏置。

b h h b_{hh} bhh 是隐藏层到隐藏层的偏置。

σ \sigma σ 表示激活函数。


虽然对序列数据建模时,它对信息有一定的记忆能力,但是单纯的RNN会随着递归次数的增加,出现权重指数级爆炸或消失的问题,从而难以捕捉长时间的关联,并且导致训练时收敛困难,而 LSTM 网络通过引入门的机制,使网络有更强的记忆能力。

LSTM

LSTM(Long Short-Term Mermory)网络即长短期记忆网络,能在更长的序列中获得更好的分析效果。

详细的 LSTM 讲解可以参考这篇博客:http://colah.github.io/posts/2015-08-Understanding-LSTMs/ 。

Pytorch:循环神经网络与文本预处理_第1张图片

i t = σ ( W i i x t + b i i + W h i h t − 1 + b h i ) f t = σ ( W i f x t + b i f + W h f h t − 1 + b h f ) g t = tanh ⁡ ( W i g x t + b i g + W h g h t − 1 + b h g ) o t = σ ( W i o x t + b i o + W h o h t − 1 + b h o ) c t = f t × c t − 1 + i t × g t h t = o t × tanh ⁡ ( c t ) \begin{aligned} i_t &= \sigma(W_{ii}x_t+b_{ii}+W_{hi}h_{t-1}+b_{hi})\\ f_t &= \sigma(W_{if}x_t+b_{if}+W_{hf}h_{t-1}+b_{hf})\\ g_t &= \tanh(W_{ig}x_t+b_{ig}+W_{hg}h_{t-1}+b_{hg})\\ o_t &= \sigma(W_{io}x_t+b_{io}+W_{ho}h_{t-1}+b_{ho})\\ c_t &= f_t\times c_{t-1}+i_t\times g_t\\ h_t &= o_t\times\tanh(c_t) \end{aligned} itftgtotctht=σ(Wiixt+bii+Whiht1+bhi)=σ(Wifxt+bif+Whfht1+bhf)=tanh(Wigxt+big+Whght1+bhg)=σ(Wioxt+bio+Whoht1+bho)=ft×ct1+it×gt=ot×tanh(ct)

公式中:

i t , f t , g t , o t i_t,f_t,g_t,o_t it,ft,gt,ot 分别是输入门、遗忘门、选择门和输出门。

c t c_t ct t t t 时刻的元组状态。

h t h_t ht t t t 时刻的隐藏状态。

x t x_t xt t t t 时刻的输入。

h t − 1 h_{t-1} ht1 t − 1 t-1 t1 时刻的隐藏状态,初始时刻的隐藏状态为 0 0 0

W i h W_{ih} Wih 是隐藏层到隐藏层的权重。

b i h b_{ih} bih 是输入到隐藏层的偏置。

b h h b_{hh} bhh 是隐藏层到隐藏层的偏置。

σ \sigma σ 表示激活函数。


在每个单元的传递过程中,通常 c t c_t ct 是上一个状态传过来的 c t − 1 c_{t-1} ct1 在加上一些数值,其改变的速度较慢,而 h t h_t ht 的取值变化则较大,不同结点往往有很大区别。

LSTM 的信息处理分为三个阶段:

  1. 遗忘阶段。对上一个结点传进来的输入进行选择性忘记,“忘记不重要的,记住重要的”。即通过 f t f_t ft 的值来控制上一状态 c t − 1 c_{t-1} ct1 中哪些需要记住,哪些需要遗忘。

  2. 选择记忆阶段。将输入 X t X_t Xt 有选择性地进行"记忆"。哪些重要则着重记录。当前单元的输入内容是计算得到的 i t i_t it ,可以通过 g t g_t gt 对其进行有选择地输出。

  3. 输出阶段。决定哪些会被当成当前状态的输出。主要通过 o i o_i oi 进行控制,并且要对 c t c_t ct 使用 tanh ⁡ \tanh tanh 激活函数进行缩放。

LSTM 网络输出 y t y_t yt 通常可以通过 h t h_t ht 变化得到。

GRU

不像普通的 RNN 那样只能够一种记忆叠加,LSTM 通过门控状态来控制传输状态,记住需要长时间记忆的,忘记不重要的。对于需要"长期记忆"的任务来说效果显著,但是也因为多个门控状态的引入,导致需要训练更多的参数,使得难度大大增加。

Pytorch:循环神经网络与文本预处理_第2张图片

针对这种情况,GRU(Gate Recurrent Unit) 网络即循环门控单元网络被踢出,将遗忘门和输入门组合在一起,减少了们的数量。且通过其他的一些改变,在保证记忆能力的同时,提升了网络的训练效率。

r t = σ ( W i r x t + b i r + W h r h t − 1 + b h r ) z t = σ ( W i z x t + b i z + W h z h t − 1 + b h z ) n t = tanh ⁡ ( W i n x t + b i n + r t × ( W h n h t − 1 + b h n ) ) h t = ( 1 − z t ) × n t + z t × h t − 1 \begin{aligned} r_t &= \sigma(W_{ir}x_t+b_{ir}+W_{hr}h_{t-1}+b_{hr})\\ z_t &= \sigma(W_{iz}x_t+b_{iz}+W_{hz}h_{t-1}+b_{hz})\\ n_t &= \tanh(W_{in}x_t+b_{in}+r_t\times(W_{hn}h_{t-1}+b_{hn}))\\ h_t &= (1-z_t)\times n_t + z_t\times h_{t-1} \end{aligned} rtztntht=σ(Wirxt+bir+Whrht1+bhr)=σ(Wizxt+biz+Whzht1+bhz)=tanh(Winxt+bin+rt×(Whnht1+bhn))=(1zt)×nt+zt×ht1

公式中:

r t , z t . n t r_t, z_t. n_t rt,zt.nt 分别是重置门、更新门和计算候选隐藏层。

h t h_t ht t t t 时刻的隐藏状态。

x t x_t xt t t t 时刻的输入。

h t − 1 h_{t-1} ht1 t − 1 t-1 t1 时刻的隐藏状态,初始时刻的隐藏状态为 0 0 0

W i h W_{ih} Wih 是隐藏层到隐藏层的权重。

b i h b_{ih} bih 是输入到隐藏层的偏置。

b h h b_{hh} bhh 是隐藏层到隐藏层的偏置。

σ \sigma σ 表示激活函数。


在每个单元的传递过程中, r t r_t rt 用来控制需要保留之前的记忆。如果 r t r_t rt 0 0 0 ,则 n t = tanh ⁡ ( W i n x t + b i n ) n_t=\tanh(W_{in}x_t+b_{in}) nt=tanh(Winxt+bin) 只包含当前输入状态的信息,而 z t z_t zt 则控制前一时刻的隐藏层忘记的信息量。

应用方式

根据循环单元的输入和输出数量之间的对应关系,可以划分为多种应用方式。

一对多的网络结构可以用于图像描述,即根据输入的一张图像,自动使用文章描述图像内容。

多对一的网络结构可以用于文本分类,即根据一段描述文字,自动对文本内容归类。

多对多的网络将诶狗可用于语言翻译,即针对输入的一种语言,自动翻译为另一种语言。

文本数据预处理

我们选取影评数据进行情感分类,数据来自:http://ai.stanford.edu/~amaas/data/sentiment/ 。

即 IMDB 的电影评论数据,共 5 5 5 万条数据, 25000 25000 25000 条是训练数据, 25000 25000 25000 条是测试数据。

训练数据和测试数据分别在不同文件夹中,且分别包含 pos 和 neg 两种评论。

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
import os
import re
import string
import seaborn as sns 
from wordcloud import WordCloud
import time
import copy

import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import PorterStemmer

import torch
from torch import nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import transforms
from torchtext import data
from torchtext.vocab import Vectors, GloVe
# 模型加载选择GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
print(torch.cuda.device_count())
print(torch.cuda.get_device_name(0))
cuda
1
GeForce MX250
文本预处理与探索

为了更方便在网络中使用数据,并且在预处理阶段会通过去除停顿词等操作,进一步保留文本中的有用信息,排除干扰。

# 定义读取训练数据和测试数据的函数
def load_text_data(path):
    # 获取文件夹的最后一个字段
    text_data = []
    label = []
    for dset in ['pos', 'neg']:
        path_dset = os.path.join(path, dset)
        path_list = os.listdir(path_dset)
        # 读取文件夹下的pos或neg文件
        for fname in path_list:
            if fname.endswith('.txt'):
                filename = os.path.join(path_dset, fname)
                with open(filename, 'r', encoding = 'UTF-8') as f:
                    text_data.append(f.read())
            if dset == 'pos':
                label.append(1)
            else:
                label.append(0)
    # 输出读取的文本和对应的标签
    return np.array(text_data), np.array(label)
# 读取训练集和测试集
train_path = './data/aclImdb/train'
train_text, train_label = load_text_data(train_path)
test_path = './data/aclImdb/test'
test_text, test_label = load_text_data(test_path)
print(len(train_text), len(train_label))
print(len(test_text), len(test_label))
25000 25000
25000 25000

预处理的第一步是将所有的字母转为小写,去除数字,去除标点符号,去除多余的空格

# 对文本数据预处理
def text_preprocess(text_data):
    text_pre = []
    for text1 in text_data:
        # 去除指定字符
        text1 = re.sub('

'
, '', text1) # 转为小写,去除数字,去除标点符号,去除空格 text1 = text1.lower() # 转为小写 text1 = re.sub('\d+', '', text1) # 去除数字 text1 = text1.translate( str.maketrans('', '', string.punctuation.replace("'", "")) ) # 删除标点 text1 = text1.strip() # 去除多余空格 text_pre.append(text1) return np.array(text_pre)
train_text_pre = text_preprocess(train_text)
test_text_pre = text_preprocess(test_text)

第二步是去除停顿词:

# 文本符号化处理,去除停用词
def stop_stem_word(datalist, stop_words):
    datalist_pre = []
    for text in datalist:
        text_words = word_tokenize(text)
        # 去除停用词
        text_words = [word for word in text_words if not word in stop_words]
        # 删除带 "'" 的词语,如it's
        text_words = [word for word in text_words if len(re.findall("'", word)) == 0]
        datalist_pre.append(text_words)
    return np.array(datalist_pre)
# 载入标准停顿词,并去除
stop_words = stopwords.words('english')
stop_words = set(stop_words)
train_text_pre2 = stop_stem_word(train_text_pre, stop_words)
test_text_pre2 = stop_stem_word(test_text_pre, stop_words)

# 检查去除效果
print(train_text_pre[10000])
print('=' * 10)
print(train_text_pre2[10000])
:11: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  return np.array(datalist_pre)
i really liked tom barman's awtwb you just have to let it come over you and enjoy it while it lasts and don't expect anything it's like sitting on a caféterrace with a beer in the summer sun and watching the people go by it definitely won't keep you pondering afterwards that's true but that's not a prerequisite for a good film it's just the experience during the movie that's greati felt there were a few strands that could have been worked out a little more but being a lynch fan i don't care that much anymore and i loved the style or flair of this movie it's slick but fresh and the soundtrack is a beauty any musiclover will get his kicks out of awtwb i can assure youi'll give it  out musicwise  out of
==========
['really', 'liked', 'tom', 'barman', 'awtwb', 'let', 'come', 'enjoy', 'lasts', 'expect', 'anything', 'like', 'sitting', 'caféterrace', 'beer', 'summer', 'sun', 'watching', 'people', 'go', 'definitely', 'wo', 'keep', 'pondering', 'afterwards', 'true', 'prerequisite', 'good', 'film', 'experience', 'movie', 'greati', 'felt', 'strands', 'could', 'worked', 'little', 'lynch', 'fan', 'care', 'much', 'anymore', 'loved', 'style', 'flair', 'movie', 'slick', 'fresh', 'soundtrack', 'beauty', 'musiclover', 'get', 'kicks', 'awtwb', 'assure', 'youi', 'give', 'musicwise']

将预处理后的文本转为数据表格并保存到本地,便于神经网络的数据使用

# 将处理好的文本保存到csv文件中
texts = [' '.join(words) for words in train_text_pre2]
traindatasave = pd.DataFrame({'text': texts, 'label': train_label})
texts = [' '.join(words) for words in test_text_pre2]
testdatasave = pd.DataFrame({'text': texts, 'label': test_label})
traindatasave.to_csv('./data/aclImdb/imdb_train.csv', index = False)
testdatasave.to_csv('./data/aclImdb/imdb_test.csv', index = False)

上面程序将切分好的文本保存为数据表中的text变量,词语之间使用空格连接,文本对应的情感标签保存为label变量

可视化分析

下面计算出每个影评使用的词语数量,并使用直方图可视化分布情况

# 将与处理好的文本数据转为数据表
traindata = pd.DataFrame({'train_text': train_text, 
                          'train_word': train_text_pre2, 
                          'train_label': train_label})
# 计算每个影评使用词的数量
train_word_num = [len(text) for text in train_text_pre2]
traindata['train_word_num'] = train_word_num
# 可视化影评词语长度的分布
plt.figure(figsize = (8, 5))
_ = plt.hist(train_word_num, bins = 100)
plt.xlabel('Word Number')
plt.ylabel('Freq')
plt.show()


Pytorch:循环神经网络与文本预处理_第3张图片

可见大部分评论的用词数量小于 400 400 400 个词语。

下面对训练集的正向和负向评论,使用词云可视化用词差异:

# 词云可视化两种情感的词频差异
plt.figure(figsize = (16, 10))
for ii in np.unique(train_label):
    # 准备每种情感的所有词语
    text = np.array(traindata.train_word[traindata.train_label == ii])
    text = ' '.join(np.concatenate(text))
    plt.subplot(1, 2, ii + 1)
    # 生成词云
    wordcod = WordCloud(margin = 5, width = 1800, height = 1000, max_words = 500, min_font_size = 5, background_color = 'white', max_font_size = 250)
    wordcod.generate_from_text(text) # 可视化
    plt.imshow(wordcod)
    plt.axis('off')
    if ii == 1:
        plt.title('Positive')
    else:
        plt.title('Negative')
    plt.subplots_adjust(wspace = 0.05)
plt.show()


Pytorch:循环神经网络与文本预处理_第4张图片

你可能感兴趣的:(PyTorch,rnn,pytorch,lstm,深度学习,循环神经网络)