0%

LLVM-NLP

第一章 NLP


NLP 是什么

  • 自然语言处理(NLP):让计算机理解、解释和生成人类语言的技术
  • 融合了计算机科学、人工智能、语言学等多学科
  • 核心目标:打破人类语言和计算机语言之间的障碍

NLP 核心任务

任务 说明 示例
中文分词 把连续中文切成词序列 “今天天气真好” → [“今天”,”天气”,”真”,”好”]
子词切分 把词进一步拆成更小的子词 “unhappiness” → [“un”,”happi”,”ness”]
词性标注 给每个词标注词性 “She/代词 is/动词 playing/动词…”
文本分类 把文本归入预定义类别 NBA新闻 → “体育”
实体识别 识别文本中的人名、地名等 “李雷” → 人名,”上海” → 地名
关系抽取 识别实体之间的语义关系 “比尔·盖茨”—创始人—“微软”
文本摘要 生成简洁摘要(抽取式/生成式) 长新闻 → 一句话概括
机器翻译 源语言 → 目标语言 “今天天气很好” → “The weather is nice today”
自动问答 理解问题并自动给出答案 检索式 / 知识库 / 社区问答

词向量

计算机只认数字,不认文字,所以第一步是:把文字变成数字

在完成分词等工作后,就需要找到这些词汇对应的词向量,以进行下一步操作

假设我们用 4 维向量表示:

1
2
3
4
5
6
       [动物性, 大小, 可爱度, 能飞]        ///计算机内部不会是这些人类能读懂的语言
猫 → [ 0.9, 0.3, 0.8, 0.0]
狗 → [ 0.9, 0.5, 0.7, 0.0]
鱼 → [ 0.8, 0.2, 0.3, 0.0]
汽车 → [ 0.0, 0.7, 0.1, 0.0]
飞机 → [ 0.0, 0.9, 0.0, 0.9]

这就是词向量的核心价值:语义相近的词,向量在空间中距离就近

这个存储词向量的表就是嵌入矩阵.

那这个嵌入矩阵是怎么产生的呢?

Word2Vec 和 ELMo 都是词向量(word embedding)方法:

Word2Vec(静态)

  • 静态词向量:为每个词学习一个固定的向量表示
  • 预训练方式:通过 CBOW 或 Skip-gram 在大规模语料上训练
  • 特点:同一个词无论出现在什么上下文中,向量都是一样的
  • 可以看作是对”嵌入词表”的预处理训练,训练完成后得到一张固定的词-向量查找表

总结:word2vec最终的产物就是这样一个嵌入词表,他是静态的,只能从表中取词向量,无法处理一词多义问题.

1
2
3
4
5
6
比如针对苹果这个词:

句子 A:「我今天吃了一个苹果」

句子 B:「苹果发布了新款iPhone」

Word2Vec 的处理方式:

训练完成后,产出的嵌入词表长这样(简化为3维):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<TEXT>

嵌入词表(固定的查找字典)

────────────────────────

"苹果" → [0.3, 0.7, 0.2]

"手机" → [0.8, 0.1, 0.5]

"香蕉" → [0.4, 0.6, 0.3]

"今天" → [0.1, 0.9, 0.4]

...

处理句子 A「我今天吃了一个苹果」:

  • 查表 → [0.3, 0.7, 0.2]

处理句子 B「苹果发布了新款iPhone」:

  • 查表 → [0.3, 0.7, 0.2]

结果完全一样。 它不管上下文是水果还是公司,”苹果”永远是同一个向量。这个向量大概是两种含义的某种混合平均,两边都沾点,两边都不精确。

ELMo (动态)

  • 动态/上下文相关词向量:同一个词在不同上下文中会有不同的向量
  • 预训练方式:通过双向 LSTM 语言模型在大规模语料上训练
  • 特点:不是简单的查找表,而是需要把整个句子输入模型后才能得到每个词的表示
  • 严格来说不是对嵌入词表的预处理,而是一个预训练的特征提取器

总结:相比于word2vec的静态处理方式,elmo的产出物是一个模型,他会有像word2vec中的静态嵌入矩阵,但是他的模型能力会根据矩阵以及上下文生成一个动态的词向量,成功解决了一词多义问题.

还是刚刚的那个例子:

ELMo 的处理方式

ELMo 有两部分:

第一部分:底层词表(类似 Word2Vec)

1
2
3
4
5
6
7
8
9
<TEXT>

底层词表

────────────────────────

"苹果" → [0.3, 0.7, 0.2] ← 初始向量,也是静态的

...

第二部分:双向 LSTM 模型(关键区别在这里)

处理句子 A「我今天吃了一个苹果」:

1
2
3
4
5
6
7
8
9
<TEXT>

输入层: 我 → 今天 → 吃 → 了 → 一个 → 苹果

前向LSTM: → → → → → → 读到"吃""一个"这些上下文

后向LSTM: ← ← ← ← ← ← 读到整句语境

输出: "苹果" → [0.1, 0.9, 0.1] ← 偏向水果含义

处理句子 B「苹果发布了新款iPhone」:

1
2
3
4
5
6
7
8
<TEXT>
输入层: 苹果 → 发布 → 了 → 新款 → iPhone↓

前向LSTM: → → → → → 读到"发布""iPhone"这些上下文

后向LSTM: ← ← ← ← ← 读到整句语境

输出: "苹果" → [0.8, 0.2, 0.7] ← 偏向公司含义

同一个词,不同上下文,产出不同向量。

两种方式对比:

