Embedding层

TensorFlow 中,可以通过 layers.Embedding(𝑁_vocab,𝑛)来定义一个 Word Embedding
层,其中𝑁_vocab参数指定词汇数量, 𝑛指定embedding后的单词向量的长度

1
import tensorflow as tf
1
2
3
4
5
6
7
#这里应该是对一个句子而言
x=tf.range(10)#生成10个单词的数字编码:[0,9]
x=tf.random.shuffle(x)
print(x)
#创建10个单词,每个单词用长度为4的向量表示
net=tf.keras.layers.Embedding(10,4)#10是单词个数,4是embedding后的向量长度
out=net(x)
tf.Tensor([1 0 8 9 3 4 5 6 2 7], shape=(10,), dtype=int32)

(10,4)是一个句子的单词个数和embedding后的向量长度,若有NUM个句子,则shape变为(NUM,10,4)

1
2
#Embedding层内部的查询表table
net.embeddings
<tf.Variable 'embedding_10/embeddings:0' shape=(10, 4) dtype=float32, numpy=
array([[-0.04157337,  0.00370337, -0.04709859, -0.02110542],
       [-0.012863  , -0.0340884 ,  0.01296239, -0.02337525],
       [-0.017204  ,  0.02752179,  0.03275616,  0.04036881],
       [ 0.01910862,  0.02488795,  0.04844334, -0.00992166],
       [ 0.04279174, -0.01695721,  0.04229338, -0.01208165],
       [ 0.04743797, -0.04877844,  0.03140882, -0.0335933 ],
       [-0.03579624,  0.03655416, -0.0206341 ,  0.00822543],
       [ 0.0418619 ,  0.00207099, -0.04340398, -0.00929434],
       [ 0.00421256, -0.00119591,  0.04572577,  0.02811921],
       [-0.01074129, -0.02198823,  0.00441085, -0.0223695 ]],
      dtype=float32)>
1
out
<tf.Tensor: shape=(10, 4), dtype=float32, numpy=
array([[-0.012863  , -0.0340884 ,  0.01296239, -0.02337525],
       [-0.04157337,  0.00370337, -0.04709859, -0.02110542],
       [ 0.00421256, -0.00119591,  0.04572577,  0.02811921],
       [-0.01074129, -0.02198823,  0.00441085, -0.0223695 ],
       [ 0.01910862,  0.02488795,  0.04844334, -0.00992166],
       [ 0.04279174, -0.01695721,  0.04229338, -0.01208165],
       [ 0.04743797, -0.04877844,  0.03140882, -0.0335933 ],
       [-0.03579624,  0.03655416, -0.0206341 ,  0.00822543],
       [-0.017204  ,  0.02752179,  0.03275616,  0.04036881],
       [ 0.0418619 ,  0.00207099, -0.04340398, -0.00929434]],
      dtype=float32)>
1
net.embeddings.trainable#可学习
True

SimpleRNNCell

一层SimpleRNNCell网络

仅仅是完成了一个时间戳的前向运算

1
2
#创建RNN Cell,内存向量长度为3
cell=tf.keras.layers.SimpleRNNCell(3)
1
2
#输出特征长度n=4,即embedding后的每个单词向量长度为4
cell.build(input_shape=(None,4))#None格式可能是[句子个数,每个句子的单词个数]?
1
cell.trainable_variables # 打印 wxh, whh, b 张量
[<tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy=
 array([[-0.12730736,  0.49879634, -0.12697208],
        [ 0.62330437,  0.8244791 , -0.45662448],
        [ 0.89570665, -0.20918423, -0.8737987 ],
        [-0.54977036, -0.05649453, -0.00607061]], dtype=float32)>,
 <tf.Variable 'recurrent_kernel:0' shape=(3, 3) dtype=float32, numpy=
 array([[-0.22971189,  0.9539083 ,  0.19310963],
        [-0.36177534,  0.10050881, -0.9268314 ],
        [-0.90352154, -0.2827664 ,  0.32201245]], dtype=float32)>,
 <tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]

kernel 变量即𝑾 _xh张量, recurrent_kernel
变量即𝑾 _hh张量, bias 变量即偏置𝒃向量

但是 RNN Memory 向量 h并不由SimpleRNNCell 维护,需要用户自行初始化向量h0并记录每个时间戳上的 𝒕。

