循环神经网络实例——序列预测

news2024/11/16 9:54:38

我们生活的世界充满了形形色色的序列数据,只要是有顺序的数据统统都可以看作是序列数据,比如文字是字符的序列,音乐是音符组成的序列,股价数据也是序列,连DNA序列也属于序列数据。循环神经网络RNN天生就具有处理序列数据的能力。我们在上一篇文章中介绍了循环神经网络,这次我们来看一个具体的序列生成问题:

假设一个简单的序列生成问题,01序列生成。一个序列数据由0和1组成,并且每个0之间是连续的,每个1之间也是连续的,并且0和1的个数相等,比如:

000111

00001111,...,等等

这种序列被称为“上下文无关文法”。如果现在的数据是0000,那下一位其实不好判断,可能是0,也可能是1;如果现在的数据是00001,那下一位一定是1;如果现在数据是00001111,那由于0和1相等,接下来应该结束这个序列。

我们首先尝试生成这种数据:

class MyDataset(Dataset):
    def __init__(self, samples = 2000, max_size=10, transform=None):
        max_size=10
        self.data = []
        for m in range(samples):
            probability = 1.0*np.array([10,6,4,3,1,1,1,1,1,1])
            probability = probability[:max_size]
            # 生成概率值
            probability = probability/np.sum(probability)
            # 生成训练样本
            # 对于每一个生成的字符串,随机选择一个n,n被选择的权重记录在probability中
            n = np.random.choice(range(1, max_size+1), p=probability)
            inputs = [0]*n+[1]*n # 生成仅包含0和1的序列
            inputs.insert(0,3) # 序列开始处插入3,作为标志位
            inputs.append(2) # 序列结束处插入2,作为标志位
            self.data.append(inputs)
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        return self.data[idx]

这里,我们首先默认生成2000个样本,每个样本的最大长度为10个数字,probability代表的是生成10个概率值,我们可以把这10个概率值输出来看看:

array([0.34482759, 0.20689655, 0.13793103, 0.10344828, 0.03448276,
       0.03448276, 0.03448276, 0.03448276, 0.03448276, 0.03448276])

可以看到第一个概率最大,接近0.35,之后依次减少,最后六个概率都仅仅是第一个概率的十分之一,0.035左右。np.random.choice(array, prob),是指根据prob指明的概率值输出array中的值,我们可以把生成的n打印出来:

for i in range(10):
    n = np.random.choice(range(1, max_size+1), p=probability)
    print(n, end=' ') # 以空格分隔
# 这其实就是说,从[1,2,3,4,5,6,7,8,9,10]中,按概率probability随机抽取数字
# 输出:
# 5 3 1 1 1 1 1 1 6 7  

可以多尝试几次,总之,1出现的概率最大,1和2出现的次数都很多,符合我们的直观印象。目的就是为了生成长度为n的随机的01字符串(n个0加上0个1)。同时在字符串的前端加上3,表示字符串起始位置,在字符串的末尾加上2,表示字符串的结束位置,如3000011112,30001112,30000001111112。

我们把生成的样本库用pytorch的dataset和dataloader包装一下,并取出来观察一下:

if __name__ == '__main__':
    # 定义训练数据集
    trainDataset = MyDataset()
    trainDataloader = DataLoader(dataset=trainDataset, batch_size=1, shuffle=True)
    # 查看数据集内容
    for i,seq in enumerate(trainDataloader):
        print(seq)
# 输出
[tensor([3]), tensor([0]), tensor([1]), tensor([2])]
[tensor([3]), tensor([0]), tensor([0]), tensor([0]), tensor([0]), tensor([1]), tensor([1]), tensor([1]), tensor([1]), tensor([2])]
[tensor([3]), tensor([0]), tensor([0]), tensor([1]), tensor([1]), tensor([2])]
[tensor([3]), tensor([0]), tensor([0]), tensor([0]), tensor([1]), tensor([1]), tensor([1]), tensor([2])]
[tensor([3]), tensor([0]), tensor([1]), tensor([2])]
[tensor([3]), tensor([0]), tensor([1]), tensor([2])]

