ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

结构感知Seq2seq:表格到文本生成的技术解析

结构感知Seq2seq:表格到文本生成的技术解析

1. 这篇论文到底在解决什么问题

1.1 一个容易被忽视的任务形态

表格到文本生成(table-to-text generation)这个任务,乍一听好像没那么复杂:把一张表格“翻译”成一段人话。但真正上手做就会发现,它和普通文本生成完全不是一回事。

普通文本生成(比如写摘要、写新闻)的输入是连续文本,词与词之间存在天然的语言上下文。而表格的输入是一堆离散的键值对、数字、实体名,它们之间没有语法、没有语序。比如“某球员、球队、出场次数、本垒打数、年度数据”这些字段,彼此靠的是逻辑关系而不是语言关系。模型要做的不是“改写”,而是“从结构化数据中决定说什么、按什么顺序说、数字怎么表达、哪些信息该突出”,这比单纯的语言建模要复杂得多。

我前阵子重新把Structure-aware Seq2seq这篇论文翻出来细读,就是想把它当成一个坐标原点:看看在预训练语言模型铺天盖地之前,学术界是怎么用最朴素的Seq2seq框架,靠“结构感知”四个字把这个任务做出成效的。这篇论文的核心贡献可以概括为:把表格按单元拆解并注入特殊标记,让编码器看到结构,解码器在生成时也能感知结构。

如果你正在做结构化数据生成、信息抽取、自动报告生成,或者要给表格生成任务找一个靠谱的基线模型,这篇论文非常值得读。它发表于2018年,今天来看模型骨架确实老了,但它的设计思路和实验分析方法,放到现在仍然不过时。

1.2 为什么“结构感知”四个字是关键

很多人读论文容易把注意力放在“Seq2seq”上,觉得这就是一个普通的编码器解码器模型,顶多加个Attention。但其实这篇论文真正的灵魂是“Structure-aware”,也就是结构感知。

先想一个问题:一张表格输入给模型,最朴素的处理方式是什么?就是把每个单元格当成一个token,或者把一行拼接成字符串,然后丢给编码器。这种方式的问题很明显,模型看到的是一串平坦的token流,哪里是列名、哪里是值、哪个值对应哪个列,全部混在一起。生成的文本自然就容易张冠李戴,比如该说“红袜队球员”的地方说成“洋基队球员”,该写“345”的地方写成“435”,数字计算更是完全指望不上。

论文的做法是,在输入序列里显式注入“结构标记”。比如单位标记(unit)、实体标记(entity)、数字结束标记(number-end)、聚合占位符(aggregator)。这些标记相当于给模型的输入加了一套导航坐标系,让模型知道:当前位置是一个表格单元的起始点、当前位置的实体是专有名词、当前位置的数字需要特别处理。这套标记的设计非常朴素,但非常有效,它把“结构信息”从一种隐含特征变成了模型可以直接学习的信号。

这就是结构感知的核心逻辑,与其让模型从一堆布尔值里自己摸索表格结构,不如在数据层面把结构写出来。

2. 方法整体拆解:从表格到文本的转换管线

2.1 输入序列的构造方式

要理解论文的方法,第一步要看懂它的输入是怎么构造的。

一张表格通常包含若干行和若干列,但本质上可以看成N个单元(cell)的集合。每个单元是一个“列名-值”对。论文的预处理方式非常工程化:把表格按某种顺序展开成单元序列,每个单元再拆成一组离散token,token之间用逗号分隔。

举个例子,表格里如果有一行是“出生地:某市、职业:运动员”,处理后的序列大致长这样:

unit 出生地 , entity 某市 , unit 职业 , entity 运动员

这里“unit”是单元起始标记,表示接下来是一个“列名”,“entity”标记表示后面跟的是一个实体值。普通的值(比如某个非实体的描述性短语)会直接保留原词。这样做的好处是,模型在编码阶段就能区分“列名”和“值”,不会再出现把列名当成内容生成出来的低级错误。