1
2
3
4
5
6
7
8
# 初始化状态向量,用列表包裹,统一格式
h0 = [tf.zeros([4, 64])]
x = tf.random.normal([4, 80, 100]) # 生成输入张量, 4 个 80 单词的句子,embedding后的单词向量长度 n=100
xt = x[:,0,:] # 所有句子的第 1 个单词
# 构建输入特征即embedding后的单词向量长度 n=100,序列长度 s=80,状态长度=64 的 Cell
cell = tf.keras.layers.SimpleRNNCell(64)
out, h1 = cell(xt, h0) # 前向计算
print(out.shape,h1[0].shape)
(4, 64) (4, 64)
1
2
#对于 SimpleRNNCell 来说, 𝒐𝑡 = ht,并没有经过额外的线性层转换, 是同一个对象
print(id(out),id(h1[0]))
3078969475672 3078969475672

以上仅仅做了一个时间戳的前向运算,对于长度为s的序列来说,需要循环通过
Cell 𝑠次才算完成一次网络层的前向运算

1
2
3
4
5
6
h = h0 # h 保存每个时间戳上的状态向量列表
# 在序列长度的维度解开输入,得到 xt:[b,n]
for xt in tf.unstack(x, axis=1):
out,h = cell(xt, h) # 前向计算,out 和 h 均被覆盖
# 最终输出可以聚合每个时间戳上的输出,也可以只取最后时间戳的输出
out = out
1
out[-1]
<tf.Tensor: shape=(64,), dtype=float32, numpy=
array([-0.25621325,  0.51310164, -0.8620935 , -0.91738695,  0.23226644,
       -0.66992235, -0.63344294, -0.9089617 ,  0.72135305, -0.7318457 ,
       -0.99582106, -0.9522287 , -0.6830161 , -0.7123331 ,  0.95459735,
       -0.5337273 ,  0.2837275 ,  0.972833  ,  0.99199206,  0.9326225 ,
        0.92419845,  0.9897123 ,  0.98730296, -0.8111788 ,  0.18107522,
        0.6471946 ,  0.95291704,  0.9300036 , -0.69558764,  0.758556  ,
        0.21924002, -0.86336696, -0.2925538 , -0.53042555,  0.61276007,
        0.31824008,  0.50052625, -0.609673  ,  0.91918457,  0.31857985,
        0.4469507 ,  0.13685822, -0.03755331,  0.4636307 ,  0.9822013 ,
       -0.71981126, -0.9569911 ,  0.9512179 ,  0.86438775,  0.01853882,
       -0.86230564,  0.86030304, -0.19372608, -0.99768096,  0.9988538 ,
        0.77483326,  0.9892455 ,  0.01369001,  0.9266391 , -0.9306773 ,
        0.00980736,  0.29978752,  0.24117659, -0.45769033], dtype=float32)>

多层 SimpleRNNCell 网络

和卷积神经网络一样,循环神经网络虽然在时间轴上面展开了多次, 但只能算一个网络层

以两层的循环神经网络为例,介绍利用Cell方式构建多层 RNN 网络。

1
2
3
4
5
6
7
8
#生成输入张量,4个句子,每个句子80个单词,embedding后的单词向量长度为100
x = tf.random.normal([4,80,100])
xt = x[:,0,:] # 取第一个时间戳的输入 x0
# 构建 2 个 Cell,先 cell0,后 cell1,内存状态向量长度都为 64
cell0 = tf.keras.layers.SimpleRNNCell(64)
cell1 = tf.keras.layers.SimpleRNNCell(64)
h0 = [tf.zeros([4,64])] # cell0 的初始状态向量
h1 = [tf.zeros([4,64])] # cell1 的初始状态向量

前向计算有两种方式:

方法1:在时间轴上面循环计算多次来实现整个网络的前向运算,每个时间戳上的输入 xt 首先通过第一层,得到输出 out0,再通过第二层,得到输出 out1

1
2
3
4
5
for xt in tf.unstack(x, axis=1):
# xt 作为输入,输出为 out0
out0,h0 = cell0(xt,h0)
# 上一个 cell 的输出 out0 作为本 cell 的输入
out1,h1 = cell1(out0,h1)

上述方式先完成一个时间戳上的输入在所有层上的传播, 再循环计算完所有时间戳上的输

方法2:先完成输入在第一层上所有时间戳的计算, 并保存第一层在所有时间戳上的输出列表,再计算第二层、第三层等的传播。代码如

1
2
3
4
5
6
7
8
9
# 保存上一层的所有时间戳上面的输出
middle_sequences = []
# 计算第一层的所有时间戳上的输出,并保存
for xt in tf.unstack(x, axis=1):
out0, h0 = cell0(xt, h0)
middle_sequences.append(out0)
#计算第二层的所有时间戳上的输出
for xt in middle_sequences:
out1,h1=cell1(xt,h1)

一般来说,最末层Cell的状态有可能保存了高层的全局语义特征, 因此一般使用最末层的输出作为后续任务网络的输入。

