引言

本文一起来看下什么是RNN(循环神经网络),以及如何从零实现一个RNN。

RNN的结构

RNN的特点是能适应可变长度序列,下面是RNN的5种结构:

本文我们构建一个多对一的RNN来实现情感分类。

我们来考虑多对多的RNN结构,输入是x⟨1⟩,x⟨2⟩,⋯,x⟨Tx⟩x^{\langle 1 \rangle} ,x^{\langle 2 \rangle},\cdots,x^{\langle T_x \rangle}x⟨1⟩,x⟨2⟩,⋯,x⟨Tx​⟩,想要产生的输出为y⟨1⟩,y⟨2⟩,⋯,y⟨Tx⟩y^{\langle 1 \rangle} ,y^{\langle 2 \rangle},\cdots,y^{\langle T_x \rangle}y⟨1⟩,y⟨2⟩,⋯,y⟨Tx​⟩。其中x⟨i⟩x^{\langle i \rangle}x⟨i⟩和y⟨i⟩y^{\langle i \rangle}y⟨i⟩是任意维度的向量。

RNN原理是循环地更新隐藏状态(激活值a⟨i⟩a^{\langle i \rangle}a⟨i⟩),激活值也可以是任意维度,在任意时间步ttt:

  1. 下个隐藏状态a⟨t⟩a^{\langle t \rangle}a⟨t⟩是通过前一个隐藏状态a⟨t−1⟩a^{\langle t-1 \rangle}a⟨t−1⟩和当前输入x⟨t⟩x^{\langle t \rangle}x⟨t⟩来计算的。
  2. 而输出值y⟨t⟩y^{\langle t \rangle}y⟨t⟩(预测值)是基于a⟨t⟩a^{\langle t \rangle}a⟨t⟩来计算的。

要注意的是上图是RNN的一个展开图例,实际上都是同一个网络结构,权重和偏差都是一样的。

下面介绍涉及到的符号。

总体来说是这样的: a5(2)[3]⟨4⟩a^{(2)[3]\langle 4 \rangle}_5a5(2)[3]⟨4⟩​ 表示第2个训练样本 (2), 第3层 [3], t=4t=4t=4时 <4>, 激活值向量的第5个元素。

输入向量

对于单个时间点(时间步)的单个样本,x(i)⟨t⟩x^{(i) \langle t \rangle }x(i)⟨t⟩是一维的向量。比如在NLP中,假设字典大小为5000,那么单词就有(5000,)(5000,)(5000,)大小的one-hot编码,x(i)⟨t⟩x^{(i) \langle t \rangle }x(i)⟨t⟩的形状也是(5000,)(5000,)(5000,)。

我们用nxn_xnx​来表示单个时间点的单个样本的单元数,这个例子是5000。

用ttt来索引时间步,输入向量时间步的长度记为TxT_xTx​,假设我们的例子中Tx=10T_x=10Tx​=10。

如果我们用了小批次(mini-batch),每个批次有20个样本,批次样本数记为mmm,为了向量化,我们会按列叠加20个样本,得到一个形状为(5000,20,10)(5000,20,10)(5000,20,10)的张量。

所以小批次的形状(nx,m,Tx)(n_x,m,T_x)(nx​,m,Tx​)。

对于每个时间步x⟨t⟩x^{\langle t \rangle}x⟨t⟩的形状为(nx,m)(n_x,m)(nx​,m)。

隐藏状态

从一个时间步传递到另一个时间步的激活值a⟨t⟩a^{\langle t \rangle}a⟨t⟩叫做隐藏状态。

单个训练样本的隐藏状态长度记为nan_ana​

预测值

y^\hat yy^​也是一个3维的张量(ny,m,Ty)(n_y,m,T_y)(ny​,m,Ty​)。

  • nyn_{y}ny​: 预测向量的单元数
  • mmm: 批次大小
  • TyT_{y}Ty​: 预测的时间步长

同样,对于单个时间点,我们有(ny,m)(n_y,m)(ny​,m)大小的预测值y^⟨t⟩\hat{y}^{\langle t \rangle}y^​⟨t⟩。

前向传播


从RNN前向传播(预测过程)来看是如何从输入到输出的。整个过程是上面的两个公式:

