【Pytorch】nn.LSTM的理解

class torch.nn.LSTM(*args, **kwargs)

Pytorch中nn.LSTM的参数列表

Pytorch中LSTM总共有7个参数,前面3个是必须输入的

  • input_size – The number of expected features in the input x
  • hidden_size – The number of features in the hidden state h
  • num_layers – Number of recurrent layers. E.g., setting num_layers=2 would mean stacking two LSTMs together to form a stacked LSTM, with the second LSTM taking in outputs of the first LSTM and computing the final results. Default: 1
  • bias – If False, then the layer does not use bias weights b_ih and b_hh. Default: True
  • batch_first – If True, then the input and output tensors are provided as (batch, seq, feature). Default: False
  • dropout – If non-zero, introduces a Dropout layer on the outputs of each LSTM layer except the last layer, with dropout probability equal to dropout. Default: 0
  • bidirectional – If True, becomes a bidirectional LSTM. Default: False

1. input_size: 输入特征维数,即每一行输入元素的个数。输入是一维向量。如:[1,2,3,4,5,6,7,8,9],input_size 就是9

2. hidden_size: 隐藏层状态的维数,即隐藏层节点的个数,这个和单层感知器的结构是类似的。这个维数值是自定义的,和输入的维度没有关系,如下图:

input_size:就是输入层,左边蓝色方格 [i0,i1,i2,i3,i4],hidden_size:就是隐藏层,中间黄色圆圈 [h0,h1,h2,h3,h4]。最右边蓝色圆圈 [o0,o1,o2] 的是输出层,节点个数也是按具体业务需求决定的。

3. num_layers: LSTM 堆叠的层数,默认值是1层,如果设置为2,第二个LSTM接收第一个LSTM的计算结果。也就是第一层输入 [ X0 X1 X2 ... Xt],计算出 [ h0 h1 h2 ... ht ],第二层将 [ h0 h1 h2 ... ht ] 作为 [ X0 X1 X2 ... Xt] 输入再次计算,输出最后的 [ h0 h1 h2 ... ht ]。

4. bias: 隐层状态是否带bias,默认为true。bias是偏置值,或者偏移值。没有偏置值就是以0为中轴,或以0为起点。偏置值的作用请参考单层感知器相关结构。

5. batch_first: 输入输出的第一维是否为 batch_size,默认值 False。因为 Torch 中,人们习惯使用Torch中带有的dataset,dataloader向神经网络模型连续输入数据,这里面就有一个 batch_size 的参数,表示一次输入多少个数据。 在 LSTM 模型中,输入数据必须是一批数据,为了区分LSTM中的批量数据和dataloader中的批量数据是否相同意义,LSTM 模型就通过这个参数的设定来区分。 如果是相同意义的,就设置为True,如果不同意义的,设置为False。 torch.LSTM 中 batch_size 维度默认是放在第二维度,故此参数设置可以将 batch_size 放在第一维度。如:input 默认是(4,1,5),中间的 1 是 batch_size,指定batch_first=True后就是(1,4,5)。所以,如果你的输入数据是二维数据的话,就应该将 batch_first 设置为True;

6. dropout: 默认值0。是否在除最后一个 RNN 层外的其他 RNN 层后面加 dropout 层。输入值是 0-1 之间的小数,表示概率。

7. bidirectional: 是否是双向 RNN,默认为:false,若为 true,则:num_directions=2,否则为1。

Pytorch中nn.LSTM的输入输出格式

输入数据格式:
input(seq_len, batch, input_size)
h0(num_layers * num_directions, batch, hidden_size)
c0(num_layers * num_directions, batch, hidden_size)
 
输出数据格式:
output(seq_len, batch, hidden_size * num_directions)
hn(num_layers * num_directions, batch, hidden_size)
cn(num_layers * num_directions, batch, hidden_size)

举个栗子

1、在nlp中,假设有3个句子,每个句子5个单词,每个单词用10维表示,那么对应的维度应该是(5,3,10)

2、在时序预测中,假设有128个时间数据(行),每个时间点有10个特征,那么此时可以理解为seq_len为1,对应的维度就是(1,128,10)

Pytorch里的LSTM单元接受的输入都必须是3维的张量(Tensors).每一维代表的意思不能弄错。

第一维体现的是序列(sequence)结构,也就是序列的个数,用文章来说,就是每个句子的长度,因为是喂给网络模型,一般都设定为确定的长度,也就是我们喂给LSTM神经元的每个句子的长度,当然,如果是其他的带有带有序列形式的数据,则表示一个明确分割单位长度,

