2018 AAAI论文;
2017年其实就已经发到arxiv ;—bert为2019年提出,现在是否有基于b ert来做的呢?;
GitHub : https://github.com/tyliupku/wiki2bio;
作者: 教育部计算语言学重点实验室,北京大学电子工程与计算机科学学院;
本质感觉就是seq2seq attention的新玩法。。。
一、要完成的事
根据结构化表格,自动生成人物的传记! 听起来很酷!但是仔细一想,其实类似于医疗领域的自动诊断单的生成,不过医疗的话其实是先对图像分析,得到数据之后,套用到已经写好的模版里。。。(目前已经转行不做医疗了,,这个就不谈了。)使用seq2seq自动的生成的话,其实就是串词造句,比较高级的串词造句(可能会调整串词的顺序和词语的选择。。)——“选词造句”。
结构化表格输入:
一个人物的平生结构化表格,涉及的条目有“出生”、“死亡”、“死因”、“出生地”、“职业”、“工作单位”、“因啥被人们熟知”、“title”…等等。每一个条目都有对应的关于这个人的描述。
我们要做的就是使用改进的seq2seq,输入上面的这个表格,然后得到描述这个人的一段话:
Charles Winstead (1891 - 1973) was an FBI agent in the 1930s - 40s,
famous for being one of the agents who shot and killed John Dillinger.
仔细看的话,如果是用ai的话除非过拟合,不然肯定不会有“ was an FBI agent in the 1930s - 40s”的“ in the 1930s - 40s”的时间信息,因为结构化数据中没有体现,属于无中生有啊,要是能生成出来很定是过拟合。或者拿这种数据训练也是不妥当的感觉。
二、使用的网络
变体的seq2seq attention。
(PS : seq2seq本质上还是LSTM,现在NLP的所有任务基本上都已经被transfomer家族给垄断了。。现在肯定有更好的基于bert或者transfomer的模型吧。。。。)
如果对seq2seq attention还不是很了解的话:
关于seq2seq attention非常好的资料:
- 20分钟掌握RNN与LSTM原理及其结构应用(Seq2Seq & Attention)
- 真正的完全图解Seq2Seq Attention模型
- 完全图解RNN、RNN变体、Seq2Seq、Attention机制
三、数据的预处理
构造数据
一句话可以拆解成词,词可以进行编码,编码之后才能够被计算机识别。
左边为原始的结构化数据,有两列,第一列是各种条目(姓名、出生日期、出生地等。。。),第二列的是对应每一行条目的内容,比如,姓名:George Mikell等,相当于一种Key-Value的词典形式。
不过第二列才是我们整合的信息内容,论文中对每一个词语都进行了Field embedding,将领域或者是条目信息以及词语的位置信息结合进来。
field embeddding用 Z w = Z_w = Zw= { f w ; p w f_w ; p_w fw;pw }来表示单词w的领域编码, f w f_w fw是对应的条目, p w p_w pw是位置信息,位置信息以一个元祖来表示 ( p w + , p w − ) (p^+_w , p^−_w ) (pw+,pw−) 一个代表从前往后数第几个位置,另一个是从后往前数。确保位置信息唯一。自己可以对应到上表上看一下。
数据输入的时候,都会对这三个信息(word, field, position)进行编码,编码的长度:
论文提供了 word的词典和Field的词典(英文),word选择了训练集中最常用的2万个,条目信息有1480个(每个条目信息要至少在训练集中出现超过100次)
(PS:要出现足够的次数才能让网络记住啊。。这个就有点。。。。自己构造合适的数据集??!!出现次数不超过100次的条目怎么办呢?好像也没给个解释。。。去掉了多少个条目呢?。。)
任务的数学表达
对于结构化表格
T
T
T,,n个条目-value,field-value 记录,记
{
R
1
,
R
2
,
R
3
,
.
.
.
,
R
n
}
\{R_1, R_2, R_3, ..., R_n\}
{R1,R2,R3,...,Rn},对于每一条记录,
R
i
R_i
Ri有很多描述这个条目的词语,
{
d
1
,
d
2
,
d
3
,
.
.
.
d
m
}
\{d_1,d_2,d_3,...d_m\}
{d1,d2,d3,...dm},他们相应的条目:
{
Z
d
1
,
Z
d
2
,
.
.
.
.
,
Z
d
m
}
\{Z_{d_1}, Z_{d_2},...., Z_{d_m}\}
{Zd1,Zd2,....,Zdm}.
任务的目的是从表
T
T
T得到描述
S
S
S,得到的描述
S
S
S有p个token
{
w
1
,
w
2
,
.
.
.
,
w
p
}
\{w_1,w_2,...,w_p\}
{w1,w2,...,wp},
w
t
w_t
wt指再
t
t
t时间生成的词语。
搭建的模型类型属于概率生成模型,生成序列
w
1
:
p
∗
w^*_{1:p}
w1:p∗,
最大化
P
(
w
1
:
p
∣
R
1
:
n
)
P(w_{1:p}|R_{1:n})
P(w1:p∣R1:n)。
w 1 : p ∗ = a r g m a x w 1 : p ∏ t = 1 p P ( w t ∣ w 0 : t − 1 , R 1 : n ) w^*_{1:p} = argmax_{w_{1:p}}\prod_{t=1}^pP(w_t|w_{0:t-1},R_{1:n}) w1:p∗=argmaxw1:pt=1∏pP(wt∣w0:t−1,R1:n)
四、网络创新
1. Field-gating Table Encoder
以前的seg2seq只有词编码输入,现在除了词 d j d_j dj之外,还多了个领域编码信息 Z d j Z_{d_j} Zdj,那么就需要将 Z d j Z_{d_j} Zdj也加入到输入,岂不是快哉~
原始的LSTM:
公式:
代码部分:
def __call__(self, x, s, finished = None):
h_prev, c_prev = s
x = tf.concat([x, h_prev], 1)
i, j, f, o = tf.split(tf.nn.xw_plus_b(x, self.W, self.b), 4, 1)
# Final Memory cell
c = tf.sigmoid(f+1.0) * c_prev + tf.sigmoid(i) * tf.tanh(j)
h = tf.sigmoid(o) * tf.tanh(c)
out, state = h, (h, c)
添加
Z
d
j
Z_{d_j}
Zdj为额外输入之后,除了上面的结构和公式描述外,新添加:
新添加的公式,按照上面的公式照猫画虎:
代码部分,多了几行:
def __call__(self, x, fd, s, finished = None):
"""
:param x: batch * input
:param s: (h,s,d)
:param fd: field embedding word
:param finished:
:return:
"""
h_prev, c_prev = s # batch * hidden_size
x = tf.concat([x, h_prev], 1)
# fd = tf.concat([fd, h_prev], 1)
i, j, f, o = tf.split(tf.nn.xw_plus_b(x, self.W, self.b), 4, 1)
r, d = tf.split(tf.nn.xw_plus_b(fd, self.W1, self.b1), 2, 1)
# Final Memory cell
c = tf.sigmoid(f+1.0) * c_prev + tf.sigmoid(i) * tf.tanh(j) + tf.sigmoid(r) * tf.tanh(d) # batch * hidden_size
h = tf.sigmoid(o) * tf.tanh(c)
out, state = h, (h, c)
照猫画虎。
其实最简单的方式是跟concat
x
t
x_t
xt和
h
t
−
1
h_{t-1}
ht−1一样,将
z
t
z_t
zt与word embedding进行concat,作者也的确这样做了,作为来比较的一个base line。但是,作者发现,但是,word embedding和field embedding的concat仅将字段信息视为某些token的附加标签,这会丢失表的结构信息。所以最终采取了上图的结构。
2. Decoder with Dual Attention
相比于原始的seq2seq attention多了对field embeddings的attention。
对比看:
s
t
s_t
st为第
t
t
t个decoder编码器的隐藏单元。
a
t
a_t
at为attention vector。
attention vector值在描述在
t
t
t时刻的
s
t
s_t
st与所有隐藏层
{
h
t
}
t
=
1
L
\{h_t\}^L_{t=1}
{ht}t=1L的语义相似性。如下图所示(图片来自于https://zhuanlan.zhihu.com/p/47063917):
正常的seq2seq attention得到
s
t
s_t
st的attention vector,如下公式:
α
t
i
=
e
g
(
s
t
,
h
i
)
∑
j
=
1
N
e
g
(
s
t
,
h
j
)
\alpha_{t_i} = \frac{e^{g(s_t, h_i)}}{\sum_{j=1}^Ne^{g(s_t, h_j)}}
αti=∑j=1Neg(st,hj)eg(st,hi)
a
t
=
∑
i
=
1
L
α
t
i
h
i
a_t = \sum_{i=1}^L\alpha_{t_i}h_i
at=i=1∑Lαtihi
L为L个隐藏层h。
g
(
s
t
,
h
i
)
g(s_t, h_i)
g(st,hi)为相关性计算,此处使用的是模型参数点成,
以上就是词级别的attention,就是最基本的attention形式,代码如下:
def __call__(self, x, finished = None):
#x: s
#phi_hs: hj
gamma_h = tf.tanh(tf.nn.xw_plus_b(x, self.Ws, self.bs))
weights = tf.reduce_sum(self.phi_hs * gamma_h, reduction_indices=2, keep_dims=True)
weight = weights
weights = tf.exp(weights - tf.reduce_max(weights, reduction_indices=0, keep_dims=True))
weights = tf.divide(weights, (1e-6 + tf.reduce_sum(weights, reduction_indices=0, keep_dims=True)))
context = tf.reduce_sum(self.hs * weights, reduction_indices=0)
# print wrt.get_shape().as_list()
out = tf.tanh(tf.nn.xw_plus_b(tf.concat([context, x], -1), self.Wo, self.bo))
if finished is not None:
out = tf.where(finished, tf.zeros_like(out), out)
return out, weights
但是,上述单词级别attention只能捕获生成的token与表中的内容信息之间的语义相关性,而忽略表的结构信息。 为了充分利用结构信息,我们建议对生成的token和表的字段嵌入有更高的关注。 字段级注意力可以通过对所有field embedding
{
z
t
}
t
=
1
L
\{z_t\}^L_{t=1}
{zt}t=1L与第t次解码器状态
w
t
w_t
wt之间的相关性进行建模,从而在描述中生成下一个token时定位应关注的特定字段值记录。
这里的构造过程跟普通的词编码attention是几乎一样的,不一样的是
γ
t
i
\gamma_{t_i}
γti的构造结合了
α
t
i
\alpha_{t_i}
αti,并进行了简单的归一化。。
代码相比于上一个,添加了几行:
def __call__(self, x, coverage = None, finished = None):
gamma_h = tf.tanh(tf.nn.xw_plus_b(x, self.Ws, self.bs)) # batch * hidden_size
alpha_h = tf.tanh(tf.nn.xw_plus_b(x, self.Wr, self.br))
fd_weights = tf.reduce_sum(self.phi_fds * alpha_h, reduction_indices=2, keep_dims=True)
fd_weights = tf.exp(fd_weights - tf.reduce_max(fd_weights, reduction_indices=0, keep_dims=True))
fd_weights = tf.divide(fd_weights, (1e-6 + tf.reduce_sum(fd_weights, reduction_indices=0, keep_dims=True)))
weights = tf.reduce_sum(self.phi_hs * gamma_h, reduction_indices=2, keep_dims=True) # input_len * batch
weights = tf.exp(weights - tf.reduce_max(weights, reduction_indices=0, keep_dims=True))
weights = tf.divide(weights, (1e-6 + tf.reduce_sum(weights, reduction_indices=0, keep_dims=True)))
weights = tf.divide(weights * fd_weights, (1e-6 + tf.reduce_sum(weights * fd_weights, reduction_indices=0, keep_dims=True)))
context = tf.reduce_sum(self.hs * weights, reduction_indices=0) # batch * input_size
out = tf.tanh(tf.nn.xw_plus_b(tf.concat([context, x], -1), self.Wo, self.bo))
if finished is not None:
out = tf.where(finished, tf.zeros_like(out), out)
return out, weights
此外,我们对生成的未知(UNK)token使用后处理操作,以缓解词汇量不足(OOV)问题。 我们根据相关的双重关注矩阵,用对应表中最相关的token替换特定生成的UNK令牌。
3. Local and Global Addressing
Local addressing 和global addressing 确定在不同的描述生成阶段中应更关注表的哪一部分。
寻址的两个范围在理解和表示表的内部结构中起着非常重要的作用。接下来,我们将借助图介绍我们的模型如何对表到文本生成进行本地和全局寻址。
Local addressing
一个表格可以被认为是一系列的领域—值的记录。每一条记录的值平均有2.7个token。一些在维基百科的记录包含很多不同的阶段和语句。
以前使用one-hot或统计语言模型对字段内容进行编码的模型无法有效地捕获字段内单词之间的语义相关性。 seq2seq结构本身具有强大的能力来模拟单词的上下文。 一方面,LSTM编码器可以捕获表中单词之间的长期依赖关系。 另外,所提出的双重关注机制的单词级别关注也可以在描述中的单词与表中的标记之间建立联系。
图片3中生成的单词‘actor’就是值在‘Occupation’领域的‘actor’,而不是其他领域或者含义的‘actor’。一看论文就是中国人写的,多读几遍就理解了。。
Global addressing
本地寻址的目的是表示内部记录信息,而全局寻址的目的是对表中的记录间相关性进行建模。 例如,值得注意的是,图3中生成的令牌“演员”已映射到表2中的“占用”字段。提出了用于全局寻址的字段门控表表示和字段级别关注机制。 对于表表示,我们通过将字段和位置嵌入到表表示中(而不是词嵌入)来对表的结构进行编码。
此外,表中的信息可能是多余的。 表中的某些记录对于生成描述不重要,甚至无用。 从所有表中选择有用的信息时,我们应该做出适当的选择。 记录的顺序也可能影响生成的性能(Vinyals,Bengio和Kudlur,2015年)。 我们应该通过表的字段信息及其描述之间的全局寻址来弄清楚要生成的令牌的记录集中在哪个记录上。 引入了双重注意机制的场关注度,以确定在特定时间步长内发生器关注的场。 实验表明,我们的双重关注机制对于从某些表生成描述而对表记录的不同顺序不敏感,这有很大的帮助。
实验
》〉》〉》〉》〉》〉
在生成表描述时,在解码阶段开始时,将特殊的起始token ⟨sos⟩输入到生成器中。 然后,我们将最后生成的token用作下一步。 特殊的结束标记用于标记解码的结束。 我们还将生成的文本限制为预定义的最大长度,以避免产生多余或无关紧要的内容。 我们还尝试使用2-10的beam大小进行beam search以增强性能。 我们使用网格搜索来确定模型的参数。
关于beam search: