RNN

递归神经网络(Recurrent Neural Network, RNN)是一种专门处理序列的神经网络。它们通常用于自然语言处理(NLP) 任务,因为RNN在处理文本方面非常有效。

第一句话:我喜欢吃苹果!

第二句话:苹果真是一家很棒的公司!

如果任务是要给苹果打标签,RNN就结合上下文去训练模型,能够准确识别出第一个苹果是一种水果,第二个是苹果公司,

RNN能够将可变长度序列作为输入和输出

断续器

  • 固定大小的输入到固定大小的输出(例如图像分类)。
  • 序列输出(例如,从图像字幕生成)。
  • 序列输入(例如,句子情感分析)。
  • 序列输入和序列输出(例如机器翻译)。
  • 同步序列输入和输出(例如,视频分类)

实现过程

正向传播:

如果使用一个“多对一”的RNN,来进行情感分析,即输入词向量 x0,x1,...,xn,获得y\ x_0,x_1,...,x_n,获得y属于正向\负向情感的概率

如下图:

img

RNN的计算遵循如下规则:

  • 下一个隐藏状态是使用上一个隐藏状态和下一个输入决定的

     ht=tanh(Wxhxt+Whhht1+bh)\ h_t=tanh(W_{xh}x_t+W_{hh}h_{t-1}+b_h),式子中使用了tanh作为激活函数

  • 输出向量yh所决定

     yt=Whtht+by\ y_t=W_{ht}h_t+b_y

其中, Wxh\ W_{xh}为用来链接** xh\ x→h的权重, Whh\ W_{hh}用来链接 hnhn+1\ h_n→h_{n+1}的权重, Why\ W_{hy}用来链接 htyt\ h_t→y_t** 的权重,

 bh用来链接\ b_{h}用来链接 xh\ x→h的偏差, by\ b_{y}用来链接  hy\ 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\ W_{xh}会影响每一个 hn\ h_n,这都会影响最终的损失函数,因此我们需要将每个时间的梯度都计算出来然后加起来,作为每次 Wxh\ W_{xh}更新的梯度值,这称为时序反向传播算法(BPTT)

img

= ht(Wxhxt+Whhht1+bh)(Wxhxt+Whhht1+bh)Wxh=(1ht2)xt(5)=\frac{∂\ h_t}{∂(W_{xh}x_t+W_{hh}h_{t-1}+b_h)}*\frac{∂(W_{xh}x_t+W_{hh}h_{t-1}+b_h)}{∂W_{xh}}\\ =(1-h_t^2)x_t \tag {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><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代后,能够正确的预测结果了