例如是如果是股票数据内,这表示特定时间单位内,有多少条数据。这个参数也就是明确这个层中有多少个确定的单元来处理输入的数据。

第二维度体现的是batch_size,也就是一次性喂给网络多少条句子,或者股票数据中的,一次性喂给模型多少是个时间单位的数据,具体到每个时刻,也就是一次性喂给特定时刻处理的单元的单词数或者该时刻应该喂给的股票数据的条数

第三位体现的是输入的元素(elements of input),也就是,每个具体的单词用多少维向量来表示,或者股票数据中 每一个具体的时刻的采集多少具体的值,比如最低价,最高价,均价,5日均价,10均价,等等

H0-Hn是什么意思呢?就是每个时刻中间神经元应该保存的这一时刻的根据输入和上一课的时候的中间状态值应该产生的本时刻的状态值,

这个数据单元是起的作用就是记录这一时刻之前考虑到所有之前输入的状态值,形状应该是和特定时刻的输出一致

c0-cn就是开关,决定每个神经元的隐藏状态值是否会影响的下一时刻的神经元的处理,形状应该和h0-hn一致。

当然如果是双向,和多隐藏层还应该考虑方向和隐藏层的层数。

注:上式中的 q后面跟一个单词,表示该单词的一定维度的向量表示,该维度即是LSTM接受的张量中的第三个维度。

记住这里存在一个尺寸为1的第二维度。此外,如果你希望一次在网络中走完整个序列,你可以将第一个维度的尺寸也设为1。

下面我简单看一下例子:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

lstm = nn.LSTM(3, 3)  # 输入单词用一个维度为3的向量表示, 隐藏层的一个维度3,仅有一层的神经元,
#记住就是神经元,这个时候神经层的详细结构还没确定,仅仅是说这个网络可以接受[seq_len,batch_size,3]的数据输入
print(lstm.all_weights)

inputs = [torch.randn(1, 3) for _ in range(5)]
 # 构造一个由5个单单词组成的句子 构造出来的形状是 [5,1,3]也就是明确告诉网络结构我一个句子由5个单词组成,
#每个单词由一个1X3的向量组成,就是这个样子[1,2,3] 
#同时确定了网络结构,每个批次只输入一个句子,其中第二维的batch_size很容易迷惑人
#对整个这层来说,是一个批次输入多少个句子,具体但每个神经元,就是一次性喂给神经元多少个单词。
print('Inputs:',inputs)

# 初始化隐藏状态
hidden = (torch.randn(1, 1, 3),
          torch.randn(1, 1, 3))
print('Hidden:',hidden)
for i in inputs:
 # 将序列的元素逐个输入到LSTM,这里的View是把输入放到第三维,看起来有点古怪,
#回头看看上面的关于LSTM输入的描述,这是固定的格式,以后无论你什么形式的数据,
#都必须放到这个维度。就是在原Tensor的基础之上增加一个序列维和MiniBatch维,
#这里可能还会有迷惑,前面的1是什么意思啊,就是一次把这个输入处理完,
#在输入的过程中不会输出中间结果,这里注意输入的数据的形状一定要和LSTM定义的输入形状一致。
    # 经过每步操作,hidden 的值包含了隐藏状态的信息
 out, hidden = lstm(i.view(1, 1, -1), hidden)
print('out1:',out)
print('hidden2:',hidden)
# 另外, 我们还可以一次对整个序列进行训练. LSTM 返回的第一个值表示所有时刻的隐状态值,
# 第二个值表示最近的隐状态值 (因此下面的 "out"的最后一个值和 "hidden" 的值是一样的).
# 之所以这样设计, 是为了通过 "out" 的值来获取所有的隐状态值, 而用 "hidden" 的值来
# 进行序列的反向传播运算, 具体方式就是将它作为参数传入后面的 LSTM 网络.

# 增加额外的第二个维度
inputs = torch.cat(inputs).view(len(inputs), 1, -1)
hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3))  # clean out hidden state
out, hidden = lstm(inputs, hidden)
print('out2',out)
print('hidden3',hidden)

运行输出:

