第02回 词的数字化——独热编码与词袋模型

字海无涯先立典,一词一位列如兵。
满袋不问先后序,频疏高下自分明。

上回说到,万物皆可化作向量,住进同一座“空间”里,方能相似可量、远近可算。可看官心中怕还悬着一问:

“向量我懂了,可一句‘我爱吃饭’,究竟怎么变成一串数字?”

这便是本回要传的入门心法:先教你“立字典”,再教你“独热点兵”,最后教你“词袋装粮”,并以 TF‑IDF 做一把秤,称出哪些词真有分量。


一、先立字典:没有名册,何来点兵

要把词变成数字,第一步不是算,而是“编名册”。

设我们只管三句话:

  1. 我 爱 吃饭 我
  2. 你 爱 吃饭 吗
  3. 吃饭 吃饭 真 好吃

把出现过的词收集起来,得一册名录(词表):

V={,,,吃饭,,,好吃}V=\{\text{我},\text{你},\text{爱},\text{吃饭},\text{吗},\text{真},\text{好吃}\}

词表大小 V|V| 就是向量维度。

这一步看似土,却是后面所有文本表示的起手式:
无论你后来用的是词袋、Word2vec、Transformer 还是更前沿的 tokenizer‑free 模型,你都绕不开一个问题:你要用什么“符号单位”来承载意义?


二、独热编码:一词一位,点亮一盏灯

独热编码(one‑hot),做法最直白:
给词表里每个词分一个位置;出现哪个词,就把那一位点成 1,其余皆为 0。

若词表按顺序为 [,,,吃饭,,,好吃][\text{我},\text{你},\text{爱},\text{吃饭},\text{吗},\text{真},\text{好吃}],则:

  • “我” = [1,0,0,0,0,0,0][1,0,0,0,0,0,0]
  • “吃饭” = [0,0,0,1,0,0,0][0,0,0,1,0,0,0]

独热的妙处是“清清楚楚”:
它把“这个词是谁”表达得毫不含糊。

可独热也有两大硬伤:

  1. 维度灾难:词表若有十万词,向量就十万维,且大多数位永远为 0。
  2. 语义断情:在独热世界里,“吃饭”与“用餐”不管多像,点积仍是 0,余弦仍是 0,仿佛素不相识。

看官记住这句话:
独热能“点名”,却不会“认亲”。

下一回的“嵌入(embedding)”,正是为了让词之间有亲疏远近。


三、词袋模型:不问顺序,只数多少

一句话里不止一个词。把一句话变成向量,最常见的入门法叫词袋模型(Bag of Words, BoW)。

它有个江湖规矩:
只管每个词出现几次,不问先后顺序。

于是上面三句话可写成 7 维向量(每一维对应词表中的一个词,值是出现次数):

  • 句1:“我 爱 吃饭 我”
    [2,0,1,1,0,0,0][2,0,1,1,0,0,0]
  • 句2:“你 爱 吃饭 吗”
    [0,1,1,1,1,0,0][0,1,1,1,1,0,0]
  • 句3:“吃饭 吃饭 真 好吃”
    [0,0,0,2,0,1,1][0,0,0,2,0,1,1]

此时两句话的“像不像”,便可用上回的余弦相似度来衡量。

词袋的好处是“简单能用”,坏处也很明显:

  • 忽略顺序: “我 打 你” 与 “你 打 我” 在词袋里长得一模一样
  • 偏爱高频词: “的、是、了” 这种词几乎篇篇都有,反而会干扰相似度

于是江湖再出一把秤:TF‑IDF。


四、TF‑IDF:一把称词分量的秤

TF‑IDF 的思想一句话就能讲明白:

  • 一个词在某篇文档里出现越多,越像“主题词”(TF 大)
  • 但如果这个词在所有文档里到处都是,它又不稀奇,分量应当变轻(IDF 小)

写成式子(只保留直觉,不纠缠细枝末节):

TF‑IDF(t,d)=TF(t,d)×IDF(t)\text{TF‑IDF}(t,d)=\text{TF}(t,d)\times \text{IDF}(t)

其中 IDF 常见取法是:

IDF(t)=logNDF(t)+1\text{IDF}(t)=\log\frac{N}{\text{DF}(t)+1}

