文本表示方法演进(词袋模型→Word2Vec→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. 低资源语言支持")

关键要点

  1. 词袋模型是最基础的表示方法,简单但忽略了词序和语义
  2. TF-IDF通过加权改进了词袋模型,但仍然是静态稀疏表示
  3. Word2Vec/GloVe引入了稠密词嵌入,能够捕捉语义相似性
  4. FastText通过子词信息处理OOV问题
  5. BERT等预训练模型实现了上下文感知的动态表示,成为现代NLP的主流

选择哪种表示方法,取决于任务需求、数据规模、计算资源等多个因素。在实际应用中,预训练语言模型通常能提供最好的性能,但在资源受限或数据量小的情况下,传统方法仍然是可行的选择。

相关推荐
卷福同学21 小时前
【养虾日记】Openclaw操作浏览器自动化发文
人工智能·后端·算法
春日见1 天前
如何入门端到端自动驾驶?
linux·人工智能·算法·机器学习·自动驾驶
vistaup1 天前
claude 任务完成通知
ai
光锥智能1 天前
从自动驾驶到 AI 能力体系,元戎启行 GTC 发布基座模型新进展
人工智能
luoganttcc1 天前
自动驾驶 世界模型 有哪些
人工智能·机器学习·自动驾驶
禁默1 天前
光学与机器视觉:解锁“机器之眼”的核心密码-《第五届光学与机器视觉国际学术会议(ICOMV 2026)》
人工智能·计算机视觉·光学
深小乐1 天前
不是DeepSeek V4!这两个神秘的 Hunter 模型竟然来自小米
人工智能
laozhao4321 天前
科大讯飞中标教育管理应用升级开发项目
大数据·人工智能
rainbow7242441 天前
AI人才简历评估选型:技术面试、代码评审与项目复盘的综合运用方案
人工智能·面试·职场和发展