D:\Users\admin\anaconda3\python.exe D:/PycharmWorkSpace/AnyNet/model_LSTM/lstm_ex1.py
[[Parameter containing:
tensor([[ 0.2975, -0.2548, -0.1119],
        [ 0.2710, -0.5435,  0.3462],
        [-0.1188,  0.2937,  0.0803],
        [-0.0707,  0.1601,  0.0285],
        [ 0.2109, -0.2250, -0.0421],
        [-0.0520,  0.0837, -0.0023],
        [ 0.5047,  0.1797, -0.2150],
        [-0.3487, -0.0968, -0.2490],
        [-0.1850,  0.0276,  0.3442],
        [ 0.3138, -0.5644,  0.3579],
        [ 0.1613,  0.5476,  0.3811],
        [-0.5260, -0.5489, -0.2785]], requires_grad=True), Parameter containing:
tensor([[ 0.5070, -0.0962,  0.2471],
        [-0.2683,  0.5665, -0.2443],
        [ 0.4330,  0.0068, -0.3042],
        [ 0.2968, -0.3065,  0.1698],
        [-0.1667, -0.0633, -0.5551],
        [-0.2753,  0.3133, -0.1403],
        [ 0.5751,  0.4628, -0.0270],
        [-0.3854,  0.3516,  0.1792],
        [-0.3732,  0.3750,  0.3505],
        [ 0.5120, -0.3236, -0.0950],
        [-0.0112,  0.0843, -0.4382],
        [-0.4097,  0.3141, -0.1354]], requires_grad=True), Parameter containing:
tensor([ 0.2820,  0.0329,  0.1896,  0.1270,  0.2099,  0.2862, -0.5347,  0.2906,
        -0.4059, -0.4356,  0.0351, -0.0984], requires_grad=True), Parameter containing:
tensor([ 0.3391, -0.3344, -0.5133,  0.4202, -0.0856,  0.3247,  0.1856, -0.4329,
         0.1160,  0.1387, -0.3866, -0.2739], requires_grad=True)]]
Inputs: [tensor([[-0.5525,  0.6355, -0.3968]]), tensor([[-0.6571, -1.6428,  0.9803]]), tensor([[-0.0421, -0.8206,  0.3133]]), tensor([[-1.1352,  0.3773, -0.2824]]), tensor([[-2.5667, -1.4303,  0.5009]])]
Hidden: (tensor([[[ 0.5438, -0.4057,  1.1341]]]), tensor([[[-1.1115,  0.3501, -0.7703]]]))
out1: tensor([[[-0.3600,  0.0893,  0.0215]]], grad_fn=<StackBackward>)
hidden2: (tensor([[[-0.3600,  0.0893,  0.0215]]], grad_fn=<StackBackward>), tensor([[[-1.1298,  0.4467,  0.0254]]], grad_fn=<StackBackward>))
out2 tensor([[[-0.0187,  0.1713, -0.2944]],

        [[-0.3521,  0.1026, -0.2971]],

        [[-0.3191,  0.0781, -0.1957]],

        [[-0.1634,  0.0941, -0.1637]],

        [[-0.3368,  0.0959, -0.0538]]], grad_fn=<StackBackward>)
hidden3 (tensor([[[-0.3368,  0.0959, -0.0538]]], grad_fn=<StackBackward>), tensor([[[-0.9825,  0.4715, -0.0633]]], grad_fn=<StackBackward>))

Process finished with exit code 0

接下来我们要做一件事,

就是训练网络帮我我们标注词性,当然实际的自然语言处理我们有很多成功的算法,但是应对新词总会有点麻烦,我们想啊,既然神经网络可以帮我们做了很多神奇的事,那么我们可不可以训练一个网路模型来帮我我们自动的标注词性呢,显然这个思路靠谱,使用神经网络的套路:

  1. 准备训练数据,这一步最是头大的,最好的办法就是找各大机构提供的标准的标注库,实在找不到,自己处理,国内外很多的分词标准库和工具可以用,jieba分词标注是一个不错的选择,使用起来也简单。
  2. 读取数据文件
  3. 分词
  4. 把词语和标注分别放在两个数组里面
  5. 构建词汇表、构建标注表
  6. 把分词结果转换成对应词汇表和标签表中的序号。
  7. 构建网络模型,这里使用Word2Vec预处理一下输入文本
  8. 训练网络
  9. 分析结果

下面按照这个套路上源码:

import jieba.posseg
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# import sys

import gensim
torch.manual_seed(2)
# sys.stdout = open('1.log', 'a')
sent='明天是荣耀运营十周年纪念日。' \
     '荣耀从两周年纪念日开始,' \
     '在每年的纪念日这天凌晨零点会开放一个新区。' \
     '第十版账号卡的销售从三个月前就已经开始。' \
     '在老区玩的不顺心的老玩家、准备进入荣耀的新手,都已经准备好了新区账号对这个日子翘首以盼。' \
    '陈果坐到了叶修旁边的机器,随手登录了她的逐烟霞。' \
     '其他九大区的玩家人气并没有因为第十区的新开而降低多少,' \
     '越老的区越是如此,实在是因为荣耀的一个账号想经营起来并不容易。' \
     '陈果的逐烟霞用了五年时间才在普通玩家中算是翘楚,哪舍得轻易抛弃。' \
     '更何况到最后大家都会冲着十大区的共同地图神之领域去。'