a⟨t⟩=tanh⁡(Waaa⟨t−1⟩+Waxx⟨t⟩+ba)(1)a^{\langle t \rangle} = \tanh(W_{aa} a^{\langle t-1 \rangle} + W_{ax} x^{\langle t \rangle} + b_a) \tag{1} a⟨t⟩=tanh(Waa​a⟨t−1⟩+Wax​x⟨t⟩+ba​)(1)
z⟨t⟩=Wyaa⟨t⟩+byy^⟨t⟩=softmax(z⟨t⟩)(2)z^{\langle t \rangle} = W_{ya} a^{\langle t \rangle} + b_y \\ \hat{y}^{\langle t \rangle} = softmax(z^{\langle t \rangle} ) \tag{2} z⟨t⟩=Wya​a⟨t⟩+by​y^​⟨t⟩=softmax(z⟨t⟩)(2)

其中WaaW_{aa}Waa​是由a⟨t−1⟩a^{\langle t-1 \rangle}a⟨t−1⟩计算a⟨t⟩a^{\langle t \rangle}a⟨t⟩的相关权重, WaxW_{ax}Wax​是由xxx计算aaa的相关权重,以及计算aaa的偏差bab_aba​;
经过tanh⁡\tanhtanh激活函数后得到新的激活值,经过这个公式可以看出,新的激活值同时考虑了当前的输入和前个时间点的激活值。

得到了新的激活值后再进行一次线性运算,把结果传入softmaxsoftmaxsoftmax(多分类时)中得到输出。涉及到的权重是WyaW_{ya}Wya​,由激活值aaa计算输出yyy,以及计算输出的相关偏差byb_yby​。

上面是计算时间点ttt的输出过程,如果考虑整个输入序列的话就是下图所示:

一般初始激活值a⟨0⟩=0a^{\langle0 \rangle}=0a⟨0⟩=0,是一个零向量。

代码实现

回到我们本文情感分析的实例,是一个多对一的结构,我们的RNN读完整段句子,然后来判断这段句子对应的情感类别(只有一个输出y^\hat yy^​)。

import numpy as npdef softmax(x):e_x = np.exp(x - np.max(x))return e_x / e_x.sum(axis=0)def sigmoid(x):return 1 / (1 + np.exp(-x))class RNN:def __init__(self, n_x, n_y, n_a=32):'''初始化n_x : 输入向量x的大小 词典大小n_y : 输出向量y的大小 类别数量n_a :隐藏单元数'''#np.random.seed(1)self.Wax = np.random.randn(n_a, n_x) / 10000self.Waa = np.random.randn(n_a, n_a) / 10000self.Wya = np.random.randn(n_y, n_a) / 10000# 偏差self.ba = np.random.randn(n_a, 1)self.by = np.random.randn(n_y, 1)def forward(self, x):'''前向传播(预测过程)x : 所有时间步的输入,形状(n_x,m,T_x)返回: n_y,m'''n_x, m, T_x = x.shapen_y, n_a = self.Wya.shapea = np.zeros((n_a, m))  # 初始激活值 a<⁰> = 0# 保存输入x,用于反向传播self.x = x# 保存每个时间点的激活值self.a_his = [a]for t in range(T_x):# 时间点t时的输入xt = x[:, :, t]a = np.tanh(self.Wax.dot(xt) + self.Waa.dot(a) + self.ba)  # ba会广播为(n_a,m)self.a_his.append(a)# 多对一的结构,只有在读完整个序列,即最后一个 时间步才计算输出y_pred = softmax(self.Wya.dot(a) + self.by)return y_pred

反向传播

为了进行反向传播,我们需要定义一个损失函数。鉴于我们的输出类别可能有多个,我们就用交叉熵损失函数。其中y^\hat yy^​是我们的预测值,这里是由最后一个时间步的激活值a⟨Tx⟩a^{\langle T_x \rangle}a⟨Tx​⟩进行线性运算后经过softmax得到的;而yyy是真实值。

L=−∑cyclog⁡y^c=−∑cyclog⁡(softmax(zc))(3)L = -\sum_c y_c \log \hat y_c =-\sum_c y_c \log (softmax(z_c)) \tag{3} L=−c∑​yc​logy^​c​=−c∑​yc​log(softmax(zc​))(3)