SimpleRNN 层

更高层的接口,可以省略之前手动参与的内部计算过程,比如每一层的 状态向量的初始化,以及每一层在时间轴上展开的运算

单层SimpleRNN 网络的前向运算

1
2
3
layer=tf.keras.layers.SimpleRNN(64)# 创建状态向量长度为 64 的 SimpleRNN 层
x = tf.random.normal([4, 80, 100])#4句话,每句含80个单词,embedding后的单词向量长度为100
out=layer(x)
1
out.shape
TensorShape([4, 64])

通过SimpleRNN可以仅需一行代码即可完成整个前向运算过程, 它默认返回最
后一个时间戳上的输出。

如果希望返回所有时间戳上的输出列表,可以设置 return_sequences=True 参数

1
2
3
4
# 创建 RNN 层时,设置返回所有时间戳上的输出
layer = tf.keras.layers.SimpleRNN(64,return_sequences=True)
out = layer(x) # 前向计算
out # 输出,自动进行了 concat 操作
<tf.Tensor: shape=(4, 80, 64), dtype=float32, numpy=
array([[[ 0.7579574 ,  0.5203536 , -0.13247532, ..., -0.97075224,
         -0.5602517 ,  0.1896142 ],
        [ 0.70799   ,  0.8877379 ,  0.8039977 , ..., -0.9796105 ,
         -0.90698063,  0.92094153],
        [-0.98230916,  0.38763395, -0.24368586, ..., -0.23982975,
         -0.5587594 ,  0.8005439 ],
        ...,
        [ 0.9696948 , -0.56924766,  0.99460095, ...,  0.86883426,
          0.7421469 , -0.16692714],
        [ 0.98573875, -0.866003  , -0.8699877 , ..., -0.70909184,
          0.79517734,  0.21344577],
        [-0.4993558 ,  0.9063643 ,  0.90756595, ...,  0.63874185,
          0.3061087 ,  0.94876754]],

       [[-0.953717  ,  0.5357896 , -0.24438739, ..., -0.9902813 ,
          0.65611756,  0.52405304],
        [-0.945761  ,  0.92643136,  0.93036115, ..., -0.9637114 ,
          0.86534184, -0.19020523],
        [-0.11487287, -0.8078128 ,  0.95136124, ...,  0.79756886,
          0.7799482 ,  0.208571  ],
        ...,
        [-0.92995924, -0.49086258, -0.49593148, ..., -0.50804   ,
          0.5525898 , -0.92352575],
        [ 0.87116635, -0.3758928 ,  0.2159305 , ..., -0.6538404 ,
          0.0528454 ,  0.971082  ],
        [-0.37672698, -0.9683858 , -0.9913551 , ...,  0.8241966 ,
          0.99546564, -0.9172934 ]],

       [[ 0.44561443, -0.72680014, -0.98325783, ..., -0.30991045,
          0.7665605 ,  0.0668956 ],
        [ 0.9603438 ,  0.9961231 ,  0.43043685, ..., -0.3882459 ,
         -0.53074616,  0.04012485],
        [ 0.6547422 ,  0.001742  , -0.18013465, ...,  0.72801995,
          0.5303095 , -0.3371497 ],
        ...,
        [ 0.71766   , -0.52994007, -0.03237867, ...,  0.2299321 ,
          0.9955149 ,  0.9458545 ],
        [ 0.9850791 ,  0.90992504, -0.44711468, ..., -0.9919823 ,
         -0.16220197,  0.5074431 ],
        [ 0.0717535 ,  0.8818335 , -0.6376899 , ..., -0.86398774,
          0.91119236,  0.99637604]],

       [[-0.6396239 , -0.9482835 ,  0.48356768, ...,  0.1447552 ,
          0.24843507,  0.85139376],
        [-0.9784054 , -0.60325694, -0.54072136, ...,  0.5444716 ,
         -0.20786293, -0.9302095 ],
        [ 0.92882097,  0.24866687, -0.02795087, ..., -0.8263675 ,
         -0.23303777,  0.55453205],
        ...,
        [ 0.6856851 ,  0.18871786,  0.11841806, ..., -0.9009302 ,
          0.9300211 ,  0.311716  ],
        [ 0.7124896 ,  0.9189299 , -0.2578693 , ..., -0.4982193 ,
         -0.98169625,  0.3771086 ],
        [-0.96602875,  0.2520068 ,  0.8261244 , ..., -0.7265942 ,
          0.5231435 , -0.8301581 ]]], dtype=float32)>

返回的输出张量 shape[4,80,64],中间维度的 80 即为时间戳维度

多层SimpleRNN 网络的前向运算

