【深度学习】RNN学习笔记

RNN学习笔记

时间序列

 将单词序列转换为向量,这里有五个单词,然后对于每一个单词都进行独热编码,编码成一个特定的向量。

在这里插入图片描述

对于RNN网络,需要一次性读取多个句子,那么涉及到batch_size,这里第二个表达就是:batch,单词,单词的表达方式

在这里插入图片描述

RNN原理

在这里插入图片描述

 这里生成一个5 x 100的向量,对于每一个单词我们都使用100个特征进行表示,然后经过每一个线性层,这100维的向量转换为更小的向量 比如五个向量特征,那么对于一个句子的每一个单词都进行这样的提取特征,那么就需要对每一个单词都进行线性层的变换,最后得到一个5x5的向量

但是上面的做法有两个缺点:

  • 单词数量太多 句子太长
  • 参数两太多 对于每一个单词都需要生成特定的w b的线性层 这样参数过多
  • 只考虑每一个单词 没有考虑语境信息

那么,对于每一个线性层,我们不仅输入每一个单词的向量,还需要输入语境信息h

在这里插入图片描述

现在举一个例子,对于每一个单词我们使用 100个特征进行表示,对于一个句子五个单词就是5 x 100的向量表示,那么我们选择batch_size是3 一次输入三个单词,那么对于每次的输入xt,就是[3,100]的向量,我们初始化语境信息向量h[0,0…],输入网络

在这里插入图片描述

在这里插入图片描述

针对上面的讲述,整理成公式化,对于每一个输出ht 都是由ht-1 和xt得到的,也就是当前层的输入xt和上一层计算的输出作为当前层的输入,然后,使用Whh和WXh分别对输入的xt和ht-1进行特征提取,得到的输出向量经过激活函数进行激活

在这里插入图片描述

如何推导出梯度?

为RNN网络的权重whh和Wxh都是共享的,那么最后的输出一定是关系到每一个时刻的权重,所以我们需要使用损失对每一个时刻的权重进行求导,然后进行累加

在这里插入图片描述

在这里插入图片描述

对于ht求导hi

在这里插入图片描述

RNN Layer的使用

 输入的向量xt是[3,100],那么 Wxh是[100,20]的形状的参数向量,也就是说相乘之后变成了[3,20] 原先单词使用100个特征进行表示,现在使用20个特征进行表示。同样的对于语境信息向量ht[3,20] 那么Whh是[20,20]的向量,想成之后变成了[3,20],然后两个[3,20]的向量相加得到ht+1向量

在这里插入图片描述

rnn=nn.RNN(100,10)  #每个单词用100维表示,memory/hidden为10维
rnn._parameters.keys()
#out:odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0'])
 
rnn.weight_hh_l0.shape,rnn.weight_ih_l0.shape
#out:(torch.Size([10, 10]), torch.Size([10, 100]))
 
rnn.bias_hh_l0.shape,rnn.bias_ih_l0.shape
out:(torch.Size([10]), torch.Size([10]))

在这里插入图片描述

这里创建了一个RNN模型,其中参数100表示输入数据的特征维度,每个单词用100维的向量表示;参数10表示RNN内部隐层的大小,也称为记忆维度,即隐状态的维度。

nn.RNN模块有四个可训练参数,它们是:

weight_ih_l0: 输入到隐层的权重矩阵,形状为(10, 100),表示从输入层到隐层的权重参数。
weight_hh_l0: 隐层到隐层的权重矩阵,形状为(10, 10),表示从上一时刻隐层到当前时刻隐层的权重参数。
bias_ih_l0: 输入到隐层的偏置向量,形状为(10),表示从输入层到隐层的偏置参数。
bias_hh_l0: 隐层到隐层的偏置向量,形状为(10),表示从上一时刻隐层到当前时刻隐层的偏置参数。

输入到隐层的权重矩阵(weight_ih_l0)的形状为 (hidden_size, input_size),而不是 (input_size, hidden_size)。这里 (10, 100) 表示有 10 个隐状态单元(记忆单元)和每个单词用 100 维的向量表示作为输入特征。

