当前位置:   article > 正文

【pytorch】双向LSTM实现文本情感分析_pytorch双向lstm

pytorch双向lstm

整个个专栏记录一下深度学习的相关文章
(一)认识深度学习

(二)机器学习应用策略
(三)卷积神经网络
(四)序列模型
(五)pytorch从零实现手写数字识别
(六)认识NLP,RNN实现文本情感分析
(六)经典试题整合

循环神经网络

tokenization:分词 token:具体的词

常见工具

  • jieba
  • THULAC(不推荐)

分词方法:

  • 句子转为词语
  • 句子转换为单个字

N-gram

准备词语特征的方法,N代表能够一起使用的词的数量

分词长度:len(cuted) -1 、获取分词(以2为例)cuted[i:i+2]

举例程序:

text = input()
cuted = jieba.lcut(text)
cutlist = [cuted[i:i+2] for i in range(len(cuted)-1)]
  • 1
  • 2
  • 3

比传统优秀的原因:词语有了一定的顺序

向量化

文本转化为项链

  • one-hot编码:使用稀疏向量将词语独一编号,缺点是浪费空间
  • word embedding

word-embedding(词嵌入)

应用了浮点型稠密矩阵,根据词典决定向量的维度,如100、256、300等。向量的每一个值都是一个超参数,初始值是随机生成,之后会在训练的过程中进行学习。(这里的数值化一般是根据具体的词语的相关性确定,吴恩达的课没有说明具体获取的方法,这里表明了所有的值都是训练获得,并不是人工标记哈)

将长度优化 NxN(ont-hot) -> Nxn(n为选择的向量维度)

需要先把词语数值化:维度变化:[batch_sizer(有多少个句子),N(句子中词语的长度)] -> [batch_size,N,300(300代表的是超参数)]

具体形状变化:经历embedding

[batch_size,10] -[20,4]--> [batch_size,10,4]

调用torch中的API:

embedding = nn.Embedding(vocab_size,300) # 词典的大小,自定义单词超参数长度
input_embeded = embedding(input) # embedding操作(初始化)
  • 1
  • 2

情感分析

  1. 数据集
  2. 模型
  3. 训练
  4. 评估

1.数据集

需要解决问题:

  • 句子长度不一致
  • 文本如何转换为数字序列

2.准备数据

观察数据内容,决定切分单词方法,去除符号的方法(参考)

def tockenize(text):
  filters = ['!','"','#','$','%','&','\(','\)','\*','\+',',','-','\.','/',':',
            ';','<','=','>','\?','@','\[','\]','\\','^','_','`','{','}','\|',
            '~','\t','\n','\x97','\x96','”','“',]
  text = re.sub("<.*?>"," ",text,flags=re.S)
  text = re.sub("|".join(filters)," ",text,flag=re.S)
  return [i.strip() for i in text.split()]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.文本序列化

将每个词语和对应的数字用字典保存,把句子通过字典映射为包含数字的列表

  • 如何使用字典把词语和数字进行对应:遍历
  • 考虑是否过滤高频词和低频词
  • 如何把数字序列转换为句子,句子转换为数字序列
  • 句子长度不同,batch的句子长度也不一样:所以将长句进行裁剪,短句进行填充
  • 词典中没有的词语怎么办:使用<unk>填充

初始化类:

