一切都是为了构建更深的网络
很多实验表明:深度网络可以比浅层网络更有效地表示某些函数。但是,随着深度的增加,优化的问题也就接踵而来。那么,为了解决难以优化的问题,提出了相当多的策略:开发更好的优化器(Optimizer),(Well-designed)精心设计参数的初始化、对特定的激活函数维持方差标准化。但是,以上这些策略过于定制化,不同的数据可能需要不同策略。
Highway Networks由此诞生,它受LSTM网络启发(不懂LSTM网络也没有关系,懂的话就更好)。采用控制门(gate)的思想,控制信息的流动。在前馈网络,我们的输入x,经过一层映射
W
H
W_H
WH和一层非线性转换H,从而生成y,以下是数学表达:
y
=
H
(
x
,
W
H
)
y=H(x, W_H)
y=H(x,WH)
例如,在全连接网络中,
y
=
sigmoid
(
x
A
+
b
)
y=\text{sigmoid}(xA + b)
y=sigmoid(xA+b),以下是简单的流程图。
highway的中采用了gate的思想,将最后的输出看成两份,一份是经过转变后的(transform gate),一份是原本的信息(carry gate)。数学模型为:
y
=
α
y
′
+
β
x
y = \alpha y' + \beta x
y=αy′+βx
这里的α和β的计算为:
α
=
T
(
x
,
W
T
)
β
=
T
(
x
,
W
C
)
\alpha = T(x,W_T) \\ \beta=T(x,W_C)
α=T(x,WT)β=T(x,WC)
为了计算简单,将
β
=
1
−
α
\beta=1-\alpha
β=1−α。最终数学模型为:
y
=
H
(
x
,
W
H
)
∗
T
(
x
,
W
T
)
+
x
∗
(
1
−
T
(
x
,
W
T
)
)
y = H(x, W_H) * T(x,W_T) + x * (1-T(x,W_T))
y=H(x,WH)∗T(x,WT)+x∗(1−T(x,WT))
这里,要注意,x的维度或许与转变之后的维度有所不同,所以在实际中,有两种做法
-
在第一层中不使用highway,将此时的y作为输入,highway networks内部使用统一维度进行计算。
-
首先将x转变成符合条件的x’,然后再进行计算。本文介绍的文章使用的就是这种技巧。
这里的 H ( x , W H ) H(x,W_H) H(x,WH)有可能是卷积或者递归网络的形式。
- 卷积形式,就是每次在感受野计算完成之后,进行highway networks,但是要注意计算后输出矩阵与计算范围矩阵的长宽。
- 递归形式,在计算完每个单元后,进行highway networks,同样的,需要注意输入和输入的维度。
-----------------------------------------------分割线,以下讲解LSTM使用highway network的例子-------------------------------------------
接下来,我们主要来探讨文章《Deep Semantic Role Labeling: What Works and What’s Next》的网络结构。
左边的方框大家应该非常熟悉,就是一个LSTM Cell的输出。
那么highway的输出就会被改变,根据(12)和(14),(13)式与(8)式输出的式同一个东西,只是 y ′ y' y′表明不是最终输出。那么,现在让我们重新构建一个新的LSTM Cell,数学表达式已经在(12)和(14)式,先看看流程如何展示。
接下来用pytorch来展示,刚好pytroch有LSTM Cell这个组件,可以很简单的修改就能成为一个新的LSTM Cell。
import torch.nn as nn
import torch
import torch.nn.functional as F
class HwLSTMCell(nn.Module):
def __init__(self, input_size, hidden_size, dropout_ratio):
"""
args:
input_size: 输入特征的大小
hidden_size: 输出特征的大小
dropout_ratio: dropout的概率,(0,1)表示需要dropout, (-∞, 0]∪[, +∞)表示不需要
"""
super().__init__()
self.input_size = input_size
self.hidden_size= hidden_size
self.dropout_ratio = dropout_ratio
self.lstm_cell = nn.LSTMCell(input_size = self.input_size, hidden_size = self.hidden_size)
self.W_R = nn.Linear(self.input_size + self.hidden_size, self.hidden_size) # (12)式的W_r
# 个人认为,如果当input_size == hidden_size时,就不需要了,这个方法是为了最后一步相加时维度保持一致
self.W_h = nn.Linear(self.input_size, self.hidden_size)
def forward(self, input, hidden = None):
"""
args:
input: 输入, shape = [batch, embed_dim]
hidden: 上一个hidden的输出, 是一个元组,(hidden, cell)
hidden.shape = [batch, hidden_size]
cell.shape = [batch, hidden_size]
"""
if hidden is None:
hidden = input.new_zeros(input.shape[0], self.hidden_size)
hidden = (hidden, hidden)
hy, cy = self.lstm_cell(input, hidden) # 此时的是hy'
# 现在的hy还不是最终输出,hy = α * hy' + (1 - α) * x
# alpha = σ(W * [h_{t-1}, x_t])
alpha = F.sigmoid(self.W_R(torch.cat([hidden[0], x], dim = 1)))
hy = alpha * hy + (1 - alpha) * self.W_C(input) # 如果本来的input_size == hidden_size, 就不用这个转换了。
if self.training and 0 < self.dropout_ratio and self.dropout_ratio < 1: # 如果需要dropout的话,就可以这样使用
hy = F.dropout(hy, p=0.5)
return hy, cy
那么最终的highway版本的LSTM网络可以变为:
class HwLSTM(nn.Module):
def __init__(self, input_size, hidden_size, dropout_ratio, batch_first):
self.input_size = input_size
self.hidden_size= hidden_size
self.dropout_ratio = dropout_ratio
self.batch_first = batch_first
self.lstm_cell = HwLSTMCell(self.input_size, self.hidden_size, self.dropout_ratio)
def forward(self, input, reverse = True):
"""
args:
input: 这里假设是词嵌入层的输出,如果batch_first = True,就需要更改形状为[seq_len, batch_size, embed_dim]
reverse:看结果是否需要反向输出
"""
output, hidden = [], None
if batch_first:
input = input.permute(1, 0, 2)
for i in range(input.shape[0]): # 需要迭代句子的长度
hidden = self.lstm_cell(input[i], hidden)
output.append(hidden[0])
if reverse:
output.reverse()
return torch.stack(output)
那么,截至到目前为止,highway在lstm上的应用就到这里了。引用的文献和博客也在次文章上以链接的形式体现。流程图我是用mermaid画的,在typroa里,< sub>< /sub >的下标显示是正常的,在CSDN的markdown里编辑就显示不了了,em…。没办法,麻烦大家把里面内容看成是下标吧。