单层的RNN

因为是单层的RNN,那么h向量只有一个

# RNN创建
rnn = nn.RNN(input_size = 100,hidden_size = 20,num_layers=1)
print(rnn)

#  10代表每一个句子的单词数量  3代表三个句子  100 代表每一个单词的维度
x = torch.randn(10,3,100)

#  第一个参数是输入向量x 第二个参数是h向量  只有一个单词 每一层三个句子 每一个单词使用20个维度
out,h = rnn(x,torch.zeros(1,3,20))

# 最后的输出out 是一个 10 x 3 x 20 的向量 也就是被提取成20个维度  然后h是一个1 3 20的向量
print(out.shape,h.shape)

多层的RNN

因为有多层RNN,那么就有多个h向量,但是out输出形状不会变

在这里插入图片描述

rnn = nn.RNN(input_size = 100,hidden_size = 20,num_layers = 4)
print(rnn)

x = torch.randn(10,3,100)

# 这里面的h 是多层的 应该是[4,3,20]
out,h = rnn(x)

print(out.shape,h.shape)

单层RNNCell

 处理三个句子,每一个句子10个单词,每一个单词使用长度100的向量,送入RNN的shape就是[10,3,100]

 如果使用RNNCell 针对每一个时刻都分开处理,这里十个单词就是十个时刻,每次输入的向量是[3,100] 那么计算单元运行十次,

显然,RNNCelll没办法想RNN直接求出网络的输出,那么只需要将最后一层每一个时刻得输出h组合起来。out = torch.stack([h1,h2,…,ht])


#  100代表输入得特征维度  20代表提取的特征维度
cell = nn.RNNCell(100,20)

# 初始化输入  某一个时刻得输入
x = torch.randn(3,100)

# 初始化所有时刻得输入
xs = [torch.randn(3,100) for i in range(10)]

# 初始化隐藏层记忆单元
h = torch.zeros(3,20)


#  针对每一个时刻得输入 传入RNN单元
for xt in xs:
    h = cell(xt,h)

# 查看最后的输出
print(h.shape)


多层RNNCell


#  定义两层计算单元
cell_l0 = nn.RNNCell(100,30)
cell_l1 = nn.RNNCell(30,20)


# 定义两层的隐藏单元
h_l0 = torch.zeros(3,30)
h_l1 = torch.zeros(3,20)


# 初始化原始输入
xs = [torch.randn(3,100) for i in range(4)]

for xt in xs:
    h_l0 = cell_l0(xt,h_l0)
    h_l1 = cell_l1(h_l0,h_l1)


print(h_l0.shape)
print(h_l1.shape)

简单案例

预测正弦曲线的下一段波形

因为是输入一段波形曲线,不同于一个句子,句子中的每一个单词都是不能直接输入,需要做embedding,也就是对每一个单词进行编码成一个向量,但是波形曲线每一个点都是一个数字,不需要做embedding,那么如果给出五十个点,只提供一条曲线,那么seq_len = 50,feature_len =1,那么输入就是[50,1,1],但是batch需要提前,也就是输入[1,50,1],但是因为最后的输出是[seq_len,batch,hidden_len] 我们还是需要添加一个线性层变换一下,不能是hidden_len,需要变换成1 也就是[seq_len,batch,1]

  • 定义网络
class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        # RNN
        self.rnn = nn.RNN(
            input_size = 1, # feature_len = 1
            hidden_size = 16, # 隐藏层记忆单元尺寸
            num_layers = 1,# 层数
            batch_first = True  # 确保输入时 按照[batch,seq_len,feature_len]的模式 1 x 50 x 1
        )

        #  对RNN 进行参数初始化
        for p in self.rnn.parameters():
            nn.init.normal_(p,mean=0.0,std=0.001)

        #  输出层 直接使用一个线性变换 吧每一个时刻的记忆单元的Hidden_len的输出为所需要的feature_len = 1 因为是一个数据点
        #  50 x 16 -> 50 x 1
        self.linear = nn.Linear(16,1)


    def forward(self,x,h):
        # 这里的out原始形状是 1 x 50 x 16 最后变成 50 x 16
        # h的原始形状是 1x 1  x 16  
        out,h = self.rnn(x,h)
        # 因为最后需要传递给线性层处理,所以需要展平
        out = out.view(-1,16)
        out = self.linear(out)

        # 在把batch维度添加进去
        out = out.unsqueeze(dim=0)

        return out,h

  • 训练网络