words=jieba.posseg.cut(sent,HMM=True) #分词
processword=[]
tagword=[]
for w in words:
    processword.append(w.word)
    tagword.append(w.flag)
#词语和对应的词性做一一对应
texts=[(processword,tagword)]

#使用gensim构建本例的词汇表
id2word=gensim.corpora.Dictionary([texts[0][0]])
#每个词分配一个独特的ID
word2id=id2word.token2id

#使用gensim构建本例的词性表
id2tag=gensim.corpora.Dictionary([texts[0][1]])
#为每个词性分配ID
tag2id=id2tag.token2id


def sen2id(inputs):
    return [word2id[word] for word in inputs]

def tags2id(inputs):
    return [tag2id[word] for word in inputs]
#根据词汇表把文本输入转换成对应的词汇表的序号张量
def formart_input(inputs):
    return torch.tensor(sen2id(inputs),dtype=torch.long)

#根据词性表把文本标注输入转换成对应的词汇标注的张量
def formart_tag(inputs):
    return torch.tensor(tags2id(inputs),dtype=torch.long)
#定义网络结构
class LSTMTagger(torch.nn.Module):
    def __init__(self,embedding_dim,hidden_dim,voacb_size,target_size):
        super(LSTMTagger,self).__init__()
        self.embedding_dim=embedding_dim
        self.hidden_dim=hidden_dim
        self.voacb_size=voacb_size
        self.target_size=target_size
        # 使用Word2Vec预处理一下输入文本
        self.embedding=nn.Embedding(self.voacb_size,self.embedding_dim)
        #  LSTM 以 word_embeddings 作为输入, 输出维度为 hidden_dim 的隐状态值
        self.lstm=nn.LSTM(self.embedding_dim,self.hidden_dim)
        ## 线性层将隐状态空间映射到标注空间
        self.out2tag=nn.Linear(self.hidden_dim,self.target_size)

        self.hidden = self.init_hidden()

    def init_hidden(self):
        # 开始时刻, 没有隐状态
        # 关于维度设置的详情,请参考 Pytorch 文档
        # 各个维度的含义是 (Seguence, minibatch_size, hidden_dim)
        return (torch.zeros(1, 1, self.hidden_dim),
                torch.zeros(1, 1, self.hidden_dim))

    def forward(self,inputs):

        # 预处理文本转成稠密向量
        embeds=self.embedding((inputs))
        #根据文本的稠密向量训练网络
        out,self.hidden=self.lstm(embeds.view(len(inputs),1,-1),self.hidden)
        #做出预测
        tag_space=self.out2tag(out.view(len(inputs),-1))
        tags=F.log_softmax(tag_space,dim=1)
        return tags


model=LSTMTagger(10,10,len(word2id),len(tag2id))
loss_function=nn.NLLLoss()
optimizer=optim.SGD(model.parameters(),lr=0.1)
#看看随机初始化网络的分析结果
with torch.no_grad():
    input_s=formart_input(texts[0][0])
    print(input_s)
    print(processword)
    tag_s=model(input_s)
    for i in range(tag_s.shape[0]):
        print(tag_s[i])
    # print(tag_s)
for epoch in range(300):
    # 再说明下, 实际情况下你不会训练300个周期, 此例中我们只是构造了一些假数据
    for p ,t in texts:
        # Step 1. 请记住 Pytorch 会累加梯度
        # 每次训练前需要清空梯度值
        model.zero_grad()

        # 此外还需要清空 LSTM 的隐状态
        # 将其从上个实例的历史中分离出来
        # 重新初始化隐藏层数据,避免受之前运行代码的干扰,如果不重新初始化,会有报错。
        model.hidden = model.init_hidden()

        # Step 2. 准备网络输入, 将其变为词索引的Tensor 类型数据
        sentence_in=formart_input(p)
        tags_in=formart_tag(t)

        # Step 3. 前向传播
        tag_s=model(sentence_in)

        # Step 4. 计算损失和梯度值, 通过调用 optimizer.step() 来更新梯度
        loss=loss_function(tag_s,tags_in)
        loss.backward()
        print('Loss:',loss.item())
        optimizer.step()

#看看训练后的结果
with torch.no_grad():
    input_s=formart_input(texts[0][0])
    tag_s=model(input_s)
    for i in range(tag_s.shape[0]):
        print(tag_s[i])

代码详细地址:https://github.com/oliverwy/pytorch04ex/blob/master/jiebapossi.py

  • 35
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值