文章目录
-
- 文本表示方法演进:从词袋模型到BERT
-
- 一、为什么需要文本表示?
- [二、词袋模型(Bag of Words, BoW)](#二、词袋模型(Bag of Words, BoW))
-
- [2.1 BoW原理](#2.1 BoW原理)
- [2.2 手动实现词袋模型](#2.2 手动实现词袋模型)
- [2.3 TF-IDF:改进的词袋模型](#2.3 TF-IDF:改进的词袋模型)
- [2.4 词袋模型的局限性](#2.4 词袋模型的局限性)
- 三、N-gram模型:捕捉局部上下文
- 四、Word2Vec:词嵌入的革命
-
- [4.1 Word2Vec原理](#4.1 Word2Vec原理)
- [4.2 使用Gensim训练Word2Vec](#4.2 使用Gensim训练Word2Vec)
- [4.3 词嵌入的可视化](#4.3 词嵌入的可视化)
- 五、GloVe:全局向量表示
- 六、FastText:子词信息
- 七、BERT:预训练语言模型的革命
-
- [7.1 BERT的核心思想](#7.1 BERT的核心思想)
- [7.2 使用Transformers库](#7.2 使用Transformers库)
- [7.3 文本表示方法的演进总结](#7.3 文本表示方法的演进总结)
- 八、实战:从词袋到BERT的完整流程
- 九、总结
文本表示方法演进:从词袋模型到BERT
在自然语言处理中,如何将文本转换为计算机可以理解和处理的数学表示,是一个核心问题。本文将带你深入了解文本表示方法的演进历程,从最简单的词袋模型到革命性的BERT模型,展示NLP技术如何一步步突破传统方法的局限。
一、为什么需要文本表示?
计算机只能处理数字,无法直接理解文本。因此,我们需要将文本转换为向量或矩阵形式的数学表示。一个好的文本表示应该具备以下特点:
文本表示的要求
语义保留性
固定维度
稀疏性可控
计算效率
泛化能力
相似语义接近
保留上下文信息
批处理友好
模型输入统一
存储优化
计算加速
快速检索
实时处理
未见词处理
迁移学习
python
import numpy as np
# 文本示例
texts = [
"我喜欢自然语言处理",
"深度学习很有趣",
"人工智能改变了世界"
]
print("文本表示的必要性:")
print("计算机无法直接理解文本,需要转换为数字向量")
print(f"示例文本1: {texts[0]}")
print(f"示例文本2: {texts[1]}")
print(f"示例文本3: {texts[2]}")
二、词袋模型(Bag of Words, BoW)
词袋模型是最早、最简单的文本表示方法。它忽略词序,只统计词频。
2.1 BoW原理
python
from sklearn.feature_extraction.text import CountVectorizer
# 示例文本
documents = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"深度学习和人工智能都很有趣"
]
# 创建词袋模型
vectorizer = CountVectorizer()
bow_matrix = vectorizer.fit_transform(documents)
# 查看词汇表
print("词汇表:")
print(vectorizer.get_feature_names_out())
# 查看词袋矩阵
print("\n词袋矩阵:")
print(bow_matrix.toarray())
# 查看每个词的统计
print("\n词频统计:")
for doc_idx, doc in enumerate(documents):
print(f"文档{doc_idx+1}: {doc}")
for word, count in zip(vectorizer.get_feature_names_out(), bow_matrix.toarray()[doc_idx]):
if count > 0:
print(f" {word}: {count}")
print()
2.2 手动实现词袋模型
python
from collections import Counter
import numpy as np
class BagOfWords:
"""
简单的词袋模型实现
"""
def __init__(self):
self.vocabulary = {} # 词汇表:{词: 索引}
self.vocab_size = 0
def build_vocabulary(self, documents):
"""
构建词汇表
"""
word_counts = Counter()
for doc in documents:
# 简单分词(按空格)
words = doc.split()
word_counts.update(words)
# 过滤低频词(可选)
min_freq = 1
self.vocabulary = {
word: idx
for idx, (word, count) in enumerate(word_counts.items())
if count >= min_freq
}
self.vocab_size = len(self.vocabulary)
print(f"词汇表大小: {self.vocab_size}")
print(f"词汇表: {list(self.vocabulary.keys())}")
def transform(self, documents):
"""
将文档转换为词袋向量
"""
# 初始化矩阵
num_docs = len(documents)
bow_matrix = np.zeros((num_docs, self.vocab_size))
for doc_idx, doc in enumerate(documents):
words = doc.split()
# 统计词频
word_count = Counter(words)
# 填充矩阵
for word, count in word_count.items():
if word in self.vocabulary:
word_idx = self.vocabulary[word]
bow_matrix[doc_idx, word_idx] = count
return bow_matrix
def get_feature_names(self):
"""
获取特征名称
"""
return list(self.vocabulary.keys())
# 测试
documents = [
"我喜欢 自然语言 处理",
"自然语言 处理 很 有趣",
"深度学习 和 人工智能 都 很 有趣"
]
bow = BagOfWords()
bow.build_vocabulary(documents)
bow_matrix = bow.transform(documents)
print("\n词袋矩阵:")
for i, doc in enumerate(documents):
print(f"文档{i+1}: {doc}")
print(f"向量: {bow_matrix[i]}")
print()
2.3 TF-IDF:改进的词袋模型
词袋模型的一个问题是:常见词(如"的"、"是")会占据主导地位。TF-IDF通过降低常见词的权重来解决这个问题。
python
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
# 示例文档
documents = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"深度学习和人工智能都很有趣",
"我喜欢人工智能"
]
# TF-IDF向量化
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(documents)
print("TF-IDF矩阵:")
print(tfidf_matrix.toarray())
print("\n每个词的TF-IDF值:")
feature_names = tfidf_vectorizer.get_feature_names_out()
for doc_idx, doc in enumerate(documents):
print(f"\n文档{doc_idx+1}: {doc}")
doc_vector = tfidf_matrix.toarray()[doc_idx]
# 按TF-IDF值排序
word_tfidf = list(zip(feature_names, doc_vector))
word_tfidf.sort(key=lambda x: x[1], reverse=True)
print("TF-IDF排名:")
for word, tfidf in word_tfidf[:5]: # 显示前5个
print(f" {word}: {tfidf:.4f}")
# 手动实现TF-IDF
class TFIDF:
"""
简单的TF-IDF实现
"""
def __init__(self):
self.vocabulary = {}
self.idf = {}
self.num_docs = 0
def fit(self, documents):
"""
训练模型,计算IDF
"""
self.num_docs = len(documents)
# 构建词汇表和统计文档频率
doc_freq = Counter()
for doc in documents:
words = set(doc.split()) # 每个文档只算一次
doc_freq.update(words)
self.vocabulary = {word: idx for idx, word in enumerate(doc_freq.keys())}
# 计算IDF
for word, freq in doc_freq.items():
# IDF = log(总文档数 / 包含该词的文档数)
self.idf[word] = np.log(self.num_docs / (freq + 1)) + 1
def transform(self, documents):
"""
计算TF-IDF
"""
tfidf_matrix = []
for doc in documents:
words = doc.split()
word_count = Counter(words)
# 计算TF
total_words = len(words)
tf = {word: count / total_words for word, count in word_count.items()}
# 计算TF-IDF
tfidf_vector = np.zeros(len(self.vocabulary))
for word in word_count.keys():
if word in self.vocabulary:
idx = self.vocabulary[word]
tfidf_vector[idx] = tf[word] * self.idf.get(word, 0)
tfidf_matrix.append(tfidf_vector)
return np.array(tfidf_matrix)
# 测试TF-IDF
tfidf = TFIDF()
tfidf.fit(documents)
tfidf_matrix = tfidf.transform(documents)
print("\n\n手动实现的TF-IDF:")
for i, doc in enumerate(documents):
print(f"\n文档{i+1}: {doc}")
print(f"TF-IDF向量: {tfidf_matrix[i]}")
2.4 词袋模型的局限性
python
# 演示词袋模型的问题
documents = [
"狗咬人",
"人咬狗"
]
vectorizer = CountVectorizer()
bow_matrix = vectorizer.fit_transform(documents)
print("词袋模型无法区分词序:")
for i, doc in enumerate(documents):
print(f"\n文档{i+1}: {doc}")
print(f"向量: {bow_matrix.toarray()[i]}")
print(f"向量相同: {np.array_equal(bow_matrix.toarray()[0], bow_matrix.toarray()[1])}")
print("\n问题:")
print("1. 词序信息丢失")
print("2. 语义鸿沟:无法理解词的相似性")
print("3. 稀疏性:特征空间巨大")
print("4. 维度灾难:词汇表增长导致维度爆炸")
三、N-gram模型:捕捉局部上下文
N-gram模型通过考虑连续的N个词来捕捉局部上下文信息。
python
from sklearn.feature_extraction.text import CountVectorizer
documents = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"深度学习和人工智能都很有趣"
]
# Unigram(1-gram)
unigram_vectorizer = CountVectorizer(ngram_range=(1, 1))
unigram_matrix = unigram_vectorizer.fit_transform(documents)
print("Unigram特征:")
print(unigram_vectorizer.get_feature_names_out())
print()
# Bigram(2-gram)
bigram_vectorizer = CountVectorizer(ngram_range=(2, 2))
bigram_matrix = bigram_vectorizer.fit_transform(documents)
print("Bigram特征:")
print(bigram_vectorizer.get_feature_names_out())
print()
# Trigram(3-gram)
trigram_vectorizer = CountVectorizer(ngram_range=(3, 3))
trigram_matrix = trigram_vectorizer.fit_transform(documents)
print("Trigram特征:")
print(trigram_vectorizer.get_feature_names_out())
# 手动实现N-gram
def generate_ngrams(text, n):
"""
生成n-gram
"""
words = text.split()
ngrams = []
for i in range(len(words) - n + 1):
ngram = ' '.join(words[i:i+n])
ngrams.append(ngram)
return ngrams
text = "我喜欢自然语言处理"
print(f"\n原文: {text}")
print(f"Unigram: {generate_ngrams(text, 1)}")
print(f"Bigram: {generate_ngrams(text, 2)}")
print(f"Trigram: {generate_ngrams(text, 3)}")
# N-gram语言模型(简单的平滑)
class NGramLanguageModel:
"""
简单的N-gram语言模型
"""
def __init__(self, n=2):
self.n = n
self.ngram_counts = Counter()
self.context_counts = Counter()
self.vocab = set()
def train(self, documents):
"""
训练语言模型
"""
for doc in documents:
words = doc.split()
self.vocab.update(words)
# 添加开始和结束标记
words = ['<s>'] * (self.n - 1) + words + ['</s>']
# 统计n-gram
for i in range(len(words) - self.n + 1):
ngram = tuple(words[i:i+self.n])
context = tuple(words[i:i+self.n-1])
self.ngram_counts[ngram] += 1
self.context_counts[context] += 1
def probability(self, word, context):
"""
计算P(word | context)
"""
ngram = tuple(context + [word])
# 使用加1平滑(Laplace smoothing)
numerator = self.ngram_counts[ngram] + 1
denominator = self.context_counts[tuple(context)] + len(self.vocab)
return numerator / denominator
def generate(self, context, max_length=10):
"""
生成文本
"""
result = list(context)
for _ in range(max_length):
# 获取上下文
current_context = result[-(self.n-1):] if len(result) >= self.n-1 else result
# 寻找概率最高的下一个词
best_word = None
best_prob = 0
for word in self.vocab:
prob = self.probability(word, current_context)
if prob > best_prob:
best_prob = prob
best_word = word
if best_word is None or best_word == '</s>':
break
result.append(best_word)
return ' '.join(result)
# 训练语言模型
documents = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"我喜欢深度学习",
"深度学习很有趣",
"人工智能很有趣"
]
model = NGramLanguageModel(n=2)
model.train(documents)
print("\n\n语言模型训练完成")
print(f"词汇表大小: {len(model.vocab)}")
# 计算词的概率
print("\n词的概率:")
context = ['我']
for word in ['喜欢', '爱', '学习']:
prob = model.probability(word, context)
print(f"P('{word}' | '我') = {prob:.4f}")
# 生成文本
print(f"\n生成文本:")
generated = model.generate(['我'], max_length=5)
print(generated)
四、Word2Vec:词嵌入的革命
2013年,Word2Vec的发布标志着NLP进入了一个新时代。它学习到的词向量能够捕捉词语的语义信息,相似的词语在向量空间中会靠近。
Word2Vec
CBOW
Skip-gram
输入: 上下文词
预测: 目标词
输入: 目标词
预测: 上下文词
4.1 Word2Vec原理
python
import numpy as np
from collections import Counter
class Word2VecSimple:
"""
简化的Word2Vec实现(概念演示)
"""
def __init__(self, vocab_size, embedding_dim=100, window_size=2):
self.vocab_size = vocab_size
self.embedding_dim = embedding_dim
self.window_size = window_size
# 词向量矩阵
self.center_embeddings = np.random.randn(vocab_size, embedding_dim) * 0.01
self.context_embeddings = np.random.randn(vocab_size, embedding_dim) * 0.01
def train_batch(self, center_idx, context_indices, learning_rate=0.01):
"""
训练一个batch
"""
# 获取中心词和上下文词的向量
center_vec = self.center_embeddings[center_idx]
context_vecs = self.context_embeddings[context_indices]
# 计算相似度分数
scores = np.dot(context_vecs, center_vec)
# 应用softmax
exp_scores = np.exp(scores)
probs = exp_scores / np.sum(exp_scores)
# 损失:对数似然的负值
loss = -np.log(probs.mean())
# 计算梯度(简化版)
error = probs - 1.0 / len(context_indices)
# 更新词向量
center_grad = np.dot(error, context_vecs) / len(context_indices)
self.center_embeddings[center_idx] -= learning_rate * center_grad
for i, context_idx in enumerate(context_indices):
context_grad = error[i] * center_vec
self.context_embeddings[context_idx] -= learning_rate * context_grad
return loss
def get_word_vector(self, word_idx):
"""
获取词向量
"""
return self.center_embeddings[word_idx]
def similarity(self, word_idx1, word_idx2):
"""
计算两个词的余弦相似度
"""
vec1 = self.get_word_vector(word_idx1)
vec2 = self.get_word_vector(word_idx2)
cosine_sim = np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
return cosine_sim
# 训练一个简单的Word2Vec模型
documents = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"我喜欢深度学习",
"深度学习很有趣",
"人工智能很有趣",
"机器学习很有趣"
]
# 构建词汇表
vocab = list(set(word for doc in documents for word in doc.split()))
word_to_idx = {word: idx for idx, word in enumerate(vocab)}
idx_to_word = {idx: word for word, idx in word_to_idx.items()}
print(f"词汇表: {vocab}")
print(f"词汇表大小: {len(vocab)}")
# 创建模型
model = Word2VecSimple(vocab_size=len(vocab), embedding_dim=50)
# 准备训练数据
training_data = []
for doc in documents:
words = doc.split()
word_indices = [word_to_idx[word] for word in words]
# 生成(中心词,上下文词)对
for i, center_idx in enumerate(word_indices):
# 获取上下文窗口
start = max(0, i - model.window_size)
end = min(len(word_indices), i + model.window_size + 1)
context_indices = [
word_indices[j]
for j in range(start, end)
if j != i
]
training_data.append((center_idx, context_indices))
# 训练模型
print("\n训练Word2Vec模型...")
num_epochs = 100
for epoch in range(num_epochs):
total_loss = 0
for center_idx, context_indices in training_data:
loss = model.train_batch(center_idx, context_indices, learning_rate=0.1)
total_loss += loss
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {total_loss/len(training_data):.4f}")
# 计算词的相似度
print("\n词向量相似度:")
word_pairs = [
("喜欢", "爱"),
("有趣", "好玩"),
("自然语言", "深度学习"),
("处理", "学习"),
]
for word1, word2 in word_pairs:
if word1 in word_to_idx and word2 in word_to_idx:
idx1 = word_to_idx[word1]
idx2 = word_to_idx[word2]
sim = model.similarity(idx1, idx2)
print(f"{word1} - {word2}: {sim:.4f}")
# 词类比示例
print("\n词类比:")
print("类似于 king - man + woman = queen")
4.2 使用Gensim训练Word2Vec
python
# 注意:实际使用时需要安装 gensim
# pip install gensim
from gensim.models import Word2Vec
import jieba
# 准备数据
documents = [
list(jieba.cut("我喜欢自然语言处理")),
list(jieba.cut("自然语言处理很有趣")),
list(jieba.cut("我喜欢深度学习")),
list(jieba.cut("深度学习很有趣")),
list(jieba.cut("人工智能很有趣")),
list(jieba.cut("机器学习很有趣")),
list(jieba.cut("我喜欢人工智能")),
list(jieba.cut("人工智能改变世界")),
list(jieba.cut("深度学习改变世界")),
]
print("训练数据:")
for i, doc in enumerate(documents):
print(f"{i+1}. {doc}")
# 训练Word2Vec模型
print("\n训练Word2Vec模型...")
model = Word2Vec(
sentences=documents,
vector_size=100, # 词向量维度
window=5, # 上下文窗口大小
min_count=1, # 最小词频
workers=4, # 使用4个线程
sg=1 # 1表示使用Skip-gram,0表示CBOW
)
print(f"模型训练完成")
print(f"词汇表大小: {len(model.wv)}")
# 查看词向量
word = "喜欢"
if word in model.wv:
print(f"\n'{word}'的词向量:")
print(model.wv[word])
print(f"向量维度: {len(model.wv[word])}")
# 查找最相似的词
print(f"\n与'喜欢'最相似的词:")
similar_words = model.wv.most_similar('喜欢', topn=5)
for word, similarity in similar_words:
print(f" {word}: {similarity:.4f}")
# 词类比
print("\n词类比:")
try:
result = model.wv.most_similar(
positive=['学习', '有趣'],
negative=['喜欢'],
topn=1
)
print(f"学习 - 喜欢 + 有趣 ≈ {result[0][0]} (相似度: {result[0][1]:.4f})")
except Exception as e:
print(f"词类比计算失败: {e}")
# 保存和加载模型
model.save("word2vec_model.bin")
print("\n模型已保存为 word2vec_model.bin")
# 加载模型
loaded_model = Word2Vec.load("word2vec_model.bin")
print("模型加载成功")
4.3 词嵌入的可视化
python
import numpy as np
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
# 获取词向量
words = list(model.wv.key_to_index.keys())
word_vectors = np.array([model.wv[word] for word in words])
# 使用PCA降维到2维
pca = PCA(n_components=2)
word_vectors_2d = pca.fit_transform(word_vectors)
print(f"原始维度: {word_vectors.shape[1]}")
print(f"降维后维度: {word_vectors_2d.shape[1]}")
# 绘制词向量(概念代码)
print("\n词向量可视化:")
print(f"可以绘制散点图,每个点代表一个词")
print(f"语义相似的词在图中应该聚集在一起")
# 显示一些词的坐标
sample_words = words[:10]
print("\n示例词的2D坐标:")
for i, word in enumerate(sample_words):
x, y = word_vectors_2d[i]
print(f"{word}: ({x:.2f}, {y:.2f})")
五、GloVe:全局向量表示
GloVe(Global Vectors for Word Representation)结合了Word2Vec的局部上下文和词共现矩阵的全局统计信息。
python
import numpy as np
from collections import Counter
class GloVeSimple:
"""
简化的GloVe实现(概念演示)
"""
def __init__(self, vocab_size, embedding_dim=100):
self.vocab_size = vocab_size
self.embedding_dim = embedding_dim
# 词向量矩阵
self.W = np.random.randn(vocab_size, embedding_dim) * 0.01
self.b = np.zeros(vocab_size)
# 上下文向量矩阵
self.W_tilde = np.random.randn(vocab_size, embedding_dim) * 0.01
self.b_tilde = np.zeros(vocab_size)
def train(self, cooccurrence_matrix, learning_rate=0.05, x_max=100, alpha=0.75):
"""
训练GloVe模型
cooccurrence_matrix: 词共现矩阵
x_max: 加权函数参数
alpha: 加权函数参数
"""
num_pairs = np.sum(cooccurrence_matrix > 0)
total_loss = 0
for i in range(self.vocab_size):
for j in range(self.vocab_size):
X_ij = cooccurrence_matrix[i, j]
if X_ij == 0:
continue
# 加权函数
f_ij = min((X_ij / x_max) ** alpha, 1.0)
# 预测值
pred = np.dot(self.W[i], self.W_tilde[j]) + self.b[i] + self.b_tilde[j]
# 损失
loss = f_ij * (pred - np.log(X_ij)) ** 2
total_loss += loss
# 梯度
grad = 2 * f_ij * (pred - np.log(X_ij))
# 更新参数
self.W[i] -= learning_rate * grad * self.W_tilde[j]
self.W_tilde[j] -= learning_rate * grad * self.W[i]
self.b[i] -= learning_rate * grad
self.b_tilde[j] -= learning_rate * grad
return total_loss / num_pairs
def get_word_vector(self, word_idx):
"""
获取词向量(中心词向量和上下文向量的平均)
"""
return (self.W[word_idx] + self.W_tilde[word_idx]) / 2
# 构建词共现矩阵
def build_cooccurrence_matrix(documents, window_size=2, vocab=None):
"""
构建词共现矩阵
"""
if vocab is None:
vocab = list(set(word for doc in documents for word in doc.split()))
word_to_idx = {word: idx for idx, word in enumerate(vocab)}
vocab_size = len(vocab)
cooccurrence_matrix = np.zeros((vocab_size, vocab_size))
for doc in documents:
words = doc.split()
word_indices = [word_to_idx[word] for word in words]
for i, center_idx in enumerate(word_indices):
start = max(0, i - window_size)
end = min(len(word_indices), i + window_size + 1)
for j in range(start, end):
if i != j:
cooccurrence_matrix[center_idx, word_indices[j]] += 1
return cooccurrence_matrix, word_to_idx
# 示例
documents = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"我喜欢深度学习",
"深度学习很有趣",
]
cooccurrence_matrix, word_to_idx = build_cooccurrence_matrix(documents)
print("词共现矩阵:")
vocab = list(word_to_idx.keys())
print(" " + " ".join(f"{word[:4]:4s}" for word in vocab[:8]))
for i, word in enumerate(vocab[:8]):
print(f"{word[:4]}: " + " ".join(f"{int(cooccurrence_matrix[i,j]):4d}" for j in range(len(vocab[:8]))))
# 训练GloVe模型
print("\n训练GloVe模型...")
model = GloVeSimple(vocab_size=len(vocab), embedding_dim=50)
num_epochs = 50
for epoch in range(num_epochs):
loss = model.train(cooccurrence_matrix, learning_rate=0.1)
if (epoch + 1) % 10 == 0:
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {loss:.4f}")
print("\n训练完成")
六、FastText:子词信息
FastText在Word2Vec的基础上,引入了子词信息,能够更好地处理未登录词(OOV)问题。
python
# FastText概念演示
class FastTextSimple:
"""
简化的FastText实现
"""
def __init__(self, vocab_size, embedding_dim=100, min_n=3, max_n=6):
self.vocab_size = vocab_size
self.embedding_dim = embedding_dim
self.min_n = min_n
self.max_n = max_n
# 词向量
self.word_embeddings = np.random.randn(vocab_size, embedding_dim) * 0.01
# 子词向量
self.ngram_embeddings = {}
def get_ngrams(self, word):
"""
获取词的n-gram子词
"""
ngrams = ['<w>', word, '</w>'] # 添加边界标记
for n in range(self.min_n, min(self.max_n, len(word)) + 1):
for i in range(len(word) - n + 1):
ngrams.append(word[i:i+n])
return ngrams
def train(self, documents, learning_rate=0.1):
"""
训练FastText模型(概念)
"""
for doc in documents:
words = doc.split()
for word in words:
# 获取子词
ngrams = self.get_ngrams(word)
# 训练时同时考虑完整词和子词
# 这里只是概念演示,实际实现更复杂
pass
# 示例
word = "学习"
fasttext = FastTextSimple(vocab_size=100, embedding_dim=50)
print("FastText子词分解:")
print(f"词: {word}")
print(f"子词: {fasttext.get_ngrams(word)}")
word2 = "机器学习"
print(f"\n词: {word2}")
print(f"子词: {fasttext.get_ngrams(word2)}")
print("\n优点:")
print("1. 可以处理未登录词(OOV)")
print("2. 共享子词信息")
print("3. 适合形态变化丰富的语言")
七、BERT:预训练语言模型的革命
2018年,BERT(Bidirectional Encoder Representations from Transformers)的出现彻底改变了NLP领域。它通过在大规模语料上预训练,然后微调到特定任务,取得了前所未有的效果。
BERT架构
多层Transformer Encoder
双向注意力机制
Masked Language Model
Next Sentence Prediction
随机Mask词语
预测被Mask的词
判断两句话是否连续
预训练
大规模语料
学习语言表示
微调
下游任务
7.1 BERT的核心思想
python
# BERT概念演示
class BERTSimple:
"""
简化的BERT概念模型
"""
def __init__(self, vocab_size, hidden_size=768, num_layers=12):
self.vocab_size = vocab_size
self.hidden_size = hidden_size
self.num_layers = num_layers
print("BERT模型架构:")
print(f" 词汇表大小: {vocab_size}")
print(f" 隐藏层维度: {hidden_size}")
print(f" Transformer层数: {num_layers}")
print(f" 注意力头数: 12")
print(f" 参数总数: ~110M (BERT-Base)")
def masked_language_model(self, tokens, mask_positions):
"""
Masked Language Model任务
"""
print("\nMasked Language Model示例:")
print(f"原始句子: {' '.join(tokens)}")
masked_tokens = tokens.copy()
for pos in mask_positions:
masked_tokens[pos] = '[MASK]'
print(f"Masked句子: {' '.join(masked_tokens)}")
print("模型需要预测: " + ", ".join([tokens[pos] for pos in mask_positions]))
def next_sentence_prediction(self, sentence1, sentence2, is_next):
"""
Next Sentence Prediction任务
"""
print("\nNext Sentence Prediction示例:")
print(f"句子1: {sentence1}")
print(f"句子2: {sentence2}")
print(f"是否连续: {'是' if is_next else '否'}")
print("模型需要判断这两句话是否在原文中连续")
# 示例
bert = BERTSimple(vocab_size=30000, hidden_size=768, num_layers=12)
# MLM示例
tokens = ['我', '喜欢', '自然', '语言', '处理']
mask_positions = [1, 4]
bert.masked_language_model(tokens, mask_positions)
# NSP示例
bert.next_sentence_prediction(
"我喜欢自然语言处理",
"它很有趣",
is_next=True
)
bert.next_sentence_prediction(
"我喜欢自然语言处理",
"今天天气很好",
is_next=False
7.2 使用Transformers库
python
# 注意:实际使用时需要安装 transformers 和 torch
# pip install transformers torch
from transformers import BertTokenizer, BertModel
import torch
# 加载预训练模型和tokenizer
print("加载BERT模型...")
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')
# 示例文本
text = "我喜欢自然语言处理"
# 分词
print(f"\n原文: {text}")
tokens = tokenizer(text, return_tensors='pt')
print(f"Token IDs: {tokens['input_ids']}")
# 解码
decoded_tokens = tokenizer.convert_ids_to_tokens(tokens['input_ids'][0])
print(f"Tokens: {decoded_tokens}")
# 获取BERT表示
with torch.no_grad():
outputs = model(**tokens)
# 获取最后一层的隐藏状态
last_hidden_states = outputs.last_hidden_state
# [CLS]标记的表示(通常用于分类任务)
cls_representation = last_hidden_states[:, 0, :]
print(f"\n隐藏状态形状: {last_hidden_states.shape}")
print(f"[CLS]表示形状: {cls_representation.shape}")
# 词级别表示
word_representations = last_hidden_states[0]
print(f"\n词表示:")
for i, (token, vec) in enumerate(zip(decoded_tokens, word_representations)):
if token not in ['[CLS]', '[SEP]']:
print(f"{token}: 维度={vec.shape}, 前5维={vec[:5].tolist()}")
7.3 文本表示方法的演进总结
文本表示演进
One-Hot
词袋模型
TF-IDF
N-gram
Word Embeddings
预训练模型
Word2Vec
GloVe
FastText
BERT
GPT
T5
LLaMA
稀疏表示
稠密表示
上下文感知
python
# 各种文本表示方法的对比
representations = [
{
'方法': 'One-Hot',
'优点': '简单、易实现',
'缺点': '维度灾难、稀疏、无语义信息',
'应用': '早期文本分类'
},
{
'方法': '词袋模型',
'优点': '考虑词频、相对简单',
'缺点': '忽略词序、语义鸿沟',
'应用': '传统文本分类'
},
{
'方法': 'TF-IDF',
'优点': '降低常见词权重',
'缺点': '仍然稀疏、无语义',
'应用': '搜索引擎、信息检索'
},
{
'方法': 'N-gram',
'优点': '捕捉局部上下文',
'缺点': '参数爆炸、长期依赖弱',
'应用': '语言模型'
},
{
'方法': 'Word2Vec',
'优点': '稠密表示、捕捉语义、高效',
'缺点': '一词多义、静态表示',
'应用': '词相似度、词类比'
},
{
'方法': 'GloVe',
'优点': '结合局部和全局信息',
'缺点': '需要计算共现矩阵',
'应用': '词嵌入'
},
{
'方法': 'FastText',
'优点': '处理OOV、子词信息',
'缺点': '计算量大',
'应用': '多语言处理'
},
{
'方法': 'BERT',
'优点': '上下文感知、双向、预训练',
'缺点': '模型大、计算慢',
'应用': '几乎所有NLP任务'
}
]
print("文本表示方法对比:")
print("-" * 100)
print(f"{'方法':<12} {'优点':<30} {'缺点':<30} {'应用'}")
print("-" * 100)
for rep in representations:
print(f"{rep['方法']:<12} {rep['优点']:<30} {rep['缺点']:<30} {rep['应用']}")
print("\n\n关键洞察:")
print("1. 从稀疏到稠密:词袋模型→词嵌入")
print("2. 从静态到动态:固定词向量→上下文感知")
print("3. 从单任务到通用:每个任务单独训练→预训练+微调")
print("4. 从浅层到深层:线性模型→深度神经网络")
八、实战:从词袋到BERT的完整流程
python
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 示例数据
texts = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"我喜欢深度学习",
"深度学习很有趣",
"人工智能很有趣",
"机器学习很有趣",
"我喜欢人工智能",
"人工智能改变世界",
"深度学习改变世界",
]
labels = [1, 1, 1, 1, 1, 1, 1, 0, 0] # 1=正面,0=负面(简化示例)
print("数据集:")
for i, (text, label) in enumerate(zip(texts, labels)):
print(f"{i+1}. {text} (标签: {'正面' if label else '负面'})")
# 方法1:词袋模型
print("\n\n方法1: 词袋模型")
bow_vectorizer = CountVectorizer()
bow_features = bow_vectorizer.fit_transform(texts)
print(f"特征矩阵形状: {bow_features.shape}")
# 训练分类器
clf_bow = LogisticRegression()
clf_bow.fit(bow_features, labels)
predictions_bow = clf_bow.predict(bow_features)
accuracy_bow = accuracy_score(labels, predictions_bow)
print(f"准确率: {accuracy_bow:.4f}")
# 方法2:TF-IDF
print("\n方法2: TF-IDF")
tfidf_vectorizer = TfidfVectorizer()
tfidf_features = tfidf_vectorizer.fit_transform(texts)
clf_tfidf = LogisticRegression()
clf_tfidf.fit(tfidf_features, labels)
predictions_tfidf = clf_tfidf.predict(tfidf_features)
accuracy_tfidf = accuracy_score(labels, predictions_tfidf)
print(f"准确率: {accuracy_tfidf:.4f}")
# 方法3:词嵌入平均(概念演示)
print("\n方法3: 词嵌入平均(概念演示)")
print("使用Word2Vec将每个词映射为向量")
print("句子向量 = 所有词向量的平均值")
print("然后在句子向量上训练分类器")
# 方法4:BERT(概念演示)
print("\n方法4: BERT(概念演示)")
print("使用预训练的BERT模型")
print("句子向量 = [CLS]标记的隐藏状态")
print("或者在BERT基础上添加分类层进行微调")
print("\n\n方法对比:")
results = [
("词袋模型", accuracy_bow),
("TF-IDF", accuracy_tfidf),
("Word2Vec", "N/A (需完整实现)"),
("BERT", "N/A (需完整实现)")
]
print("-" * 40)
for method, acc in results:
print(f"{method:<15} 准确率: {acc}")
print("\n\n实践建议:")
print("1. 小数据集:词袋模型或TF-IDF可能足够")
print("2. 中等数据集:Word2Vec/GloVe + 简单模型")
print("3. 大数据集:预训练模型(BERT等)")
print("4. 复杂任务:优先使用预训练模型")
九、总结
文本表示方法经历了从简单到复杂、从稀疏到稠密、从静态到动态的演进过程:
python
# 文本表示方法演进时间线
timeline = {
"1950s-1990s": "One-Hot, 词袋模型, TF-IDF - 稀疏表示时代",
"2000s": "N-gram语言模型 - 捕捉局部上下文",
"2003": "Word2Vec诞生 - 词嵌入革命",
"2014": "GloVe发布 - 结合全局统计信息",
"2016": "FastText - 子词信息",
"2018": "BERT发布 - 预训练语言模型时代",
"2020s": "GPT-3, T5, LLaMA - 大规模预训练模型"
}
print("文本表示方法演进时间线:")
print("=" * 60)
for year, milestone in timeline.items():
print(f"{year:<15} {milestone}")
print("\n\n未来趋势:")
print("1. 更大的模型(万亿参数)")
print("2. 更高效的训练(参数高效微调)")
print("3. 多模态融合(文本+图像+音频)")
print("4. 长文本处理(长上下文窗口)")
print("5. 低资源语言支持")
关键要点:
- 词袋模型是最基础的表示方法,简单但忽略了词序和语义
- TF-IDF通过加权改进了词袋模型,但仍然是静态稀疏表示
- Word2Vec/GloVe引入了稠密词嵌入,能够捕捉语义相似性
- FastText通过子词信息处理OOV问题
- BERT等预训练模型实现了上下文感知的动态表示,成为现代NLP的主流
选择哪种表示方法,取决于任务需求、数据规模、计算资源等多个因素。在实际应用中,预训练语言模型通常能提供最好的性能,但在资源受限或数据量小的情况下,传统方法仍然是可行的选择。