1
2
3
4
5
6
7
8
#以两层为例
net=tf.keras.Sequential([
#除最末层外,都需要返回所有时间戳的输出,用作下一层的输入
tf.keras.layers.SimpleRNN(64,return_sequences=True),
#tf.keras.layers.SimpleRNN(64,return_sequences=True),#此时out shape:4, 80, 64
tf.keras.layers.SimpleRNN(64),#只要最后一个时间戳的输出,此时 out shape:4,64
])
out=net(x)
1
out.shape
TensorShape([4, 64])

LSTMCell

LSTMCell 的用法和SimpleRNNCell基本一致, 区别在于LSTM的状态变量 List 有两个, 即[ h𝑡, 𝒄𝑡], 需要分别初始化,其中List第一个元素为 h𝑡,第二个元素为𝒄𝑡

1
2
3
4
#新建一个状态向量长度ℎ = 64的 LSTM Cell,其中状态向量𝒄𝑡和输出向量 𝑡的长度都为ℎ
x=tf.random.normal([2,80,100])#2个句子,每个句子包含80个单词,embedding后的单词向量长度为100
xt=x[:,0,:]#得到一个时间戳的输入
cell=tf.keras.layers.LSTMCell(64)#创建LSTMCell
1
2
3
4
#初始化状态和输出List:[h,c]
state=[tf.zeros([2,64]),tf.zeros([2,64])]
#前向计算
out,state=cell(xt,state)
1
id(out),id(state[0]),id(state[1])
(3078980607848, 3078980607848, 3078978869912)

返回的输出 outList的第一个元素 h𝑡id 是相同的,这与基础的RNN初衷一致,都是为了格式的统一

通过在时间戳上展开循环运算,即可完成一次层的前向传播

1
2
3
for xt in tf.unstack(x,axis=1):
#前向计算
out,state=cell(xt,state)
1
out.shape
TensorShape([2, 64])

LSTM 层

一层LSTM层

1
2
3
4
#一层LSTM层,内存向量长度为64
layer=tf.keras.layers.LSTM(64)
# 序列通过 LSTM 层,默认返回最后一个时间戳的输出 h
out = layer(x)
1
out.shape
TensorShape([2, 64])

经过 LSTM 层前向传播后,默认只会返回最后一个时间戳的输出, 如果需要返回每个时间
戳上面的输出, 需要设置return_sequences=True标志。

1
2
3
4
# 创建 LSTM 层时,设置返回每个时间戳上的输出
layer = tf.keras.layers.LSTM(64, return_sequences=True)
# 前向计算,每个时间戳上的输出自动进行了 concat,拼成一个张量
out = layer(x)
1
out.shape#80代表80个时间戳
TensorShape([2, 80, 64])

多层LSTM层

对于多层神经网络, 可以通过 Sequential 容器包裹多层 LSTM 层,并设置所有非末层网络 return_sequences=True,这是因为非末层的 LSTM 层需要上一层在所有时间戳的输出
作为输入。

1
2
3
4
5
6
net = tf.keras.Sequential([
tf.keras.layers.LSTM(64, return_sequences=True), # 非末层需要返回所有时间戳输出
tf.keras.layers.LSTM(64)
])
# 一次通过网络模型,即可得到最末层、最后一个时间戳的输出
out = net(x)
1
out.shape
TensorShape([2, 64])

GRUCell

1
2
3
4
5
6
7
8
9
x=tf.random.normal([2,80,100])
# 初始化状态向量, GRU 只有一个
h = [tf.zeros([2,64])]
cell = tf.keras.layers.GRUCell(64) # 新建 GRU Cell,向量长度为 64
# 在时间戳维度上解开,循环通过 cell
for xt in tf.unstack(x, axis=1):
out, h = cell(xt, h)
# 输出形状
out.shape
TensorShape([2, 64])

GRU层

单层GRU

1
2
3
4
5
6
#一层GRU层,内存向量长度为64
#layer=tf.keras.layers.GRU(64,return_sequences=True)#此时out的shape为:[2,80,64],其中80代表80个时间戳
layer=tf.keras.layers.GRU(64)
# 序列通过 GRU层,默认返回最后一个时间戳的输出 h
out = layer(x)
out.shape
TensorShape([2, 64])

多层GRU

通过 layers.GRU 类可以方便创建一层 GRU 网络层,通过 Sequential 容器可以堆叠多
GRU 层的网络

1
2
3
4
5
6
net = tf.keras.Sequential([
tf.keras.layers.GRU(64, return_sequences=True),
tf.keras.layers.GRU(64)
])
out = net(x)
out.shape
TensorShape([2, 64])