我们求LLL对zzz的导数dz,具体过程可以参考博客 Softmax与Cross-entropy的求导,得到:
y^−y(4)\hat y- y \tag{4} y^​−y(4)

我们上面说过,这里输入和真实值都是one-hot向量,也就是说,这个公式可以简化为:
y^i−1i(5)\hat y_i - 1_i \tag{5} y^​i​−1i​(5)
也就是说,就是用预测值的某一列减去111即可,其他为零的列不变。

对于最后一个时间步来说:
z⟨Tx⟩=Wyaa⟨Tx⟩+byy^⟨t⟩=softmax(z⟨Tx⟩)z^{\langle T_x \rangle} = W_{ya} a^{\langle T_x \rangle} + by \\ \hat y ^{\langle t \rangle} = softmax(z^{\langle T_x \rangle} ) z⟨Tx​⟩=Wya​a⟨Tx​⟩+byy^​⟨t⟩=softmax(z⟨Tx​⟩)

下面我们求对Wya,byW_{ya},b_yWya​,by​的梯度,此时只需要考虑最后一个激活值到RNN的输出值:

对于WyaW_{ya}Wya​,我们有:

∂L∂Wya=∂L∂z⟨Tx⟩⋅∂z⟨Tx⟩∂Wya(6)\frac{\partial L}{\partial W_{ya}} = \frac{\partial L}{\partial z^{\langle T_x \rangle}} \cdot \frac{\partial z^{\langle T_x \rangle}}{\partial W_{ya}} \tag{6} ∂Wya​∂L​=∂z⟨Tx​⟩∂L​⋅∂Wya​∂z⟨Tx​⟩​(6)

其中a⟨Tx⟩a^{\langle T_x \rangle}a⟨Tx​⟩是最后一个时间步的激活值。有:
∂z⟨Tx⟩∂Wya=a⟨Tx⟩(7)\frac{\partial z^{\langle T_x \rangle}}{\partial W_{ya}} = a^{\langle T_x \rangle} \tag{7} ∂Wya​∂z⟨Tx​⟩​=a⟨Tx​⟩(7)
∂L∂Wya=∂L∂z⟨Tx⟩a⟨Tx⟩(8)\frac{\partial L}{\partial W_{ya}} = \boxed{ \frac{\partial L}{\partial z^{\langle T_x \rangle}} a^{\langle T_x \rangle}} \tag{8} ∂Wya​∂L​=∂z⟨Tx​⟩∂L​a⟨Tx​⟩​(8)

同理,可以得到
∂z⟨Tx⟩∂by=1(9)\frac{\partial z^{\langle T_x \rangle}}{\partial b_y} = 1 \tag{9} ∂by​∂z⟨Tx​⟩​=1(9)
∂L∂by=∂L∂z⟨Tx⟩(10)\frac{\partial L}{\partial b_y} = \boxed {\frac{\partial L}{\partial z^{\langle T_x \rangle}}} \tag{10} ∂by​∂L​=∂z⟨Tx​⟩∂L​​(10)

最后,我们需要计算Waa,Wax,baW_{aa},W_{ax},b_aWaa​,Wax​,ba​的梯度,它们会在RNN的每个时间步中使用。有:

∂L∂Wax=∂L∂z⟨Tx⟩∑tTx∂z⟨Tx⟩∂a⟨t⟩⋅∂a⟨t⟩∂Wax\frac{\partial L}{\partial W_{ax}} = \frac{\partial L}{\partial z^{\langle T_x \rangle}} \sum_t^{T_x} \frac{\partial z^{\langle T_x \rangle}}{\partial a^{\langle t \rangle}} \cdot \frac{\partial a^{\langle t \rangle}}{\partial W_{ax}} ∂Wax​∂L​=∂z⟨Tx​⟩∂L​t∑Tx​​∂a⟨t⟩∂z⟨Tx​⟩​⋅∂Wax​∂a⟨t⟩​
因为改变WaxW_{ax}Wax​会影响所有的a⟨t⟩a^{\langle t \rangle}a⟨t⟩,然后会影响z⟨Tx⟩z^{\langle T_x \rangle}z⟨Tx​⟩和最终的LLL。在计算前向传播时,从左到右,时间点不断增加;而计算反向传播时,我们需要考虑所有的时间,时间点不断减小,来进行反向传播,这被称为BPTT(Backpropagation Through Time)。

