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

20 行原始语料:去标签后还是 20 行,去空行剩 16,归一化去重剩 13, 去掉含 URL 的行剩 11; 切出 12 句、164 个单字,去停用字后 155,最后词表 98 项。

📝 本节和前面几节的关系

前面四节都在讲「怎么表示、怎么算」,这一节讲最不体面但最影响结果的一步: 把脏语料洗成能用的样子。

一、六个步骤,Demo 拆成九行看

书里说的六步是:清洗、归一化、分句、分词、去停用词、建词表。 Demo 把「清洗」拆成了三行(去标签、去空行、去 URL),所以表里有九行,从 0 到 8。 顺序不是随便定的:先做便宜的(正则、strip),再做贵的(分词);先去重再分句,能省一半分词开销。 这个顺序在小语料上看不出差别,语料上百万行时能差出几个小时。

二、每一步砍掉多少

20 行进,16 行过空行关(4 行是空的或只有全角空格); 归一化去重后剩 13 行(3 行完全重复); 去掉含 URL 的 2 行剩 11 行。 从 20 到 11,砍掉 45%。这个比例在爬来的网页语料里算少的,真实场景经常留下不到两成。 归一化那一步用的是 NFKC:把全角字母变半角、全角空格变半角。 不做这一步,「ABC」和「abc」会被当成两个不同的词。

三、分词这一步,我用了单字切

沙箱里没有 jieba,也没有别的分词库,所以我按单字切。11 行切出 164 个字。 单字切的问题很明显:没有词边界,「路灯」和「路」「灯」是一回事,下游任务的天花板当场就被压低了。 这一步我没有取巧假装用了分词库。表里写的是「按单字切(无分词库)」,读的人一眼能看出限制在哪。 去停用字用的是 10 个字的表(的、了、在、是、也、有、和、与、该、约),去掉 9 个,剩 155。 词表去重后 98 项。

四、预处理最容易被低估

做 NLP 的人有个共识:数据决定上限,模型决定离上限多近。而预处理是唯一能改上限的环节。 三个常见的坑: 1. 顺序错。先分词再去重,等于把同一句话分了两遍,白烧一半算力。 2. 归一化过头。把大小写全抹平,人名地名就分不出来了。 3. 停用词表抄别人的。别人为搜索场景做的表,用在情感分析上会把「不」「没」删掉,情感直接反了。 第三条最要命。停用词表必须按任务定,不能通用。

五、动手 Demo:语料预处理:从 20 行脏数据到 98 项词表

造 20 行带噪声的语料(HTML 标签、空行、全角字母、重复行、含 URL 的行); 依次做去标签、去空行、NFKC 归一化去重、去 URL 行、分句、单字切分、去停用字、建词表, 每一步都打印剩余规模。

l1 = [re.sub(r'<[^>]+>', u'', s) for s in raw]        # 去标签
l3 = [unicodedata.normalize('NFKC', s) for s in l2]   # 全半角归一
l4 = [s for s in l3 if u'http' not in s]              # 去 URL 行

for s in l4:
    for p in re.split(u'[。!?;]', s):               # 分句
        if p.strip():
            sents.append(p.strip())

实跑输出:

--------------------------------------------------------------------
语料预处理六步:每一步砍掉多少
--------------------------------------------------------------------
  步骤           做了什么                   剩余规模
  -------------------------------------------------
  0 原始         逐行读入                         20
  1 去标签       正则剥掉尖括号                   20
  2 去空行       strip 后非空                     16
  3 归一化去重   NFKC + 完全相同去重              13
  4 去 URL 行    含 http 的整行丢弃               11
  5 分句         按句末标点切                     12
  6 分词         这里按单字切(无分词库)        164
  7 去停用词     10 个停用字                     155
  8 建词表       去重                             98

  从 20 行到 11 行         砍掉 9 行
  从 164 个词到 98 个词表项 压缩 1.7 倍
  词表                     一、三、下、不、业、主、二、亮、人、仍、体、余、全、内、准、分、制、前…

  注意第 6 步:这里按单字切,因为沙箱没有分词库。单字切会丢掉词边界,下游任务的天花板就被压低了。

「0 原始」和「1 去标签」都是 20 行——去标签不减少行数,只减字符数。 表里只看行数会以为这步没用,其实它把尖括号里的东西清掉了。 「2 去空行」从 20 掉到 16,砍掉的是两行真空行和一行只有全角空格的行。 全角空格用 strip 能去掉,这是 NFKC 之前就能做的。 「5 分句」从 11 行变成 12 句——有一行里有两个句号,被切开成两句。 行数和句数不是一回事,这一点在算语料规模时经常搞混。 「6 分词」那一行的 164 是字数不是词数,因为这里按单字切。换成真分词库,这个数会掉到六七十。

30 秒实验

  1. 把停用字表里的「不」加进去,看词表少几项、句子意思变了多少。
  2. 把 NFKC 归一化那一步删掉,看去重后还剩几行。
  3. 想一想:为什么情感分析的停用词表绝不能删「不」和「没」?

常见误区

  1. 「预处理是杂活,随便洗洗」——它决定整个任务的上限。
  2. 「停用词表可以通用」——删错一个「不」,情感分析直接反。
  3. 「行数就是句子数」——一行里可能有好几句,切完会变多。
自测三问(点开对答案) 1. 为什么先去重再分句? —— 重复句只分一次,省掉一半分词开销;语料越大省得越多。 2. NFKC 归一化解决什么问题? —— 全角字母、全角空格和半角混用,不归一化会被当成不同的词。 3. 情感分析的停用词表为什么要特别小心? —— 「不」「没」被删掉,否定语气丢失,情感判断会整个反过来。

语料洗完了,这一章的基础部分到此结束。下一节开始看五类具体场景,第一个是最常用的情感分析。

相关笔记