更关键的是数字的处理方式。表格里经常有大量数字,而Seq2seq模型对数字的生成非常不友好,它很难把一个具体数字从词表里正确地挑出来。论文的做法是:在数字位置放一个统一的占位符X,数字真正的内容则和“聚合器”(aggregator)标记绑定在一起。数字后面还会跟一个number-end标记,用来告诉解码器:这个数字到这里就结束了。

具体到某个表格单元“本垒打:45”,预处理后就变成:

unit 本垒打 , entity 45, number X

X代表“这里有一个数字值”,但真正的数字内容被单独记录到了一张查找表里。到解码阶段,模型要么直接生成一个词,要么通过复制机制从查找表里取出真实的数字内容写入输出。我在复现时觉得这个设计尤其巧妙:它把“数字生成”从“从词表里挑词”变成了“从候选数字池里做复制”,难度降了一个量级。

2.2 结构感知编码器的设计逻辑

输入序列构造好了,接下来交给编码器。论文用的是双向LSTM,这在当时是非常主流的选择。我刚开始读的时候也好奇,为什么不直接用更简单的单向RNN?后来想明白了:表格单元之间是有前后关系的,比如一张人物履历表里,年份是递增排列的,位置信息是连续出现的,前向LSTM能看到“这个单元左边有什么”,后向LSTM能看到“这个单元右边有什么”,双向叠加之后,每个单元格可以获得左右两侧更多的上下文。

底层编码的细节也值得关注。每个单元的初始向量,是把“列名向量”和“值向量”拼在一起得到的,再叠加上单元标记、实体标记、数字标记等可学习的向量。你可以在脑内把这个过程想象成做菜:食材还是那些食材,但每个食材都被贴上了“主料”“辅料”“调味料”的标签,厨师看一眼就知道该怎么处理。

编码器输出的隐层状态,会同时服务于解码端的Attention机制和分类器模块。所以它不只是一个文本编码器,而是整个结构感知体系的信息枢纽。

2.3 解码端的三类结构信息

解码部分用的是带Attention的单向LSTM。但论文的巧劲在于,解码器生成每个词的时候,不会只盯着一个普通的源端context向量,而是同时引入三类结构化的Attention特征。

第一类是实体层面的Attention,它让解码器在生成实体相关词汇时,能明确知道“当前这个实体在表格里的哪个位置”。第二类是数字层面的Attention,专门处理数字内容的复制对齐。第三类是整体单元结构Attention(对应原文里的whole-unit attention),它把“列名+值”作为一个整体来做匹配,解决的是跨列信息选择的问题。

我当时在复现笔记里写了一句:这三个Attention本质上是在一个常规Attention之外,又给解码器开了三扇观察窗口。普通Attention看到的是“整个序列对我当前生成的词的贡献分布”,而这三个窗口分别告诉模型“实体在哪里”“数字在哪里”“哪个单元格与我相关”。

解码器最终生成词的时候,会把当前解码隐层状态、普通context向量、以及三种结构感知向量拼在一起,再过几层MLP映射到词表分布。这样一来,模型每一步生成都同时受到语言上下文和表格结构的约束,生成的文本自然会更贴近表格的原始信息。

3. 三个容易被忽略的细节机制

3.1 数字编码与聚合器占位符

很多人第一次读这篇论文,最容易忽略的就是聚合器(aggregator)这个占位符的作用。我在1.2节已经提了一句,但这里值得单独展开。

表格里经常出现这样的需求:不仅要生成单个数字,还要生成像“2454场”“762个本垒打”这种数字+单位的长表达。如果模型的词表里没有“2454”这个词,它根本不可能生成出来。即便词表里有,让模型在几万个词里精确找到这个数字,概率也极低。

论文的办法是:把数字内容换成X占位符,把真正的数字内容放进一个查找表。解码的时候,模型在数字位置生成X,然后一个专门的二进制分类器决定“这个X要不要被替换成真实数字”。如果决定替换,就从查找表里找到对应位置的那个数字,原样复制到输出。整个过程中,模型对数字内容本身不求甚解,它只需要学会“该在这里放一个数字”。