在给定的时间点ttt,我们需要计算∂a⟨t⟩∂Wax\frac{\partial a^{\langle t \rangle}}{\partial W_{ax}}∂Wax​∂a⟨t⟩​:

a⟨t⟩=tanh⁡(Waaa⟨t−1⟩+Waxx⟨t⟩+ba)a^{\langle t \rangle} = \tanh(W_{aa} a^{\langle t-1 \rangle} + W_{ax} x^{\langle t \rangle} + b_a) a⟨t⟩=tanh(Waa​a⟨t−1⟩+Wax​x⟨t⟩+ba​)
tanh⁡\tanhtanh的导数我们也推导过:
∂tanh⁡(x)∂x=1−tanh⁡2(x)\frac{\partial \tanh(x)} {\partial x} = 1 - \tanh^2(x) ∂x∂tanh(x)​=1−tanh2(x)
使用链式求导法则可以得到:
∂a⟨t⟩∂Wax=(1−a⟨t⟩2)x⟨t⟩(11)\frac{\partial a^{\langle t \rangle}}{\partial W_{ax}} = \boxed{(1-{a^{\langle t \rangle}}^2)x^{\langle t \rangle}} \tag{11} ∂Wax​∂a⟨t⟩​=(1−a⟨t⟩2)x⟨t⟩​(11)
同理,
∂a⟨t⟩∂Waa=(1−a⟨t⟩2)a⟨t−1⟩(12)\frac{\partial a^{\langle t \rangle}}{\partial W_{aa}} = \boxed{(1-{a^{\langle t \rangle}}^2)a^{\langle t -1\rangle}} \tag{12} ∂Waa​∂a⟨t⟩​=(1−a⟨t⟩2)a⟨t−1⟩​(12)
∂a⟨t⟩∂ba=(1−a⟨t⟩2)(13)\frac{\partial a^{\langle t \rangle}}{\partial b_a} = \boxed{(1-{a^{\langle t \rangle}}^2)} \tag{13} ∂ba​∂a⟨t⟩​=(1−a⟨t⟩2)​(13)

现在就剩下∂z⟨Tx⟩∂a⟨t⟩\frac{\partial z^{\langle T_x \rangle}}{\partial a^{\langle t \rangle}}∂a⟨t⟩∂z⟨Tx​⟩​了,,我们可以递归地求解:
∂z⟨Tx⟩∂a⟨t⟩=∂z⟨Tx⟩∂a⟨t+1⟩⋅∂a⟨t+1⟩∂a⟨t⟩=∂z⟨Tx⟩∂a⟨t+1⟩(1−a⟨t⟩2)Waa(14)\begin{aligned} \frac{\partial z^{\langle T_x \rangle}}{\partial a ^{\langle t \rangle}} &=\frac{\partial z^{\langle T_x \rangle}}{\partial a ^{\langle t+1 \rangle}} \cdot \frac{\partial a^{\langle t+1 \rangle}}{\partial a ^{\langle t \rangle}} \\ &= \frac{\partial z^{\langle T_x \rangle}}{\partial a ^{\langle t+1 \rangle}}(1 - {a ^{\langle t \rangle}}^2) W_{aa} \end{aligned} \tag{14} ∂a⟨t⟩∂z⟨Tx​⟩​​=∂a⟨t+1⟩∂z⟨Tx​⟩​⋅∂a⟨t⟩∂a⟨t+1⟩​=∂a⟨t+1⟩∂z⟨Tx​⟩​(1−a⟨t⟩2)Waa​​(14)

上述公式是我们这个多对一RNN结构的公式,如果是多对多的情况,公式会不一样。

如上图所示,在多对多结构中,在TxT_xTx​时,y^⟨Tx⟩\hat y ^{\langle T_x \rangle}y^​⟨Tx​⟩作为后续节点,此时的计算公式如下(16)(16)(16)所示。而在时间步t(1≤t<Tx)t\,\,(1 \leq t < T_x)t(1≤t<Tx​)时,后续节点有两个,分别是当前时刻的输出z⟨t⟩z ^{\langle t \rangle}z⟨t⟩和当前时刻的激活值a⟨t⟩a ^{\langle t \rangle}a⟨t⟩。在求梯度时要考虑这两种情况。

