文本表示方法演进(词袋模型→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的主流

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

相关推荐
查无此人byebye2 小时前
从零解读CLIP核心源码:PyTorch实现版逐行解析
人工智能·pytorch·python·深度学习·机器学习·自然语言处理·音视频
PKUMOD2 小时前
论文导读 | 在长上下文及复杂任务中的递归式语言模型架构
人工智能·语言模型·架构
AC赳赳老秦2 小时前
等保2.0合规实践:DeepSeek辅助企业数据分类分级与自动化报告生成
大数据·人工智能·分类·数据挖掘·自动化·数据库架构·deepseek
FansyMeng2 小时前
AI入门之anaconda安装
人工智能
小雨下雨的雨2 小时前
HarmonyOS 应用开发实战:高精图像处理与头像裁剪持久化技术深度解析
图像处理·人工智能·华为·ai·交互·harmonyos·鸿蒙系统
共享家95272 小时前
LangChain初识
人工智能·langchain
ASD123asfadxv2 小时前
SAR图像地面军事目标识别与分类:YOLO11-Seg-RFAConv实现教程
人工智能·目标跟踪·分类
Marry Andy2 小时前
Atlas 300l Duo部署qwen3_32b_light
linux·人工智能·经验分享·语言模型·自然语言处理
铁蛋AI编程实战2 小时前
Agentic AI/GPT-4o替代/Spring AI 2.0/国产大模型轻量化
java·人工智能·spring