2020机器学习循环神经网(1)

machine_learning.jpg

循环神经网络

这是一个假期,与机器学习摸爬滚打已经快半年,经历了几个阶段走到今天,开始的热情,过程的痛苦和绝望,今天的坚持。希望不但分享机器学习中的技术,重在分享学习过程

推荐

推荐李宏毅老师的机器学习(bu)
推荐动手深度学习
推荐吴恩达的机器学习
国外论坛分享

大纲

  • 应用

  • 语言模型

  • 词向量

  • 隐马尔科夫模型

  • 循环神经网

  • LSTM(Long short-Term Memory)

  • 循环神经网络反向传播

  • 实例

    • 创作音乐
    • 自然语言处理

应用

我们今天说到自然语言处理就会想到循环神经网络,所循环神经网最佳实践就是自然语言处理。

今天完全用 numpy 来实现一个循环神经网。
slot Filling(技术
在这里李宏毅老师举一个智能订票系统举例,我们通过客户语音提取一些关键词。

循环神经网络

将词汇以向量形式输入到模型,我们得到该词是目的地时间。但是我们词汇失去上下文也就是失去意义,例如地点是出发地还是目的地是由其附近的动词所决定。这时候我们就需要让神经网有记忆,记忆下中心词周围背景词汇来作为推断中心词的数据。

序列问题

我们知道每一个机器学习模型都是针对解决一类问题,卷积神经网络是用来解决图像相关问题。那么循环神经网络又是用来解决什么样的问题呢?今天先拿出一个问题来引出今天主角循环神经网。
举一个自动订票系统。系统可以根据客户输入,文字或语音来完成订票。客户信息中可能包括始发站和终点站信息,例如我要订从沈阳到上海途径辽阳的xxx车次。如果只是用简单神经网络,虽然他们推出站名,却无法分清从哪到哪。因为在神经网对输入同一个词汇,输出也是相同,这是因为这个词缺失其所在语境。但是在现实生活中,同一个词出现不同位置就有不同含义,甚至我们还要看说话人表情和语气。这也是为什么自然语言处理是一个难题的原因。
那么我们人类是如何解决这个问题的呢,这是因为我们通读一段文章,或者倾听人们一段话再做出判断,和推测其含义。这都是归功我们是有记忆的。所以只要在神经网络添加记忆功能,让神经网络在时间序列上看的更远才能解决这个问题。
那么我们是怎么来做这件事,就是在神经元添加存储单元(下图中 store),当神经元有输出时,就会将下这些输出保存到 store,然后在下一次有输入的时候,神经元不仅考虑输入还会考虑这些 store 中保存的数据。下面我们来举一个例子

rnn_001.png

这里我们建立一个简单神经网络来模拟这件事,神经网络中的所有神经元的 weight 都是 1,没有激活函数和偏置,这里在隐藏层连接一个 store 用于存储隐藏层的输出,store 初始值是 0。

import numpy as np
input_sequence = np.array([
    [1,1],
    [1,1],
    [2,2]
])
print(input_sequence[0].shape)
(2,)
store = np.zeros((2))

def NN(input_seq,_store):
    epoches,input_dim = input_seq.shape
    weights = np.ones((input_dim))
    for i in range(epoches):
        print("step %d " % i)
        # 计算第一层神经网络
        hidden_1 = np.zeros((input_dim))
        print(f'input:{input_sequence[i]}')
        for j in range(input_dim):
            hidden_1[j] = np.sum(np.dot(weights,input_sequence[i]))
        print(f'hidden_1 ouput {hidden_1 + _store}')
        _store = hidden_1
    return hidden_1

print(NN(input_sequence,store))

step 0 
input:[1 1]
hidden_1 ouput [2. 2.]
step 1 
input:[1 1]
hidden_1 ouput [4. 4.]
step 2 
input:[2 2]
hidden_1 ouput [6. 6.]
[4. 4.]
rnn_002.png

这里例子只是想说明一个一件事,有了记忆单元之后,即使每一次输入都是相同,但是神经网络输出且不再是同样输入,code 我们只是演示输入对一层神经网络输出的影响。

Elman Network 和 Jordan Network

rnn_003.png

有关这两个循环神经网络并不是今天重点,但是还是简单介绍一下,从图上可以清晰看到他们的不同,就是在 Jordan Network 是每一个时刻输出作为下一个时刻输入,这样好处就是我们可以控制和可见输入。

双向循环神经网

rnn_005.png

在双向循环神经网络上,神经元在时间上看的更广,不仅可以看到当前时刻的上一个时刻,而且能够看到其下一时刻,也就是每一次可以看到整个序列。

LSTM

当然我们看循环神经网结构并不复杂,接下来我们介绍就是循环神经网在产品中会使用 LSTM(Long Short-Term Memory),其实简单理解就是在存储单元前后各加一个阀门,前置门用于控制是否可以将数据写入到存储单元,后置门用于控制是否可以将数据从存储单元读取到神经网中。至于什么时候开启他们是由神经网络自己学出来的,听起来有点神奇。除了控制输入和输出两个门还有一个遗忘门,用于清空存储单元。


lstm_001.png
lstm_002.jpeg

我们想一想这里,函数使用的sigmoid 函数,因为 sigmoid 输出是 0 或 1 用于控制输入
C_t = \sigma(x_t)\sigma_i(x_t) + \sigma_f(x_t)C_t

  • \sigma(x_t) 表示激活函数
  • \sigma_i 控制输入激活函数
  • \sigma_f 表示控制遗忘的激活函数
  • C_t^{\prime} 表示更新的存储单元

h_t = \sigma_o(x_t)C_t

lstm_003.png

这一块感觉李宏毅教授讲比较细,所以我直接引用跟大家一起重温一下这个可以帮助我们很好理解 LSTM 的例子。看上面图,假设输入 X 都是 3 维向量,输出 y 是 1 维向量。


lstm_005.png

假设第 2 维的值是 1 ,那么x_1就会被写入到存储单元。

lstm_006.png

如果第 2 维的值是 -1 时候,那么存储单元值就会被遗忘。

lstm_007.png

如果第 3 维值是 1 就是存储单元进行对外输出他的值。

lstm_008.png

那么就有上面情况,蓝色部分表示存储单元在某一个时刻的值,这个我们就不解释了吧,很好理解自己推导一下。

lstm_009.png

在上图中我们刚才做的事情挪到LSTM上再做一遍,我只是把LSTM中是如何实现对输入、输出和遗忘的控制简单说明一下,具体推导过程我就不再重复了。

  • 输入w_1 = [1,0,0,0] 乘以 X 表示输入 x_1
  • 输入门: 这里偏置为 -10 也就是默认时这个门是关闭,只有输入 X 的第 2 维为 1 时候 1 \times 100 - 10 = 90 时候这个输入门才开启

其实对于 LSTM 我们也不用想太多,其实他也是神经元,只不过他参数更多,因为 LSTM 神经元根据输入要做更多事情所以他的参数更多。

lstm_010.png

其实现在我们做的循环神经网(RNN)都是 LSTM,其实这个看起来相对于其他神经网络,的确有点复杂,所谓复杂是我们如何定义损失函数,然后如果通过优化来更新怎么多参数,接下来我们一起看一看。

循环神经网络反向传播

lstm_015.png

我们先把按神经网前向传播过程中传递函数都写一下,这里h_t表示隐藏层,W_{hx}W_{hh}分别表示隐藏层权重和状态转移的权限。其实在神经网络时候我们一定要清楚的就是每一个变量的和参数的形状。这个可是大家看似简单,但是确实比较重要的部分。
h_t = \phi(W_{hx}x_t + W_{hh}h_{t-1})

  • h_t(h \times 1)
  • x_t(N \times 1)
  • W_{hx}(h \times N)
  • h_{t-1}(h \times 1)
  • W_{hh}(h \times h)
    然后接下来写从隐藏层到输出层
    o_t = W_{yh} h_t
  • o_t(y \times 1)
  • W_{yh}(y \times h)
  • h_t(h \times 1)
    最后写损失函数,损失函数是将序列中每一个时刻损失函数进行求和来得出损失函数,这里 L 是一个标量。
    L = \frac{1}{T} \sum_{t=1}^T l(o_t,y_t)

先 L 对于 o 进行求导
\frac{\partial L}{\partial o_t} = \frac{\partial l(o_t,y_t)}{T \cdot \partial o_t}

  • \frac{\partial L}{\partial o_t}(y \times 1)

然后就可以用反向传播来求循环神经网中存储和梯度下降更新参数

L 对于 W_{yh} 进行求导,每一个时刻 o_t 都会连接到 L 损失函数(根据链式求导)L 对于 W_{yh} 求导可以写成下面样子
\frac{\partial L}{\partial W_{yh}} = \sum_{t=1}^T \frac{\partial L}{\partial o_t}\frac{\partial o_t}{\partial W_{yh}} = \sum_{i=1}^T \frac{\partial L}{\partial o_t} h_t^T

L 对于 h_t 求导
这里 L 对于h_t 的求导,相比我们之前学习到求导略显复杂,我们先考虑每一个事件序列最后一个h_T,这里 T 表示序列最后一个时刻,在最后一个时刻没有其他途径只有一条途径可以从h_T 到 损失函数 L
\frac{\partial L}{\partial h_T} = \frac{\partial L}{\partial o_T}\frac{\partial o_T}{\partial h_T} = W_{yh}^T \frac{\partial L}{\partial o_T}

我们在求导过程建议一定把计算图清楚画出来,这样就可清晰看出求导流向,这里我们选择h_2 作为h_t 观察 L 是如何对中间过程的h_t 进行求导的。
\frac{\partial L}{\partial h_t} = \frac{\partial L}{\partial h_{t+1}} \frac{\partial h_{t+1}}{\partial h_t} + \frac{\partial L}{\partial o_t} \frac{\partial o_t}{\partial h_t} = W_{hh}^T \frac{\partial L}{\partial h_{t+1}} + W_{yh}^T \frac{\partial L}{\partial o_t}

最后就是 L 对隐藏层参数 W_{hx}W_{hh} 进行求导来更新这两参数,这里W_{hx}W_{hh} 每一个时刻都有一个途径流向损失函数,所有可以对他们进行求和来进行求导。

\frac{\partial L}{\partial W_{hx}} = \sum_{t=1}^T \frac{\partial L}{\partial h_t} \frac{\partial h_t}{\partial W_{hx}} = \sum_{t=1}^T \frac{\partial L}{\partial h_t} x_t^T

\frac{\partial L}{\partial W_{hh}} = \sum_{t=1}^T \frac{\partial L}{\partial h_t} \frac{\partial h_t}{\partial W_{hh}} = \sum_{t=1}^T \frac{\partial L}{\partial h_t} h_{t-1}^T

<img src="images/lstm_012.png" src="100%"/>

在循环神经网络中,我们训练是在早期是一个比较困难事情,因为损失函数很不稳定
<img src="images/lstm_011.jpeg" src="100%"/>

最后希望大家关注我们微信公众号


wechat.jpeg
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容