我们实现BPTT的时候会从最后一个激活值开始,然后反向传播。所以当需要计算∂z⟨Tx⟩a⟨t⟩\frac{\partial z^{\langle T_x \rangle}}{a ^{\langle t \rangle}}a⟨t⟩∂z⟨Tx​⟩​时,我们已经计算了∂z⟨Tx⟩a⟨t+1⟩\frac{\partial z^{\langle T_x \rangle}}{a ^{\langle t+1 \rangle}}a⟨t+1⟩∂z⟨Tx​⟩​,除了计算最后一个激活值:

∂z⟨Tx⟩a⟨Tx⟩=Wya(15)\frac{\partial z^{\langle T_x \rangle}}{a ^{\langle T_x \rangle}}= W_{ya} \tag{15} a⟨Tx​⟩∂z⟨Tx​⟩​=Wya​(15)
∂La⟨Tx⟩=∂LZ⟨Tx⟩⋅∂z⟨Tx⟩a⟨Tx⟩=∂LZ⟨Tx⟩Wya(16)\frac{\partial L}{a ^{\langle T_x \rangle}}= \frac{\partial L}{Z ^{\langle T_x \rangle}} \cdot \frac{\partial z^{\langle T_x \rangle}}{a ^{\langle T_x \rangle}} =\boxed{ \frac{\partial L}{Z ^{\langle T_x \rangle}} W_{ya}} \tag{16} a⟨Tx​⟩∂L​=Z⟨Tx​⟩∂L​⋅a⟨Tx​⟩∂z⟨Tx​⟩​=Z⟨Tx​⟩∂L​Wya​​(16)

现在我们有了需要实现BPTT的所有等式了。

代码实现

def backward(self, dz, learning_rate):'''反向传播的实现dz: 对z的梯度 形状(n_y,m)learning_rate: 学习率'''n_x, m, T_x = self.x.shapen_a = self.Wax.shape[0]# 计算dWya和dbydWya = dz.dot(self.a_his[T_x].T)dby = np.sum(dz, axis=1, keepdims=True)dWax = np.zeros((n_a, n_x))dWaa = np.zeros((n_a, n_a))dba = np.zeros((n_a, 1))# 计算最后一个激活值的梯度 公式(16)da = np.dot(self.Wya.T, dz)  # (n_a,m)# BPTTfor t in reversed(range(T_x)):# 计算da * (1-a^2)temp = np.multiply(da, 1 - self.a_his[t + 1] ** 2)  # (n_a,m)# 计算dbadba += np.sum(temp, axis=1, keepdims=True)dWaa += temp.dot(self.a_his[t].T)dWax += temp.dot(self.x[:, :, t].T)  # n_a,n_xda += np.dot(self.Waa, temp)# 防止梯度爆炸for d in [dWax, dWaa, dWya, dba, dby]:np.clip(d, -1, 1, out=d)  # 将梯度限制在[-1,1]self.Waa -= learning_rate * dWaaself.Wax -= learning_rate * dWaxself.Wya -= learning_rate * dWyaself.ba -= learning_rate * dbaself.by -= learning_rate * dby

完整代码