NN 为文档总数,DF(t)\text{DF}(t) 为包含词 tt 的文档数。

这把秤的用处,是把“常见却无用”的词压下去,把“稀有却关键”的词抬起来。

在很多检索与分类的入门任务里,TF‑IDF 依旧是一个非常强的基线:
它不华丽,却可靠,像老镖局的路数——走得慢,但不容易翻车。


五、从词袋到“分词”:为什么大模型仍绕不开这一步

看官或许听说过“大模型的 token”。你可能会问:
“既然词袋都能表示,何必又弄出 token、BPE、SentencePiece 这些名堂?”

原因很现实:词袋维度太大、太稀疏、且不保序;而神经网络要吃的是“序列”,并且希望单位更细、更灵活。于是出现了各种分词与子词方案,把文本切成更适合训练的单位。

更进一步,2024–2025 又有人把矛头指向“分词本身”,认为它是一种人为的启发式预处理,并尝试直接从字节学习,做 tokenizer‑free 的语言模型。比如 Byte Latent Transformer(BLT)就以“从原始字节出发、用更强的压缩与 patching 机制”来规避传统 tokenization 的偏置。1

你不必立刻把这些前沿细节学全,但要记住这条主线:

无论叫“词”、叫“子词”、叫“字节”,你总要把文本变成离散单位;而离散单位如何选,会影响模型的世界观。


六、极简代码:从词袋到 TF‑IDF(纯 Python 可跑)

下面给一段极简可运行代码:输入多条句子,输出词表、词袋向量与 TF‑IDF 向量。

import math
from collections import Counter


def build_vocab(docs):
    vocab = {}
    for doc in docs:
        for w in doc:
            if w not in vocab:
                vocab[w] = len(vocab)
    return vocab


def bow_vector(doc, vocab):
    vec = [0] * len(vocab)
    counts = Counter(doc)
    for w, c in counts.items():
        if w in vocab:
            vec[vocab[w]] = c
    return vec


def tf_idf_vectors(docs, vocab):
    n = len(docs)
    df = [0] * len(vocab)
    doc_counts = []
    for doc in docs:
        counts = Counter(doc)
        doc_counts.append(counts)
        for w in set(doc):
            if w in vocab:
                df[vocab[w]] += 1

    idf = [math.log(n / (df_i + 1)) for df_i in df]
    out = []
    for counts in doc_counts:
        vec = [0.0] * len(vocab)
        for w, c in counts.items():
            i = vocab[w]
            vec[i] = float(c) * idf[i]
        out.append(vec)
    return out


if __name__ == "__main__":
    docs = [
        "我 爱 吃饭 我".split(),
        "你 爱 吃饭 吗".split(),
        "吃饭 吃饭 真 好吃".split(),
    ]
    vocab = build_vocab(docs)
    print("vocab:", vocab)
    for i, doc in enumerate(docs, 1):
        print("bow", i, ":", bow_vector(doc, vocab))
    for i, vec in enumerate(tf_idf_vectors(docs, vocab), 1):
        print("tfidf", i, ":", [round(x, 3) for x in vec])

运行之后,你会亲眼看到:
同样是“吃饭”,在不同文档集合里,它的分量会变;同样是“我”,因为太常见,分量就会被压下去。


七、小结:本回留下的两处悬念

本回的三把兵器,你已经握在手中:

  • 独热:点名最清楚,但不懂亲疏
  • 词袋:能做相似度与分类基线,但不懂顺序
  • TF‑IDF:能称词分量,压制无用高频词

可它们共同的痛点也显而易见:
维度大、稀疏、语义不连。

因此下一回,我们要请“嵌入(embedding)”出场:
让词在低维空间里彼此认亲,让“吃饭”与“用餐”不再形同陌路。

正是:独热点兵名册立,词袋装粮计数勤。
若问语义何处寄,还看嵌入点化神。

欲知后事如何,且听下回分解。


引用与溯源

Footnotes

  1. Pagnoni, A., et al. Byte Latent Transformer: Patches Scale Better Than Tokens arXiv:2412.09871(2024-12)https://arxiv.org/abs/2412.09871