最近业余时间弄了一个文本生成的项目,在此将相关知识点总一下总结。
项目说明
本项目中,我们作为输入的原文称之为 source,待生成的目标文本 称之为 target ,用来作为 target 好坏的参考文本称之为 reference。
在本项目的数据源来自于某电商的发现好货栏目,source 主 要由三部分构成:1 是商品的标题,2 是商品的参数,3 是商品宣传图片里 提取出来的宣传文案,reference 则是实际该商品的营销文案;
如下是某一条样本以及训练好的模型的预测结果,其中beam是解码出来的文本,reference是参考文本。
source
瑜伽 服 上衣 女 长袖 运动 T恤 性感 露肩 轻质 薄款 跑步 健身 套头衫 上衣 深紫色 精选 搭配 , 拇指 洞 细节 , 产品 介绍 , 细节 处 的 温暖 设计 , 产品 信息 , 型录 , 时尚 的 后背 反光 印花 , 洗涤 小贴士 , 肩部 缕空 设计 , 适用 场景 , 面料 , 专业 的 运动 面料 需 单独 洗涤 ,尽量避免 浸泡 洗涤 , 并 尽快 清洗 , 建 , 议 轻柔 手洗 , 若用 洗衣机 清洗 , 应先 装入 洗衣袋 中 , 产品 系列 : 时尚 搭配 , 秋冬 日常 内 搭 or 运动 外穿 皆 可 , 舒适 体验 每 项 运动 , 是否 含胸 垫 : 否 , 品名 : 休闲 T恤 , 透气 : , 适中 , 不可 漂白 , 针织 面料 , 加厚 , 既 时尚 又 能 为 夜 跑 的 你 带来 , 柔软度 : , 清爽 快干 的 面料 , 贴 合身 型 的 剪裁 , 在 降温 时 带来 温暖 的 安心 感 , 肩部 缕空 运动 上衣 , 偶遇 小 性感 的 日常 系列 , 支撑 度 : , 衣 长 , 身高 , 胸围 , 有害物质 , 与 国内 化学 染料 不同 , 天然染料 存有轻微 浮色 现象 , 介 , 四向 弹力 , 满足 运动 的 灵活 需求 , 加长 的 袖口 配合 拇指 洞 , 低温 滚筒 烘干 , 款式 : , 日常 , 透气 快干 , 紧身 , 体重 , 强度 , 安全 , 柔软 , 偏软 , 优良 , 宽松 , 高强 , 弹力 : , 修身 , 健身 , 肩宽 , 偏硬 , 中度 , 轻度 , 瑜伽 ,普通 , 厚度 : , 良好 , 微弹 , 无弹 , 88% 涤 訾 12% 氨 訾 , 体重 : 100袖长 长袖 衣长 其它 适用季节 四季 是否含胸垫 不含胸垫 适用人群 女士 材质 其它 功能 吸湿排汗。
beam
运动 休闲 的 短袖 T恤 , 采用 优质 的 面料 材质 , 带来 舒适 的 穿着 体验 , 同时 具有 良好 的 透气性 , 即使 在 炎热 的 夏季 也 能 穿 出 清凉 的 感觉 。 后背 的 反光 印花 , 让 夜间 跑步 更加 安全 。
reference
这款 性感 露肩 运动 上衣 , 轻薄 的 面料 , 吸湿 排汗 性能 良好 , 跑步 或 做 瑜伽 , 让 肢体 更 轻松 有 动力 。 利落 的 线条 贴合 上身 , 勾勒 出 性感 身材 , 让 健身运动 充满 时尚 气息 。
项目设计的知识点
1.Seq2Seq+Attention模型(baseline);
2.在baseline基础上,引入PGN(Pointer-Generator Networks) + coverage机制;
3.引入 “Weight tying”、"Scheduled sampling"优化机制;
4.进行 Beam Search 优化。
下面依次阐述
Seq2Seq+Attention模型(复习)
首先是基准seq2seq+attention的模型结构,原文的Pointer-Generator Networks也是在此基础上构建的,框架如图1所示:
正常来讲,Seq2Seq的模型结构就是两部分–编码器和解码器。正常思路是先用编码器将原文本编码成一个中间层的隐藏状态,然后用解码器来将该隐藏状态解码成为另一个文本。Seq2Seq在编码器端是一个双向的LSTM,这个双向的LSTM可以捕捉原文本的长距离依赖关系以及位置信息。编码时词嵌入经过双向LSTM后得到编码状态
h
i
h_i
hi。在解码器端,解码器是一个单向的LSTM,训练阶段时参考摘要词依次输入(测试阶段时是上一步的生成词),在时间步
t
t
t得到解码状态
s
t
s_t
st。使用
h
i
h_i
hi和
s
t
s_t
st得到该时间步原文第
i
i
i个词注意力权重:
e
i
t
=
v
T
t
a
n
h
(
W
h
h
i
+
W
s
s
t
+
b
a
t
t
)
a
t
=
s
o
f
t
m
a
x
(
e
t
)
e^t_i=v^Ttanh(W_hh_i+W_ss_t+b_{att}) \\ a^t=softmax(e^t) \\
eit=vTtanh(Whhi+Wsst+batt)at=softmax(et)
得到的注意力权重和
h
i
h_i
hi 加权求和得到重要的上下文向量
h
i
∗
h_i^*
hi∗ (context vector):
h
i
∗
=
∑
i
a
i
t
h
i
h_i^*=\sum_ia_i^th_i
hi∗=i∑aithi,
h
i
∗
h_i^*
hi∗可以看成是该时间步通读了原文的固定尺寸的表征。然后将
s
t
s_t
st 和
h
i
∗
h_i^*
hi∗经过两层线性层得到单词表分布
P
v
o
c
a
b
P_{vocab}
Pvocab :
P
v
o
c
a
b
=
s
o
f
t
m
a
x
(
V
′
(
V
[
s
t
,
h
t
∗
]
+
b
)
+
b
′
)
P_{vocab}=softmax(V'(V[s_t,h_t^*]+b)+b')
Pvocab=softmax(V′(V[st,ht∗]+b)+b′),其中
[
s
t
,
h
t
∗
]
[s_t,h_t^*]
[st,ht∗]是拼接,这样就得到了一个softmax的概率分布,就可以预测需要生成的词:$
P
(
w
)
=
P
v
o
c
a
b
(
w
)
P(w)=P_{vocab}(w)
P(w)=Pvocab(w),在训练阶段,时间步
t
t
t 时的损失为:
l
o
s
s
t
=
−
l
o
g
P
(
w
t
∗
)
loss_t=-logP(w_t^*)
losst=−logP(wt∗),那么原输入序列的整体损失为:
l
o
s
s
=
1
T
∑
t
=
0
T
l
o
s
s
t
loss=\frac{1}{T}\sum_{t=0}^Tloss_t
loss=T1t=0∑Tlosst
指针生成网络其实是PointerNetwork的延续,应用于摘要生成任务中。该网络使用generator保留了其生成能力的同时,用pointer从原文中Copy那些OOV词来保证信息正确的重复。原文更为重要的创新点是应用了coverage mechanism来解决了seq2seq的通病–repitition,这个机制可以避免在同一位置重复,也因此避免重复生成文本。下面是模型结构。
Pointer-Generator Networks
Pointer-Generator Networks是一个混合了 seq2seq+atten和PointerNetwork的网络,它具有seq2seq的生成能力和PointerNetwork的Copy能力。如何权衡一个词应该是生成的还是复制的?论文中引入了一个权重
P
g
e
n
P_{gen}
Pgen 。图2是该网络的模型结构:
从 seq2seq的模型结构中得到了
s
t
s_t
st 和
h
t
∗
h_t^*
ht∗,和解码器输入
x
t
x_t
xt一起来计算
P
g
e
n
P_{gen}
Pgen:
P
g
e
n
=
σ
(
w
h
∗
T
h
t
∗
+
w
s
T
s
t
+
w
x
T
x
t
+
b
p
t
r
)
P_{gen}=\sigma(w_{h^*}^Th_t^*+w_s^Ts_t+w_x^Tx_t+b_{ptr})
Pgen=σ(wh∗Tht∗+wsTst+wxTxt+bptr)这时,会扩充单词表形成一个更大的单词表–扩充单词表(将原文当中的单词也加入到其中),该时间步的预测词概率为:
P
(
w
)
=
P
g
e
n
P
v
o
c
a
b
(
w
)
+
(
1
−
P
g
e
n
)
∑
i
:
w
i
=
w
a
i
t
P(w)=P_{gen}P_{vocab}(w)+(1-P_{gen})\sum_{i:w_i=w}a_i^t
P(w)=PgenPvocab(w)+(1−Pgen)i:wi=w∑ait其中
a
i
t
a_i^t
ait表示的是原文档中词的注意力权重分布。我们可以看到解码器一个词的输出概率由其是否拷贝和是否生成的概率共同决定。当一个词不出现在常规的单词表上时,
P
v
o
c
a
b
P_{vocab}
Pvocab 为0,当该词不出现在文档中时
∑
i
:
w
i
=
w
a
i
t
\sum_{i:w_i=w}a_i^t
∑i:wi=wait 为0。
Coverage Mechanism
原文中最大的亮点就是运用了Coverage Mechanism来解决重复生成文本的问题,具体实现上,就是将先前时间步的注意力权重加到一起得到所谓的覆盖向量
c
t
c^t
ct(coverage vector),用先前的注意力权重决策来影响当前注意力权重的决策,这样就避免在同一位置重复,从而避免重复生成文本。计算上,先计算coverage vector
c
t
c^t
ct:
c
t
=
∑
t
′
=
0
t
−
1
a
t
′
c^t=\sum_{t'=0}^{t-1}a^{t'}
ct=t′=0∑t−1at′然后添加到注意力权重的计算过程中,
c
t
c^t
ct用来计算
e
i
t
e_i^t
eit :
e
i
t
=
v
T
t
a
n
h
(
W
h
h
i
+
W
s
s
t
+
w
c
c
i
t
+
b
a
t
t
n
)
e_i^t=v^Ttanh(W_hh_i+W_ss_t+w_cc_i^t+b_{attn})
eit=vTtanh(Whhi+Wsst+wccit+battn)同时,为coverage vector添加损失是必要的,coverage loss计算方式为:
c
o
v
l
o
s
s
t
=
∑
i
m
i
n
(
a
i
t
,
c
i
t
)
covloss_t=\sum_imin(a_i^t,c_i^t)
covlosst=i∑min(ait,cit)这样coverage loss是一个有界的量
c
o
v
l
o
s
s
t
<
=
∑
i
a
i
t
=
1
covloss_t<=\sum_ia_i^t=1
covlosst<=∑iait=1。因此最终的LOSS为:
l
o
s
s
t
=
−
l
o
g
P
(
w
t
∗
)
+
λ
∑
i
m
i
n
(
a
i
t
,
c
i
t
)
loss_t=-logP(w_t^*)+\lambda\sum_imin(a_i^t,c_i^t)
losst=−logP(wt∗)+λi∑min(ait,cit)从结果看,就是加了一个损失正则项。
Weight tying
这个很简单,本质上就是:Encoder的input embedding,Decoder的input emdedding和Decoder的output embedding(pre-softmax)之间的权重共享。即three-way tying。相关论文
Attention is all you need 一文中提到:In our model, we share the same weight matrix between the two embedding layers and the pre-softmax linear transformation, similar to [29].
Weight Tying 可以显著减小模型的参数量,模型更小,收敛更快更容易,共享的权重矩阵在语义上是相通的,虽然weight共享了,但是embedding和pre-softmax仍然是两个不同的层,因为bias是彼此独立的。这算是一种训练的trick,详情可以参考另一篇文章
Scheduled sampling
对于seq2seq模型而言,训练时该模型将目标序列中的真实元素作为解码器每一步的输入,然后最大化下一个元素的概率。但是在预测解码时则是把上一步解码得到的元素被用作当前的输入,然后生成下一个元素。可见这种情况下训练阶段和预测阶段的解码器输入数据的概率分布并不一致。
试想,如果某个step预测的时候,前面预测的结果是错的,那么后面的结果则会错得越来越离谱,这种现象被称为“偏差的累积”,Scheduled Sampling就是一种解决训练和生成时输入数据分布不一致的方法。在训练早期该方法主要使用目标序列中的真实元素作为解码器输入,可以将模型从随机初始化的状态快速引导至一个合理的状态。随着训练的进行,该方法会逐渐更多地使用生成的元素作为解码器输入,以解决数据分布不一致的问题。
标准的序列到序列模型中,如果序列前面生成了错误的元素,后面的输入状态将会收到影响,而该误差会随着生成过程不断向后累积。Scheduled Sampling以一定概率将生成的元素作为解码器输入,这样即使前面生成错误,其训练目标仍然是最大化真实目标序列的概率,模型会朝着正确的方向进行训练。因此这种方式增加了模型的容错能力。
Scheduled Sampling主要应用在序列到序列模型的训练阶段,而生成阶段则不需要使用。训练阶段解码器在最大化第
t
t
t个元素概率时,标准序列到序列模型使用上一时刻的真实元素
y
t
−
1
y_{t−1}
yt−1作为输入。设上一时刻生成的元素为
g
t
−
1
g_{t−1}
gt−1,Scheduled Sampling算法会以一定概率使用
g
t
−
1
g_{t−1}
gt−1作为解码器输入。
设当前已经训练到了第
i
i
i个mini-batch,Scheduled Sampling定义了一个概率
p
p
p控制解码器的输入。
p
p
p是一个随着
i
i
i增大而衰减的变量.
在解码器的
t
t
t时刻Scheduled Sampling以概率
p
p
p使用上一时刻的真实元素
y
t
−
1
y_{t−1}
yt−1作为解码器输入,以概率1−
p
p
p使用上一时刻生成的元素
g
t
−
1
g_{t−1}
gt−1作为解码器输入。这样一来,随着
i
i
i的增大,
p
p
p会不断减小,解码器将不断倾向于使用生成的元素作为输入,训练阶段和生成阶段的数据分布将变得越来越一致。
至于
i
i
i和
p
p
p的关系,可以是线性衰减、指数衰减,看具体实现吧。