import numpy as npdef softmax(x):e_x = np.exp(x - np.max(x))return e_x / e_x.sum(axis=0)def sigmoid(x):return 1 / (1 + np.exp(-x))class RNN:def __init__(self, n_x, n_y, n_a=32):'''初始化n_x : 输入向量x的大小 词典大小n_y : 输出向量y的大小 类别数量n_a :隐藏单元数'''#np.random.seed(1)self.Wax = np.random.randn(n_a, n_x) / 10000self.Waa = np.random.randn(n_a, n_a) / 10000self.Wya = np.random.randn(n_y, n_a) / 10000# 偏差self.ba = np.random.randn(n_a, 1)self.by = np.random.randn(n_y, 1)def forward(self, x):'''前向传播(预测过程)x : 所有时间步的输入,形状(n_x,m,T_x)返回: n_y,m'''n_x, m, T_x = x.shapen_y, n_a = self.Wya.shapea = np.zeros((n_a, m))  # 初始激活值 a<⁰> = 0# 保存输入x,用于反向传播self.x = x# 保存每个时间点的激活值self.a_his = [a]for t in range(T_x):# 时间点t时的输入xt = x[:, :, t]a = np.tanh(self.Wax.dot(xt) + self.Waa.dot(a) + self.ba)  # ba会广播为(n_a,m)self.a_his.append(a)# 多对一的结构,只有在读完整个序列,即最后一个 时间步才计算输出y_pred = softmax(self.Wya.dot(a) + self.by)return y_preddef backward(self, dz, learning_rate):'''反向传播的实现dz: 对z的梯度 形状(n_y,m)learning_rate: 学习率'''n_x, m, T_x = self.x.shapen_a = self.Wax.shape[0]# 计算dWya和dbydWya = dz.dot(self.a_his[T_x].T)dby = np.sum(dz, axis=1, keepdims=True)dWax = np.zeros((n_a, n_x))dWaa = np.zeros((n_a, n_a))dba = np.zeros((n_a, 1))# 计算最后一个激活值的梯度 公式(16)da = np.dot(self.Wya.T, dz)  # (n_a,m)# BPTTfor t in reversed(range(T_x)):# 计算da * (1-a^2)temp = np.multiply(da, 1 - self.a_his[t + 1] ** 2)  # (n_a,m)# 计算dbadba += np.sum(temp, axis=1, keepdims=True)dWaa += temp.dot(self.a_his[t].T)dWax += temp.dot(self.x[:, :, t].T)  # n_a,n_xda += np.dot(self.Waa, temp)# 防止梯度爆炸for d in [dWax, dWaa, dWya, dba, dby]:np.clip(d, -1, 1, out=d)  # 将梯度限制在[-1,1]self.Waa -= learning_rate * dWaaself.Wax -= learning_rate * dWaxself.Wya -= learning_rate * dWyaself.ba -= learning_rate * dbaself.by -= learning_rate * dbydef fit(self, X_train, Y_train, epochs=30, mini_batch_size=20, learning_rate=2e-2, print_cost=False, X_test=None,Y_test=None):'''param X_train:  input data of size (n_x, m,T_x)param Y_train:  labels of shape (n_y,m)return'''m = X_train.shape[1]for i in range(epochs):indexes = np.random.permutation(m)X_mini_batches = [X_train[:, indexes, :][:, k:k + mini_batch_size, :] for k in range(0, m, mini_batch_size)]y_mini_batches = [Y_train[:, indexes][:, k:k + mini_batch_size] for k in range(0, m, mini_batch_size)]num_correct = 0for X_batch, y_batch in zip(X_mini_batches, y_mini_batches):y_pred = self.forward(X_batch)dz = y_pred - y_batchself.backward(dz, learning_rate)i_pred = np.argmax(y_pred, axis=0)i_batch = np.argmax(y_batch, axis=0)num_correct += np.sum(i_pred == i_batch)if print_cost and i % 100 == 99 and X_test is not None:print("Train accuracy : %.3f \t Test accuracy : %.3f after iteration %i" % (num_correct/m,self.evaluate(X_test,Y_test) , i+1))def evaluate(self, X_test, Y_test):y_pred = self.forward(X_test)i_pred = np.argmax(y_pred, axis=0)i_test = np.argmax(Y_test, axis=0)return np.sum(i_pred == i_test) / X_test.shape[1]

下面测试一下我们的RNN