可以看到,生成了3开头,2结尾的01序列,只不过都转成了Tensor类型。

下面,我们来实现一下自定义的RNN模型:

class MyRnn(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers = 1):
        # 定义
        super(MyRnn, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        # 一个embedding层
        self.embedding = nn.Embedding(input_size, hidden_size)
        # PyTorch的RNN层,batch_first标识可以让输入的张量的第一个维度表示batch指标
        self.rnn = nn.RNN(hidden_size, hidden_size, num_layers, batch_first = True)
        # 输出的全连接层
        self.fc = nn.Linear(hidden_size, output_size)
        # 最后的LogSoftmax层
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input):
        # 运算过程
        # 先进行embedding层的计算
        # 它可以把一个数值先转化为one-hot向量,再把这个向量转化为一个hidden_size维的向量
        # input的尺寸为:batch_size, num_step, data_dim
        x = self.embedding(input)
        # 从输入层到隐含层的计算
        # x的尺寸为:batch_size, num_step, hidden_size
        h0 = torch.zeros(self.num_layers, 1, self.hidden_size)
        output, hidden = self.rnn(x, h0)
        # 从输出output中取出最后一个时间步的数值,注意output包含了所有时间步的结果
        # output尺寸为:batch_size, num_step, hidden_size
        output = output[:,-1,:]
        # output尺寸为:batch_size, hidden_size
        # 把前面的结果输入给最后一层全连接网络
        output = self.fc(output)
        # output尺寸为:batch_size, output_size
        # softmax函数
        output = self.softmax(output)
        return output, hidden

这里,有个地方需要注意,和我上次的循环神经网络简介中介绍的RNN模型不同,多了一个embedding层。这里要特别提一下,embedding层是RNN,特别是自然语言处理(NLP)等工作中特别重要的一个步骤。embedding就是做一个查表,把输入数据映射到一个新的表空间的,本质上其实就是进行了一次降维或升维操作,有点类似卷积神经网络的1x1卷积的作用,下面我们来具体看看embedding操作到底是干了什么。

import torch
import torch.nn as nn

embedding = nn.Embedding(10, 3) # an Embedding module containing 10 tensors of size 3
input = torch.LongTensor([[1,2,4,5],[4,3,2,9]]) # a batch of 2 samples of 4 indices each
e = embedding(input)
print(e)
# 输出
tensor([[[-1.1222,  0.5364, -1.5284],
         [-0.4762,  0.7091, -0.0703],
         [-1.1492,  0.2443,  0.4336],
         [ 1.8044,  0.5492,  1.1109]],

        [[-1.1492,  0.2443,  0.4336],
         [-0.4599, -0.3097,  0.2294],
         [-0.4762,  0.7091, -0.0703],
         [ 0.6926,  1.7407,  1.5432]]], grad_fn=<EmbeddingBackward0>)

是不是很奇怪?我们的输入仅仅是一个2x4的二维张量,怎么变成了2x4x3的三维张量了,并且这些值是哪儿来的呢?别急,我们一步步来看。

print(input.shape)
print(e.shape)
print(embedding.weight)
print(embedding.weight.shape)
# 输出
torch.Size([2, 4])
torch.Size([2, 4, 3])
Parameter containing:
tensor([[ 1.5728,  0.1152,  2.1069],
        [-1.1222,  0.5364, -1.5284],
        [-0.4762,  0.7091, -0.0703],
        [-0.4599, -0.3097,  0.2294],
        [-1.1492,  0.2443,  0.4336],
        [ 1.8044,  0.5492,  1.1109],
        [-1.3228,  0.2966,  0.5020],
        [-0.4501, -0.1242, -0.2341],
        [ 0.0743,  0.8168, -1.2459],
        [ 0.6926,  1.7407,  1.5432]], requires_grad=True)
torch.Size([10, 3])

可以看到,我们输入的大小确实是(2,4),经过embedding的输出也确实是(2,4,3)了,我们可以看到,embedding的大小是(10,3),其实简单来说就是,embedding生成了一张10行3列的表,表中的数据是embedding自动计算出来的权重,input里面的值可以看成是索引,[[1,2,4,5],[4,3,2,9]],我们看到第一个数字1,那我们从embedding表中取到第1行,[-1.1222, 0.5364, -1.5284],依次类推,得到最后的结果,如下图所示:

等于说input里面的值变成了embedding权重表里面的索引值,根据这个索引值去查询embedding表的权重,组成的结果就是把输入进行embedding后的结果。

下面我们再来详细分析一下我们的循环神经网络模型MyRnn里面的运算过程,只要真正理解了这个过程,那就明白了模型里面的运作原理了。

首先,我有一个输入,假设是tensor[1]

input = torch.LongTensor([1]).unsqueeze(0) # 扩展一个维度,作为batch_size
print(input.shape)
print(input)
# 输出
torch.Size([1, 1])
tensor([[1]])

下面,我们进行embedding操作,我们的模型假设input_size=4,hidden_size=2,output_size=3,因为输入有0,1,2,3四种可能,输出有0,1,2三种可能。

# input_size=4,hidden_size=2
embedding = nn.Embedding(4, 2)
print(embedding.weight)
print(embedding.weight.shape)
x = embedding(input) # embedding操作
print(x)
print(x.shape)
# 输出
Parameter containing:
tensor([[ 0.1870,  0.5711],
        [ 0.2994, -0.4649],
        [-0.3611, -0.1150],
        [-1.0868, -0.1419]], requires_grad=True)
torch.Size([4, 2])
tensor([[[ 0.2994, -0.4649]]], grad_fn=<EmbeddingBackward0>)
torch.Size([1, 1, 2])

可以看到,做了embedding操作后,我们的输入从[[1]]变成了[[[ 0.2994, -0.4649]]],接下来,我们把这个结果和初始化的隐藏层hidden一起送入RNN。

h = torch.zeros(1, x.size(0), 2) # 隐藏层参数layer_size,batch_size,hidden_size
print(h)
print(h.shape)
rnn = nn.RNN(2, 2, batch_first=True) # RNN模型,因为经过了embedding,input的shape变成了和hidden一样的shape

隐藏层的三个参数分别为隐藏层大小layer_size,这里取1,batch_size这里就是x第一个维度,也就是1,hidden_size,设置隐藏层的大小为2。

out, _ = rnn(x, h) # RNN结果
print(out.shape)
print(out)
print(out[:,-1,:]) # 取出结果
# 输出
torch.Size([1, 1, 2])
tensor([[[-0.2823, -0.5443]]], grad_fn=<TransposeBackward1>)
tensor([[-0.2823, -0.5443]], grad_fn=<SliceBackward0>)

可以看到我们现在取出的out结果的shape从[1,1,2]变成了[1,2],最后我们做一个全连接运算,把分类结果输出:

fc = nn.Linear(2, 3) # 全连接层
out = fc(out[:, -1, :]) # 分类结果
print(out)
# 输出
tensor([[0.7543, 0.5506, 0.8528]], grad_fn=<AddmmBackward0>)

可以看到得到了三个值的分类结果,根据这个值再去获得概率最高的结果,就是最终的输出结果。好了,这就是我们整个模型内部的运行过程。下面给出完整的训练和测试代码:

训练代码:

def train(net, dataloader, epochs=50):
    criterion = torch.nn.NLLLoss()
    optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
    for epoch in range(epochs):
        train_loss = 0.
        for i, seq in enumerate(dataloader):
            loss = 0
            hidden = net.initHidden()
            for k in range(len(seq)-1):
                x = torch.LongTensor([seq[k]]).unsqueeze(0) # 增加一个batch_size的维度
                y = torch.LongTensor([seq[k+1]])
                #print(x)
                #print(y)
                output, hidden = net(x, hidden)
                #output, _ = net(x)
                loss += criterion(output, y)
            loss = 1.0*loss/len(seq) # 计算每个字符的损失值
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            train_loss += loss
            if i>0 and i%500==0:
                print('第{}轮,第{}个,训练Loss:{:.2f}'.format(epoch, i, train_loss.data.numpy()/i))
    torch.save(net, 'checkpoints/RNN.pt')

验证代码:

def val(net, dataloader):
    valid_loss = 0
    errors = 0
    show_out = ''
    net = torch.load('checkpoints/RNN.pt')
    net.eval()
    with torch.no_grad():
        for i, seq in enumerate(dataloader):
        # 对valid_set中的每一个字符串进行循环
            loss = 0
            outstring = ''
            targets = ''
            diff = 0
            hidden = net.initHidden() # 初始化隐含神经元
            for t in range(len(seq) - 1):
                # 对每一个字符进行循环
                x = torch.LongTensor([seq[t]]).unsqueeze(0)
                # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
                y = torch.LongTensor([seq[t + 1]])
                # y尺寸:batch_size = 1, data_dimension = 1
                output, hidden = net(x, hidden)
                # output尺寸:batch_size, output_size = 3
                # hidden尺寸:layer_size =1, batch_size=1, hidden_size
                mm = torch.max(output, 1)[1][0] # 将概率最大的元素作为输出
                outstring += str(mm.data.numpy()) # 合成预测的字符串
                targets += str(y.data.numpy()[0]) # 合成目标字符串
                #loss += criterion(output, y) # 计算损失函数
                diff += 1 - mm.eq(y).data.numpy()[0] # 计算模型输出字符串与目标字符串之间存在差异的字符数量
            #loss = 1.0 * loss / len(seq)
            #valid_loss += loss # 累积损失函数值
            errors += diff # 计算累积错误数
            show_out = outstring + '\n' + targets
        # 打印结果
        print(output[0][2].data.numpy())
        print(show_out)

测试代码:

def test():
    net = torch.load('checkpoints/RNN.pt')
    net.eval()
    for n in range(20):
        inputs = [0]*n + [1]*n
        inputs.insert(0,3)
        inputs.append(2)
        outstring = ''
        targets = ''
        diff = 0
        hiddens = []
        hidden = net.initHidden()
        for t in range(len(inputs)-1):
            x = Variable(torch.LongTensor([inputs[t]]).unsqueeze(0))
            y = Variable(torch.LongTensor([inputs[t+1]]))
            output,hidden = net(x, hidden)
            hiddens.append(hidden.data.numpy()[0][0])
            mm = torch.max(output, 1)[1][0]
            outstring += str(mm.data.numpy())
            targets += str(y.data.numpy()[0])
            diff += 1-mm.eq(y).data.numpy()[0]
        # 打印每一个生成的字符串和目标字符串
        print("n======",n)
        print(outstring)
        print(targets)
        print('Diff:{}'.format(diff))

最终输出:

第49轮,第500个,训练Loss:0.24
第49轮,第1000个,训练Loss:0.24
第49轮,第1500个,训练Loss:0.24
-0.030197786
0100112
0001112
n====== 0
0
2
Diff:1
n====== 1
012
012
Diff:0
n====== 2
01012
00112
Diff:2
n====== 3
0100112
0001112
Diff:2
n====== 4
010001112
000011112
Diff:2
n====== 5
01000011112
00000111112
Diff:2
n====== 6
0100000111112
0000001111112
Diff:2
n====== 7
010000001111112
000000011111112
Diff:2
n====== 8
01000000011111112
00000000111111112
Diff:2
n====== 9
0100000000111111112
0000000001111111112
Diff:2
n====== 10
010000000001111111112
000000000011111111112
Diff:2
n====== 11
01000000000011111111112
00000000000111111111112
Diff:2
n====== 12
0100000000000111111111112
0000000000001111111111112
Diff:2
n====== 13
010000000000001111111111112
000000000000011111111111112
Diff:2
n====== 14
01000000000000011111111111112
00000000000000111111111111112
Diff:2
n====== 15
0100000000000000111111111111112
0000000000000001111111111111112
Diff:2
n====== 16
010000000000000001111111111111112
000000000000000011111111111111112
Diff:2
n====== 17
01000000000000000011111111111111112
00000000000000000111111111111111112
Diff:2
n====== 18
0100000000000000000111111111111111112
0000000000000000001111111111111111112
Diff:2
n====== 19
010000000000000000001111111111111111112
000000000000000000011111111111111111112
Diff:2

