在人工智能领域,尤其是自然语言处理(NLP)中,将文本信息转化为机器可以理解的形式是一个至关重要的步骤。本文探讨如何将文本转换为向量表示的过程,包括分词、ID映射、One-hot编码以及最终的词嵌入(Embedding),并通过具体的案例代码来辅助解释这些概念。
处理字符还是数字
人工智能算法只能处理数字形式的数据,特别是浮点数。这意味着任何非数字的信息,如汉字、字母等,都需要被转换成数值形式才能用于模型训练或预测。
由于AI算法不能直接处理汉字或其他字符,因此必须通过特定的方法将这些字符转换为数字表示。这一过程通常涉及到两个主要步骤:文本向量化和词向量生成。
如何把词变为向量?
假设有一个简单的句子:“我爱北京天安门!”,将其转化为向量的具体步骤分为分词、ID映射、One-hot编码、降维矩阵
第一步分词
第一步是对句子进行分词,即将一句话按照最小的语义单元拆解成一个个特征词/token。例如:
- 原始句子:“我爱北京天安门!”
- 分词结果:“我”|“爱”|“北京”|“天安门”|“!”
第二步ID映射
第二步是为每个token分配一个唯一的ID号。这一步通常需要借助一个词汇表(dictionary),其中每个单词都有一个对应的ID。例如:
- “我” -> 3
- “爱” -> 54
- “北京” -> 65
- “天安门” -> 78
- “!” -> 89
第三步One-hot编码
第三步是进行One-hot编码,这是一种将分类数据转换为可提供给机器学习算法处理的形式的方法。然而,这种方法的一个显著缺点是它会产生高度稀疏的向量,即大部分元素都是0。
import numpy as np
# token_id:表示单词在词汇表中的唯一标识符(ID)。
# vocab_size:词汇表的大小,即词汇表中不同单词的数量。
def one_hot_encode(token_id, vocab_size):
return np.eye(vocab_size)[token_id]
vocab_size = 10 # 假设词汇表大小为10
encoded_vector = one_hot_encode(3, vocab_size)
print(encoded_vector)
将token爱对应的 ID 3,输入到one_hot_encode函数,输出:
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
这是一个长度为10的数组,只有第4个位置上的值是1,其余均为0。这代表了ID为3的单词的One-hot编码。
np.eye(vocab_size)
会创建一个vocab_size x vocab_size
的单位矩阵(对角线上是1,其余位置都是0,例如:np.eye(10),生成一个 10 x 10 的单位矩阵:
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])
当你用token_id作为索引访问这个矩阵的一行时,实际上就是获取了一个只在token_id位置上有1,其他地方全是0的向量,这就是所谓的One-hot编码。
我爱北京天安门!做 one hot 编码之后的高度稀疏矩阵就形如以下结构
3:[0, 0, 0, 1, … 0, 0, 0, …N-1]
54:[0, 0, … 0, 1, 0, 0, 0, …N-1]
65:[0, 0, 0, … 0, 0, 0, 1, …N-1]
…
第四步降维
通常One-hot编码使用的词汇表都很很大,上万的词汇表,不会是上面所列举的词汇表大小为10,
使用上万的词汇表进行One-hot编码之后,接下来,使用一个可学习的降维矩阵,把高维向量变成低维向量,比如:512维,这种低维向量不仅减少了计算复杂度,还能捕捉到单词之间的语义关系。
数据结构变化
整个过程中的数据结构变化如下:
- 原始句子:[batch_size, seq_len],batch_size指的是有几个句子,seq_len指的是每一句被拆分成多少个词,也可以说有多少个token。
- 向量化后:[batch_size, seq_len, embedding_dim],每句话中的每个词都被替换为其对应的embedding_dim维向量。
上面这 2 个步骤,一是One-hot 编码,二是降维,可以合并一起做,通常使用 Embedding 层 一步到位解决!
import torch
import torch.nn as nn
# 定义词汇表大小和嵌入维度
vocab_size = 10000 # 假设词汇表中有10,000个单词
embedding_dim = 128 # 每个单词的嵌入向量维度为128
# 创建 Embedding 层,
embedding = nn.Embedding(vocab_size, embedding_dim)
# 假设我们有一些单词索引
word_indices = torch.LongTensor([1, 2, 3, 4, 5])
# 获取嵌入向量,该层将词汇表中的每个单词索引映射到一个128维的向量。
# embedded_words 的形状为 (5, 128),表示5个单词,每个单词是128维嵌入向量。
embedded_words = embedding(word_indices)
print(embedded_words)
print(embedded_words.shape) # 输出应为 torch.Size([5, 128])
嵌入向量输出:
tensor([[-4.6017e-01, 1.4473e+00, 1.3787e+00, -5.0634e-01, -1.4204e+00,
-1.4081e+00, 9.2571e-01, -6.7002e-02, 1.1946e+00, 2.6210e-01,
......
-2.5038e-01, 1.7327e+00, 6.1820e-01, -2.5919e+00, -7.2244e-01,
-1.2460e+00, 1.8335e-01, -6.0779e-01],
[ 8.9795e-01, 1.4445e-01, 3.1429e-01, 9.1202e-01, 4.9661e-01,
-8.2164e-01, -5.8186e-02, -5.8424e-01, 1.6252e+00, -8.2808e-01,
-6.9109e-01, -6.8143e-01, 8.2476e-01, -6.3271e-04, -2.0201e+00,
......
5.3912e-02, 6.3298e-01, 1.4325e-01, 1.5289e+00, -1.7104e+00,
-1.4025e+00, -1.2311e+00, -2.2923e-01],
[-6.1438e-01, 2.2085e-01, -3.0483e-01, 1.5419e+00, -1.5819e+00,
-3.1500e-01, 2.9932e-01, -6.7334e-01, 5.3232e-01, 1.2593e+00,
......
1.1688e+00, 3.6866e-02, 1.1927e+00, 6.4568e-01, 2.6445e+00,
4.6353e-01, 1.1167e+00, 1.1976e+00],
[-9.9785e-01, -2.2409e-01, 2.2492e-01, 1.1209e+00, 5.7221e-01,
-1.7736e+00, 3.1228e-01, 1.4183e-01, -5.0892e-01, -7.8893e-01,
......
1.7181e-02, 6.5555e-01, -3.8586e-01, -6.2199e-01, 1.9411e+00,
-1.6575e+00, 5.7638e-01, -9.1050e-02],
[-1.8926e-01, -5.7981e-01, -9.2958e-01, -5.5733e-01, 2.2229e-01,
8.1851e-01, 7.6115e-01, -6.0253e-01, 4.3705e-01, 6.8111e-01,
-1.3935e+00, 1.0176e+00, 3.8146e-01, 3.0739e-01, 2.0513e+00,
1.2356e+00, -2.3017e-02, 5.3749e-01, -7.2238e-01, 4.8466e-01,
......
1.1089e+00, 1.7572e+00, -2.1472e-01, -8.5052e-01, -3.2292e-01,
1.0617e+00, 8.3969e-02, 9.5239e-01]], grad_fn=<EmbeddingBackward0>)
torch.Size([5, 128])
nn.Embedding(...)
是 PyTorch 中的一个模块,用于将离散的输入(如词索引)转换为连续的向量表示(嵌入向量),这些向量通常称为嵌入(embeddings)
如何理解将离散的输入数据转换为连续的向量表示?
在自然语言处理(NLP)中,最常见的离散数据是单词。每个单词可以用一个唯一的索引(整数)来表示。例如,假设我们有一个词汇表,其中每个单词都有一个唯一的索引:
0 -> "apple"
1 -> "banana"
2 -> "cherry"
3 -> "date"
...
在这种情况下,单词 “apple” 的索引是0,“banana” 的索引是1,依此类推。这种单词索引,经过nn.Embedding层处理之后,索引会被映射为下面这样的[4,N]向量
tensor([[ 0.0347, 0.0254, -0.0123, ..., -0.0056, 0.0123, 0.0045],
[ 0.0123, -0.0045, 0.0034, ..., 0.0234, -0.0123, 0.0056],
[-0.0045, 0.0123, 0.0034, ..., 0.0056, 0.0123, -0.0045],
[ 0.0034, 0.0123, -0.0056, ..., 0.0045, 0.0123, 0.0034]])
这样的值就是连续的向量表示,128维的嵌入向量是一个包含128个浮点数的向量,这些浮点数可以在某个区间内取任意值,通常初始化为某个分布(如标准正态分布或均匀分布)。
nn.Embedding 的作用
- 将离散数据转换为连续向量:nn.Embedding 将离散的输入(如单词索引)映射到固定维度的向量空间中。这些向量通常被称为嵌入向量。
- 捕捉语义信息:嵌入向量可以捕捉输入数据的语义信息。例如,在 NLP 中,相似的单词通常会有相似的嵌入向量。相似的单词在向量空间中距离较近。
- 减少维度:嵌入向量通常比原始的独热编码(one-hot encoding)维度要小得多,这有助于减少模型的复杂性和计算成本。
nn.Embedding 主要有两个参数:
num_embeddings
:表示嵌入矩阵的大小,例如,如果你的词汇表中有10,000个单词,num_embeddings 应该设置为10,000。embedding_dim
:表示每个嵌入向量的维度。例如,如果你希望每个单词的嵌入向量是128维,embedding_dim 应该设置为128。
embedding_dim为什么是128维?根据什么来确定embedding_dim的大小呢?
选择嵌入向量的维度(如128维)是一个重要的超参数选择,它会影响模型的性能和训练效率。没有严格的规则规定必须选择某个特定的维度,选择嵌入向量的维度(如128维)是一个平衡模型容量、任务复杂度、数据规模和计算资源的决策。
没有固定的规则,但可以通过实验和经验来确定最适合你任务的维度。
- 模型容量
高维度
:更高的维度(如256、512)通常意味着模型有更多的参数,可以捕捉更复杂的语义信息。这在处理大型词汇表和复杂任务时可能更有优势。
低维度
:较低的维度(如64、128)通常意味着模型参数较少,训练速度更快,内存占用更少。这在资源受限的环境中或处理较小词汇表和简单任务时可能更有优势。 - 任务复杂度
简单任务
:对于简单的任务(如情感分析、文本分类),较低的维度(如64、128)通常足够。
复杂任务
:对于复杂的任务(如机器翻译、问答系统),可能需要更高的维度(如256、512)来捕捉更多的语义信息。 - 数据规模
大数据集
:对于大规模数据集,可以选择较高的维度,因为有足够的数据来训练更多的参数。
小数据集
:对于小规模数据集,选择较低的维度可以减少过拟合的风险。 - 计算资源
资源充足
:如果你有充足的计算资源(如GPU、TPU),可以选择较高的维度,因为训练时间和内存占用不是主要瓶颈。
资源有限
:如果你的计算资源有限,选择较低的维度可以加快训练速度并减少内存占用。
上面示例中输出的这128维的嵌入向量,各个数据之间有什么关系吗?
128维的嵌入向量中的各个数据之间并没有固定的数学关系,但它们通过训练过程逐渐学习到一些有意义的结构。这些嵌入向量的设计目的是捕捉输入数据(如单词)的语义信息
- 语义相似性
相似单词的嵌入向量相近:在训练过程中,相似的单词(如 “猫” 和 “狗”)的嵌入向量会在向量空间中靠近彼此。这意味着它们之间的欧几里得距离较小。
上下文相关性:嵌入向量还会捕捉单词在不同上下文中的使用情况。例如,“银行” 在金融上下文中和河流岸边的上下文中会有不同的嵌入向量。 - 方向和角度
方向:嵌入向量的方向可以表示某些语义属性。例如,在某些嵌入空间中,向量 “king” - “man” + “woman” 可能接近 “queen”。
角度:嵌入向量之间的夹角可以反映它们之间的语义关系。例如,余弦相似度可以用来衡量两个向量的相似性。 - 维度的意义
无明确意义:通常情况下,嵌入向量的各个维度本身并没有明确的语义意义。每个维度的值是通过训练过程自动学习的,目的是使模型在特定任务上的表现更好。
潜在特征:虽然单个维度没有明确意义,但整个向量可以看作是单词在多维特征空间中的表示。每个维度可以被视为一个潜在特征,这些特征共同决定了单词的语义。 - 训练过程
反向传播:在训练过程中,嵌入向量的值会通过反向传播算法不断调整,以最小化损失函数。这使得嵌入向量能够更好地捕捉输入数据的语义信息。
优化目标:优化目标通常是为了使模型在特定任务(如语言模型、分类任务等)上的性能最大化。在这个过程中,嵌入向量逐渐学习到有意义的结构。
从网络上找了一个图,很形象的描述了向量化的过程