1. 从登月任务看AI如何理解人类语言
上周我在浏览阿尔忒弥斯2号登月任务的新闻时,突然想到一个有趣的问题:为什么我在搜索引擎输入"最近的登月计划",它就能准确地给我推荐阿尔忒弥斯2号的相关报道?电脑明明不认识汉字,它是怎么"理解"我在问什么的?
这个看似简单的功能背后,隐藏着AI理解人类语言的核心技术——文本向量化。今天,我就用这个登月任务作为案例,带大家彻底搞懂这个技术的原理和实现。
2. 文本向量化的核心思想
2.1 电脑的"语言障碍"
电脑本质上只能处理数字。当我们输入"登月计划"这几个字时,对电脑来说只是一串无法理解的字符编码。要让AI"理解"文本,我们必须找到一种方法,把文字转换成电脑擅长处理的数字形式。
2.2 向量化的本质
文本向量化的核心思想很简单:把一段文字转换成一串有意义的数字,这串数字就是向量。比如:
"我喜欢吃苹果" → [1, 0, 1, 0, 1]
"阿尔忒弥斯2号" → [0.35, 0.71, 0.35, 0, 0, 0, 0, 0.35, 0.35]
这串数字不是随机的,它们代表了文本的某些特征。在自然语言处理中,最常见的做法是基于词频构建向量。
3. 五步实现文本向量化
3.1 准备工作:定义知识库和词汇表
首先我们需要确定两个关键要素:
go复制// 知识库:关于阿尔忒弥斯2号的新闻片段
var knowledgeBase = []string{
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步",
"该任务将使用太空发射系统火箭和猎户座飞船,搭载宇航员进行绕月飞行",
// 更多新闻片段...
}
// 词汇表:我们关心的10个关键词
var vocab = []string{"阿尔忒弥斯", "月球", "任务", "宇航员", "火箭", "飞船", "测试", "系统", "飞行", "轨道"}
词汇表决定了向量的维度。这里有10个词,所以最终的向量就是10维的。选择哪些词作为词汇表很关键,这需要根据具体应用场景来确定。
3.2 文本清洗:去除噪音
原始文本中包含很多对理解语义无用的"噪音",比如标点符号、数字等。我们需要先清洗文本:
go复制func cleanText(text string) string {
var cleaned strings.Builder
for _, r := range text {
// 只保留中文字符
if r >= 0x4e00 && r <= 0x9fff {
cleaned.WriteRune(r)
}
}
return cleaned.String()
}
清洗示例:
code复制原始文本: 阿尔忒弥斯2号是美国宇航局...
清洗后: 阿尔忒弥斯号是美国宇航局...
3.3 分词处理:提取关键词
接下来,我们从清洗后的文本中找出词汇表里包含的关键词:
go复制func tokenize(text string, vocab []string) []string {
var tokens []string
for _, vocabWord := range vocab {
count := strings.Count(text, vocabWord)
for i := 0; i < count; i++ {
tokens = append(tokens, vocabWord)
}
}
return tokens
}
处理结果示例:
code复制清洗后文本: 阿尔忒弥斯号是美国宇航局...
提取关键词: [阿尔忒弥斯 月球 轨道 飞行 任务 月球]
3.4 词频统计:计算关键词出现次数
统计每个关键词在文本中出现的频率:
go复制func countFrequency(tokens []string) map[string]int {
freq := make(map[string]int)
for _, token := range tokens {
freq[token]++
}
return freq
}
统计结果示例:
code复制阿尔忒弥斯: 1次
月球: 2次
任务: 1次
宇航员: 0次
...
3.5 构建向量:将词频转换为数字序列
按照词汇表的顺序,将词频排列成一串数字:
go复制func buildVector(freq map[string]int, vocab []string) []float64 {
vector := make([]float64, len(vocab))
for i, word := range vocab {
vector[i] = float64(freq[word])
}
return vector
}
得到的向量:
code复制[1.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0]
3.6 向量归一化:消除文本长度影响
不同长度的文本生成的向量长度也不同,为了公平比较,我们需要进行L2归一化:
go复制func normalize(vector []float64) {
sum := 0.0
for _, v := range vector {
sum += v * v
}
if sum == 0 {
return
}
magnitude := math.Sqrt(sum)
for i := range vector {
vector[i] /= magnitude
}
}
归一化后的向量:
code复制[0.354, 0.707, 0.354, 0, 0, 0, 0, 0, 0.354, 0.354]
4. 完整Go语言实现
以下是完整的文本向量化实现代码:
go复制package main
import (
"fmt"
"math"
"strings"
)
var knowledgeBase = []string{
"阿尔忒弥斯2号是美国宇航局计划中的载人月球轨道飞行任务,标志着人类重返月球的重要一步",
// 更多新闻片段...
}
var vocab = []string{"阿尔忒弥斯", "月球", "任务", "宇航员", "火箭", "飞船", "测试", "系统", "飞行", "轨道"}
func cleanText(text string) string {
var cleaned strings.Builder
for _, r := range text {
if r >= 0x4e00 && r <= 0x9fff {
cleaned.WriteRune(r)
}
}
return cleaned.String()
}
func tokenize(text string, vocab []string) []string {
var tokens []string
for _, vocabWord := range vocab {
count := strings.Count(text, vocabWord)
for i := 0; i < count; i++ {
tokens = append(tokens, vocabWord)
}
}
return tokens
}
func countFrequency(tokens []string) map[string]int {
freq := make(map[string]int)
for _, token := range tokens {
freq[token]++
}
return freq
}
func buildVector(freq map[string]int, vocab []string) []float64 {
vector := make([]float64, len(vocab))
for i, word := range vocab {
vector[i] = float64(freq[word])
}
return vector
}
func normalize(vector []float64) {
sum := 0.0
for _, v := range vector {
sum += v * v
}
if sum == 0 {
return
}
magnitude := math.Sqrt(sum)
for i := range vector {
vector[i] /= magnitude
}
}
func main() {
text := knowledgeBase[0]
cleanedText := cleanText(text)
tokens := tokenize(cleanedText, vocab)
freq := countFrequency(tokens)
vector := buildVector(freq, vocab)
normalize(vector)
fmt.Printf("原始文本: %s\n", text)
fmt.Printf("归一化向量: %v\n", vector)
}
5. 实际应用与优化建议
5.1 文本相似度计算
有了文本向量后,我们可以通过计算向量之间的余弦相似度来判断文本的相似程度:
code复制相似度 = (向量A · 向量B) / (||向量A|| * ||向量B||)
由于我们的向量已经归一化,计算简化为:
code复制相似度 = 向量A · 向量B
5.2 实际应用场景
- 搜索引擎:通过比较查询词向量和文档向量的相似度来排序结果
- 推荐系统:找到与用户浏览历史向量相似的内容
- 文本分类:基于向量特征训练分类模型
5.3 优化建议
- 词汇表选择:可以根据TF-IDF等算法自动选择最有代表性的词汇
- 分词优化:使用专业分词工具提高准确性
- 向量维度:可以考虑使用词嵌入(Word2Vec)等更高级的方法
- 性能优化:对于大规模文本,可以使用稀疏向量存储
6. 常见问题与解决方案
6.1 为什么需要归一化?
归一化解决了文本长度不同带来的偏差。例如:
- 长文本中"月球"出现10次
- 短文本中"月球"出现5次
如果不归一化,长文本的数值会更大,但实际上短文本提到"月球"的频率更高。
6.2 如何处理新词?
基础词频方法对词汇表外的词无能为力。解决方案:
- 定期更新词汇表
- 使用可以处理未知词的模型如Word2Vec
- 结合字符级或子词级表示
6.3 词频向量化的局限性
- 无法捕捉词序信息:"猫追狗"和"狗追猫"向量相同
- 无法理解同义词和一词多义
- 维度随词汇表增长而增长
这些局限性催生了更先进的文本表示方法,如词嵌入和预训练语言模型。