可以看到,模型预测还是比较准确的,就是第二个数字预测错误,以及由0变1的那个位置会预测错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1616373.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式linux学习之arm开发板移植ssh

1.下载源码 &#xff08;1&#xff09;zlib 下载网址&#xff1a;http://www.zlib.net/fossils/ 教程中版本选择的是: zlib-1.2.11.tar.gz &#xff08;2&#xff09;openssl下载网址&#xff1a;https://www.openssl.org/source/mirror.html 教程中版本选择的是: openssl-1.1…

用友U8-Cloud api/hr接口存在SQL注入漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 U8 Cloud是由用友推出的新一代云ERP系统&#xff0…

VSCode的C/C++开发 ===> Windows

一、开发环境搭建 安装mingw-w64编译器(GCC for Windows 64 & 32 bits)、Cmake工具(选装) VSCode插件安装 C/C cmake cmake tools 二、代码实践演练 基于g命令 g编译单文件&#xff0c;生成带调试信息的可执行文件、并调试 g -g main.cpp -o my_single_swap g编译多文件…

【C#】rdlc报表答应报错:未能加载文件或程序集“Microsoft.SqlServer.Types

文章目录 一、报错信息二、解决方式 一、报错信息 Microsoft.Reporting.WinForms.LocalProcessingException: An error occurred during local report processing. —> Microsoft.Reporting.DefinitionInvalidException: The definition of the report ‘’ is invalid. —&…

算法练习|Leetcode49字母异位词分词 ,Leetcode128最长连续序列,Leetcode3无重复字符的最长子串,sql总结

目录 一、Leetcode49字母异位词分词题目描述解题思路方法:哈希总结 二、Leetcode128最长连续序列题目描述解题思路方法:总结 三、Leetcode3无重复字符的最长子串题目描述解题思路方法:双指针法总结sql总结 一、Leetcode49字母异位词分词 题目描述 给你一个字符串数组&#xf…

数据结构与算法解题-20240422

这里写目录标题 一、2. 两数相加二、67. 二进制求和三、415. 字符串相加四、LCS 01. 下载插件五、71. 简化路径 一、2. 两数相加 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 …

分享三个转换速度快、准确率高的视频转文字工具

想要直接将视频转换成文字&#xff0c;转换工具很重要&#xff01;给大家分享三个转换速度快、准确率高的视频转文字工具&#xff0c;轻松完成转换。 1.网易见外 https://sight.youdao.com/ 网易家的智能转写翻译服务工作站&#xff0c;网页端就可以直接使用&#xff0c;支持视…

vi, vim,data,wc,系统常用命令-读书笔记(十)

vi 文本编辑器 基本上 vi 共分为三种模式&#xff0c;分别是“一般指令模式”、“编辑模式”与“命令行命令模式”。这三种模式的作用分别是&#xff1a; 一般指令模式&#xff08;command mode&#xff09;以 vi 打开一个文件就直接进入一般指令模式了&#xff08;这是默认的…

Elasticsearch:崭新的打分机制 - Learning To Rank (LTR)

警告&#xff1a;“学习排名 (Learning To Rank)” 功能处于技术预览版&#xff0c;可能会在未来版本中更改或删除。 Elastic 将努力解决任何问题&#xff0c;但此功能不受官方 GA 功能的支持 SLA 的约束。 注意&#xff1a;此功能是在版本 8.12.0 中引入的&#xff0c;并且仅适…

