🧠 第六章 自然语言处理
📄 一句话概括

40 条正、40 条负的情感语料,同用 bigram 特征:朴素贝叶斯测试集从 0.700 升到 0.833; 手写单隐层网络最好也只到 0.667,10 条样本时只有 0.500——正好是瞎猜。

📝 本节和前面几节的关系

上一节讲表示,这一节讲做法。 同样一份数据、同样一套特征,换模型换出完全不同的结果,这件事值得单独看一节。

一、两条路的区别不在模型,在「谁定特征」

传统路子:人定特征(这里是字符 bigram 计数),模型只学权重。 深度路子:也用人定的输入(还是 bigram),但中间加隐层,让模型自己组合。 注意这个 Demo 里两条路用的是同一套特征。所以差别不在特征,在模型有没有能力「自己造中间表示」。 很多人以为传统和深度的差别是「有没有神经网络」,其实更准的说法是特征是谁找的。 真端到端连特征都不用人定,直接喂原始字符。

二、结果:老办法赢了

训练 10 条时:朴素贝叶斯测试 0.700,网络 0.500。 加到 50 条:朴素贝叶斯 0.833,网络 0.667。 两个模型的训练集都是 1.000——也就是说它们都把这几十条背下来了。区别在背完之后还能不能应付没见过的。 网络一直卡在 0.500 附近,就是「全判一类」的成绩:测试集正负各半,蒙一类正好一半。 结论很直白:这个数据量上,网络连门都没摸到。

三、为什么网络会输

算一下参数量:26 维输入配 12 个隐单元,加偏置和输出层,一共 337 个参数,而样本只有 10 条。 参数比样本多 30 多倍。 这种情况下,网络完全可以靠死记硬背把训练集打满,而它对没见过的输入没有任何泛化能力。 朴素贝叶斯呢?它假设各维独立,把问题砍成了一堆「某个 bigram 出现几次」的独立计数。 假设很强,但在小数据上正好当正则用。 所以不是「深度不如传统」,是「这个数据量配这个模型不划算」。 样本上万、特征上百万的时候,结论会翻过来。

四、什么时候该换哪条路

三条判断标准,按顺序问: 1. 样本有多少。几百条以内,先上传统方法,它便宜、能解释、不容易崩。 2. 特征是现成的吗。表格类数据特征现成,树模型往往就够了; 图像、语音、原始文本没有现成特征,才需要深度模型自己学。 3. 要不要解释。风控、医疗、审核这类场景,能说清「为什么判成这样」比多两个点准确率重要。 三条里有一条指向传统,就别急着上网络。反过来,三条都指向深度,也别硬用传统凑。

五、动手 Demo:同一份语料,朴素贝叶斯与单隐层网络对照

造 80 条情感短句(40 正、40 负,负面里一半是「很 + 负面词」、一半是「不 + 正面词」); 固定 30 条做测试集,剩下 50 条按 10/20/30/50 逐档做训练集; 每档都建 bigram 词表,跑朴素贝叶斯和 12 隐单元的手写网络,打出训练集与测试集准确率。

p1 = X[y == 1].sum(0) + 1.0          # 拉普拉斯平滑
l1 = np.log(p1 / p1.sum())

h = np.tanh(X @ W1 + b1)             # 12 个隐单元
p = sigmoid(h @ W2 + b2)
W1 -= lr * (X.T @ ((p - y) * (1 - h * h) @ W2.T))

实跑输出:

--------------------------------------------------------------------
传统与深度:同一份情感任务,两套做法
--------------------------------------------------------------------
  语料条数                 80(正 40 / 负 40)

  训练条数   词表维   NB 训练   NB 测试   网络 训练   网络 测试
  ------------------------------------------------------------
        10       26     1.000     0.700       1.000       0.500
        20       36     1.000     0.767       1.000       0.500
        30       43     1.000     0.767       1.000       0.500
        50       56     1.000     0.833       1.000       0.667

  两个模型的训练集都是 1.000,说明它们都把这几十条背了下来。区别在测试集:NB 从 0.700 一路升到 0.833,网络从 0.500 走到 0.667。
  测试集正负各半,所以全判一类的准确率正好 0.500——网络在 10 条样本上就停在这个数,说明它连门都没摸到。
  原因不是网络不行,是样本太少:26 维输入配 12 个隐单元,参数 337 个,样本只有 10 条。
  网络参数量               337 个
  对应训练样本             10 条

训练集那一列两个模型全是 1.000,这本身是个警告:在这份数据上,训练集准确率完全不能用来选模型。 测试集那一列才是有效信息:朴素贝叶斯 0.700→0.833 稳步上升,网络 0.500→0.667 一直贴着瞎猜线。 网络没有调参调好不是主要原因。主要原因是 337 个参数对 10 条样本,怎么调都躲不开过拟合。 这份语料是我按模板造的,句式高度重复,所以两个模型都能到 1.000。 真实语料没这么整齐,训练集准确率会低一些,但结论方向一样。

30 秒实验

  1. 把隐层从 12 个减到 3 个,重跑,看网络测试集能不能追上朴素贝叶斯。
  2. 把训练轮数从 400 改成 50,看网络是变好还是变差。
  3. 想一想:为什么参数比样本多的时候,模型一定会背答案?

常见误区

  1. 「深度一定比传统强」——小数据上传统方法经常赢,而且更稳。
  2. 「训练集 100% 说明模型好」——只说明它背下来了。
  3. 「准确率低就调参」——先看参数量和样本量的比例。
自测三问(点开对答案) 1. 传统方法和深度方法最本质的差别是什么? —— 特征是谁找的:传统靠人定,深度让模型自己组合出中间表示。 2. 为什么网络在 10 条样本时只有 0.500? —— 337 个参数对 10 条样本,学不到可泛化的东西,只能全判一类,正好一半。 3. 朴素贝叶斯为什么在小数据上反而稳? —— 它假设各维独立,把问题砍成独立计数,强假设起了正则作用。

路线比完了,下一节换个角度:NLP 的任务其实只有两大类——理解和生成。

相关笔记