
你随手拍了一张照片发到群里群里有人回了一句“这只猫蹲在键盘上看起来很不服气”。人能轻松做到“看图说话”但对计算机来说这件事在2015年以前基本靠手工模板硬凑。直到CVPR 2015上出现了一篇论文《Show and Tell: A Neural Image Caption Generator》用一套端到端的神经网络把“图像”和“文本”两个方向焊在了一起第一次让机器能像人一样看着图写出一句通顺、自然、匹配内容的句子。后面无数多模态模型、视觉问答、图文生成的研究几乎都能从这篇论文里找到影子。这篇文章不是泛泛介绍我会从论文的核心思想、模型架构、公式直觉、训练细节、评估指标一直讲到复现时容易踩的坑。新手不需要先懂LSTM和CNN里的每个细节我会用日常类比把原理讲清楚。读完你会彻底搞懂这张图是怎么变成一个词一个词的句子的。1. 看图说话为什么难这篇论文要打的靶子1.1 让机器写一句人话难在哪里图像描述生成Image Captioning和目标检测、图像分类不太一样。检测只需要回答“这图里有什么物体分别在哪”分类只需要回答“这张图属于哪一类”但描述任务要回答的是“这张图里发生了什么事物体之间是什么关系有没有动作和属性”比如一张照片里有一位老太太、一只猫、窗台、阳光。机器要写“一位老人在窗边晒太阳怀里抱着一只猫”它至少得知道“老人”是主体“猫”是被抱着的“窗边”是位置“晒太阳”是环境暗示。这已经不只是识别而是要求模型有某种程度上的“场景理解”和“语言组织”能力。在《Show and Tell》之前主流做法大概分成两派一派是“检测模板”先检出一堆物体然后填进预制的句子里比如“A _ is _ in the _”。这类方法的致命问题是句式僵硬检测少一个物体句子就垮了生成的内容完全没有“新意”另一派是“检索式”把待描述图和数据库里大量图文做相似度匹配找到最像的一条描述直接输出这种办法永远说不出一句数据库里不存在的话最多算“看图找话”不算“看图说话”。这两派都没有真正解决一个问题模型自己能不能从像素出发通过数据学习直接生成一个自然的描述句子要回答这个问题需要的不是更复杂的规则而是一整个可以直接端到端优化的生成模型。1.2 论文给的核心答案一句“端到端”写完了这篇论文的思路用一句话总结就是把图像描述建模成“给定一张图像生成对应单词序列”的条件概率问题。模型学习的是概率分布p(S|I) p(w1|I) × p(w2|I,w1) × p(w3|I,w1,w2) × ...其中I是图像S是生成的句子w1, w2, ... 是句子里的每个单词。这句话看起来很数学但想通以后整个论文就通了一大半模型不需要为“句子结构”或“物体关系”设计任何规则它只需要学会一件事——每生成下一个词的时候心里同时装着“这张图看到了什么”和“前面已经说了什么”。这也是“端到端”的含义。图像特征提取和语言生成不是两个分开训练的模块而是通过一个统一的损失函数一起反向传播、一起收敛。图像特征不再只是给人看的而是专门为预测下一个词服务的。你甚至可以理解为CNN负责“看”LSTM负责“说”它们俩在同一个目标下互相磨合。1.3 论文的整体框架一图流用一段伪代码描述整个推理过程比看流程图更直观输入: 图像 I # 1. CNN 提取图像特征向量 v CNN(I) # 通常是 4096 维特征 # 2. 把特征作为 LSTM 的第一个输入 输入序列 [v, 词向量(w1), 词向量(w2), ..., 词向量(wT)] # 3. LSTM 按顺序生成下一个词的概率分布 for t in range(max_len): p_t softmax(LSTM(输入序列[t])) 采样/选择 词 w_t argmax(p_t) 如果 w_t EOS: 停止生成 输出: 句子 w1 w2 ... wT注意第二步图像特征不是单一地被“读一遍”然后丢弃而是作为句子的第一个“词”塞进LSTM。这个细节极其重要我后面专门讲。2. 整体架构拆开看CNN当眼睛LSTM当嘴2.1 图像侧用预训练CNN抽出4096维特征《Show and Tell》的图像编码器用的是深度卷积神经网络CNN并且不是在图像描述数据集上从零训练而是在大规模图像分类数据集上先预训练然后取出倒数第二个全连接层的激活值得到一个4096维的向量作为整张图像的“向量化印象”。为什么要预训练原因很朴实图像描述数据集相对较小图像内容又丰富如果从随机初始化开始训练CNN模型会花大量资源去学最基础的边缘、纹理、形状很难学到“这是个人”“这是辆车”这样的语义特征。预训练相当于让模型先去大型数据上学会“看世界”再回来做具体任务时它已经知道什么是桌子、什么是人、什么是运动场景。这一选择到今天仍然有效。你在现代多模态模型里还能看到同样的思路先做大规模预训练再做任务微调。区别只是“视觉底座”从CNN变成了ViT但核心逻辑一脉相承。这里有一个论文里很重要的选择取哪一层特征作者对比了不同层的CNN特征发现取倒数第二层全连接层正好是4096维比取最后一层softmax概率效果更好。我自己的理解是最后一层输出已经是为了分类而高度抽象的结果丢掉了很多生成句子需要的细节信息而倒数第二层保留的既有语义又有结构更适合作“自由发挥”的起点。2.2 语言侧LSTM如何一步步把特征变成句子有了图像特征剩下的事情就是把向量变成句子。论文选的语言模型是LSTM长短期记忆网络结构上它是一个带内部“记忆细胞”的循环神经网络。LSTM最出众的地方在于它会记得自己说过什么。普通RNN处理长句子时容易“失忆”生成到第七八个词的时候早忘了开头的主题。LSTM通过门控机制专门解决这个问题它内部有一个记忆细胞c可以长期保存信息又有遗忘门、输入门、输出门来控制“删掉什么旧记忆、写进什么新信息、从当前状态里读出什么”。打个比方理解LSTM像一个手里拿着便签本的人。看见图像后先把整体印象写在便签本第一页然后每说一个词就翻一页再把之前说的关键词抄到当前页上结合画面细节决定下一句说什么。有时候该忘掉画面左上角的小细节就忘掉有时候该死死记住“主角是个穿着红衣服的人”就一直记着。2.3 “联合训练”到底联合了什么这是新手最常有的疑惑既然CNN和LSTM各干各的为什么不分开训练分开训练的问题出在“目标不一致”。如果先训好一个CNN去做分类再用分类特征去训LSTM做句子生成CNN并不知道自己提取的特征会被拿去生成“颜色”“动作”“位置关系”这些语言信息它的特征表达就不是最合适的。联合训练的时候损失函数同时驱动CNN和LSTM当LSTM预测下一个词出错时这个误差会一路反向传播回CNN告诉CNN“你刚才提取的特征不够好下次要更突出某个区域的信息”。好比一个实习生先学会看轮廓被上司骂了几次“你看漏了颜色之后他开始下意识关注颜色”。两个人的磨合比分开培训再临时组队要高效得多。3. 核心公式背后的直觉从特征注入到损失函数3.1 图像特征是怎么“喂”给LSTM的论文最关键的细节之一是特征注入方式。通常你会直觉认为图像特征应该在每个时间步都拼进去或者只在最开始拼一次。论文实际做法是把图像特征v作为t-1时刻的输入让LSTM先“看”一眼画面然后从t0时刻开始输入依次是 、w1、w2……一直到 。这样做的好处在于LSTM的第一步就带着完整图像信息生成首个单词。后续每个单词的生成虽然表面上只输入了“上一个词”但图像信息已经通过LSTM的记忆细胞长期保存在内部状态里了。也就是说模型在生成第三个词时虽然手上拿的是第二个词但它心里还清楚地记得那张图长什么样。这也解释了LSTM记忆细胞为什么是核心没有记忆图像信息会在生成三五个词后被冲淡句子就会开始跑题。3.2 LSTM三个门与“记忆体”的工作方式LSTM在时刻t的更新过程是三组“门”共同作用的结果。遗忘门f_t决定上一个时刻的记忆c_{t-1}有多少被保留。它读入当前输入x_t和上一个输出h_{t-1}输出一个0到1之间的数值。1表示全记住0表示全忘掉。输入门i_t决定当前候选信息有多大比例被写入记忆细胞。候选信息通常由tanh层生成是“此刻值得记下来的新内容”。输出门o_t决定当前记忆细胞里有多少信息能被读出形成当前时刻的输出h_t。用生活场景类比你在写一句话描述照片时脑子里要不断更新“我已经说了什么接下来重点是什么”。遗忘门负责把“已经说完的修饰语”淡忘掉输入门负责记住“下一段要强调的主体”输出门负责把这个主体组织成词说出来。三个门之间的配合就是LSTM生成流畅句子的原因。理解了LSTM就会明白为什么论文作者强调“这是一个纯粹的生成模型”它从头到尾不做任何物体框检测也不做任何规则匹配每一个词的概率都是记忆细胞和当前输入的综合结果。想复杂也复杂不到哪去但它就是从大量“图句”样本里学会了描述。3.3 softmax与词表概率每个词都有个分数LSTM每个时刻输出的隐向量h_t本身不是一个词而是一个高维向量。为了让这个词变成我们能读懂的句子模型在LSTM顶部接了一个softmax层。softmax把高维输出映射成词表大小的一堆概率所有词的概率加起来等于1概率最大的那个词就是当前时刻最可能的输出。举个例子假设模型词表里有“一只”“猫”“在”“键盘”“上”等8000个词t0时刻softmax输出概率分布可能就是“一只”概率0.7“这个”概率0.1“猫”概率0.05……模型取argmax选择“一只”。然后“一只”这个词的词向量会作为t1时刻的输入继续生成下一个词。这个过程就是自回归生成拿自己上一轮的结果作为下一轮的输入循环往复直到遇到句尾标志 。词表里的词用什么向量表示论文用可学习的词嵌入word embedding每个词有一个固定维度的向量。这些向量是端到端训练出来的训练完后你会发现语义相近的词向量往往在空间里离得比较近。词嵌入和图像特征都映射进同一个向量空间模型在这两者之间学出了“图像的某一方面”和“某个词”的对应关系。3.4 训练目标最大化正确句子的对数概率训练阶段每张训练图片I都对应若干条人工标注句子S。模型的训练目标是让所有正确句子的概率尽量高。实际操作上损失函数通常写成负对数似然L -Σ log p(w_t | I, w_1, ..., w_{t-1})等价于把每步的交叉熵累加。也就是说模型每预测一个词如果预测错了就会被惩罚如果预测对了就会得到鼓励。这里有个新手容易忽略的点训练时LSTM的每一步输入是真实的标注词称为teacher forcing而不是自己上一步生成的词。这样做的好处是训练更快、更稳定因为模型不用承担“一步错步步错”的连锁误差代价是训练和推理之间存在不一致性也就是后来研究者常说的exposure bias问题。这篇论文当年没有正面处理这个问题后面很多工作用强化学习或者计划采样来缓解但这是后话。损失函数设计看似简单却直接决定了整个模型能学成什么样。因为CNN和LSTM是在同一个负对数似然下联合更新的所以CNN学会提取的特征会朝“有助于预测下一个词”的方向偏移而不是朝“有助于图像分类”的方向偏移。这种“为语言服务”的视觉特征是这篇论文跨时代意义的关键。4. 实践细节与训练经验论文没细写复现时必踩坑4.1 数据集与预处理从Flickr到COCO论文实验用到了三个经典数据集Flickr8k、Flickr30k和MS COCO。它们的特点是每张图配5条人工描述句。别看只是“图配文字”预处理工作比想象中琐碎图片要缩放、加随机裁剪和翻转句子要全部转为小写、按空格分词、清理标点然后把所有出现过的词整理成词表。三个数据集的规模差距很大。Flickr8k只有8000张图COCO则超过12万张训练效果自然差异明显。复现建议优先用COCO规模够大模型容易收敛Flickr8k太小很容易过拟合。如果你想快速验证代码有没有写错可以先用Flickr8k跑一个迷你实验确认损失下降、能出句子再上COCO正式训练。4.2 词表构建与未登录词影响生成质量的隐形因素词表大小是新手很容易忽略但影响很大的参数。论文在Flickr8k和Flickr30k上使用的词表规模大约在8000左右在COCO上达到16000以上。词表太小稀有词大量被丢弃生成句子容易变得单调词表太大softmax计算量剧增训练变慢而且许多低频词学不到有效表示。没进词表的词怎么处理一般统一替换成 标记。但 一旦过多模型就会学出一种惰性遇到不确定的词就输出 句子质量直线下降。我自己复现时会设置一个频率阈值比如出现次数小于2的单词一律归为 同时把词表容量设成一个上限。另外所有句子要加上 和 两个特殊标记别忘了它们也要占用词表位置。4.3 训练超参与调参记录照着这个配置先跑通论文的训练配置放在今天看依然有参考价值。优化器用SGD加momentum初始学习率0.01训练过程中每隔固定轮数学习率衰减一次LSTM隐层维度和词嵌入维度都设在512附近。我在复现时发现这个配置最需要盯着的两个地方是梯度爆炸和过拟合。梯度爆炸是RNN类模型的老毛病。LSTM虽然有缓解但长句子的反向传播仍然可能让梯度失控所以梯度裁剪gradient clipping几乎是必须的。我自己一般把梯度模长限制在5左右稳定省心。过拟合则要靠早停和数据增强控制。图像随机水平翻转是非常廉价的有效手段别小看这一步它等于把数据量近似翻倍。随机裁剪也能增加模型对不同构图比例的鲁棒性。论文当年在Flickr8k这种小数据集上还能取得不错效果除了模型本身预处理和增强的贡献不可忽视。4.4 语言模型的独立预训练与微调论文里还有一个容易被忽略但很实用的操作作者先在更大的纯文本语料上单独训练语言模型也就是LSTM然后用图像描述数据集微调。思路也很直接LSTM要先学会“人话怎么说”再来学“看着图说人话”效率会更高。复现时可以简化如果不方便单独找纯文本语料可以先在COCO的所有标注句子上预训练一个语言模型再和CNN特征联合微调。这样做的好处是初始阶段LSTM不会输出一堆乱码训练曲线也稳定得多。当年很多复现报告都说这个细节能带来一两个点的BLEU提升值得加进自己的训练策略里。5. 生成阶段与评估指标为什么说Bleu又爱又恨5.1 贪心解码与beam search生成质量的关键训练完成后模型拿到一张新图如何生成句子最简单的方式是贪心解码每一时刻直接选概率最大的词。贪心的问题是短视它只看当前一步没有考虑到“这一步选了‘一只’下一步可能只能搭配‘小鸟’但如果选‘这个’后面能接出更长的完整描述”。所谓“一步错步步错”贪心容易出现这种局部最优。论文采用beam search集束搜索来缓解。核心思想是每一时刻保持K个候选序列而不仅仅是一个。算法不断扩展这K个候选序列根据累计对数概率筛选最后从候选序列里挑总概率最高的一条。K越大搜索越充分生成句子往往越长、越完整但计算开销也增加。论文在实验里测试了不同beam size的效果一个明显趋势是随着K从1增大到20生成效果持续变好。K继续增大收益就开始递减。实操中K3或K5往往已经是性价比很高的选择。如果你复现时发现模型生成短句偏多优先把beam size调大一点效果立竿见影。5.2 评估指标对比Bleu、METEOR、CIDEr机器生成的句子质量怎么量化图像描述领域最常用三个指标。BLEU最早来自机器翻译核心是统计生成句子和参考句子之间的n-gram重合度。它能反映“用词对不对”但它更偏重“字面匹配”对“换一种说法但意思一样”不敏感。METEOR比BLEU更聪明一些考虑同义词匹配、词干匹配和句子长度和人类评分的相关性通常比BLEU好。CIDEr专门为图像描述设计它给那些在参考句中出现频率更高的n-gram更高权重相当于更强调“关键信息点是否说中”。三个指标各有偏向所以论文报告结果时一般会同时列好几个。我的经验是BLEU高不代表句子读起来自然可能出现“词语都对了但连不成人话”的情况METEOR和CIDEr高的句子听起来更贴切一些。人工评估永远是最终标准自动指标只是让我们在迭代时有个快速反馈。5.3 论文里的实际效果这些句子证明模型真的在“看图说话”论文在Flickr8k、Flickr30k和COCO上都做了实验BLEU指标全面超过当时基于模板和检索的方法。更直观的证据是一些生成的例句输入一张冲浪者站在浪板上的照片模型生成“一个人在冲浪”输入一张餐桌上摆着食物和酒杯的照片生成“桌子上摆着一些食物和酒杯”。这些句子看起来朴素但在当时是第一次由纯神经网络从像素“凭空”组织出来的。对比一下旧方法的产物就不难体会差异模板法生成“A person is surfing”可能没问题但面对“一只狗坐在摩托车油箱上”这种少见构图模板法大概率输出“A dog is on a motorcycle”这种残缺句子而神经网络模型能生成“一只狗坐在一辆摩托车的座位上”因为它学会了“坐”“座位”“在……上”的组合方式而不是靠预先设定好的槽位。这个能力本质上是模型从大量图句对里学到的统计规律图像特征空间里“冲浪动作”区域和语言空间里“一个人在冲浪”这句话形成了对应。模型未必理解冲浪是什么但它成功学到了“这类画面搭配这类句子”的模式这就已经足以让它“看起来像真的在理解”。6. 复盘与延伸这个骨架如何长成了今天的大模型6.1 这篇论文的四个值得记住的创新点第一个也是最重要的首次把图像描述任务做成了真正的端到端深度学习系统。此前各种“分步走”方案都需要人为设计的中间环节这篇论文证明了“纯学习、零模板”是可行的。第二个创新点在于“CNN特征直接作为LSTM输入”的架构设计。这个设计被后面几乎所有的图像描述工作继承甚至后来的视觉语言大模型里“图像token作为文本序列的初始输入”依旧是同一种思想。第三个是训练策略上的可复现性预训练特征、语言模型预训练、beam search生成这三个技巧组合在一起让模型效果稳定且容易复现。一个方法能被广泛引用往往不是因为理论多漂亮而是因为别人也能跑出同样的效果。第四个是它确立了图像描述任务的标准评估体系和实验范式。此后几年新论文如果不在这几个数据集和指标上报告读者根本无法横向比较。6.2 它的短板和后来者补上的洞回头挑毛病这篇论文的问题也很明显。模型没有“注意力”机制整张图像被压缩成一个固定长度向量生成句子时模型看的是全局印象无法聚焦到具体区域。这就导致复杂场景中模型容易漏掉小物体或混淆物体位置。一年之后的《Show, Attend and Tell》就专门解决了这个问题把注意力机制引入图像描述模型能一边生成词一边“看向”画面相关区域。此外模型的词表是封闭的遇到没见过的新词只能输出 描述内容高度依赖训练数据的覆盖范围。对“一个穿着紫色连衣裙的女人骑着一头斑马”这种罕见组合模型非常容易逻辑错乱。到今天的多模态大模型时代这些问题被更大规模的参数和训练数据大幅缓解但你会发现当年那套“提取特征-序列生成-概率最大化”的基本骨架其实一直在原地没有变。6.3 新手读这篇论文的路线图与复现建议如果你是第一次读建议按这个顺序摘要和引言读两遍弄懂“模型要解决什么问题”就停下然后读模型章节只看图不看公式再读实验章节重点关注数据集和生成效果最后才回头啃LSTM公式和损失函数定义配合本篇文章里门的解释会顺畅很多。如果你要动手复现我的建议是先用PyTorch搭一个最小版本CNN直接用预训练好的模型冻住只训练LSTM和词嵌入在COCO的小子集上跑通完整流程确认能生成句子再放开CNN层进行微调。整个流程熟练的话一周左右能跑通核心代码不过几百行但对理解整个系统的好处远超想象。读完这篇再去看后来的注意力机制论文你会明显感受到那种“原来大家都在这条路上添砖加瓦”的脉络。看多了之后你会发现所有复杂模型的解释拆到底都是朴素的几个原则提取特征、建立联系、逐步生成。论文里那台替人“看图说话”的机器到今天已经长大无数倍但它蹒跚学步时的第一行脚印依然清晰可见。