Word2Vec ELMo
产出物 一张词表 词表 + LSTM模型
使用时 查表,O(1) 查表 + 模型前向推理
同一个词不同句子 向量相同 向量不同
类比 字典:查”苹果”永远是同一个解释 人:看到上下文后判断”苹果”在这里是什么意思

Word2Vec 是字典,ELMo 是一个会阅读理解的模型。

总结:

输入一句话:”我喜欢cat”
第一步:分词(Tokenization)

1
2
3
4
5
6
7
8
9
10
<TEXT>
原文:"我喜欢cat"
分词器不是按"词"切的,是按训练好的词表规则切的(BPE算法)
切出来的 token:
"我" | "喜欢" | "cat"
每个 token 在词表里有个编号(ID):
"我" → ID: 9356
"喜欢" → ID: 42871
"cat" → ID: 2189
所以这句话变成了:[9356, 42871, 2189]

注意:token 不一定是一个完整的词

1
2
3
4
5
6
7
8
9
10
<TEXT>
比如输入 "unbelievable"
可能被切成:
"un" | "believ" | "able"
→ [556, 31420, 481]
再比如输入 "今天天气不错"
可能被切成:
"今天" | "天气" | "不" | "错"
→ [8837, 10294, 223, 9754]
怎么切取决于词表,常见的词整个保留,罕见的词拆成片段

第二步:查嵌入矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<TEXT>
嵌入矩阵(整个模型共用这一个):
假设词表大小 = 100000,嵌入维度 = 6(实际是4096,这里简化)
维度1 维度2 维度3 维度4 维度5 维度6
ID 0 "the" [ 0.21, 0.53, 0.11, 0.82, 0.34, 0.67]
ID 1 "a" [ 0.15, 0.49, 0.08, 0.77, 0.29, 0.61]
...
ID 2189 "cat" [ 0.91, 0.12, 0.33, 0.05, 0.78, 0.44] ← 查到
...
ID 9356 "我" [ 0.43, 0.87, 0.22, 0.65, 0.11, 0.55] ← 查到
...
ID 42871 "喜欢" [ 0.55, 0.33, 0.71, 0.88, 0.46, 0.19] ← 查到
...
ID 99999 "..." [ 0.02, 0.99, 0.50, 0.31, 0.14, 0.73]
10万行,每行一个 token,中英日韩全部混在一起

查完之后

1
2
3
4
5
6
<TEXT>
"我喜欢cat" → [9356, 42871, 2189]
查表得到 3 个向量:
"我" → [0.43, 0.87, 0.22, 0.65, 0.11, 0.55]
"喜欢" → [0.55, 0.33, 0.71, 0.88, 0.46, 0.19]
"cat" → [0.91, 0.12, 0.33, 0.05, 0.78, 0.44]

第三步:送进 Transformer 层

1
2
3
4
5
<TEXT>
这 3 个向量排成矩阵:
[0.43, 0.87, 0.22, 0.65, 0.11, 0.55] ← "我"
[0.55, 0.33, 0.71, 0.88, 0.46, 0.19] ← "喜欢"
[0.91, 0.12, 0.33, 0.05, 0.78, 0.44] ← "cat"

然后经过:

1
2
3
4
5
6
7
  第1层 Transformer:矩阵乘法 → 注意力 → 矩阵乘法 → 输出新的3个向量
第2层 Transformer:再来一遍
第3层 ...
...
第96层:最终输出 3 个向量
每过一层,向量的数值都在变化
"我" 的向量会融入 "喜欢" 和 "cat" 的信息

这就是注意力机制在做的事
第四步:预测下一个词

1
2
3
4
5
6
7
8
9
10
11
<TEXT>
取最后一个 token "cat" 的最终向量:
[0.12, 0.94, 0.56, 0.03, 0.81, 0.37] ← 经过96层之后的值
用它和输出矩阵相乘,得到10万个概率:
"。" → 35% ← 最高
"咪" → 8%
"的" → 6%
"and" → 3%
...
模型预测:下一个 token 最可能是 "。"
输出:"我喜欢cat。"

总结整个流程

1
2
3
4
5
6
7
8
9
10
<TEXT>
文字 → token(切片段)→ ID(查编号)→ 向量(查嵌入矩阵)
→ 过几十层 Transformer(矩阵运算)→ 预测下一个 token
"我喜欢cat"
→ ["我", "喜欢", "cat"]
→ [9356, 42871, 2189]
→ [[0.43,...], [0.55,...], [0.91,...]]
→ 96层矩阵运算
→ 下一个词概率分布
→ "。"

这就是大模型干的全部事情:一个超大的、把文字变成数字再反复做矩阵运算的预测机器。

关于token的消耗:
你以为你只发了一句话:
“请用Python写一个冒泡排序” ← 7个token

但实际发给模型的是这些东西拼在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>┌─────────────────────────────────────────────┐
>│ 系统提示词(system prompt) │
>│ "你是一个有用的AI助手,你需要..." │
>│ → 约 200 token │
>├────────────────────────────── ──────────────┤
>│ 历史对话(之前聊过的所有内容) │
>│ 用户:你好 │
>│ AI:你好!有什么可以帮你的? │
>│ 用户:我想学Python │
>│ AI:好的,Python是一门很棒的语言... │
>│ → 假设之前聊了 3 轮,约 500 token │
>├─────────────────────────────────────────────┤
>│ 你这次的提问 │
>│ "请用Python写一个冒泡排序" │
>│ → 7 token │
>└─────────────────────────────────────────────┘

实际输入 = 200 + 500 + 7 = 707 个 token

这就是为什么聊得越久越费 token
因为每次都要把之前所有对话重新发一遍给模型