递归神经网络(Recurrent Neural Network, RNN)是一种专门处理序列的神经网络。它们通常用于自然语言处理(NLP) 任务,因为RNN在处理文本方面非常有效。
第一句话:我喜欢吃苹果!
第二句话:苹果真是一家很棒的公司!
如果任务是要给苹果打标签,RNN就结合上下文去训练模型,能够准确识别出第一个苹果是一种水果,第二个是苹果公司,
RNN能够将可变长度序列作为输入和输出

- 固定大小的输入到固定大小的输出(例如图像分类)。
- 序列输出(例如,从图像字幕生成)。
- 序列输入(例如,句子情感分析)。
- 序列输入和序列输出(例如机器翻译)。
- 同步序列输入和输出(例如,视频分类)
实现过程
正向传播:
如果使用一个“多对一”的RNN,来进行情感分析,即输入词向量 x0,x1,...,xn,获得y属于正向\负向情感的概率
如下图:

RNN的计算遵循如下规则:
-
下一个隐藏状态是使用上一个隐藏状态和下一个输入决定的
ht=tanh(Wxhxt+Whhht−1+bh),式子中使用了tanh作为激活函数
-
输出向量y由h所决定
yt=Whtht+by
其中, Wxh为用来链接** x→h的权重, Whh用来链接 hn→hn+1的权重, Why用来链接 ht→yt** 的权重,
bh用来链接 x→h的偏差, by用来链接 h→y 的偏差
反向传播
训练RNN权重参数,首先需要一个损失函数。通常使用Softmax与交叉熵损失函数,作为损失函数。
将L对y进行求导:
softmax(y_i),\quad i\neq c\\
\end{cases}例如,如 softmax(y) = [0.2,0.2,0.6],正确的类别为 0 ,那么对L求导得到:[-0.8, 0.2, 0.6]
再将L分别对RNN中所有参数进行求导:
因为变化 Wxh会影响每一个 hn,这都会影响最终的损失函数,因此我们需要将每个时间的梯度都计算出来然后加起来,作为每次 Wxh更新的梯度值,这称为时序反向传播算法(BPTT)

=∂(Wxhxt+Whhht−1+bh)∂ ht∗∂Wxh∂(Wxhxt+Whhht−1+bh)=(1−ht2)xt(5)\frac{∂h_t}{∂W_{hh}}=(1-h_t^2)h_{t-1} \tag 6
\frac{∂h_t}{∂b_{h}}=(1-h_t^2) \tag7
<hr>\frac{∂y}{∂h_{t}}=\begin{cases}
\frac{∂y}{∂h_{t+1}}*(1-h_t^2)W_{hh} \quad t\neq n\
W_{hy}\quad t=n
\end{cases}
\tag 8
联合①⑤⑧得到$\ {∂L}/{∂W_{xh}}$
联合①⑥⑧得到$\ {∂L}/{∂W_{hh}}$
联合①⑦⑧得到$\ {∂L}/{∂b_{h}}$
### **梯度下降**
计算完所有梯度后,使用**梯度下降来**更新权重和偏差。
W_{hh}= W_{hh}-\mu\frac{∂L}{∂W_{hh}}\\
W_{hy}= W_{hy}-\mu\frac{∂L}{∂W_{hy}}\\
b_{y}= b_{y}-\mu\frac{∂L}{∂b_{y}}\\
## 代码
```python
import numpy as np
from numpy import random
'''
h=[64,1]
whh[64,64]
bh[64,1]
y=[2,1]
class RNN:
# Define hidden layer has 64 neurons
self.Wxh = random.randn(hidden_size, input_size) / 1000
self.bh = np.zeros((hidden_size, 1))
self.last_hs = {}
h = np.zeros((self.Whh.shape[0], 1))
self.last_hs = {0: h}
h = np.tanh(self.Wxh @ x + self.Whh @ h + self.bh)
# enumerate:枚举 @:矩阵的乘法
return y
def backprop(self, d_y, learn_rate=2e-2):
d_Why = d_y @ self.last_hs[n].T
d_Wxh = np.zeros(self.Wxh.shape)
d_by = d_y
d_h = self.Why.T @ d_y
temp = ((1 - self.last_hs[t + 1] ** 2) * d_h)
d_Whh += temp @ self.last_hs[t].T
d_h = self.Whh @ temp
for d in [d_Wxh, d_Whh, d_Why, d_bh, d_by]:
# Using gradient descent to update the weights and biases
self.Wxh -= learn_rate * d_Wxh
self.bh -= learn_rate * d_bh
def softMax(x):
def createInputs(text):
for w in text.split(' '):
v[word_to_idx[w]] = 1
return inputs
items = list(data.items())
loss = 0
for x, y in items:
target = int(y)
prob = softMax(out)
num_correct += int(np.argmax(prob) == target) # argmax :index of the max value
# get DL\Dy
d_y[target] -= 1
rnn.backprop(d_y)
if __name__ == '__main__':
vocab = list(set([w for text in train_data.keys() for w in text.split(' ')]))
word_to_idx = {w: i for i, w in enumerate(vocab)}
# Generate rnn
print("负情感" if np.argmax(softMax(rnn.forward(createInputs("i am not earlier")))) == 0 else "正情感")
train_loss, train_acc = processData(train_data)
print(f'--- Epoch {epoch + 1}')
test_loss, test_acc = processData(test_data, backprop=False)
print("负情感"if np.argmax(softMax(rnn.forward(createInputs("i am not earlier"))))==0 else "正情感")
```
简单的数据集:
```python
'good': True,
'happy': True,
'not good': False,
'not happy': False,
'very good': True,
'very happy': True,
'i am happy': True,
'i am bad': False,
'i am sad': False,
'i am not happy': False,
'i am not bad': True,
'i am very happy': True,
'i am very bad': False,
'this is very happy': True,
'this is good not bad': True,
'i am good and happy': True,
'i am not at all good': False,
'i am not at all happy': False,
'this is not at all happy': False,
'i am bad right now': False,
'i am sad right now': False,
'i was happy earlier': True,
'i was sad earlier': False,
'this is very good right now': True,
'this was bad earlier': False,
'this was very bad earlier': False,
'this was very sad earlier': False,
'i was not good and not happy earlier': False,
'i am not at all good or happy right now': False,
}
test_data = {
'i am good': True,
'i am not good': False,
'i am not sad': True,
'this is very bad': False,
'this is bad not good': False,
'i am not good and not happy': False,
'this is not at all good': False,
'this is good right now': True,
'this is very bad right now': False,
'i was not happy and not good earlier': False,
```
运行结果
```python
--- Epoch 100
Test Loss: [0.699] | Accuracy: 0.5
--- Epoch 1000
Test Loss: [0.004] | Accuracy: 1.0
```
在训练了1000代后,能够正确的预测结果了