赞
踩
本文将分享发表在2018年的NAACL上,outstanding paper。论文链接ELMo。该论文旨在提出一种新的词表征方法,并且超越以往的方法,例如word2vec、glove等。
论文中所提出的词表征方法是基于语言模型的。
如上图所示,左侧是正向的L层的语言模型,右边是
L
L
L层的反向网络。SF表示
S
o
f
t
m
a
x
Softmax
Softmax 结构。
其中 x i L M x_i^{LM} xiLM 表示词 t i t_i ti 经过 c h a r C N N charCNN charCNN 后的向量, h → i , j L M \overrightarrow{h}^{LM}_{i,j} h i,jLM、 h ← i , j L M \overleftarrow{h}^{LM}_{i,j} h i,jLM分别表示正向反向 L S T M LSTM LSTM 的第 j j j 层的第 i i i 个 L S T M C e l l LSTMCell LSTMCell 的隐藏层状态。
其中正向语言模型(从前往后预测):
p
(
t
1
,
t
2
,
.
.
,
t
n
)
=
∏
k
=
1
n
p
(
t
k
∣
t
1
,
t
2
,
.
.
.
,
t
k
−
1
)
p(t_1,t_2, ..,t_n) = \prod_{k=1}^np(t_k|t_1,t_2,...,t_{k-1})
p(t1,t2,..,tn)=k=1∏np(tk∣t1,t2,...,tk−1)
反向语言模型(从后往前预测):
p
(
t
1
,
t
2
,
.
.
,
t
n
)
=
∏
k
=
1
n
p
(
k
k
∣
t
k
+
1
,
t
k
+
2
,
.
.
.
,
t
n
)
p(t_1,t_2, ..,t_n) = \prod_{k=1}^np(k_k|t_{k+1},t_{k+2},...,t_n)
p(t1,t2,..,tn)=k=1∏np(kk∣tk+1,tk+2,...,tn)
需要注意的是,正向反向语言模型会共享部分参数。,两者并不是完全独立的。
优化目标,联合正反向网络:
o
b
j
=
M
A
X
(
∑
k
=
1
n
(
l
o
g
p
(
t
k
∣
t
1
,
t
2
,
.
.
.
,
t
k
−
1
:
θ
x
,
θ
→
L
S
T
M
,
θ
s
)
+
l
o
g
p
(
t
k
∣
t
k
+
1
,
t
k
+
2
,
.
.
.
,
t
n
:
θ
x
,
θ
←
L
S
T
M
,
θ
s
)
)
)
obj = MAX (\sum_{k=1}^{n}(log\ p(t_k|t_1,t_2,...,t_{k-1}: \theta_x,\overrightarrow{\theta}_{LSTM}, \theta_s) + log\ p(t_k|t_{k+1},t_{k+2},...,t_n: \theta_x,\overleftarrow{\theta}_{LSTM}, \theta_s)) )
obj=MAX(k=1∑n(log p(tk∣t1,t2,...,tk−1:θx,θ
LSTM,θs)+log p(tk∣tk+1,tk+2,...,tn:θx,θ
LSTM,θs)))
其中
θ
x
\theta_x
θx 表示词表征参数,
θ
s
\theta_s
θs 表示
s
o
f
t
m
a
x
softmax
softmax 参数,
θ
L
S
T
M
\theta_{LSTM}
θLSTM表示
L
S
T
M
LSTM
LSTM 网络参数。
显然模型采用的损失函数就是
M
L
E
MLE
MLE,这样我们就可以训练这个网络。
对于训练好的
L
L
L 层的双向语言模型,每个
t
o
k
e
n
token
token,例如第
k
k
k 个词
t
k
t_k
tk 都可以用
2
L
+
1
2L+1
2L+1 个向量集合表示,如下:
R
k
=
{
x
k
L
M
,
h
→
k
,
j
L
M
,
h
←
k
,
j
L
M
∣
j
=
1
,
.
.
,
L
}
R_k = {\{x_k^{LM},\overrightarrow{h}^{LM}_{k,j}, \overleftarrow{h}^{LM}_{k,j} | j = 1,..,L }\}
Rk={xkLM,h
k,jLM,h
k,jLM∣j=1,..,L}
为了下游的模型能更好的使用上面得出的词表征,我们需要将
2
L
+
1
2L+1
2L+1 向量集合压缩成一个向量表示。
E
L
M
o
k
=
E
(
R
k
;
θ
e
)
{ELMo}_k=E(R_k;\theta_e)
ELMok=E(Rk;θe)
例如,我们可以仅仅使用双向语言模型的最后一层的输出:
E
(
R
k
)
=
h
k
,
L
L
M
E(R_k) = h^{LM}_{k,L}
E(Rk)=hk,LLM
其中
h
k
,
L
L
M
=
[
h
→
k
,
j
L
M
:
h
←
k
,
j
L
M
]
h^{LM}_{k,L} = [\overrightarrow{h}^{LM}_{k,j}: \overleftarrow{h}^{LM}_{k,j}]
hk,LLM=[h
k,jLM:h
k,jLM]
更普遍的做法是:
E
L
M
o
k
t
a
s
k
=
E
(
R
k
;
θ
t
a
s
k
)
=
γ
t
a
s
k
∑
j
=
0
L
s
j
t
a
s
k
h
k
,
j
L
M
{ELMo}_{k}^{task}=E(R_k;\theta^{task})=\gamma^{task}\sum_{j=0}^{L}s_j^{task}h_{k,j}^{LM}
ELMoktask=E(Rk;θtask)=γtaskj=0∑Lsjtaskhk,jLM
s j t a s k s_j^{task} sjtask 表示按特定任务对权重做 s o f t m a x − n o r m a l i z e d softmax-normalized softmax−normalized, γ \gamma γ是需要根据经验调试的超参数。每层做个集成,能捕捉到不同的语义信息。
论文的实验部分验证了加入 M L M o MLMo MLMo 的词向量在各个NLP任务上都得到了很好的表现。这里面举一些个人觉得比较有亮点的部分分析下。
B
a
s
e
l
i
n
e
Baseline
Baseline 是没有使用
E
L
M
o
ELMo
ELMo 的词向量,
l
a
s
t
o
n
l
y
last\ only
last only 是仅仅使用双向语言模型的最后一层的词向量,
A
l
l
l
a
y
e
r
s
All\ layers
All layers 使用了双向语言模型所有层的词向量集成,显然我们可以看出当正则
λ
=
0.001
\lambda = 0.001
λ=0.001 并且使用了所有层的词向量集成,效果答复提升。说明这种集成的做法是比较有效的。
with tf.variable_scope("elmo_rnn_cell"): self.forward_cell = tf.nn.rnn_cell.LSTMCell(self.hidden_size, reuse=tf.AUTO_REUSE) self.backward_cell = tf.nn.rnn_cell.LSTMCell(self.hidden_size, reuse=tf.AUTO_REUSE) if config.get("use_skip_connection"):## 残差连接 self.forward_cell = tf.nn.rnn_cell.ResidualWrapper(self.forward_cell) self.backward_cell = tf.nn.rnn_cell.ResidualWrapper(self.backward_cell) with tf.variable_scope("elmo_softmax"):## 下面的forward_softmax_w 就是上面所讲的$s^{task}_j$ softmax_weight_shape = [config["word_vocab_size"], config["elmo_hidden"]] self.forward_softmax_w = tf.get_variable("forward_softmax_w", softmax_weight_shape, dtype=tf.float32) self.backward_softmax_w = tf.get_variable("backward_softmax_w", softmax_weight_shape, dtype=tf.float32) self.forward_softmax_b = tf.get_variable("forward_softmax_b", [config["word_vocab_size"]]) self.backward_softmax_b = tf.get_variable("backward_softmax_b", [config["word_vocab_size"]])
embedding_output = self.embedding.forward(data)## 将data经过char-cnn得到普通词向量 with tf.variable_scope("elmo_rnn_forward"): forward_outputs, forward_states = tf.nn.dynamic_rnn(self.forward_cell, inputs=embedding_output, sequence_length=data["input_len"], dtype=tf.float32) with tf.variable_scope("elmo_rnn_backward"): backward_outputs, backward_states = tf.nn.dynamic_rnn(self.backward_cell, inputs=embedding_output, sequence_length=data["input_len"], dtype=tf.float32) # #将正反向模型链接起来 forward_projection = tf.matmul(forward_outputs, tf.expand_dims(tf.transpose(self.forward_softmax_w), 0)) forward_projection = tf.nn.bias_add(forward_projection, self.forward_softmax_b) backward_projection = tf.matmul(backward_outputs, tf.expand_dims(tf.transpose(self.backward_softmax_w), 0)) backward_projection = tf.nn.bias_add(backward_projection, self.backward_softmax_b) return forward_outputs, backward_outputs, forward_projection, backward_projection
上面只是定义了一层的LSTM网络,但是通过残差连接,把一层的输入和输出连接在一起了。就相当于将不同层的隐状态集成到一起了。
def train(self, data, global_step_variable=None): forward_output, backward_output, _, _ = self.forward(data) ## 注意data[target]只是比输入延后了一步 forward_target = data["target"] forward_pred = tf.cast(tf.argmax(tf.nn.softmax(forward_output, -1), -1), tf.int32) forward_correct = tf.equal(forward_pred, forward_target) forward_padding = tf.sequence_mask(data["target_len"], maxlen=self.seq_len, dtype=tf.float32) forward_softmax_target = tf.cast(tf.reshape(forward_target, [-1, 1]), tf.int64) forward_softmax_input = tf.reshape(forward_output, [-1, self.hidden_size]) forward_train_loss = tf.nn.sampled_softmax_loss( weights=self.forward_softmax_w, biases=self.forward_softmax_b, labels=forward_softmax_target, inputs=forward_softmax_input, num_sampled=self.config["softmax_sample_size"], num_classes=self.config["word_vocab_size"] ) forward_train_loss = tf.reshape(forward_train_loss, [-1, self.seq_len]) forward_train_loss = tf.multiply(forward_train_loss, forward_padding) forward_train_loss = tf.reduce_mean(forward_train_loss) ## 反向模型需要将target翻转,因为是从后向前预测的。 backward_target = tf.reverse_sequence(data["target"], data["target_len"], seq_axis=1, batch_axis=0) backward_pred = tf.cast(tf.argmax(tf.nn.softmax(backward_output, -1), -1), tf.int32) backward_correct = tf.equal(backward_pred, backward_target) backward_padding = tf.sequence_mask(data["target_len"], maxlen=self.seq_len, dtype=tf.float32) backward_softmax_target = tf.cast(tf.reshape(backward_target, [-1, 1]), tf.int64) backward_softmax_input = tf.reshape(backward_output, [-1, self.hidden_size]) backward_train_loss = tf.nn.sampled_softmax_loss( weights=self.backward_softmax_w, biases=self.backward_softmax_b, labels=backward_softmax_target, inputs=backward_softmax_input, num_sampled=self.config["softmax_sample_size"], num_classes=self.config["word_vocab_size"] ) backward_train_loss = tf.reshape(backward_train_loss, [-1, self.seq_len]) backward_train_loss = tf.multiply(backward_train_loss, backward_padding) backward_train_loss = tf.reduce_mean(backward_train_loss) train_loss = forward_train_loss + backward_train_loss train_correct = tf.concat([forward_correct, backward_correct], axis=-1) train_acc = tf.reduce_mean(tf.cast(train_correct, tf.float32)) tf.summary.scalar("train_acc", train_acc) tf.summary.scalar("train_loss", train_loss) train_ops = tf.train.AdamOptimizer().minimize(train_loss) return train_loss, train_acc, train_ops
def pred(self, data):
-,-, forward_projection, backward_projection= self.forward(data)
eval_output = tf.concat([forward_projection, backward_projection], axis=-1)
return eval_output
上面的 f o r w a r d _ p r o j e c t i o n , b a c k w a r d _ p r o j e c t i o n forward\_projection, backward\_projection forward_projection,backward_projection 分别表示正向,反向网络集成各层隐状态得到词向量表示,将其连接起来就得到了上某种形式的 E M L o EMLo EMLo 词向量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。