class Word2Seq:
    UNK_TAG = "UNK"
    PAD_TAG = "PAD"

    UNK = 0
    PAD = 1

    def __init__(self):
      # 词典构建
      # 特殊符合和填充符号
      self.dict = {
        self.UNK_TAG:self.UNK,
        self.PAD_TAG:self.PAD
      }

      self.count = {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

具体过程:

  1. 对所有句子分词(在加载数据集的同时操作,这里就不演示了)
  2. 词语存入字典
def fit(self,sentence):
    """单个句子保存到字典
          :param sentence : [word1,word2,...]
          """
    for word in sentence:
      self.count[word] = self.count.get(word, 0) + 1 # 在+1,不在为1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 根据次数对词语进行过滤并统计次数,构建字典并实现文本转换为数字序列的方法
# 2 词语存入字典,根据次数对词语进行过滤
def build_vocab(self,min=5,max=None,max_features=None):
  """生成词典
        :param min:最小词频
        :param max_features:一共保存多少词语
        """
  # 删除小词频
	if min is not None:
    self.count = {word:value for word,value in self.count.items() if value>min}
  if max is not None:
    self.count = {word:value for word,value in self.count.items() if value<min}
		# 限制保留的词语数
    if max_features is not None:
      # 先对字典内容排序以后选择前
      temp = sorted(self.count.items(),key=lambda x:x[-1],reverse=True)[:max_features]
    # 转换为字典
    self.count = dict(temp)
    for word in self.count:
      # 将字典当前的长度作为单词的代表,是不会重复的
      self.dict[word] = len(self.dict) 

      #得到一个反转的字典
      self.inverse_dict = dict(zip(self.dict.values(),self.dict.keys()))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. 实现数字序列转文本的方法
#序列转文本
def inverse_transform(self,indices):
    """
          将数字序列转换为句子
          :param indices:
          :return:
          """
    return [self.inverse_dict.get(idx) for idx in indices]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.构建模型

使用词嵌入模型,直接通过全连接层,使用带权损失

  1. 准备数据
  2. 构建模型
  3. 模型训练
  4. 模型评估

常见模型

RNN:

总之一句话:循环神经网络的最大的特点就是,神经元的输出可以在下一个时间步直接作用到自身(作为输入),这就使得这种模型具有短期记忆的能力。

LSTM:

解决长期依赖

  • 记忆细胞:通过sigmoid函数和t-1,点乘实现记忆

  • 遗忘门:通过sigmoid函数决定哪些信息需要被遗忘

  • 输入门:决定哪些信息会被更新

    • tanh:决定输入什么信息
    • sigmoid:决定输入多少比例信息
  • 输出门:输出当前时刻的结果和隐藏的状态

  • 输入和输出

    • 输入:隐藏状态,X-t,C_t-1
    • 输出:H_t,H_t,C_t

双向:组合,既有正向的记忆也有反向的记忆

在这里插入图片描述

GRU:

将遗忘门和输入门组合为更新门(只有两个门)

输入:上一个隐藏状态

输出:当前隐藏状态,更新

API应用

LSTM

torch.nn.LSTM

默认输入:实例化后需要输入数据、前一次隐层状态和前一次记忆细胞,lstm(input,(h0,c0))

  • input_size输入的最后的一维的长度
  • hidden_size设置隐藏层单元数目,即每一层有多少个单元
  • num_layer设置多少层,为1时不会dropout
  • batch_first参数的输入顺序,默认为False即原输入顺序[seq_len,batch_size,feature],如果为True[batch_size,seq_len,feature]
  • dropout,0-1层随机失活的比例,在最后一层的每个输出
  • bidirectional选择双向

默认输出:output,(h_n,c_n)

具体API

batch_size = 10
seq_len = 20 # 句子长度
vocab_size = 100 # 词典的长度
embedding_dim = 30 # 长30的向量表示一个词语
hidden_size = 18 # 隐藏层单元数
num_layer = 2 # 隐藏层数

# 模拟输入
inputs = torch.randint(low=0,high=100,size=[batch_size,seq_len]) # [10,20]
# embedding处理
embedding = nn.Embedding(vocab_size,embedding_dim)
input_embedded = embedding(inputs) # [10,20,30]

# embedding之后的数据传入lstm
lstm = nn.LSTM(input_size=embedding_dim,hidden_size=hidden_size,num_layers=num_layer,batch_first=True,bidirectional=False)
output,(h_n,c_n) = lstm(input_embedded)
print(output.size()) #[10,20,18*1 if bidirectional else 18*2]
print(h_n.size()) # [layer*2 if bidirectional else layer*1,10,18]
print(c_n.size()) # [layer*2 if bidirectional else layer*1,10,18]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以验证最后一次隐藏层状态和最后一个时间步上的输出相等

# 最后一个时间步上的输出
last_output = output[:,-1,:]
# 获取最后一次的隐藏层
last_hidden_state= h_n[-1,:,:]
print(last_output == last_hidden_state)
  • 1
  • 2
  • 3
  • 4
  • 5

损失函数

多分类,使用softmax进行输出,使用的时需要去计算样本属于每个类别的概率。

带权损失,权重乘以值之和的负
l o s s = − ∑ Y t r u e ∗ l o g ( P ) , P 是 s o f t m a x 的 输 出 l o s s = − ∑ w i x i loss = -\sum Y_{true} * log(P),P是softmax的输出\\ loss = -\sum w_i x_i loss=Ytruelog(P),Psoftmaxloss=wixi
关于双向的

output的拼接顺序:

  • 正向第一个 <–> 方向最后一个
  • 最后一个维度拼接
# 双向的LSTML中的最后一个时间步的output
# 正向
last_output = output[:,-1,:18]
# 反向
last_output = output[:,0,18:]

# 获取双向LSTM中最后一个隐藏层
# 正向
last_hidden_state = h_n[-2,:,:]
# 反向
last_hidden_state = h_n[-1,:,:]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 多层:
    • 第一层正向–第一层反向
    • 第二层正向–第二层反向

GRU

  • GRU(参数如同LSTML)

  • 输入: o u t p u t , h n = g r u ( i n p u t , h 0 ) output,h_n = gru(input,h_0) output,hn=gru(input,h0)

  • 形状同LSTM

遇到了import中的问题,发现在函数定义的默认值,是不能设置为引入变量的

实现的源码已上传,暂未整理到github

梯度消失和爆炸

当使用权重初始值过小或者使用易饱和神经元(sigmoid,tanh),比如sigmoid在y=0,1处梯度接近0,而无法更新参数,这时候神经网络在反向传播时也会呈指数倍缩小,产生消失

当初始权重过大时,神经网络在反向传播的时候也会出现呈指数倍放大,产生爆炸

sigmoid的导函数如图所示
在这里插入图片描述

解决方案:

  • 使用ReLU或者leaky ReLU,并不会出现导数的变化。
  • Adam优化器
  • 使用 batch normalization

Batch Normalization

正则化,每个batch训练过程中,对参数进行归一化处理,从而加快训练速度

以sigmoid为例,就会尽可能地将参数拉到[0,1]地范围,让参数地更新幅度更大

一般放在激活函数之后

Dropout

对参数地随机失活

  • 解决过拟合
  • 增加稳健性

序列化容器

input会被容器中的构造器类依次执行:

layer = nn.Sequential (
  nn.Linear(input_dim,n_hidden_1), # 形状变为[batch_size,n_hidden_1]
  nn.ReLU(True),
  nn.BatchNorm1d(n_hidden_1)
  nn.Dropout(0.3) # 默认值为0.5
  
  nn.Linear(n_hidden_1,n_hidden_2),  #			[batch_size,n_hidden_2]
  nn.ReLU(True),
  nn.BatchNorm1d(n_hidden_2)
  nn.DropOut(0.3)
  
  nn.Linear(n_hidden_2,output_dim) # 最后一层不需要激活函数[batch_size,output_dim]
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/273454
推荐阅读
相关标签
  

闽ICP备14008679号