train_data = {'good': 'T','bad': 'F','happy': 'T','sad': 'F','not good': 'F','not bad': 'T','not happy': 'F','not sad': 'T','very good': 'T','very bad': 'F','very happy': 'T','very sad': 'F','i am happy': 'T','this is good': 'T','i am bad': 'F','this is bad': 'F','i am sad': 'F','this is sad': 'F','i am not happy': 'F','this is not good': 'F','i am not bad': 'T','this is not sad': 'T','i am very happy': 'T','this is very good': 'T','i am very bad': 'F','this is very sad': 'F','this is very happy': 'T','i am good not bad': 'T','this is good not bad': 'T','i am bad not good': 'F','i am good and happy': 'T','this is not good and not happy': 'F','i am not at all good': 'F','i am not at all bad': 'T','i am not at all happy': 'F','this is not at all sad': 'T','this is not at all happy': 'F','i am good right now': 'T','i am bad right now': 'F','this is bad right now': 'F','i am sad right now': 'F','i was good earlier': 'T','i was happy earlier': 'T','i was bad earlier': 'F','i was sad earlier': 'F','i am very bad right now': 'F','this is very good right now': 'T','this is very sad right now': 'F','this was bad earlier': 'F','this was very good earlier': 'T','this was very bad earlier': 'F','this was very happy earlier': 'T','this was very sad earlier': 'F','i was good and not bad earlier': 'T','i was not good and not happy earlier': 'F','i am not at all bad or sad right now': 'T','i am not at all good or happy right now': 'F','this was not happy and not good earlier': 'F',
}test_data = {'this is happy': 'T','i am good': 'T','this is not happy': 'F','i am not good': 'F','this is not bad': 'T','i am not sad': 'T','i am very good': 'T','this is very bad': 'F','i am very sad': 'F','this is bad not good': 'F','this is good and happy': 'T','i am not good and not happy': 'F','i am not at all sad': 'T','this is not at all good': 'F','this is not at all bad': 'T','this is good right now': 'T','this is sad right now': 'F','this is very bad right now': 'F','this was good earlier': 'T','i was not happy and not good earlier': 'F',
}# -*- coding: utf-8 -*-
# @Time    : 2020-9-22 13:51
# @Author  : Jue
import numpy as npdef text2vector(text, vocab_size, T_x):'''返回一个句子的one-hot向量表示 (n_x,T_x)- text :句子- vocab_size : 词典大小- T_x : 句子最长长度'''inputs = np.zeros((vocab_size, T_x))t = 0for w in text.split(' '):v = np.zeros(vocab_size)v[word_to_idx[w]] = 1inputs[:, t] = vt += 1if t == T_x:breakreturn inputs# 返回句子最大长度
def getTx(train_data):return max(len(text.split(' ')) for text in train_data)def getData(data, vocab_size, label_dic):'''return:x : 所有时间步的输入,形状(n_x,m,T_x)'''# text ,labelt_x = getTx(data.keys())items = list(data.items())m = len(items)X = np.zeros((vocab_size, t_x, m))  # n_x,t_x,mY = np.zeros((2, m))i = 0for x, y in items:inputs = text2vector(x, vocab_size, t_x)X[..., i] = inputsY[label_dic[y], i] = 1i += 1X = X.transpose(0, 2, 1)return X, Yif __name__ == '__main__':labels = ['T', 'F']d = {l: i for i, l in enumerate(labels)}vocab = list(set([w for text in train_data.keys() for w in text.split(' ')]))vocab_size = len(vocab)print('%d unique words found' % vocab_size)# Assign indices to each word.word_to_idx = {w: i for i, w in enumerate(vocab)}idx_to_word = {i: w for i, w in enumerate(vocab)}X_train, Y_train = getData(train_data, vocab_size, d)X_test, Y_test = getData(test_data, vocab_size, d)

上面是把数据集转换成我们想要的向量化表示

rnn = RNN(vocab_size, 2)
rnn.fit(X_train, Y_train, mini_batch_size=8, epochs=2000, print_cost=True,X_test=X_test,Y_test=Y_test)


同时打印出训练集的准确率和测试集的准确率。由于数据比较简单,这里可以得到100%的准确率。
可以看到RNN能学到not这种否定表达。

参考

  1. 吴恩达深度学习课程
  2. An Introduction to Recurrent Neural Networks for Beginners
  3. 吴恩达深度学习——循环神经网络
  4. Softmax与Cross-entropy的求导

