CNNs 利用数据的局部相关性和权值共享的思想大大减少了网络的参数量,非常适合于图片这种具有空间局部相关性的数据。
然而除了空间维度之外,自然界还有一个时间维度。
e.g. 说话时发出的语音信号,随着时间变化的股市参数等等。这类数据并不一定具有局部相关性,同时数据在时间维度上的长度也是可变的,并不适合CNNs。
11.1 序列表示方法
具有先后顺序的数据一般叫做序列,比如随时间而变化的商品价格数据就是非常典型的序列。
如果希望神经网络能够用于 nlp 任务,那么怎么把单词或字符转化为数值就变得关键(因为,神经网络是一系列矩阵相乘,相加等运算)。
接下来探讨文本序列的表示方法,其他非数值类型的信号可以参考文本序列的表示方法。
- one-hot 编码
以英文句子为例,假设我们只考虑最常用的一万个单词,那么一个单词就可以表示为某位为1,其他位置为0的长度为10000的稀疏向量。
把文字编码为数值的算法叫做 Word embedding,它也表示经过这个过程后得到的词向量,具体表示算法还是词向量需要依语境定。
one-hot 编码的向量是高维度而且极其稀疏的,大量位置为0,计算效率较低,同时也不利于神经网络的训练。
one-hot 编码还有个问题,得到的向量没有数值关系,忽略了单词先天具有的语义相关性。e.g. like / dislike。
一个衡量表示向量的尺度就是余弦相关度:
余弦相关度11.1.1 Embedding 层
在神经网络中,单词的表示向量可以直接通过训练的方式得到,把单词的表示层叫做 Embedding 层。
Embedding 层负责把单词编码为某个向量,他接受的是采用数字编码的单词,如 2 表示 “I”,3 表示“me”等,系统总单词数量记作,输出长度为的向量:
Embedding 层通过一个 shape 为 的查询表 table,对于任意的,只需查询到对应位置上的向量返回即可:
Embedding 层是可训练的,它可放置在神经网络之前,完成单词到向量的转换,得到的表示向量可以继续通过神经网络完成后续任务。
TensorFlow中:
layers.Embedding(N_vocab, f)
11.1.2 预训练的词向量
Embedding 层的查询表是随机初始化的,需要从零开始训练。
可以使用预训练的 Word Embedding 模型来得到单词的表示方法,基于预训练模型的词向量相当于迁移了整个语义空间的知识,往往能得到更好的性能,e.g. Word2Vec,GloVe。
通过设置 Embedding 层中不采用随机初始化的方式,而是使用预训练的词向量模型来帮助提升 NLP 任务的性能:
图 1这里这个 load_embed 函数????
11.2 循环神经网络
以情感分析为例:
情感分析
通过 Embedding 层把句子转换为[b,s,f]的张量,b 是 batch,s 是句子长度,f 为词向量长度。
由于输入的文本数据,传统 CNNs 并不能取得很好的效果。
11.2.1 全连接层可行吗
对于每一个词向量,分别使用一个全连接层提取语义特征。
全连接层提取语义特征1 缺点11.2.2 共享权值
上面使用个全连接层来提取特征没有使用权值共享的思想。
全连接层提取语义特征2虽然这种方法减少了参数量,并且让每个序列的输出长度都一样,但是还是将整个句子拆开来分别理解,无法获取整体的语义信息
11.2.3 语义信息
为了让网络能够按序提取词向量的语义信息,并累积成整个句子的语境信息,使用内存(Memory)机制。
Memory机制
除了参数之外,额外增加了一个参数,每个时间戳上的状态张量刷新机制为:
最后得到的能较好地代表了句子的语义信息。
11.2.4 循环神经网络
在每一个时间戳,网络层接受当前时间戳的输入和上一个时间戳的网络状态向量,经过:
然后将输入到下一层。
在循环神经网络中,激活函数更多地采用 tanh 函数。
11.4 RNN 层使用方法
layers.SimpleRNNCell 来完成 计算
还有一个是 layers.SimpleRNN。
SimpleRNN 与 SimpleRNNCell 的区别在于:
- 带 Cell 的层仅仅是完成了一个时间戳的前向计算,
- 不带 Cell 的层一般是基于 Cell 层实现的,在其内部已经完成了多个时间戳的循环计算。
11.4.1 SimpleRNNCell
以某输入特征长度,Cell 状态向量特征长度为例:
cell =layers.SimpleRNNCell(3)
cell.build(input_shape=(None,4))
cell.variables
# 输出为
[<tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy=
array([[-0.3022833 , 0.61006415, -0.62484777],
[ 0.57937527, -0.38491082, 0.17247498],
[ 0.15173519, -0.19038242, 0.46637845],
[ 0.39670765, -0.5884889 , 0.4437238 ]], dtype=float32)>,
<tf.Variable 'recurrent_kernel:0' shape=(3, 3) dtype=float32, numpy=
array([[ 0.63223445, -0.3049811 , -0.7122262 ],
[ 0.77466154, 0.26471475, 0.5743045 ],
[ 0.01338477, -0.9148294 , 0.40361884]], dtype=float32)>,
<tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]
里面有三个变量,分别对应于。
11.4.3 SimpleRNN 层
通过 SimpleRNN 可以仅需一行代码即可完成整个前向运算过程,它默认返回最后一个时间戳上的输出,如果希望返回所有时间戳上的输出列表,可以设置 return_sequences=True 参数:
layers.SimpleRNN(64, return_sequences = True)
11.6.1 梯度裁剪
梯度裁剪:将梯度张量的数值或者范数限制在某个较小的区间内,从而将远大于1的梯度值减少。
常用的梯度裁剪方法:
- 把张量限制在一个区间内,e.g. 。通过tf.clio_by_value()实现。
x = tf.random.normal([2,2])
x
##输出
<tf.Tensor: id=5, shape=(2, 2), dtype=float32, numpy=
array([[-1.2057091, 0.7955832],
[-0.7486858, -0.9154401]], dtype=float32)>
tf.clip_by_value(x,0,0.5)
##输出
<tf.Tensor: id=10, shape=(2, 2), dtype=float32, numpy=
array([[0. , 0.5],
[0. , 0. ]], dtype=float32)>
-
通过限制梯度张量的范数来实现梯度裁剪。通过tf.clip_by_norm()实现。
e.g. 对的二范数约束在之间,如果,则按照裁剪。 -
前面两种方式都是通过改变单个梯度张量来实现梯度裁剪,但是这可能会使得网络更新的方向发生改变。为了限制网络的梯度值并且同时不改变网络的更新方向,考虑所有参数的梯度的范数,实现等比例的缩放。通过tf.clip_by_global_norm实现。
其中是指网络参数的第个梯度,是指指定的全局最大范数值。
11.6.2 梯度弥散
通过增大学习率,减少网络深度,添加 Skip Connection等措施来解决梯度弥散现象。
11.7 RNN 短时记忆
RNN 不能理解长句子,往往只能够从有限长度内的句子提取信息,而对于较长范围内的有用信息往往不能够很好的利用起来,这种现象叫做短时记忆。
(p.s 是不是能够理解为CNN那样的局部感受野,只关注到了一定范围的信息??)
11.8 LSTM 原理
在LSTM中,有两个状态向量和,作为LSTM的内部状态向量(可以理解为LSTM的内存Memory),表示LSTM的输出向量。
利用三个门控:输入门,遗忘门,输出门,来控制内部信息的流动。
11.8.1 遗忘门
遗忘门作用于LSTM状态向量上面,用于控制上一个时间戳的记忆对当前时间戳的影响。遗忘门的控制变量由:
其中表示把两个向量连接成一个更长的向量。是参数张量。一把选用 Sigmoid 函数。
经过遗忘门后,LSTM的状态向量变为。
意义:
- 时,遗忘门全部张开,LSTM接受上一个状态的全部信息。
- 时,遗忘门关闭,忽略上一个状态的信息。
11.8.2 输入门
输入门用于控制LSTM对输入的接收程度。
计算方式为:
重点在于第二步吧,控制了是否全部进入Memory内。
输入门控制变量的意义:
- 时,LSTM不接受任何的新输入;
- 时,LSTM全部接受新输入。
11.8.3 刷新Memory
在遗忘门和输入门的控制下,LSTM有选择地读取了上一个时间戳的记忆和当前时间戳的新输入,状态向量的刷新方式为:
号前面是输入门的输出,针对当前当前时间戳和上一个时间戳;
号后面是遗忘门的输出,针对上一个时间戳。
11.8.4 输出门
LSTM的内部状态向量并不会直接用于输出,而是在输出门的作用下有选择的输出。
输出门的门控变量为:
LSTM的输出由:
输出门的意义:
- 当时,输出关闭,LSTM的内部记忆被完全隔离,无法用作输出,此时输出为0的向量;
- 当时,输出完全打开,LSTM的状态向量全部用于输出。
(理解方式:输入门针对当前时间戳和上一个时间戳,遗忘门只针对上一个时间戳)
11.9 LSTM层使用方法
- 使用LSTMCell 来手动完成时间戳上面的循环计算;
- 通过LSTM层方式一步完成前向运算。
11.9.1 LSTMCell
LSTM的状态变量List有两个,即。
调用cell完成前向运算时,返回两个元素,第一个元素为cell的输出,也就是[h_{t},c_{t}]$。
x = tf.random.normal([2,80,100])
xt = x[:,0,:]
cell = tf.keras.layers.LSTMCell(64)
state = [tf.zeros([2,64]),tf.zeros([2,64])]
for xt in tf.unstack(x, axis = 1):
out, state = cell(xt, state)
11.9.2 LSTM层
通过tf.keras.layers.LSTM 层可以方便的一次完成整个序列的运算。
net = tf.keras.Sequential([
tf.keras.layers.LSTM(64, return_sequences = True),
tf.keras.layers.LSTM(64)
])
out1 = net(x)
11.10 GRU 简介
LSTM的缺点:计算代价比较高,模型参数比较大。(优点:LSTM不容易出现梯度弥散现象)。并且,遗忘门是LSTM中最重要的门口,甚至发现只有遗忘门的简化网络在多个基准数据集上面超过标准LSTM。
门控循环网络(GRU)是应用最广泛的变种之一。其把内部状态向量和输出向量合并,统一为状态向量,门控数量也减少到两个:复位门和更新门
GUU 网络结构11.10.1 复位门
复位门用于控制上一个时间戳的状态进入GRU的量。
门控向量由当前时间戳输入和上一时间戳状态变换得到:
门控向量只控制状态,而不会控制输入:
门控向量的意义:
-
当时,新输入全部来自于输入,不接受,此时相当于复位;
-
当时,和输入共同产生新输入。
复位门结构
11.10.2 更新门
更新们用控制上一时间戳状态和新输入对新状态向量的影响程度。
更新门控向量由:
用于控制新输入信号,用于控制状态信号:
意义:
- 当时,全部来自;
- 当时,全部来自。
11.10.3 GUR 使用方法
同样包括:GURCell,GUR层两种使用方法。
参考资料:《TensorFlow深度学习》
网友评论