我第一次看到这个设计时觉得有点“偷懒”,但后来在复现中才发现,这恰恰是应对表格数据里长尾数字最有效的手段。现在的很多预训练模型在数字上依然会出错,甚至出现“编造数字”的情况,而这套占位符+复制机制几乎从根源上解决了这个问题。

聚合器本身的处理方式也很巧妙。它不是一个词,而是一个触发标记。当解码器输出这个标记时,它要去查找表里把一整段数字+单位表达取出来。举个例子,表格里存的是“出场次数:2454”,那么在预处理阶段,这个单元会变成:

unit 出场次数 , entity X aggregator

同时查找表里记录“X=2454”。解码时模型生成的文本可能是“他在职业生涯中出场X次”,然后聚合器机制把X替换成2454,最终得到“他在职业生涯中出场2454次”。这种设计把需要计算的数字问题变成了对齐问题,模型不用做算术,只需要找对位置。

3.2 结构感知Attention的三个分支

这一节我想把三种Attention的细节讲透,尤其是它们各自分工的差异。

先看实体Attention。它计算的是解码器当前隐层状态与所有“实体向量”之间的匹配程度。这个实体向量不是单词embedding,而是编码器在某个实体标记位置输出的隐层状态。计算出来的权重分布会加权求和出一个“实体上下文向量”,告诉模型:如果要生成实体,哪个实体的信息最重要。我用一个例子说明:如果当前要生成“红袜队”这个词,实体Attention会自动把权重放在表格里的“球队:红袜”这个单元上。

再看数字Attention。它跟实体Attention结构上差不多,但关注的对象是数字位置的向量。它的特殊之处在于,它不仅要决定“哪个数字相关”,还要配合数字复制分类器决定“是否复制数字”。我在复现时发现一个细节:数字Attention的向量在计算时,通常会和解码隐层状态做一次精炼变换,然后再拼进后续模块,论文里对此有专门的公式描述。你不需要记住公式的每一个下标,只需要理解:数字Attention是“复制决策”的信息源。

最后是整体单元结构Attention。这是我最喜欢的一个设计。前面两种Attention分别管实体和数字,但表格里还有很多普通内容,比如一段描述性文字、一个日期、一个无实体标注的属性值。整体单元结构Attention把“列名+值”作为一个单元整体来匹配,本质上是在做“基于表格结构的信息检索”。它回答的问题不是“哪个实体重要”,而是“当前这一步生成应该参考哪一格的内容”。

三个Attention输出三个结构向量,再加上普通Attention产生的context向量,共同参与最终词分布的计算。你可以把它们想象成四个参谋同时给解码器提意见,有的管实体,有的管数字,有的管整体检索,综合下来生成的文本自然更稳。

3.3 分类器模块与训练目标

论文在解码过程中还插入了两个分类器模块。我复现的时候一开始并没有理解为什么需要这两个“额外模块”,直到自己动手做数字生成时才发现,如果不显式训练“是否复制数字”这个决策,解码器根本学不会什么时候该触发查找表。

第一个分类器是数字复制分类器。它的输入是解码隐层状态、数字Attention向量、以及普通context向量,输出一个0/1概率,决定当前位置的X是否应该替换成真实数字。训练标签很好构造:如果当前目标词是对应查找表中的一个数字,标签就是1,否则为0。

第二个分类器是聚合器分类器。它的任务是决定当前位置是否应该触发一次聚合器查找,也就是要不要把一整段“数字+单位”从表里取出来。这个分类器的输入和数字复制分类器类似,但标签的构造更严格一点:只有当前输出位置确实对应一个聚合器占位符时才标为1。

这两个分类器和解码器的词表生成是联合训练的。总损失是解码词表生成的负对数似然加两个分类器的交叉熵损失之和。我在训练时观察到,如果去掉这两个辅助分类器,模型在数字指标上的表现会掉得很明显,这也印证了论文设计它们的必要性。

4. 训练与推理的实操要点

4.1 数据预处理与标注体系

论文用的是公开的人物履历数据集,大约有几十万条训练样本。每一条对应一个维基百科风格的“信息框+概述文本”对。信息框就是那张表格,概述文本就是我们要生成的目标句子。