lr = 0.01 
model = Net()
from torch import nn, optim
criterion = nn.MSELoss()  # 均方差损失函数
optimizer = optim.Adam(model.parameters(),lr) # 学习率

# 初始化记忆单元 
h = torch.zeros(1,1,16)

# 生成样本数据
num_points = 50
seq_len = num_points - 1

import numpy as np
for iter in range(6000):
    k = np.random.randint(3, size=1)[0]
    # 取点的区间是[k, k+10],均匀地取num_points个点
    time_steps = np.linspace(k, k + 10, num_points)
    # 在这num_points个时刻上生成函数值数据
    data = np.sin(time_steps)
    # 将数据从shape=(num_points,)转换为shape=(num_points,1)
    data = data.reshape(num_points, 1)  # feature_len=1
    # 输入前49个点(seq_len=49),即下标0~48
    x = torch.tensor(data[:-1]).float().view(1, seq_len, 1)  # batch,seq_len,feature_len
    # 预测后49个点,即下标1~49
    y = torch.tensor(data[1:]).float().view(1, seq_len, 1)  # batch,seq_len,feature_len

    # 至此,生成了x->y的样本对, x和y都是shape如上面所写的序列
    
    # 将数据输入
    out,h = model(x,h)
    # h在循环中被
    h = h.detach()

    # 计算和预期输出之间的损失
    loss = criterion(out,y)

    # 更新网络参数
    model.zero_grad()

    # 计算梯度
    loss.backward()
    # 优化
    optimizer.step()


    if iter % 1000 == 0:
        print("迭代次数:{}, loss:{}".format(iter + 1, loss.item()))



  • 测试
from matplotlib import pyplot as plt
# 先用同样的方式生成一组数据x,y
k = np.random.randint(3, size=1)[0]
time_steps = np.linspace(k, k + 10, num_points)
data = np.sin(time_steps)
data = data.reshape(num_points, 1)  # feature_len=1
x = torch.tensor(data[:-1]).float().view(1, seq_len, 1)  # batch,seq_len,feature_len
y = torch.tensor(data[1:]).float().view(1, seq_len, 1)  # batch,seq_len,feature_len

# 用于记录预测出的点
predictions = []

# 取训练时输入的第一个点,即在x(1,seq_len,1)取seq_len里面第0号的数据
# 这里将输入seq_len'设置为1(而不是49)
# 输入什么长度的数据会自动调整网络结构来给出输出
input = x[:, 0, :]
# 输入的shape变成标准的(batch=1,seq_len'=1,feature_len=1)
input = input.view(1, 1, 1)

# 迭代seq_len次,每次预测出一个点
for _ in range(x.shape[1]):
    # 送入模型得到预测的序列,输入了一个点的序列也就输出了(下)一个点的序列
    pred, h = model(input, h)
    # 这里将预测出的(下一个点的)序列pred当成输入,来给到下一次循环
    input = pred
    # 把里面那个点的数取出来记录到列表里
    # 这里用ravel()而不用flatten(),因为后者是原地操作,会改变pred也就是input
    predictions.append(pred.detach().data.numpy().ravel()[0])

# 绘制预测结果predictions和真实结果y的比较
plt.scatter(time_steps[1:], y.data.numpy().ravel())
plt.scatter(time_steps[1:], predictions, c='r')
plt.show()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少写代码少看论文多多睡觉

求打赏,求关注,求点赞

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值