GEE:基于光谱距离度量方法的巴以冲突造成的地表覆盖变化检测

作者:CSDN @ _养乐多_ 本文将介绍如何在 Google Earth Engine (GEE) 平台中使用光谱距离度量方法进行地表覆盖变化检测,并以加沙地区为例,使用Sentinel2数据展示2023年3月和2024年3月的地表覆盖变化区域。 结果如下图所示, 文章目录 一、核心函数1.1 spectralDistance函数…

分布式与一致性协议之拜占庭将军问题(一)

拜占庭将军问题 概述 拜占庭将军问题其实是借拜占庭将军故事展现了分布式共识问题&#xff0c;探讨和论证了解决的办法。实际上&#xff0c;拜占庭将军问题是分布式领域最复杂的一个容错模型&#xff0c;一旦搞懂了它&#xff0c;久能掌握分布式共识问题的解决思路&#xff0…

Java HotSpot(TM) 64-Bit Server VM warning_ Sharing is only supported for boot loader classes because

今天在学习freemarker静态化页面的时候遇到了这个错误&#xff1a; Java HotSpot™ 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended 最终我找到了解决办法 解决办法原帖&#xff1a;https://s…

Java中的super

package day33; ​ public class Person {public String name;public int age; ​public Person() {System.out.println("调用了父类的无参构造");} } ​ package day33; ​ public class teacher extends Person{public teacher() {System.out.println("调用了…

C语言--基础面试真题

1、局部变量和静态变量的区别 普通局部变量和静态局部变量区别 存储位置&#xff1a; 普通局部变量存储在栈上 静态局部变量存储在静态存储区 生命周期&#xff1a; 当函数执行完毕时&#xff0c;普通局部变量会被销毁 静态局部变量的生命周期则是整个程序运行期间&#…

【R语言简介】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

微软刚开源就删库的WizardLM-2:MT-Bench 榜单评测超越GPT-4,7B追平Qwen1.5-32B

前言 微软最近发布的WizardLM-2大型语言模型因其先进的技术规格和短暂的开源后突然撤回&#xff0c;引起了科技界的广泛关注。WizardLM-2包括三个不同规模的模型&#xff0c;分别是8x22B、70B和7B&#xff0c;均展现了在多语言处理、复杂对话、推理和代理任务上的卓越能力。 H…

Ardupilot OpenIPC 基于WFB-NG构架分析和数据链路思考

Ardupilot & OpenIPC & 基于WFB-NG构架分析和数据链路思考 1. 源由2. OpenIPC安装2.1 安装2.2 配置2.2.1 天空端配置文件2.2.2 地面端配置文件 2.3 当前配置选择 3. WFB-NG安装3.1 RTL8812AU安装3.1.1 驱动安装3.1.2 定位设备 3.2 wfb-ng安装3.2.1 传输层安装3.2.2 配置…

《架构风清扬-Java面试系列第23讲》如何理解Java的泛型檫除?

晚上好&#xff0c;给大家加个餐 来&#xff0c;思考片刻&#xff0c;说出你的答案 1&#xff0c;什么是泛型檫除&#xff1f; 泛型擦除是指编译器在处理泛型代码时&#xff0c;会在编译阶段移除&#xff08;擦除&#xff09;所有与泛型相关的类型参数信息&#xff0c;将其替换…

3Darray 修改array值然后保存图片

from PIL import Image import numpy as np img_path ./000001.jpg # 读取图片 image Image.open(img_path) width, height image.size print("图片的宽度为{},高度为{}".format(width,height)) print("图片的mode为{}".format(image.mode)) print(&quo…

CSS-vminvmax单位

vmin 和 vmax 单位 vmin 是相对于视口宽度和高度中较小值进行计算&#xff0c;它的值为视口宽度和高度中的较小值的百分比。 例如&#xff0c;如果视口宽度为 800px&#xff0c;高度为 1000px&#xff0c;那么 1vmin 等于 8px&#xff08;800px 的 1%&#xff09;。 vmax 是…