预处理时要把信息框解析成“单元序列”。我当时在复现中踩过的一个坑是:信息框里的字段顺序并不固定,有的表格年份在第一个,有的表格年份排在最后,如果不做字段排序,模型学到的语序规律会很混乱。论文里的做法是相对固定的按原始信息框的顺序来,把每个信息框展开成一行,生成目标文本时按原始概述文本的顺序和句子切分。所以你真正需要关注的是对齐问题:必须保证表格单元和目标文本是准确的平行语料,否则Attention再怎么学都是白搭。

实体标记、数字标记、聚合器占位符的构建也在预处理阶段完成。建议在预处理工具里输出几行中间结果人工检查一下,标记有没有遗漏、数字有没有被错误替换,这能省去后期大量debug时间。

4.2 训练配置与损失函数

先说训练中要注意的点。论文用的是标准的NLL损失优化解码生成,附加两个分类器的交叉熵。实际操作时我建议把两个分类器的损失加权系数稍微调低一点,比如各取0.5左右,防止辅助任务喧宾夺主。

词表构建上,论文用的是预训练词向量初始化,并对低频词做了截断。我复现时把词频阈值设在5左右,低于这个频次的词统一替换为UNK。实体词和数字占位符不参与截断,因为它们太重要了,一旦被UNK吞掉,生成质量会明显下滑。

另一个容易被忽略的点是batch内长度排序。表格单元展开成token序列后,长度参差不齐,有的样本可能只有20个token,有的能到80个以上。如果不做排序和分桶,GPU利用率会很低,训练速度慢得让人怀疑人生。建议按长度排序后分桶打包,每批内部尽量保持长度相近。

训练轮数方面,我跑下来大概4到6个epoch就能看到验证集指标趋于平稳。更关键的是用验证集上的表格打分来早停,而不是只盯着训练loss,因为训练loss降得漂亮不代表结构化信息生成得准确。

4.3 推理时的beam search与规则细节

推理阶段,论文用的beam search宽度我记得是5左右。我实际测试时发现,beam size从3调到5,指标会有微弱提升,再往上调收益就开始递减,还有可能引入重复片段。这个度和论文报告的结论基本一致。

beam search之外,还需要注意两个“推理期规则”。第一个是数字复制触发规则:当解码器生成X占位符,且数字复制分类器输出概率超过一个阈值(比如0.5)时,才从查找表里替换数字。第二个是聚合器触发规则:当生成内容命中聚合器标记时,要把查找表里的完整表达原样复制进输出。这两个规则看起来简单,但如果分类器概率没有归一化好,很容易出现“该复制时不复制、不该复制时瞎复制”的情况。

我还建议在推理时给生成的句子设置一个最大长度限制,并加入轻微的长度惩罚,防止模型在长表格上无休止地重复生成同一个实体。我复现时碰到过一次极端的case:某个球员表格生成了200多字,内容翻来覆去就是那几支球队名,加了长度惩罚之后明显好转。

5. 实验结果怎么读,以及复现避坑清单

5.1 自动指标与人工评测怎么拆开看

论文的实验部分,除了常规的BLEU之外,还设计了两个更贴合任务本身的自动评价指标:表格打分和关系打分。

表格打分解决的是“内容选择准不准”的问题。它把生成句子里的内容实体提取出来,与真实表格里的实体做匹配,同时检查这些实体出现的单位信息是否正确。关系打分则进一步看“关系对不对”,比如“球员-所属球队”这个关系是否被正确表述。这两个指标加在一起,比单独看BLEU更能反映表格生成模型是否真的读懂了表。

我当时读实验时最深的感受是:表格打分很高、关系打分也很高,但BLEU并没有特别出彩。这个现象解释起来很简单:模型生成的句子可能语义信息完全正确,但用词顺序和人工参考文本不完全一致,BLEU这种偏字面匹配的指标自然给分不高。所以,如果你要在自己的任务上使用这套模型,一定不要只盯着BLEU,结构化指标更值得关注。

论文还做了一部分人工评测,让标注者判断生成文本是否“像人写的”。这部分虽然样本量不大,但结论很有参考价值:加了结构感知标记的模型,在人工评测上的相对优势明显高于在BLEU上的优势。这说明结构感知对提升文本的真实感和可读性有很大帮助。