从零实现循环神经网络相关推荐

  1. 零基础入门深度学习(5) - 循环神经网络

    往期回顾 在前面的文章系列文章中,我们介绍了全连接神经网络和卷积神经网络,以及它们的训练和使用.他们都只能单独的取处理一个个的输入,前一个输入和后一个输入是完全没有关系的.但是,某些任务需要能够更好的 ...

  2. 零基础入门深度学习(5) - 循环神经网络【转】

    本文转载自:https://zybuluo.com/hanbingtao/note/541458 在前面的文章系列文章中,我们介绍了全连接神经网络和卷积神经网络,以及它们的训练和使用.他们都只能单独的 ...

  3. 循环神经网络 递归神经网络_如何用递归神经网络预测空气污染

    循环神经网络 递归神经网络 After the citizen science project of Curieuze Neuzen, I wanted to learn more about air ...

  4. RNN 扫盲:循环神经网络解读及其 PyTorch 应用实现

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 来自 | 知乎 作者 | Lucas 地址 | https://z ...

  5. Nat. Mach. Intell. | 利用条件循环神经网络生成特定性质分子

    作者 | 陆丰庆 今天给大家介绍瑞士知名药企阿斯利康和伯尔尼大学的 Esben Jannik Bjerrum团队在Nature Machine Intelligence上的一篇论文.该研究提出基于分子 ...

  6. 独家 | 菜鸟必备的循环神经网络指南(附链接)

    作者:Victor  Zhou 翻译:王雨桐 校对:吴金迪 本文约3800字,建议阅读15分钟. 本文将介绍最基础的循环神经网络(Vanilla RNNs)的概况,工作原理,以及如何在Python中实 ...

  7. 一文搞懂RNN(循环神经网络)

    基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...

  8. 简单入门循环神经网络RNN:时间序列数据的首选神经网络

    更多深度文章,请关注:https://yq.aliyun.com/cloud 随着科学技术的发展以及硬件计算能力的大幅提升,人工智能已经从几十年的幕后工作一下子跃入人们眼帘.人工智能的背后源自于大数据 ...

  9. 循环神经网络(RNN, Recurrent Neural Networks)介绍

    循环神经网络(RNN, Recurrent Neural Networks)介绍   循环神经网络(Recurrent Neural Networks,RNNs)已经在众多自然语言处理(Natural ...

  10. 【阿里云课程】循环神经网络:RNN及其改进

    大家好,继续更新有三AI与阿里天池联合推出的深度学习系列课程,本次更新内容为第8课中的一节,介绍如下: RNN及其改进 本节课内容为:深度学习系列课程第8期,讲述循环神经网络原理与优化,门控时序网络之 ...

最新文章

  1. 沈向洋出任董事长李笛任CEO,「微软」小冰变身「中国」小冰
  2. Firebug和Yslow是个好工具
  3. 网络分析系统_MetagenoNets:在线宏基因组网络分析实操教程
  4. 【Java面试题】48 GC是什么? 为什么要有GC?
  5. Statistical language model 统计语言模型
  6. 数据源名称和 64 位操作系统
  7. 还在维护吗_你的模具生锈了吗?来了解一下这些防锈维护事项
  8. 文档丨Oracle 三种迁移方案
  9. 在 Windows 10 中查找 BitLocker 恢复密钥
  10. 21. Upgrade-Insecure-Requests: 1
  11. eclipse中输入@符号自动提示Annotation
  12. typescript step by step interface class
  13. Windows下配置安装Git(一)
  14. lisp方格网法计算土方量_CASS方格网法如何计算土方量
  15. 超级计算机计算峰值,世界运算最快计算机,中国神威·太湖之光(其峰值计算速度达每秒1...
  16. 【树莓派】树莓派系统安装
  17. 编译原理(二)文法和语言、符号和符号串、文法的类型、语法树
  18. 网站 被降权的四种处理方法
  19. 详解EBS接口开发之销售订单挑库发放
  20. 配置IKAnalyzer扩展词库

热门文章

  1. DMA驱动开发(6,参考资料)有用链接
  2. Laser Reflections solutions
  3. [MATLAB]MATLAB中SIMULINK常用命令表
  4. [微软官网] SQLSERVER 执行页面还原
  5. Openresty 与 Tengine
  6. 用 mCustomScrollbar 滚动条插件实现滚动更新添加数据
  7. jQuery - slice( start, [end] ) Method
  8. JAVA笔记12__字节、字符缓冲流/打印流/对象流/
  9. Delphi7中默认没有安装的官方控件
  10. 如何用C#写一个简单的Login窗口