自然语言处理中的词嵌入(Part2)
LLM架构专栏持续更新中,词向量部分将会分四次更新完成,以下是已更新文章:
1. LLM大模型架构专栏|| 从NLP基础谈起
2.LLM大模型架构专栏|| 自然语言处理(NLP)之建模
欢迎关注公众号【柏企科技圈】【柏企阅文】
此前我们探讨了词嵌入的基础概念,现在让我们深入到具体的技术细节中,从 N-Grams 开始,一步步揭开这些技术的神秘面纱。
3.1.4 N-Grams
N-grams 是在文本分析中可作为一个单元共同使用的单词序列。像 “Mary had a little lamb” 这句话中,“Mary had”、“had a”、“a little” 和 “little lamb” 等都是二元组(N = 2)。然而,许多 N-grams 在数据中可能出现频率过低,导致其表示稀疏且意义有限。
它主要有以下几种类型:
- Unigram:即单个单词。
- Bigram:由两个单词组成的对。
- Trigram:三个单词构成的序列。
其重要性在于能够捕捉文本中的上下文和单词之间的依赖关系。
以下是一段 Python 示例代码,展示如何生成和分析 N-grams:
import nltk
from nltk.util import ngrams
from collections import Counter
text = "The quick brown fox jumps over the lazy dog"
tokens = nltk.word_tokenize(text)
unigrams = list(ngrams(tokens, 1))
print("Unigrams:")
print(unigrams)
bigrams = list(ngrams(tokens, 2))
print("\nBigrams:")
print(bigrams)
trigrams = list(ngrams(tokens, 3))
print("\nTrigrams:")
print(trigrams)
unigram_freq = Counter(unigrams)
bigram_freq = Counter(bigrams)
trigram_freq = Counter(trigrams)
print("\nUnigram Frequencies:")
print(unigram_freq)
print("\nBigram Frequencies:")
print(bigram_freq)
print("\nTrigram Frequencies:")
print(trigram_freq)
输出结果如下:
Unigrams:
[('The',), ('quick',), ('brown',), ('fox',), ('jumps',), ('over',), ('the',), ('lazy',), ('dog',)]
Bigrams:
[('The', 'quick'), ('quick', 'brown'), ('brown', 'fox'), ('fox', 'jumps'), ('jumps', 'over'), ('over', 'the'), ('the', 'lazy'), ('lazy', 'dog')]
Trigrams:
[('The', 'quick', 'brown'), ('quick', 'brown', 'fox'), ('brown', 'fox', 'jumps'), ('fox', 'jumps', 'over'), ('jumps', 'over', 'the'), ('over', 'the', 'lazy'), ('the', 'lazy', 'dog')]
Unigram Frequencies:
Counter({('The',): 1, ('quick',): 1, ('brown',): 1, ('fox',): 1, ('jumps',): 1, ('over',): 1, ('the',): 1, ('lazy',): 1, ('dog',): 1})
Bigram Frequencies:
Counter({('The', 'quick'): 1, ('quick', 'brown'): 1, ('brown', 'fox'): 1, ('fox', 'jumps'): 1, ('jumps', 'over'): 1, ('over', 'the'): 1, ('the', 'lazy'): 1, ('lazy', 'dog'): 1})
Trigram Frequencies:
Counter({('The', 'quick', 'brown'): 1, ('quick', 'brown', 'fox'): 1, ('brown', 'fox', 'jumps'): 1, ('fox', 'jumps', 'over'): 1, ('jumps', 'over', 'the'): 1, ('over', 'the', 'lazy'): 1, ('the', 'lazy', 'dog'): 1})
尽管 N-grams 有一定的作用,但也存在一些局限性:
- 高维度和稀疏性:会产生大量的 N-grams 组合,导致向量空间维度极高且非常稀疏,增加了计算和存储的负担。
- 缺乏语义理解:仅仅基于单词的连续组合,难以深入理解单词之间的语义关系。
- 忽略上下文:不能很好地捕捉长距离的上下文信息,对文本的整体语义把握有限。
- 可扩展性问题:随着文本数据量的增加,N-grams 的数量会迅速增长,使得处理变得困难。
- 对噪声和罕见词敏感:容易受到数据中的噪声影响,并且在处理罕见词时效果不佳。
- 难以捕捉多义词和同义词:不能有效区分具有相同或相似含义的单词,以及一个单词的不同含义。
- 无法跨语言通用:在不同语言中,N-grams 的组合和语义关系差异较大,难以直接应用。
- 难以捕捉长时依赖:对于文本中较长距离的单词依赖关系,N-grams 难以有效捕捉。
3.1.5 共现矩阵(Co-occurrence Matrices)
共现矩阵用于捕捉在给定的上下文窗口中单词共同出现的频率。它量化了单词之间的统计关系,为生成词嵌入提供了基础。
例如,假设有一个包含以下三个文档的语料库:
- 文档 1:“The quick brown fox jumps over the lazy dog.”
- 文档 2:“The brown dog barks loudly.”
- 文档 3:“The lazy cat sleeps peacefully.”
我们想基于这些文档构建一个窗口大小为 1 的共现矩阵,这意味着我们考虑每个单词与其紧邻单词的共现情况,同时忽略标点并以不区分大小写的方式处理单词。
首先,根据语料库中的唯一单词构建词汇表:
Vocabulary: [the, quick, brown, fox, jumps, over, lazy, dog, barks, loudly, cat, sleeps, peacefully]
然后创建共现矩阵,其中行和列代表词汇表中的单词。矩阵中每个单元格 (i, j) 的值表示单词 i 在指定窗口大小内与单词 j 共现的次数。
the quick brown fox jumps over lazy dog barks loudly cat sleeps peacefully
the 0 1 1 0 0 0 1 1 0 0 1 1 1
quick 1 0 0 1 0 0 0 0 0 0 0 0 0
brown 1 0 0 0 0 0 0 1 1 0 0 0 0
fox 0 1 0 0 1 0 0 0 0 0 0 0 0
jumps 0 0 0 1 0 1 0 0 0 0 0 0 0
over 0 0 0 0 1 0 1 0 0 0 0 0 0
lazy 1 0 0 0 0 1 0 1 0 0 1 0 0
dog 1 0 1 0 0 0 1 0 1 1 0 0 0
barks 0 0 1 0 0 0 0 1 0 1 0 0 0
loudly 0 0 0 0 0 0 0 1 1 0 0 0 0
cat 1 0 0 0 0 0 1 0 0 0 0 1 1
sleeps 1 0 0 0 0 0 0 0 0 0 1 0 1
peacefully 1 0 0 0 0 0 0 0 0 0 1 1 0
这个共现矩阵捕捉了在窗口大小为 1 的情况下每个单词与其他单词的共现频率。例如,“the” 行和 “lazy” 列的条目值为 1,表示在整个语料库中,“lazy” 与 “the” 在指定窗口内共现了一次。
在自然语言处理中,共现矩阵的一个主要应用是生成词嵌入。通过分析语料库中单词的共现模式,共现矩阵可以捕捉每个单词周围的上下文信息。像 Word2Vec 和 GloVe 等技术利用这些矩阵创建密集、低维的单词向量表示,其中向量之间的几何关系反映了单词之间的语义相似性,从而实现单词相似性的测量、类比检测和语义搜索等任务。
以下是一段 Python 示例代码,展示如何构建和处理共现矩阵:
import numpy as np
import pandas as pd
from collections import Counter
from sklearn.preprocessing import normalize
corpus = [
"I love machine learning",
"machine learning is great",
"I love deep learning",
"deep learning and machine learning are related"
]
corpus = [sentence.lower().split() for sentence in corpus]
vocab = set([word for sentence in corpus for word in sentence])
vocab = sorted(vocab)
vocab_size = len(vocab)
co_occurrence_matrix = np.zeros((vocab_size, vocab_size))
window_size = 2
word2idx = {word: i for i, word in enumerate(vocab)}
for sentence in corpus:
for i, word in enumerate(sentence):
word_idx = word2idx[word]
start = max(0, i - window_size)
end = min(len(sentence), i + window_size + 1)
for j in range(start, end):
if i!= j:
context_word = sentence[j]
context_idx = word2idx[context_word]
co_occurrence_matrix[word_idx, context_idx] += 1
co_occurrence_df = pd.DataFrame(co_occurrence_matrix, index=vocab, columns=vocab)
co_occurrence_normalized = normalize(co_occurrence_matrix, norm='l1', axis=1)
co_occurrence_normalized_df = pd.DataFrame(co_occurrence_normalized, index=vocab, columns=vocab)
print("Co-occurrence Matrix:")
print(co_occurrence_df)
print("\nNormalized Co-occurrence Matrix:")
print(co_occurrence_normalized_df)
然而,共现矩阵也存在一些局限性:
- 高维度导致效率和存储问题:随着词汇量的增加,矩阵的维度会迅速增长,导致计算效率低下和存储需求巨大。
- 极度稀疏:由于大多数单词对很少共现,矩阵中大部分元素为 0,造成了数据的稀疏性。
- 捕捉深层语义关系的能力有限:虽然能反映共现频率,但难以深入挖掘单词之间的深层次语义联系。
- 上下文独立性:不能区分同一个单词在不同上下文中的不同含义。
- 对大型语料库的可扩展性挑战:矩阵大小随词汇量呈二次增长,在处理大规模语料库时面临巨大挑战。
- 对频繁词的偏向:频繁出现的单词在矩阵中的权重较大,对罕见词或短语的表示不可靠。
- 表达能力有限:无法捕捉复杂的语言结构和语义关系。
- 噪声数据:由于存在停用词和无意义的单词对,会引入噪声数据。
3.1.6 独热编码(One-Hot Encoding)
独热编码是自然语言处理中表示单词的一种基本方法。词汇表中的每个单词都被表示为一个独特的向量,除了对应单词索引位置的元素为 1 外,其他所有元素都设置为 0。
例如,假设有一个包含 10,000 个单词的词汇表,对于单词 “aardvark”,其向量表示为 [1, 0, 0, 0, …, 0],即在第一个位置为 1,后面跟着 9,999 个 0;对于单词 “ant”,其向量表示为 [0, 1, 0, 0, …, 0],即在第二个位置为 1,其余位置为 0,以此类推。这个过程就称为独热向量编码,它也常用于多类分类问题中表示标签。
假设我们正在构建一个自然语言处理项目中的翻译模型,需要将英文句子 “the cat is black” 翻译成另一种语言。首先要对每个单词进行独热编码。查找第一个单词 “the” 在 10,000 长的词汇表中的索引,假设其索引为 8676,那么 “the” 就可以用一个长度为 10,000 的向量表示,其中除了第 8676 个位置为 1 外,其他位置均为 0。对输入句子中的每个单词都进行这样的索引查找,并创建向量来表示每个输入单词。
以下是一段 Python 示例代码,展示如何实现独热编码:
def one_hot_encode(text):
words = text.split()
vocabulary = set(words)
word_to_index = {word: i for i, word in enumerate(vocabulary)}
one_hot_encoded = []
for word in words:
one_hot_vector = [0] * len(vocabulary)
one_hot_vector[word_to_index[word]] = 1
one_hot_encoded.append(one_hot_vector)
return one_hot_encoded, word_to_index, vocabulary
example_text = "cat in the hat dog on the mat bird in the tree"
one_hot_encoded, word_to_index, vocabulary = one_hot_encode(example_text)
print("Vocabulary:", vocabulary)
print("Word to Index Mapping:", word_to_index)
print("One-Hot Encoded Matrix:")
for word, encoding in zip(example_text.split(), one_hot_encoded):
print(f"{word}: {encoding}")
输出结果如下:
Vocabulary: {'mat', 'the', 'bird', 'hat', 'on', 'in', 'cat', 'tree', 'dog'}
Word to Index Mapping: {'mat': 0, 'the': 1, 'bird': 2, 'hat': 3, 'on': 4, 'in': 5, 'cat': 6, 'tree': 7, 'dog': 8}
One-Hot Encoded Matrix:
cat: [0, 0, 0, 0, 0, 0, 1, 0, 0]
in: [0, 0, 0, 0, 0, 1, 0, 0, 0]
the: [0, 1, 0, 0, 0, 0, 0, 0, 0]
hat: [0, 0, 0, 1, 0, 0, 0, 0, 0]
dog: [0, 0, 0, 0, 0, 0, 0, 0, 1]
on: [0, 0, 0, 0, 1, 0, 0, 0, 0]
the: [0, 1, 0, 0, 0, 0, 0, 0, 0]
mat: [1, 0, 0, 0, 0, 0, 0, 0, 0]
bird: [0, 0, 1, 0, 0, 0, 0, 0, 0]
in: [0, 0, 0, 0, 0, 1, 0, 0, 0]
the: [0, 1, 0, 0, 0, 0, 0, 0, 0]
tree: [0, 0, 0, 0, 0, 0, 0, 1, 0]
虽然独热编码能够将单词表示为实数值向量,但也存在一些问题:
- 缺乏语义相似性:无法捕捉单词之间的语义关系。例如,“cat” 和 “tiger” 被表示为完全不同的向量,没有体现出它们的相似性,这对于基于类比的向量操作等任务来说是个很大的问题。
- 高维度:独热向量的维度与词汇表大小呈线性增长。随着词汇量的增加,特征向量会变得非常大,加剧了维度灾难。这不仅增加了需要估计的参数数量,还需要指数级更多的数据来训练一个泛化良好的模型。
- 计算效率低下:独热编码向量是稀疏且高维的,大部分元素为 0。许多机器学习模型,尤其是神经网络,在处理这种稀疏数据时会遇到困难。庞大的特征空间也会导致内存和存储方面的挑战,特别是当模型不能有效地处理稀疏矩阵时。
后续我们将继续探讨如何解决这些问题,以及其他更先进的词嵌入技术,敬请期待!
LLM架构专栏持续更新中,词向量部分将会分四次更新完成,以下是已更新文章:
1. LLM大模型架构专栏|| 从NLP基础谈起
2.LLM大模型架构专栏|| 自然语言处理(NLP)之建模
欢迎关注公众号【柏企科技圈】【柏企阅文】
希望这篇文章能帮助到大家。
评论