5.2 复现时的常见问题和排查思路

我在复现这套模型时遇到过不少问题,挑几个典型的分享出来,希望你能少走弯路。

第一个问题是预处理阶段单元切分和标记后的token流对应关系。比如“unit本垒打, entity45, numberX”这种格式,如果逗号处理不对,模型会把逗号当成一个普通token,直接干扰Attention计算。排查方式很简单,把处理后的序列打印出来人工看一眼,逗号是否还在、标记是否粘连,立刻就能发现。

第二个问题是数字查找表在训练和推理时的对齐。训练阶段,查找表是从当前输入表格动态构建的,同一个数字可能在多个表格里出现,如果不按表格维度区分,推理时很容易串表。我当时的解决方式是为每个表格维护一个独立的查找表索引,并在batch内保证同一个batch的表格互不干扰。

第三个问题是分类器训练的标签不平衡。数字复制分类器的正例比例很低,大多数位置都是非数字内容,直接训练很容易让分类器把所有位置都预测成“不复制”。我试过对正例加权、也给分类器单独设了较高的loss权重,整体效果最好的是用Focal Loss替代标准交叉熵,虽然论文里没有提到这一点,但在复现中确实是行之有效的手段。

第四个问题是生成文本的重复。尤其当表格单元很多时,解码器会陷入局部循环。除了调beam size和长度惩罚,我还建议在attention计算时引入覆盖惩罚(coverage penalty),对历史attention权重的总和加一个约束,这在复现中能明显抑制重复片段。

第五个问题,也是最容易被忽视的,是预训练词向量的外部依赖。有些公开词向量覆盖不了表格里的专有名词和数字占位符,这些词如果随机初始化,在训练初期会产生较大梯度波动。建议对词向量矩阵中未命中词做适度的小范围随机初始化,并加上较大的dropout缓解过拟合。

我整理了一张速查表,方便你快速定位问题:

现象常见原因排查建议
训练loss不下降预处理标记混乱、词表比例失衡打印处理后的样本,检查标记和逗号
数字复制错误查找表对齐出错、分类器过偏按表格维度隔离查找表,检查标签分布
生成重复文本beam过宽、无覆盖惩罚调低beam size,加coverage penalty
实体张冠李戴实体attention未收敛、解码器过深加大dropout,适当缩小解码器隐层维度
表格打分高但BLEU低属于正常现象,不必焦虑以表格打分和关系打分为主指标
推理速度慢表格太长、beam太宽限制生成最大长度,使用批量推理

6. 论文给我的三个体会

读到这篇论文,我最先想到的是那句“简单但有效”。它的方法放到今天来看,既没有大规模的预训练,也没有复杂的图神经网络,就是一个标准的Seq2seq加几个特殊标记和分类器。但正是这些“小改动”,把一个看似无从下手的结构化数据生成问题变得可解。

第一个体会是:输入端的结构标记,一分钱一分货。很多现代化方法喜欢把结构信息塞进loss或者attention里,但最朴素的“在输入里加标记”反而最容易传播、最容易复现,也最稳定。你不需要修改模型架构就能获得明显的效果提升。

第二个体会是:数字问题值得单独处理。无论你未来做的是报告生成、体育数据解说还是财报摘要,数字准确率永远是用户最在意的点。把数字生成转为复制机制,是一个完全可以迁移到现代模型里的好思路。

第三个体会是:评价指标要跟着任务走。论文没有迷信BLEU,而是设计了表格打分和关系打分,这件事本身就给复现者提供了很好的方法论。我后来做类似项目时,都习惯先定义清楚“什么算对”,再开始训练模型。

如果你正在规划自己的表格到文本项目,我建议你先把这篇论文从头到尾复现一遍。它的代码量不大,模型结构也清晰,是练手和搭基线的绝佳选择。复现过程中遇到的每一个问题,都会让你对结构化生成的理解更深一层。我踩过的那些坑,你大概率也会踩,但踩过之后你会发现,这些坑本身就藏着对任务最深的理解。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进