【Python自然语言处理】实战项目:词向量表示完整实现指南

目录

[1. 项目概述](#1. 项目概述)

[2. 文本预处理模块的实现](#2. 文本预处理模块的实现)

[2.1 TextPreprocessor类的核心实现](#2.1 TextPreprocessor类的核心实现)

[2.2 词性标注与命名实体识别的实现](#2.2 词性标注与命名实体识别的实现)

[2.3 完整的预处理流程](#2.3 完整的预处理流程)

[3. 共现矩阵的实现细节](#3. 共现矩阵的实现细节)

[3.1 CooccurrenceMatrix类的设计](#3.1 CooccurrenceMatrix类的设计)

[3.2 共现矩阵的构建过程](#3.2 共现矩阵的构建过程)

[3.3 向量化和相似度计算](#3.3 向量化和相似度计算)

[3.4 矩阵的获取和操作](#3.4 矩阵的获取和操作)

[4. Word2Vec的完整实现](#4. Word2Vec的完整实现)

[4.1 Word2VecSimulator类的核心结构](#4.1 Word2VecSimulator类的核心结构)

[4.2 词汇表构建和嵌入初始化](#4.2 词汇表构建和嵌入初始化)

[4.3 负采样损失的计算](#4.3 负采样损失的计算)

[4.4 梯度更新和训练循环](#4.4 梯度更新和训练循环)

[4.5 相似度计算和词汇类比](#4.5 相似度计算和词汇类比)

[4.6 参数调优建议](#4.6 参数调优建议)

[5. 词袋模型与TF-IDF的实现](#5. 词袋模型与TF-IDF的实现)

[5.1 BagOfWordsModel的实现](#5.1 BagOfWordsModel的实现)

[5.2 TF-IDF的计算过程](#5.2 TF-IDF的计算过程)

[5.3 TF-IDF的应用示例](#5.3 TF-IDF的应用示例)

[5.4 改进和优化](#5.4 改进和优化)

[6. 情感分析的实现](#6. 情感分析的实现)

[6.1 SentimentAnalyzer的词汇库建立](#6.1 SentimentAnalyzer的词汇库建立)

[6.2 情感分析的计算过程](#6.2 情感分析的计算过程)

[6.3 批量处理和阈值调整](#6.3 批量处理和阈值调整)

[6.4 改进方向](#6.4 改进方向)

[7. 文本分类的朴素贝叶斯实现](#7. 文本分类的朴素贝叶斯实现)

[7.1 TextClassifier的初始化和词汇构建](#7.1 TextClassifier的初始化和词汇构建)

[7.2 训练过程的实现](#7.2 训练过程的实现)

[7.3 预测过程的数学原理](#7.3 预测过程的数学原理)

[7.4 拉普拉斯平滑的处理](#7.4 拉普拉斯平滑的处理)

[7.5 使用示例和效果评估](#7.5 使用示例和效果评估)

[7.6 模型改进建议](#7.6 模型改进建议)

[8. N-gram与搭配分析的实现](#8. N-gram与搭配分析的实现)

[8.1 BigramAnalyzer的实现](#8.1 BigramAnalyzer的实现)

[8.2 N-gram的获取](#8.2 N-gram的获取)

[8.3 显著搭配的识别](#8.3 显著搭配的识别)

[8.4 使用示例](#8.4 使用示例)

[8.5 改进的统计方法](#8.5 改进的统计方法)

[8.6 N-gram在语言模型中的应用](#8.6 N-gram在语言模型中的应用)

[9. 分布假说与上下文分析的实现](#9. 分布假说与上下文分析的实现)

[9.1 DistributionalSemantics的核心功能](#9.1 DistributionalSemantics的核心功能)

[9.2 上下文窗口的提取](#9.2 上下文窗口的提取)

[9.3 分布分析](#9.3 分布分析)

[9.4 应用示例](#9.4 应用示例)

[9.5 相似词的发现](#9.5 相似词的发现)

[9.6 共现模式的可视化](#9.6 共现模式的可视化)

[10. Transformer自注意力机制的实现](#10. Transformer自注意力机制的实现)

[10.1 TransformerSimulator的初始化和嵌入](#10.1 TransformerSimulator的初始化和嵌入)

[10.2 位置编码的实现](#10.2 位置编码的实现)

[10.3 自注意力机制的核心实现](#10.3 自注意力机制的核心实现)

[10.4 Softmax函数的数值稳定实现](#10.4 Softmax函数的数值稳定实现)

[10.5 完整的前向传播](#10.5 完整的前向传播)

[10.6 使用示例和输出解释](#10.6 使用示例和输出解释)

[10.7 多头注意力的优势](#10.7 多头注意力的优势)

[10.8 与BERT的联系](#10.8 与BERT的联系)

总结与实践指南

实现总结

快速选择指南

性能优化建议

生产环境部署建议

进阶扩展方向

与深度学习框架的集成


1. 项目概述

本文通过详细的代码实现展示如何从零开始构建NLP词向量系统。项目包含了文本预处理、共现矩阵、Word2Vec、词袋模型、情感分析、文本分类、搭配分析、Transformer等多个完整的实现模块。所有代码均独立实现,不依赖高级的NLTK预训练数据,便于快速运行和学习修改。

整个项目结构清晰,每个模块相对独立,既可以单独使用,也可以组合使用。通过本项目,开发者可以深入理解各种NLP技术的具体实现细节,掌握如何在实际项目中应用这些技术。

2. 文本预处理模块的实现

2.1 TextPreprocessor类的核心实现

TextPreprocessor类包含了文本处理的全套功能。首先初始化时内置了一个包含常用英文停用词的集合,共约45个词汇。这样做的好处是不需要下载NLTK数据就能直接使用。clean_text方法使用两步处理:先将文本转为小写,再使用正则表达式r'[^a-zA-Z0-9\s\u4e00-\u9fa5]'删除所有特殊字符,同时保留英文、数字和中文字符。这个正则表达式的关键是\u4e00-\u9fa5范围对应汉字的Unicode编码。

tokenize方法采用正则表达式\b\w+\b来分词。这个表达式的含义是:\b匹配词边界,\w+匹配一个或多个单词字符(字母、数字、下划线)。相比简单的split()方法,这种方式能更好地处理标点符号附近的边界情况。对于简单的英文文本,这种实现已经够用,但如果要处理中文,需要更换为更复杂的分词库如jieba。

remove_stopwords方法通过列表推导式过滤停用词,同时设置len(token) > 1的条件来排除单个字符的词汇。这是一个小技巧,能够自动过滤掉许多无意义的单字符符号。

复制代码
def clean_text(self, text: str) -> str:
    """清理文本:转小写、移除特殊字符"""
    text = text.lower()
    text = re.sub(r'[^a-zA-Z0-9\s\u4e00-\u9fa5]', '', text)
    return text.strip()

def tokenize(self, text: str) -> List[str]:
    """简单分词(基于空格和符号)"""
    tokens = re.findall(r'\b\w+\b', text.lower())
    return tokens

2.2 词性标注与命名实体识别的实现

pos_tagging方法实现了一个简单的启发式词性标注器。它根据词汇的特征进行判断:检查是否在特定的词典中(如"the"判断为冠词DT),检查后缀(如"ing"结尾判断为动名词VBG,"ed"结尾判断为过去分词VBD),检查首字母大小写(大写判断为专有名词NNP,其他判断为普通名词NN)。虽然这种方法准确率有限,但对于演示和快速原型开发来说足够用。

复制代码
def pos_tagging(self, tokens: List[str]) -> List[Tuple[str, str]]:
    """简化词性标注(演示用)"""
    simple_tags = []
    for token in tokens:
        if token in ['the', 'a', 'an']:
            tag = 'DT'  # Determiner
        elif token.endswith('ing'):
            tag = 'VBG'  # Verb, gerund
        elif token.endswith('ed'):
            tag = 'VBD'  # Verb, past tense
        elif len(token) > 0 and token[0].isupper():
            tag = 'NNP'  # Proper noun
        else:
            tag = 'NN'  # Noun
        simple_tags.append((token, tag))
    return simple_tags

命名实体识别通过检查单词首字母大小写来识别可能的人名或组织名。这是一个非常初级的方法,实际应用中应该使用序列标注模型。

2.3 完整的预处理流程

preprocess方法将清理、分词和停用词过滤整合在一起,形成一个完整的处理流程。调用者可以通过remove_stop参数控制是否进行停用词过滤。实际使用时这样调用:

复制代码
preprocessor = TextPreprocessor()
tokens = preprocessor.preprocess("Natural language processing is amazing!", remove_stop=True)
# 返回: ['natural', 'language', 'processing', 'amazing']

在实际项目中,可以扩展这个类添加更多功能,比如词根化、词条化(lemmatization)等。对于中文项目,只需要替换tokenize方法为使用jieba库的实现即可。

3. 共现矩阵的实现细节

3.1 CooccurrenceMatrix类的设计

CooccurrenceMatrix类使用defaultdict来存储共现关系。关键数据结构是self.cooccurrence,这是一个二层嵌套的defaultdict,第一层的键是目标词汇,第二层的键是上下文词汇,值是共现次数。这样的设计使得查询特定词对的共现次数非常高效,时间复杂度为O(1)。同时维护了word2idxidx2word两个字典来实现词汇和索引的相互转换。

build_vocab方法首先从所有词元中提取唯一的词汇。使用sorted()确保每次生成的索引都是确定的,这对于实验的可重复性很重要。

复制代码
def build_vocab(self, tokens: List[str]):
    """构建词汇表"""
    self.vocab = set(tokens)
    self.word2idx = {word: idx for idx, word in enumerate(sorted(self.vocab))}
    self.idx2word = {idx: word for word, idx in self.word2idx.items()}

3.2 共现矩阵的构建过程

build_cooccurrence_matrix方法是核心实现。对于每个位置i的目标词汇,定义一个对称的窗口:从max(0, i - window_size)min(len(tokens), i + window_size + 1)。注意这里min函数的使用是为了防止索引越界。然后遍历窗口内的所有位置j,如果j != i,就将该上下文词汇计数加1。

复制代码
def build_cooccurrence_matrix(self, tokens: List[str]):
    """构建词汇-词汇共现矩阵"""
    self.build_vocab(tokens)
    for i, target_word in enumerate(tokens):
        # 定义上下文窗口
        start = max(0, i - self.window_size)
        end = min(len(tokens), i + self.window_size + 1)

        for j in range(start, end):
            if i != j:
                context_word = tokens[j]
                self.cooccurrence[target_word][context_word] += 1

这个实现的时间复杂度是O(n*w),其中n是词元总数,w是窗口大小。对于百万级别的词元,处理速度仍然很快。

3.3 向量化和相似度计算

get_cooccurrence_vector方法将某个词汇的共现信息转换为向量形式。向量的长度等于词汇表大小,其中第i个位置的值表示该词汇与词汇表中第i个词汇的共现次数。这个向量是高度稀疏的,大多数元素都是0。

余弦相似度的计算在compute_similarity方法中实现。首先计算两个向量的模长(L2范数),然后计算它们的点积,最后将点积除以两个模长的乘积。这个计算过程中要注意零向量的处理,防止除以零的错误。

复制代码
def compute_similarity(self, word1: str, word2: str) -> float:
    """计算两个词之间的余弦相似度"""
    vec1 = self.get_cooccurrence_vector(word1)
    vec2 = self.get_cooccurrence_vector(word2)

    norm1 = np.linalg.norm(vec1)
    norm2 = np.linalg.norm(vec2)

    if norm1 == 0 or norm2 == 0:
        return 0.0

    return np.dot(vec1, vec2) / (norm1 * norm2)

3.4 矩阵的获取和操作

get_matrix方法返回完整的共现矩阵,以numpy数组的形式。矩阵的维度是(词汇表大小, 词汇表大小)。这个矩阵通常非常稀疏,如果词汇表有10000个词,矩阵就有1亿个元素。在实际应用中,可以使用scipy.sparse库来存储稀疏矩阵以节省内存。

使用示例:

复制代码
cooccurrence = CooccurrenceMatrix(window_size=2)
cooccurrence.build_cooccurrence_matrix(tokens)
similarity = cooccurrence.compute_similarity('word1', 'word2')
matrix = cooccurrence.get_matrix()  # 返回(vocab_size, vocab_size)的密集矩阵

对于大规模应用,应该改进为:

复制代码
from scipy.sparse import csr_matrix
# 在build_cooccurrence_matrix后,使用csr_matrix将共现关系转为稀疏矩阵存储

4. Word2Vec的完整实现

4.1 Word2VecSimulator类的核心结构

Word2VecSimulator类实现了Skip-gram模型加负采样的训练。初始化时需要指定四个关键参数:embedding_dim是词向量的维度(默认100),window_size是上下文窗口大小(默认2),learning_rate是学习率(默认0.01),negative_samples是负采样数量(默认5)。

维护两个主要的嵌入矩阵:word_vectors是目标词的嵌入,context_vectors是上下文词的嵌入。虽然在理论上这两个嵌入应该相同,但在实现中分离它们能够加快计算。同时记录词频word_freq用于后续的负采样。

复制代码
def __init__(self, embedding_dim: int = 100, window_size: int = 2,
             learning_rate: float = 0.01, negative_samples: int = 5):
    self.embedding_dim = embedding_dim
    self.window_size = window_size
    self.learning_rate = learning_rate
    self.negative_samples = negative_samples
    self.vocab = {}
    self.word_vectors = None
    self.context_vectors = None

4.2 词汇表构建和嵌入初始化

build_vocab方法只保留出现频率至少为min_count次的词汇。这是处理长尾词汇的常见做法,可以显著减少词汇表大小和内存占用。例如,设置min_count=2会排除只出现一次的词汇。

initialize_embeddings方法使用小方差的高斯分布初始化向量(方差为0.01),这是一个经验上有效的初始化方式。向量接近零但不完全为零,这样梯度更新能够立即产生显著的变化。

复制代码
def build_vocab(self, tokens: List[str], min_count: int = 1):
    """构建词汇表,过滤低频词"""
    freq = Counter(tokens)
    self.vocab = {word: idx for idx, (word, count) in enumerate(
        [(w, c) for w, c in freq.items() if c >= min_count]
    )}
    self.word2idx = self.vocab
    self.idx2word = {idx: word for word, idx in self.vocab.items()}
    self.word_freq = {word: freq[word] for word in self.vocab}

def initialize_embeddings(self):
    """初始化词向量和上下文向量"""
    vocab_size = len(self.vocab)
    self.word_vectors = np.random.randn(vocab_size, self.embedding_dim) * 0.01
    self.context_vectors = np.random.randn(vocab_size, self.embedding_dim) * 0.01

4.3 负采样损失的计算

负采样是Word2Vec的关键创新,它避免了对整个词汇表的计算。negative_sampling_loss方法计算一个正样本对(目标词,上下文词)和多个负样本对的损失。

对于正样本,计算目标词向量和上下文词向量的点积,通过sigmoid函数映射,然后取负对数作为损失。目标是最小化这个损失,即最大化正样本对的相似度。对于每个负样本,随机抽取一个词汇,计算其与目标词的点积,然后取负的sigmoid作为损失目标是最小化负样本的相似度。

复制代码
def negative_sampling_loss(self, target_idx: int, context_idx: int):
    """负采样损失计算"""
    target_vec = self.word_vectors[target_idx]
    context_vec = self.context_vectors[context_idx]

    # 正样本:最大化target和context的相似度
    pos_score = np.dot(target_vec, context_vec)
    pos_loss = -np.log(self.sigmoid(pos_score) + 1e-10)

    # 负采样
    neg_loss = 0
    for _ in range(self.negative_samples):
        neg_idx = np.random.randint(0, len(self.vocab))
        neg_vec = self.context_vectors[neg_idx]
        neg_score = np.dot(target_vec, neg_vec)
        neg_loss += -np.log(self.sigmoid(-neg_score) + 1e-10)

    return pos_loss + neg_loss

注意这里加上1e-10是为了防止log(0)的数值不稳定问题。

4.4 梯度更新和训练循环

train方法的梯度更新部分,计算sigmoid函数的导数,然后按照梯度方向更新向量。这是一个简化的梯度下降实现,忽略了高阶优化器如Adam的细节。

复制代码
# 计算误差
score = np.dot(target_vec, context_vec)
error = (self.sigmoid(score) - 1.0)

# 计算梯度
grad_target = error * context_vec
grad_context = error * target_vec

# 参数更新
self.word_vectors[target_idx] -= self.learning_rate * grad_target
self.context_vectors[context_idx] -= self.learning_rate * grad_context

完整的训练过程遍历语料库多个epoch。每个epoch中,对于每个目标词,定义其上下文窗口,对窗口中的每个上下文词计算损失并更新参数。训练过程会打印每个epoch的平均损失,用来监视模型的收敛情况。

4.5 相似度计算和词汇类比

训练完成后,可以计算任意两个词的相似度:

复制代码
sim = w2v.similarity('king', 'queen')  # 返回0到1之间的值

most_similar方法找出与给定词最相似的K个词。它计算给定词与词汇表中所有词的相似度,然后按相似度排序返回前K个。

最有趣的是analogy方法,实现了词向量的线性关系。给定三个词a, b, c,查找词d使得a:b = c:d。实现方式是计算类比向量b - a + c,然后找最相似的词。这需要对向量进行L2规范化以获得更好的结果。

复制代码
def analogy(self, word_a: str, word_b: str, word_c: str, topn: int = 5):
    """词汇类比: word_a : word_b = word_c : ?"""
    vec_a = self.get_vector(word_a)
    vec_b = self.get_vector(word_b)
    vec_c = self.get_vector(word_c)

    # 计算类比向量
    analogy_vec = vec_b - vec_a + vec_c
    norm = np.linalg.norm(analogy_vec)
    if norm > 0:
        analogy_vec = analogy_vec / norm
    # ... 继续计算相似度并排序

4.6 参数调优建议

在实际应用中,应该根据语料库大小调整参数:

  • 小语料库(<100K词元):embedding_dim=50, window_size=2, negative_samples=5
  • 中等语料库(100K-1M词元):embedding_dim=100, window_size=5, negative_samples=10
  • 大语料库(>1M词元):embedding_dim=300, window_size=10, negative_samples=25

学习率通常在0.01到0.1之间,可以根据损失函数的下降速度调整。

5. 词袋模型与TF-IDF的实现

5.1 BagOfWordsModel的实现

BagOfWordsModel类提供了两个核心功能:词袋向量转换和TF-IDF加权。build_vocab方法从多个文档中提取所有唯一词汇,建立词汇到索引的映射。这里使用sorted(words)确保词汇表的一致性和可重复性。

document_to_bow方法将分词后的文档转换为向量。向量长度等于词汇表大小,每个位置的值是对应词汇在文档中出现的次数。使用Counter可以快速统计词频,但这里使用列表推导式来避免额外的依赖。

复制代码
def document_to_bow(self, tokens: List[str]) -> np.ndarray:
    """将文档转换为词袋向量"""
    vector = np.zeros(len(self.vocab))
    for token in tokens:
        if token in self.word2idx:
            vector[self.word2idx[token]] += 1
    return vector

这个实现的优点是简单高效,缺点是生成的向量很稀疏(大多数位置是0)。对于大规模应用,应该使用scipy.sparse库。

5.2 TF-IDF的计算过程

TF-IDF由两部分组成。TF(Term Frequency)是词汇频率,计算为词汇出现次数除以文档长度。IDF(Inverse Document Frequency)是逆文档频率,计算为log(总文档数 / 包含该词汇的文档数)

tf_idf方法首先计算每个词汇的IDF值。注意这里使用了max(count, 1)来防止除以零。然后对每个文档,计算其TF向量,与IDF向量相乘得到TF-IDF向量。

复制代码
def tf_idf(self, documents: List[List[str]]) -> np.ndarray:
    """计算TF-IDF矩阵"""
    self.build_vocab(documents)
    matrix = []
    
    # 计算IDF
    doc_count = len(documents)
    idf = {}
    for word in self.vocab:
        count = sum(1 for doc in documents if word in doc)
        idf[word] = np.log(doc_count / max(count, 1))

    # 计算TF-IDF
    for doc in documents:
        vector = np.zeros(len(self.vocab))
        word_counts = Counter(doc)
        total_words = len(doc)

        for word, count in word_counts.items():
            if word in self.word2idx:
                tf = count / total_words if total_words > 0 else 0
                vector[self.word2idx[word]] = tf * idf[word]

        matrix.append(vector)

    return np.array(matrix)

返回的矩阵维度是(文档数, 词汇表大小),每个元素都是TF-IDF值。

5.3 TF-IDF的应用示例

复制代码
documents = [
    ["machine", "learning", "is", "great"],
    ["deep", "learning", "is", "powerful"],
    ["natural", "language", "processing", "is", "hard"]
]

bow = BagOfWordsModel()
tfidf_matrix = bow.tf_idf(documents)

# tfidf_matrix[0, :] 是第一个文档的TF-IDF向量
# tfidf_matrix.shape 是 (3, 词汇表大小)

在信息检索中,通常需要计算查询与文档的相似度。可以将查询也转换为TF-IDF向量,然后计算余弦相似度:

复制代码
query = ["learning", "is", "great"]
query_tfidf = bow.tf_idf([query])[0]  # 得到查询的TF-IDF向量

# 计算与所有文档的余弦相似度
similarities = []
for i in range(tfidf_matrix.shape[0]):
    doc_vec = tfidf_matrix[i]
    similarity = np.dot(query_tfidf, doc_vec) / (
        np.linalg.norm(query_tfidf) * np.linalg.norm(doc_vec) + 1e-10
    )
    similarities.append(similarity)

# 获取最相似的文档
best_doc_idx = np.argmax(similarities)

5.4 改进和优化

在实际应用中,可以对TF-IDF进行多种改进:

  1. 添加平滑项,防止IDF为0:

    idf[word] = np.log((doc_count + 1) / (count + 1))

  2. 使用L2规范化,使向量长度为1:

    norm = np.linalg.norm(vector)
    if norm > 0:
    vector = vector / norm

  3. 对于超高维稀疏矩阵,使用scipy.sparse库:

    from scipy.sparse import csr_matrix

    将矩阵转换为压缩行稀疏格式

    sparse_matrix = csr_matrix(matrix)

  4. 使用sklearn的实现(用于对比参考):

    from sklearn.feature_extraction.text import TfidfVectorizer
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(documents)

6. 情感分析的实现

6.1 SentimentAnalyzer的词汇库建立

SentimentAnalyzer类基于词汇的方法实现情感分析。初始化时定义了正面词汇集合和负面词汇集合,各包含约20个常见的情感词汇。这些词汇是手工选择的,代表了最常见的表达积极和消极态度的词汇。

复制代码
self.positive_words = {
    'love', 'amazing', 'wonderful', 'fantastic', 'excellent', 'great',
    'good', 'beautiful', 'perfect', 'awesome', 'best', 'brilliant',
    'outstanding', 'superb', 'marvelous', 'splendid', 'magnificent',
    'delightful', 'joy', 'happy', 'pleased', 'glad', 'wonderful'
}
self.negative_words = {
    'hate', 'terrible', 'awful', 'horrible', 'bad', 'worst',
    'disgusting', 'awful', 'poor', 'worse', 'disappointed',
    'dislike', 'sad', 'angry', 'upset', 'terrible', 'dreadful',
    'pathetic', 'abysmal', 'atrocious', 'deplorable', 'awful'
}

在实际应用中,可以从更大的词汇库中获取这些词汇集合,比如使用MPQA情感词库或其他公开的情感词汇资源。

6.2 情感分析的计算过程

analyze方法执行情感分析的核心逻辑。首先将文本转为小写并按空格分词。然后统计正面词汇和负面词汇的出现次数。

复合分数(Compound Score)的计算方式是:(正面词数 - 负面词数) / (正面词数 + 负面词数)。这个指标的范围从-1到1,其中1表示最积极,-1表示最消极,0表示中立。

同时计算了三个比例指标:正向比例 = 正面词数 / 总词数,负向比例 = 负面词数 / 总词数,中性比例 = 其他词数 / 总词数。

复制代码
def analyze(self, text: str) -> Dict:
    """分析文本情感"""
    tokens = text.lower().split()

    positive_count = sum(1 for token in tokens if token in self.positive_words)
    negative_count = sum(1 for token in tokens if token in self.negative_words)

    # 计算情感分数
    if positive_count + negative_count == 0:
        compound = 0
    else:
        compound = (positive_count - negative_count) / (positive_count + negative_count)

    # 确定情感极性
    if compound > 0.1:
        sentiment = 'positive'
    elif compound < -0.1:
        sentiment = 'negative'
    else:
        sentiment = 'neutral'

    total_words = len(tokens)
    return {
        'text': text,
        'sentiment': sentiment,
        'compound': compound,
        'positive': positive_count / total_words if total_words > 0 else 0,
        'negative': negative_count / total_words if total_words > 0 else 0,
        'neutral': 1 - (positive_count + negative_count) / total_words if total_words > 0 else 1
    }

6.3 批量处理和阈值调整

analyze_batch方法简化了对多个文本的处理。通过列表推导式,可以一次性分析多个文本:

复制代码
def analyze_batch(self, texts: List[str]) -> List[Dict]:
    """批量分析文本情感"""
    return [self.analyze(text) for text in texts]

# 使用示例
analyzer = SentimentAnalyzer()
texts = [
    "I love this amazing product!",
    "This is terrible and I hate it",
    "The product is okay"
]
results = analyzer.analyze_batch(texts)

情感极性的判断使用了阈值:compound > 0.1为正面,< -0.1为负面,介于两者之间为中立。这个阈值可以根据实际需求调整。较大的阈值(如0.5)会导致更多的文本被分类为中立,较小的阈值(如0.05)会导致更多的强势判断。

6.4 改进方向

基础的词汇方法有几个明显的限制,可以通过以下方式改进:

  1. 考虑否定词:处理"not good"应该识别为负面

    def analyze_improved(self, text: str) -> Dict:
    tokens = text.lower().split()
    negation_words = {'not', 'no', 'neither', 'nobody', 'nothing'}

    复制代码
     # 如果前一个词是否定词,翻转当前词的极性
     adjusted_positive = 0
     adjusted_negative = 0
     
     for i, token in enumerate(tokens):
         prev_token = tokens[i-1] if i > 0 else ""
         is_negated = prev_token in negation_words
         
         if token in self.positive_words:
             adjusted_negative += 1 if is_negated else -1
             adjusted_positive += 0 if is_negated else 1
         elif token in self.negative_words:
             adjusted_positive += 1 if is_negated else -1
             adjusted_negative += 0 if is_negated else 1
  2. 使用强度权重,不同词汇赋予不同权重

    positive_weights = {
    'good': 1.0,
    'amazing': 2.0,
    'excellent': 2.5,
    'perfect': 3.0
    }

  3. 添加情感词汇的位置权重,靠近句子末尾的词汇权重更高

  4. 集成多种情感词汇库来提高覆盖率

  5. 对于更复杂的情感分析,使用深度学习模型如LSTM或BERT

7. 文本分类的朴素贝叶斯实现

7.1 TextClassifier的初始化和词汇构建

TextClassifier类实现了朴素贝叶斯文本分类器。初始化时可以指定分类器类型(当前只实现了naive bayes,但可扩展为其他类型)。维护以下关键数据结构:

  • class_word_freq:嵌套字典,第一层键是类别,第二层键是词汇,值是该词汇在该类别中出现的次数
  • class_doc_count:每个类别的文档数
  • classes:所有类别的集合

build_vocab方法从训练文档中构建词汇表,使用sorted确保一致性:

复制代码
def build_vocab(self, documents: List[List[str]]):
    """构建词汇表"""
    words = set()
    for doc in documents:
        words.update(doc)

    self.vocab = {word: idx for idx, word in enumerate(sorted(words))}
    self.word2idx = self.vocab
    self.idx2word = {idx: word for word, idx in self.vocab.items()}

7.2 训练过程的实现

train方法统计每个类别中各个词汇的频率。核心逻辑很直接:对于每个(文档, 标签)对,更新对应类别的文档数,并对文档中的每个词汇增加其在该类别中的计数。

复制代码
def train(self, documents: List[List[str]], labels: List[str]):
    """训练分类器"""
    self.build_vocab(documents)
    self.classes = set(labels)

    # 统计每个类别中每个词的频率
    for doc, label in zip(documents, labels):
        self.class_doc_count[label] += 1
        for word in doc:
            if word in self.word2idx:
                self.class_word_freq[label][word] += 1

    print(f"分类器已训练: 类别数={len(self.classes)}, 词汇表大小={len(self.vocab)}")

7.3 预测过程的数学原理

预测时需要计算给定文档时每个类别的后验概率。根据贝叶斯定理:

P(类别|文档) ∝ P(文档|类别) * P(类别)

假设在给定类别下,文档中的每个词汇是独立的(朴素贝叶斯假设),则:

P(文档|类别) = ∏ P(词汇i|类别)

为了避免数值下溢,使用对数:

log P(类别|文档) = log P(类别) + Σ log P(词汇i|类别)

复制代码
def predict(self, tokens: List[str]) -> Tuple[str, Dict[str, float]]:
    """预测文本类别"""
    scores = {}

    for label in self.classes:
        # 计算对数概率
        score = np.log(self.class_doc_count[label] / sum(self.class_doc_count.values()))

        for word in tokens:
            if word in self.word2idx:
                word_count = self.class_word_freq[label].get(word, 0)
                total_words = sum(self.class_word_freq[label].values())

                # 拉普拉斯平滑
                prob = (word_count + 1) / (total_words + len(self.vocab))
                score += np.log(prob)

        scores[label] = score

    # 返回概率最高的类别
    best_label = max(scores, key=scores.get)

    # 转换为概率
    exp_scores = {k: np.exp(v) for k, v in scores.items()}
    total = sum(exp_scores.values())
    probabilities = {k: v / total for k, v in exp_scores.items()}

    return best_label, probabilities

7.4 拉普拉斯平滑的处理

拉普拉斯平滑是朴素贝叶斯分类中的一个关键技巧。如果某个词汇在某个类别中从未出现过,直接计算的概率会是0,导致该类别的整体概率变为0(因为要做乘积)。为了避免这个问题,对分子加1,对分母加上词汇表大小:

复制代码
prob = (word_count + 1) / (total_words + len(self.vocab))

这样,即使某个词汇未出现过,其概率也是一个很小的非零值1 / (total_words + vocab_size)

7.5 使用示例和效果评估

复制代码
# 训练数据
train_texts = [
    "excellent amazing wonderful fantastic",
    "terrible awful horrible bad",
    "good nice great beautiful",
    "worst terrible awful disgusting",
    "love adore excellent amazing",
    "hate dislike terrible awful"
]
train_labels = ['positive', 'negative', 'positive', 'negative', 'positive', 'negative']

# 创建并训练分类器
train_docs = [text.split() for text in train_texts]
classifier = TextClassifier()
classifier.train(train_docs, train_labels)

# 预测
test_text = "this product is excellent and amazing"
test_tokens = test_text.split()
predicted_label, probabilities = classifier.predict(test_tokens)

print(f"预测类别: {predicted_label}")
print(f"类别概率: {probabilities}")
# 输出: 预测类别: positive
#      类别概率: {'positive': 0.95, 'negative': 0.05}

7.6 模型改进建议

  1. 处理未知词汇:在预测时遇到训练中未见过的词汇,可以通过拉普拉斯平滑自动处理

  2. 使用更多特征:可以使用N-gram(如二元组)而不是单个词汇,以捕捉词序信息:

    def extract_features(self, tokens: List[str]):
    unigrams = tokens
    bigrams = [tokens[i:i+2] for i in range(len(tokens)-1)]
    return unigrams + ['_'.join(b) for b in bigrams]

  3. 处理类别不平衡:如果训练数据中各类别数量差异很大,可以对先验概率进行调整

  4. 特征选择:使用信息增益或卡方检验来选择最有区分力的词汇,降低特征维度

  5. 从朴素贝叶斯升级:对于更复杂的分类任务,可以使用SVM、随机森林、或深度学习模型如CNN/LSTM

8. N-gram与搭配分析的实现

8.1 BigramAnalyzer的实现

BigramAnalyzer类提供了N-gram的提取和分析功能。核心方法是_get_ngrams,这是一个静态方法,接受词元列表和N值,返回所有N元组。实现使用了列表切片的优雅方式:

复制代码
@staticmethod
def _get_ngrams(tokens: List[str], n: int) -> List[Tuple]:
    """获取n-grams"""
    return [tuple(tokens[i:i + n]) for i in range(len(tokens) - n + 1)]

这个实现的工作原理是:对于长度为L的词元列表,可以提取L-n+1个n-grams。例如,对于["a", "b", "c", "d"]:

  • n=2(二元组):["a", "b"], ["b", "c"], ["c", "d"],共2个
  • n=3(三元组):["a", "b", "c"], ["b", "c", "d"],共1个

8.2 N-gram的获取

find_bigrams方法包装了_get_ngrams,专门用于提取二元组。通过指定window_size参数,可以灵活地定义"二元组"的范围(虽然通常就是相邻的两个词):

复制代码
def find_bigrams(self, tokens: List[str], window_size: int = 2) -> List[Tuple[str, str]]:
    """查找二元组"""
    bigrams = self._get_ngrams(tokens, 2)
    return bigrams

类似地,find_trigrams用于提取三元组:

复制代码
def find_trigrams(self, tokens: List[str]) -> List[Tuple[str, str, str]]:
    """查找三元组"""
    trigrams = self._get_ngrams(tokens, 3)
    return trigrams

8.3 显著搭配的识别

find_significant_bigrams方法识别频率最高的二元组。它计算所有二元组的频率,然后返回最常见的topn个:

复制代码
def find_significant_bigrams(self, tokens: List[str], topn: int = 10) -> List[Tuple[Tuple[str, str], int]]:
    """查找显著的二元组(基于频率)"""
    bigrams = self.find_bigrams(tokens)
    freq = Counter(bigrams)
    return freq.most_common(topn)

8.4 使用示例

复制代码
from collections import Counter

text = "machine learning is great machine learning is powerful deep learning is amazing"
tokens = text.split()

analyzer = BigramAnalyzer()

# 获取所有二元组
bigrams = analyzer.find_bigrams(tokens)
# 返回: [('machine', 'learning'), ('learning', 'is'), ('is', 'great'), 
#        ('great', 'machine'), ('machine', 'learning'), ...]

# 获取最常见的二元组
top_bigrams = analyzer.find_significant_bigrams(tokens, topn=5)
# 返回: [(('machine', 'learning'), 2), (('learning', 'is'), 2), 
#        (('is', 'great'), 1), ...]

# 获取所有三元组
trigrams = analyzer.find_trigrams(tokens)
# 返回: [('machine', 'learning', 'is'), ('learning', 'is', 'great'), ...]

8.5 改进的统计方法

在实际应用中,仅靠频率来识别显著搭配是不够的。应该使用更精细的统计度量。例如,互信息(Mutual Information)能够衡量两个词的关联强度:

复制代码
import math

def mutual_information(bigram_freq, unigram_freq, total_words):
    """计算二元组的互信息"""
    w1, w2 = bigram_freq[0], bigram_freq[1]
    N = total_words
    
    # P(w1, w2)
    p_w1w2 = bigram_freq / N
    # P(w1) * P(w2)
    p_w1 = unigram_freq[w1] / N
    p_w2 = unigram_freq[w2] / N
    
    if p_w1 > 0 and p_w2 > 0 and p_w1w2 > 0:
        mi = math.log(p_w1w2 / (p_w1 * p_w2))
        return mi
    return 0

另一个有用的度量是卡方统计(Chi-Square),它检验观察频率与期望频率的偏离程度:

复制代码
def chi_square(bigram_count, w1_count, w2_count, total):
    """计算卡方统计"""
    # 期望频率
    expected = (w1_count * w2_count) / total
    # 卡方值
    chi2 = (bigram_count - expected) ** 2 / expected if expected > 0 else 0
    return chi2

8.6 N-gram在语言模型中的应用

N-gram模型可以用于计算下一个词的概率:

复制代码
class NGramLanguageModel:
    def __init__(self, n=2):
        self.n = n
        self.ngram_freq = Counter()
        self.context_freq = Counter()
    
    def train(self, tokens):
        ngrams = [tuple(tokens[i:i+self.n]) for i in range(len(tokens)-self.n+1)]
        for ngram in ngrams:
            self.ngram_freq[ngram] += 1
            context = ngram[:-1]
            self.context_freq[context] += 1
    
    def predict_next(self, context, topn=5):
        """预测context后最可能的词"""
        candidates = []
        for (ctx, word), count in self.ngram_freq.items():
            if ctx == context:
                prob = count / self.context_freq[context]
                candidates.append((word, prob))
        
        candidates.sort(key=lambda x: x[1], reverse=True)
        return candidates[:topn]

# 使用
lm = NGramLanguageModel(n=2)
lm.train(tokens)
next_words = lm.predict_next(('machine', 'learning'))

这个简单的N-gram模型可以应用于文本补全、拼写检查等任务。

9. 分布假说与上下文分析的实现

9.1 DistributionalSemantics的核心功能

DistributionalSemantics类实现了对词汇分布模式的分析。核心思想是通过研究词汇出现的上下文来理解其含义。初始化时维护两个主要属性:context_windows存储所有的上下文窗口,vocab存储所有出现过的词汇。

9.2 上下文窗口的提取

extract_context_windows方法是最重要的功能,它为每个词汇提取其周围的上下文。对于每个位置i的目标词汇,定义一个对称的窗口,范围从max(0, i - window_size)min(len(tokens), i + window_size + 1)

复制代码
def extract_context_windows(self, tokens: List[str], window_size: int = 2) -> List[Dict]:
    """提取上下文窗口"""
    windows = []

    for i, target_word in enumerate(tokens):
        start = max(0, i - window_size)
        end = min(len(tokens), i + window_size + 1)

        context = {
            'target': target_word,
            'context': [tokens[j] for j in range(start, end) if j != i],
            'position': i
        }
        windows.append(context)
        self.vocab.add(target_word)

    self.context_windows = windows
    return windows

每个上下文窗口包含三个字段:

  • target:目标词汇
  • context:目标词汇周围的词汇列表
  • position:目标词汇在文本中的位置

9.3 分布分析

analyze_distribution方法深入分析特定词汇的分布模式。它做以下工作:

  1. 找出该词汇在文本中的所有位置

  2. 统计该词汇周围上下文词汇的分布

  3. 返回一个包含频率、位置和上下文分布的字典

    def analyze_distribution(self, tokens: List[str], word: str) -> Dict:
    """分析词汇的分布"""
    positions = [i for i, t in enumerate(tokens) if t == word]

    复制代码
     if not positions:
         return {'word': word, 'frequency': 0, 'distribution': []}
    
     context_freq = defaultdict(int)
     for pos in positions:
         start = max(0, pos - 2)
         end = min(len(tokens), pos + 3)
         for j in range(start, end):
             if j != pos:
                 context_freq[tokens[j]] += 1
    
     return {
         'word': word,
         'frequency': len(positions),
         'positions': positions,
         'context_distribution': dict(context_freq)
     }

9.4 应用示例

复制代码
corpus = "machine learning is powerful learning algorithms need data good data helps learning"
tokens = corpus.split()

dist_semantics = DistributionalSemantics()
windows = dist_semantics.extract_context_windows(tokens, window_size=2)

# 查看'learning'的分布
analysis = dist_semantics.analyze_distribution(tokens, 'learning')

print(f"词汇: {analysis['word']}")
print(f"频率: {analysis['frequency']}")
print(f"位置: {analysis['positions']}")
print(f"主要上下文词: {sorted(analysis['context_distribution'].items(), 
                                key=lambda x: x[1], reverse=True)[:5]}")

# 输出示例:
# 词汇: learning
# 频率: 3
# 位置: [1, 5, 11]
# 主要上下文词: [('machine', 1), ('algorithms', 1), ('is', 1), ('need', 1), ('good', 1)]

9.5 相似词的发现

利用分布信息,可以自动发现相似的词汇。两个词汇如果有相似的上下文分布,那么它们很可能有相似的含义:

复制代码
def find_similar_words(self, tokens: List[str], target_word: str, topn: int = 5):
    """找出与target_word分布相似的词汇"""
    target_dist = self.analyze_distribution(tokens, target_word)
    target_context = target_dist['context_distribution']
    
    word_similarities = []
    
    for word in set(tokens):
        if word == target_word:
            continue
        
        word_dist = self.analyze_distribution(tokens, word)
        word_context = word_dist['context_distribution']
        
        # 计算两个分布的余弦相似度
        all_words = set(target_context.keys()) | set(word_context.keys())
        
        vec1 = np.array([target_context.get(w, 0) for w in all_words])
        vec2 = np.array([word_context.get(w, 0) for w in all_words])
        
        norm1 = np.linalg.norm(vec1)
        norm2 = np.linalg.norm(vec2)
        
        if norm1 > 0 and norm2 > 0:
            similarity = np.dot(vec1, vec2) / (norm1 * norm2)
            word_similarities.append((word, similarity))
    
    word_similarities.sort(key=lambda x: x[1], reverse=True)
    return word_similarities[:topn]

9.6 共现模式的可视化

对于更复杂的分析,可以创建共现矩阵来展示词汇之间的关系:

复制代码
def create_cooccurrence_matrix(self, tokens: List[str], window_size: int = 2):
    """创建共现矩阵"""
    vocab_list = sorted(list(self.vocab))
    word2idx = {w: i for i, w in enumerate(vocab_list)}
    
    # 初始化矩阵
    matrix = np.zeros((len(vocab_list), len(vocab_list)))
    
    # 填充共现计数
    for i, token in enumerate(tokens):
        if token not in word2idx:
            continue
        
        start = max(0, i - window_size)
        end = min(len(tokens), i + window_size + 1)
        
        for j in range(start, end):
            if i != j and tokens[j] in word2idx:
                matrix[word2idx[token]][word2idx[tokens[j]]] += 1
    
    return matrix, vocab_list

这个矩阵可以进一步进行降维(如使用SVD或t-SNE)来可视化词汇之间的关系。

10. Transformer自注意力机制的实现

10.1 TransformerSimulator的初始化和嵌入

TransformerSimulator类实现了Transformer的核心组件。初始化需要两个关键参数:dim是嵌入维度(默认64),num_heads是注意力头数(默认4)。从dimnum_heads计算出每个头的维度:head_dim = dim // num_heads

复制代码
def __init__(self, dim: int = 64, num_heads: int = 4):
    self.dim = dim
    self.num_heads = num_heads
    self.head_dim = dim // num_heads

create_embeddings方法为输入序列中的每个词汇创建随机向量作为初始嵌入。这些向量后续会被位置编码增强:

复制代码
def create_embeddings(self, tokens: List[str]) -> np.ndarray:
    """创建词汇嵌入"""
    embeddings = np.random.randn(len(tokens), self.dim) * 0.01
    return embeddings

10.2 位置编码的实现

位置编码是Transformer中的关键创新,它使模型能够考虑词汇的顺序。使用正弦和余弦函数生成位置编码:

复制代码
def add_positional_encoding(self, embeddings: np.ndarray) -> np.ndarray:
    """添加位置编码"""
    seq_len, dim = embeddings.shape
    pe = np.zeros((seq_len, dim))

    position = np.arange(seq_len).reshape(-1, 1)
    div_term = np.exp(np.arange(0, dim, 2) * -(np.log(10000.0) / dim))

    pe[:, 0::2] = np.sin(position * div_term)
    pe[:, 1::2] = np.cos(position * div_term[:dim // 2])

    return embeddings + pe

位置编码的数学公式:

  • PE(pos, 2i) = sin(pos / 10000^(2i/d))
  • PE(pos, 2i+1) = cos(pos / 10000^(2i/d))

其中pos是位置,i是维度索引。这个公式使得不同位置有不同的编码,但它们以可预测的方式变化。

10.3 自注意力机制的核心实现

自注意力机制计算序列中每个位置与所有其他位置的相关性。核心步骤包括:

  1. 计算查询(Query)、键(Key)和值(Value)向量

  2. 计算注意力权重(点积 + softmax)

  3. 对值向量进行加权求和

    def self_attention(self, embeddings: np.ndarray) -> np.ndarray:
    """多头自注意力机制"""
    seq_len = embeddings.shape[0]
    output = np.zeros_like(embeddings)

    复制代码
     # 简化版:对每个头进行自注意力计算
     for head in range(self.num_heads):
         start_idx = head * self.head_dim
         end_idx = (head + 1) * self.head_dim
    
         Q = embeddings[:, start_idx:end_idx]
         K = embeddings[:, start_idx:end_idx]
         V = embeddings[:, start_idx:end_idx]
    
         # 计算注意力权重
         scores = np.matmul(Q, K.T) / np.sqrt(self.head_dim)
         attention_weights = self.softmax(scores)
    
         # 应用注意力权重
         head_output = np.matmul(attention_weights, V)
         output[:, start_idx:end_idx] = head_output
    
     return output

关键点:

  • scores矩阵的形状是(seq_len, seq_len),表示每个位置与其他位置的相关性
  • 除以sqrt(head_dim)(缩放因子)是为了稳定梯度
  • softmax将分数转换为概率分布(权重)

10.4 Softmax函数的数值稳定实现

标准的softmax容易导致数值溢出。这里使用了一个技巧:先减去最大值,这样不改变结果但提高了数值稳定性:

复制代码
def softmax(self, x: np.ndarray) -> np.ndarray:
    """Softmax函数"""
    e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return e_x / np.sum(e_x, axis=-1, keepdims=True)

数学上,softmax(x) = softmax(x - c)对任意常数c成立,但数值上减去最大值能避免exp的溢出。

10.5 完整的前向传播

forward方法整合了所有组件,展示了Transformer的完整处理流程:

复制代码
def forward(self, tokens: List[str]) -> np.ndarray:
    """前向传播"""
    # 1. 创建词汇嵌入
    embeddings = self.create_embeddings(tokens)
    
    # 2. 添加位置编码
    embeddings = self.add_positional_encoding(embeddings)
    
    # 3. 应用自注意力
    output = self.self_attention(embeddings)
    
    return output

10.6 使用示例和输出解释

复制代码
transformer = TransformerSimulator(dim=64, num_heads=4)
tokens = ["the", "machine", "learning", "model"]

output = transformer.forward(tokens)
print(f"输入序列长度: {len(tokens)}")
print(f"输出形状: {output.shape}")  # (4, 64)

# output[i] 是第i个词汇经过自注意力后的向量表示
# 这个向量包含了该词汇与序列中所有其他词汇的交互信息

10.7 多头注意力的优势

多头注意力机制使用多组独立的注意力:

复制代码
# 每个头关注不同的信息
head_dim = 64 // 4 = 16  # 每个头16维

# 结果:
# 头1可能学到词性关系
# 头2可能学到语义关系  
# 头3可能学到句法关系
# 头4可能学到其他模式

多头注意力的数学等价于增加模型的表达能力。最终的输出是所有头的拼接:

复制代码
output = concat(head_1_output, head_2_output, head_3_output, head_4_output)

10.8 与BERT的联系

BERT使用了Transformer的编码器层。与这里的简化实现相比,完整的Transformer编码器还包括:

  1. 前向反馈网络(FFN):在注意力后接两个全连接层

    def feed_forward(self, x):
    # 第一层:从dim扩展到4dim
    hidden = np.dot(x, W1) + b1
    hidden = np.maximum(hidden, 0) # ReLU激活
    # 第二层:从4
    dim回到dim
    output = np.dot(hidden, W2) + b2
    return output

  2. 层规范化(Layer Normalization):在注意力和FFN后进行

    def layer_norm(x, epsilon=1e-6):
    mean = np.mean(x, axis=-1, keepdims=True)
    std = np.std(x, axis=-1, keepdims=True)
    return (x - mean) / (std + epsilon)

  3. 残差连接(Residual Connection)

    attention_output = self_attention(embeddings)
    output = layer_norm(embeddings + attention_output) # 残差连接

这些组件共同构成了现代深度学习语言模型的基础。

总结与实践指南

实现总结

本文详细介绍了10个完整的NLP技术实现模块,涵盖了从基础的文本预处理到高阶的Transformer架构的全套技术。每个模块都是独立完整的,可以在实际项目中直接使用或作为学习参考。

核心模块概览

  • TextPreprocessor:文本清理、分词、停用词去除、词性标注、NER
  • CooccurrenceMatrix:共现矩阵计算、余弦相似度
  • Word2VecSimulator:Skip-gram模型、负采样训练、词汇类比
  • BagOfWordsModel:词袋向量、TF-IDF加权
  • SentimentAnalyzer:基于词汇的情感分析
  • TextClassifier:朴素贝叶斯分类器、拉普拉斯平滑
  • BigramAnalyzer:N-gram提取和频率统计
  • DistributionalSemantics:上下文窗口、分布分析
  • TransformerSimulator:自注意力机制、位置编码、多头注意力

快速选择指南

根据不同的应用场景,可以选择合适的模块:

文本相似度计算 :使用CooccurrenceMatrixWord2VecSimulator

复制代码
# 快速方案(共现矩阵)
cooccurrence = CooccurrenceMatrix()
cooccurrence.build_cooccurrence_matrix(tokens)
sim = cooccurrence.compute_similarity('word1', 'word2')

文本分类 :使用TextClassifier

复制代码
classifier = TextClassifier()
classifier.train(documents, labels)
predicted, probs = classifier.predict(test_tokens)

情感分析 :使用SentimentAnalyzer

复制代码
analyzer = SentimentAnalyzer()
result = analyzer.analyze("This product is amazing!")

文本表示向量化 :使用BagOfWordsModel

复制代码
bow = BagOfWordsModel()
vector = bow.document_to_bow(tokens)

短语识别 :使用BigramAnalyzer

复制代码
analyzer = BigramAnalyzer()
top_bigrams = analyzer.find_significant_bigrams(tokens, topn=10)

性能优化建议

  1. 词汇表大小管理 :对于大规模应用,使用min_count参数过滤低频词汇

    w2v = Word2VecSimulator()
    w2v.build_vocab(tokens, min_count=5) # 只保留出现5次以上的词

  2. 矩阵存储优化:对于高维稀疏矩阵,使用scipy.sparse

    from scipy.sparse import csr_matrix
    sparse_matrix = csr_matrix(dense_matrix)

  3. 批处理:利用NumPy的向量化操作而不是循环

    不推荐

    for i in range(len(matrix)):
    result[i] = compute(matrix[i])

    推荐

    result = np.vectorize(compute)(matrix)

  4. 并行处理:对于大规模语料库,使用多进程

    from multiprocessing import Pool
    with Pool(4) as p:
    results = p.map(preprocess_function, documents)

生产环境部署建议

  1. 参数持久化:训练后保存模型参数

    import pickle
    with open('classifier.pkl', 'wb') as f:
    pickle.dump(classifier, f)

  2. 版本控制:记录使用的参数和超参数

    config = {
    'embedding_dim': 100,
    'window_size': 5,
    'negative_samples': 10,
    'learning_rate': 0.01
    }

  3. 性能监测:在验证集上持续评估

    def evaluate(classifier, test_data, test_labels):
    correct = 0
    for doc, label in zip(test_data, test_labels):
    pred, _ = classifier.predict(doc)
    if pred == label:
    correct += 1
    return correct / len(test_data)

  4. 容错机制:处理边界情况

    def safe_analyze(self, text):
    if not text or len(text.strip()) == 0:
    return {'sentiment': 'neutral', 'compound': 0}
    return self.analyze(text)

进阶扩展方向

  1. 集成多个模块:组合不同的技术提高性能

    先用Word2Vec获得向量表示,再用TextClassifier进行分类

    vectors = [w2v.get_vector(word) for word in tokens]
    classifier.predict(vectors)

  2. 自定义停用词:针对特定领域定制停用词表

    preprocessor = TextPreprocessor()
    preprocessor.stop_words.update({'domain', 'specific', 'words'})

  3. 词汇权重调整:在情感分析中使用权重

    analyzer.positive_words_weight = {'excellent': 2.0, 'good': 1.0}

  4. 超参数调优:通过交叉验证选择最优参数

    best_score = 0
    for dim in [50, 100, 200]:
    w2v = Word2VecSimulator(embedding_dim=dim)
    w2v.train(tokens)
    score = evaluate_downstream_task(w2v)
    if score > best_score:
    best_score = score
    best_dim = dim

与深度学习框架的集成

这些实现可以作为PyTorch或TensorFlow模型的预处理步骤:

复制代码
# 使用本项目的预处理
preprocessor = TextPreprocessor()
tokens = preprocessor.preprocess(text)

# 转换为PyTorch张量
import torch
token_ids = torch.tensor([vocab[t] for t in tokens])

# 输入到深度学习模型
embeddings = embedding_layer(token_ids)
output = transformer(embeddings)

通过本项目的代码实现,开发者可以深入理解各项NLP技术的细节,在解决实际问题时能够有针对性地选择和优化方案。这些基础模块虽然相对简单,但在许多实际应用中仍然能够提供优秀的性能,特别是在数据规模有限或实时性要求高的场景中。

完整代码:

复制代码
"""
【Python自然语言处理】词向量表示理论基础:从Word2Vec到BERT 完整代码实现
基于NLTK库的词向量表示、文本处理、情感分析和文本分类的综合实现
"""

import nltk
import numpy as np
import re
from collections import defaultdict, Counter
from itertools import combinations, islice
import math
from typing import List, Dict, Tuple, Set
import warnings

warnings.filterwarnings('ignore')

# NLTK初始化(可选)
print("NLTK库已就绪...")


class TextPreprocessor:
    """文本预处理类 - 实现文本清理、分词、词性标注"""

    def __init__(self, language='english'):
        # 使用内置停用词列表(无需下载)
        self.stop_words = {
            'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from',
            'has', 'he', 'in', 'is', 'it', 'its', 'of', 'on', 'or', 'that',
            'the', 'to', 'was', 'will', 'with', 'this', 'but', 'have', 'had',
            'what', 'when', 'where', 'who', 'which', 'why', 'how', 'all', 'can',
            'could', 'did', 'do', 'does', 'doing', 'done', 'each', 'few', 'if',
            'just', 'me', 'more', 'most', 'no', 'not', 'only', 'out', 'over',
            'such', 'than', 'them', 'then', 'there', 'these', 'they', 'too', 'up'
        }
        self.language = language

    def clean_text(self, text: str) -> str:
        """清理文本:转小写、移除特殊字符"""
        text = text.lower()
        text = re.sub(r'[^a-zA-Z0-9\s\u4e00-\u9fa5]', '', text)
        return text.strip()

    def tokenize(self, text: str) -> List[str]:
        """简单分词(基于空格和符号)"""
        # 替代word_tokenize,使用简单的正则表达式分词
        tokens = re.findall(r'\b\w+\b', text.lower())
        return tokens

    def remove_stopwords(self, tokens: List[str]) -> List[str]:
        """移除停用词"""
        return [token for token in tokens if token not in self.stop_words and len(token) > 1]

    def pos_tagging(self, tokens: List[str]) -> List[Tuple[str, str]]:
        """简化词性标注(演示用)"""
        # 简单的词性猜测(用于演示)
        simple_tags = []
        for token in tokens:
            if token in ['the', 'a', 'an']:
                tag = 'DT'  # Determiner
            elif token.endswith('ing'):
                tag = 'VBG'  # Verb, gerund
            elif token.endswith('ed'):
                tag = 'VBD'  # Verb, past tense
            elif len(token) > 0 and token[0].isupper():
                tag = 'NNP'  # Proper noun
            else:
                tag = 'NN'  # Noun
            simple_tags.append((token, tag))
        return simple_tags

    def named_entity_recognition(self, tokens: List[str]):
        """简化命名实体识别(演示用)"""
        entities = []
        for token in tokens:
            if token[0].isupper() and len(token) > 1:
                entities.append((token, 'PERSON/ORG'))
        return entities

    def preprocess(self, text: str, remove_stop=True) -> List[str]:
        """完整的预处理流程"""
        cleaned = self.clean_text(text)
        tokens = self.tokenize(cleaned)
        if remove_stop:
            tokens = self.remove_stopwords(tokens)
        return tokens


class CooccurrenceMatrix:
    """共现矩阵实现 - 早期方法与共现矩阵"""

    def __init__(self, window_size: int = 2):
        self.window_size = window_size
        self.vocab = set()
        self.cooccurrence = defaultdict(lambda: defaultdict(int))
        self.word2idx = {}
        self.idx2word = {}

    def build_vocab(self, tokens: List[str]):
        """构建词汇表"""
        self.vocab = set(tokens)
        self.word2idx = {word: idx for idx, word in enumerate(sorted(self.vocab))}
        self.idx2word = {idx: word for word, idx in self.word2idx.items()}

    def build_cooccurrence_matrix(self, tokens: List[str]):
        """构建词汇-词汇共现矩阵"""
        self.build_vocab(tokens)
        for i, target_word in enumerate(tokens):
            # 定义上下文窗口
            start = max(0, i - self.window_size)
            end = min(len(tokens), i + self.window_size + 1)

            for j in range(start, end):
                if i != j:
                    context_word = tokens[j]
                    self.cooccurrence[target_word][context_word] += 1

    def get_cooccurrence_vector(self, word: str) -> np.ndarray:
        """获取词汇的共现向量"""
        if word not in self.vocab:
            return np.zeros(len(self.vocab))

        vector = np.zeros(len(self.vocab))
        for context_word, count in self.cooccurrence[word].items():
            if context_word in self.word2idx:
                vector[self.word2idx[context_word]] = count
        return vector

    def compute_similarity(self, word1: str, word2: str) -> float:
        """计算两个词之间的余弦相似度"""
        vec1 = self.get_cooccurrence_vector(word1)
        vec2 = self.get_cooccurrence_vector(word2)

        norm1 = np.linalg.norm(vec1)
        norm2 = np.linalg.norm(vec2)

        if norm1 == 0 or norm2 == 0:
            return 0.0

        return np.dot(vec1, vec2) / (norm1 * norm2)

    def get_matrix(self) -> np.ndarray:
        """获取共现矩阵"""
        matrix = np.zeros((len(self.vocab), len(self.vocab)))
        for word, contexts in self.cooccurrence.items():
            if word in self.word2idx:
                for context_word, count in contexts.items():
                    if context_word in self.word2idx:
                        matrix[self.word2idx[word]][self.word2idx[context_word]] = count
        return matrix


class Word2VecSimulator:
    """Word2Vec模拟器 - 使用神经网络方法学习词向量"""

    def __init__(self, embedding_dim: int = 100, window_size: int = 2,
                 learning_rate: float = 0.01, negative_samples: int = 5):
        self.embedding_dim = embedding_dim
        self.window_size = window_size
        self.learning_rate = learning_rate
        self.negative_samples = negative_samples
        self.vocab = {}
        self.word2idx = {}
        self.idx2word = {}
        self.word_vectors = None
        self.context_vectors = None
        self.word_freq = defaultdict(int)

    def build_vocab(self, tokens: List[str], min_count: int = 1):
        """构建词汇表"""
        freq = Counter(tokens)
        self.vocab = {word: idx for idx, (word, count) in enumerate(
            [(w, c) for w, c in freq.items() if c >= min_count]
        )}
        self.word2idx = self.vocab
        self.idx2word = {idx: word for word, idx in self.vocab.items()}
        self.word_freq = {word: freq[word] for word in self.vocab}

    def initialize_embeddings(self):
        """初始化词向量和上下文向量"""
        vocab_size = len(self.vocab)
        self.word_vectors = np.random.randn(vocab_size, self.embedding_dim) * 0.01
        self.context_vectors = np.random.randn(vocab_size, self.embedding_dim) * 0.01

    def sigmoid(self, x: np.ndarray) -> np.ndarray:
        """Sigmoid激活函数"""
        return 1 / (1 + np.exp(-np.clip(x, -500, 500)))

    def negative_sampling_loss(self, target_idx: int, context_idx: int):
        """负采样损失计算 - Skip-gram with negative sampling"""
        target_vec = self.word_vectors[target_idx]
        context_vec = self.context_vectors[context_idx]

        # 正样本:最大化target和context的相似度
        pos_score = np.dot(target_vec, context_vec)
        pos_loss = -np.log(self.sigmoid(pos_score) + 1e-10)

        # 负采样
        neg_loss = 0
        for _ in range(self.negative_samples):
            neg_idx = np.random.randint(0, len(self.vocab))
            neg_vec = self.context_vectors[neg_idx]
            neg_score = np.dot(target_vec, neg_vec)
            neg_loss += -np.log(self.sigmoid(-neg_score) + 1e-10)

        return pos_loss + neg_loss

    def train(self, tokens: List[str], epochs: int = 5, batch_size: int = 32):
        """训练词向量"""
        self.build_vocab(tokens)
        self.initialize_embeddings()

        print(f"训练Word2Vec: 词汇表大小={len(self.vocab)}, 向量维度={self.embedding_dim}")

        for epoch in range(epochs):
            total_loss = 0
            batch_count = 0

            for i, target_word in enumerate(tokens):
                if target_word not in self.word2idx:
                    continue

                target_idx = self.word2idx[target_word]

                # 定义上下文窗口
                start = max(0, i - self.window_size)
                end = min(len(tokens), i + self.window_size + 1)

                for j in range(start, end):
                    if i != j and tokens[j] in self.word2idx:
                        context_idx = self.word2idx[tokens[j]]

                        # 计算损失
                        loss = self.negative_sampling_loss(target_idx, context_idx)
                        total_loss += loss
                        batch_count += 1

                        # 简化的梯度更新
                        target_vec = self.word_vectors[target_idx]
                        context_vec = self.context_vectors[context_idx]

                        score = np.dot(target_vec, context_vec)
                        error = (self.sigmoid(score) - 1.0)

                        grad_target = error * context_vec
                        grad_context = error * target_vec

                        self.word_vectors[target_idx] -= self.learning_rate * grad_target
                        self.context_vectors[context_idx] -= self.learning_rate * grad_context

            avg_loss = total_loss / max(batch_count, 1)
            if (epoch + 1) % 1 == 0:
                print(f"Epoch {epoch + 1}/{epochs}, Loss: {avg_loss:.6f}")

    def get_vector(self, word: str) -> np.ndarray:
        """获取词汇的向量表示"""
        if word not in self.word2idx:
            return np.zeros(self.embedding_dim)
        return self.word_vectors[self.word2idx[word]]

    def similarity(self, word1: str, word2: str) -> float:
        """计算两个词之间的余弦相似度"""
        vec1 = self.get_vector(word1)
        vec2 = self.get_vector(word2)

        norm1 = np.linalg.norm(vec1)
        norm2 = np.linalg.norm(vec2)

        if norm1 == 0 or norm2 == 0:
            return 0.0

        return np.dot(vec1, vec2) / (norm1 * norm2)

    def most_similar(self, word: str, topn: int = 5) -> List[Tuple[str, float]]:
        """找到最相似的词汇"""
        if word not in self.word2idx:
            return []

        word_vec = self.get_vector(word)
        similarities = []

        for other_word in self.word2idx:
            if other_word != word:
                sim = self.similarity(word, other_word)
                similarities.append((other_word, sim))

        similarities.sort(key=lambda x: x[1], reverse=True)
        return similarities[:topn]

    def analogy(self, word_a: str, word_b: str, word_c: str, topn: int = 5):
        """词汇类比: word_a : word_b = word_c : ?"""
        vec_a = self.get_vector(word_a)
        vec_b = self.get_vector(word_b)
        vec_c = self.get_vector(word_c)

        # 计算类比向量
        analogy_vec = vec_b - vec_a + vec_c
        norm = np.linalg.norm(analogy_vec)
        if norm > 0:
            analogy_vec = analogy_vec / norm

        similarities = []
        for word in self.word2idx:
            if word not in [word_a, word_b, word_c]:
                word_vec = self.get_vector(word)
                word_norm = np.linalg.norm(word_vec)
                if word_norm > 0:
                    sim = np.dot(analogy_vec, word_vec / word_norm)
                    similarities.append((word, sim))

        similarities.sort(key=lambda x: x[1], reverse=True)
        return similarities[:topn]


class BagOfWordsModel:
    """词袋模型 - 高维稀疏表示"""

    def __init__(self):
        self.vocab = {}
        self.word2idx = {}
        self.idx2word = {}

    def build_vocab(self, documents: List[List[str]]):
        """从文档集合构建词汇表"""
        words = set()
        for doc in documents:
            words.update(doc)

        self.vocab = {word: idx for idx, word in enumerate(sorted(words))}
        self.word2idx = self.vocab
        self.idx2word = {idx: word for word, idx in self.vocab.items()}

    def document_to_bow(self, tokens: List[str]) -> np.ndarray:
        """将文档转换为词袋向量"""
        vector = np.zeros(len(self.vocab))
        for token in tokens:
            if token in self.word2idx:
                vector[self.word2idx[token]] += 1
        return vector

    def tf_idf(self, documents: List[List[str]]) -> np.ndarray:
        """计算TF-IDF矩阵"""
        self.build_vocab(documents)
        matrix = []

        # 计算IDF
        doc_count = len(documents)
        idf = {}
        for word in self.vocab:
            count = sum(1 for doc in documents if word in doc)
            idf[word] = np.log(doc_count / max(count, 1))

        # 计算TF-IDF
        for doc in documents:
            vector = np.zeros(len(self.vocab))
            word_counts = Counter(doc)
            total_words = len(doc)

            for word, count in word_counts.items():
                if word in self.word2idx:
                    tf = count / total_words if total_words > 0 else 0
                    vector[self.word2idx[word]] = tf * idf[word]

            matrix.append(vector)

        return np.array(matrix)


class SentimentAnalyzer:
    """情感分析 - 基于词汇的简单实现(不依赖NLTK数据)"""

    def __init__(self):
        # 情感词汇列表
        self.positive_words = {
            'love', 'amazing', 'wonderful', 'fantastic', 'excellent', 'great',
            'good', 'beautiful', 'perfect', 'awesome', 'best', 'brilliant',
            'outstanding', 'superb', 'marvelous', 'splendid', 'magnificent',
            'delightful', 'joy', 'happy', 'pleased', 'glad', 'wonderful'
        }
        self.negative_words = {
            'hate', 'terrible', 'awful', 'horrible', 'bad', 'worst',
            'disgusting', 'awful', 'poor', 'worse', 'disappointed',
            'dislike', 'sad', 'angry', 'upset', 'terrible', 'dreadful',
            'pathetic', 'abysmal', 'atrocious', 'deplorable', 'awful'
        }

    def analyze(self, text: str) -> Dict:
        """分析文本情感"""
        tokens = text.lower().split()

        positive_count = sum(1 for token in tokens if token in self.positive_words)
        negative_count = sum(1 for token in tokens if token in self.negative_words)

        # 计算情感分数
        if positive_count + negative_count == 0:
            compound = 0
        else:
            compound = (positive_count - negative_count) / (positive_count + negative_count)

        # 确定情感极性
        if compound > 0.1:
            sentiment = 'positive'
        elif compound < -0.1:
            sentiment = 'negative'
        else:
            sentiment = 'neutral'

        total_words = len(tokens)
        return {
            'text': text,
            'sentiment': sentiment,
            'compound': compound,
            'positive': positive_count / total_words if total_words > 0 else 0,
            'negative': negative_count / total_words if total_words > 0 else 0,
            'neutral': 1 - (positive_count + negative_count) / total_words if total_words > 0 else 1
        }

    def analyze_batch(self, texts: List[str]) -> List[Dict]:
        """批量分析文本情感"""
        return [self.analyze(text) for text in texts]


class TextClassifier:
    """文本分类 - 简单的向量化分类器"""

    def __init__(self, classifier_type: str = 'naivebayes'):
        self.classifier_type = classifier_type
        self.vocab = {}
        self.word2idx = {}
        self.idx2word = {}
        self.class_word_freq = defaultdict(lambda: defaultdict(int))
        self.class_doc_count = defaultdict(int)
        self.classes = set()

    def build_vocab(self, documents: List[List[str]]):
        """构建词汇表"""
        words = set()
        for doc in documents:
            words.update(doc)

        self.vocab = {word: idx for idx, word in enumerate(sorted(words))}
        self.word2idx = self.vocab
        self.idx2word = {idx: word for word, idx in self.vocab.items()}

    def train(self, documents: List[List[str]], labels: List[str]):
        """训练分类器"""
        self.build_vocab(documents)
        self.classes = set(labels)

        # 统计每个类别中每个词的频率
        for doc, label in zip(documents, labels):
            self.class_doc_count[label] += 1
            for word in doc:
                if word in self.word2idx:
                    self.class_word_freq[label][word] += 1

        print(f"分类器已训练: 类别数={len(self.classes)}, 词汇表大小={len(self.vocab)}")

    def predict(self, tokens: List[str]) -> Tuple[str, Dict[str, float]]:
        """预测文本类别"""
        scores = {}

        for label in self.classes:
            # 计算对数概率
            score = np.log(self.class_doc_count[label] / sum(self.class_doc_count.values()))

            for word in tokens:
                if word in self.word2idx:
                    word_count = self.class_word_freq[label].get(word, 0)
                    total_words = sum(self.class_word_freq[label].values())

                    # 平滑处理
                    prob = (word_count + 1) / (total_words + len(self.vocab))
                    score += np.log(prob)

            scores[label] = score

        # 返回概率最高的类别
        best_label = max(scores, key=scores.get)

        # 转换为概率
        exp_scores = {k: np.exp(v) for k, v in scores.items()}
        total = sum(exp_scores.values())
        probabilities = {k: v / total for k, v in exp_scores.items()}

        return best_label, probabilities


class BigramAnalyzer:
    """二元组和搭配分析 - 独立实现,不依赖NLTK高级功能"""

    def __init__(self):
        self.bigrams_freq = None
        self.trigrams_freq = None

    @staticmethod
    def _get_ngrams(tokens: List[str], n: int) -> List[Tuple]:
        """获取n-grams"""
        return [tuple(tokens[i:i + n]) for i in range(len(tokens) - n + 1)]

    def find_bigrams(self, tokens: List[str], window_size: int = 2) -> List[Tuple[str, str]]:
        """查找二元组"""
        bigrams = self._get_ngrams(tokens, 2)
        return bigrams

    def find_significant_bigrams(self, tokens: List[str], topn: int = 10) -> List[Tuple[Tuple[str, str], int]]:
        """查找显著的二元组(基于频率)"""
        bigrams = self.find_bigrams(tokens)
        freq = Counter(bigrams)
        return freq.most_common(topn)

    def find_trigrams(self, tokens: List[str]) -> List[Tuple[str, str, str]]:
        """查找三元组"""
        trigrams = self._get_ngrams(tokens, 3)
        return trigrams


class DistributionalSemantics:
    """分布假说与向量化表示 - 理论与实现"""

    def __init__(self):
        self.context_windows = []
        self.vocab = set()

    def extract_context_windows(self, tokens: List[str], window_size: int = 2) -> List[Dict]:
        """提取上下文窗口"""
        windows = []

        for i, target_word in enumerate(tokens):
            start = max(0, i - window_size)
            end = min(len(tokens), i + window_size + 1)

            context = {
                'target': target_word,
                'context': [tokens[j] for j in range(start, end) if j != i],
                'position': i
            }
            windows.append(context)
            self.vocab.add(target_word)

        self.context_windows = windows
        return windows

    def analyze_distribution(self, tokens: List[str], word: str) -> Dict:
        """分析词汇的分布"""
        positions = [i for i, t in enumerate(tokens) if t == word]

        if not positions:
            return {'word': word, 'frequency': 0, 'distribution': []}

        context_freq = defaultdict(int)
        for pos in positions:
            start = max(0, pos - 2)
            end = min(len(tokens), pos + 3)
            for j in range(start, end):
                if j != pos:
                    context_freq[tokens[j]] += 1

        return {
            'word': word,
            'frequency': len(positions),
            'positions': positions,
            'context_distribution': dict(context_freq)
        }


class TransformerSimulator:
    """Transformer自注意力机制的简化模拟"""

    def __init__(self, dim: int = 64, num_heads: int = 4):
        self.dim = dim
        self.num_heads = num_heads
        self.head_dim = dim // num_heads

    def create_embeddings(self, tokens: List[str]) -> np.ndarray:
        """创建词汇嵌入"""
        embeddings = np.random.randn(len(tokens), self.dim) * 0.01
        return embeddings

    def add_positional_encoding(self, embeddings: np.ndarray) -> np.ndarray:
        """添加位置编码"""
        seq_len, dim = embeddings.shape
        pe = np.zeros((seq_len, dim))

        position = np.arange(seq_len).reshape(-1, 1)
        div_term = np.exp(np.arange(0, dim, 2) * -(np.log(10000.0) / dim))

        pe[:, 0::2] = np.sin(position * div_term)
        pe[:, 1::2] = np.cos(position * div_term[:dim // 2])

        return embeddings + pe

    def self_attention(self, embeddings: np.ndarray) -> np.ndarray:
        """多头自注意力机制"""
        seq_len = embeddings.shape[0]
        output = np.zeros_like(embeddings)

        # 简化版:对每个头进行自注意力计算
        for head in range(self.num_heads):
            start_idx = head * self.head_dim
            end_idx = (head + 1) * self.head_dim

            Q = embeddings[:, start_idx:end_idx]
            K = embeddings[:, start_idx:end_idx]
            V = embeddings[:, start_idx:end_idx]

            # 计算注意力权重
            scores = np.matmul(Q, K.T) / np.sqrt(self.head_dim)
            attention_weights = self.softmax(scores)

            # 应用注意力权重
            head_output = np.matmul(attention_weights, V)
            output[:, start_idx:end_idx] = head_output

        return output

    def softmax(self, x: np.ndarray) -> np.ndarray:
        """Softmax函数"""
        e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
        return e_x / np.sum(e_x, axis=-1, keepdims=True)

    def forward(self, tokens: List[str]) -> np.ndarray:
        """前向传播"""
        embeddings = self.create_embeddings(tokens)
        embeddings = self.add_positional_encoding(embeddings)
        output = self.self_attention(embeddings)
        return output


# ==================== 主程序演示 ====================

def main():
    """主程序:演示所有功能"""

    print("=" * 80)
    print("【Python自然语言处理】词向量表示理论基础:从Word2Vec到BERT 完整实现")
    print("=" * 80)

    # 示例文本
    texts = [
        "Natural language processing is a fascinating field in artificial intelligence",
        "Machine learning models can learn from data without explicit programming",
        "Deep learning neural networks have revolutionized the field of NLP",
        "Word embeddings capture semantic relationships between words",
        "Transformers have become the foundation of modern language models",
        "BERT uses masked language modeling for pretraining",
        "Word2Vec learns word vectors through context prediction",
        "Sentiment analysis determines the emotional tone of text"
    ]

    # 1. 文本预处理演示
    print("\n" + "=" * 80)
    print("1. 文本预处理演示(NLTK分词、词性标注、NER)")
    print("=" * 80)

    preprocessor = TextPreprocessor()
    sample_text = texts[0]
    print(f"\n原始文本:{sample_text}")

    tokens = preprocessor.tokenize(sample_text)
    print(f"\n分词结果:{tokens}")

    pos_tags = preprocessor.pos_tagging(tokens)
    print(f"\n词性标注:{pos_tags[:5]}...")

    cleaned_tokens = preprocessor.preprocess(sample_text, remove_stop=True)
    print(f"\n移除停用词后:{cleaned_tokens}")

    # 2. 共现矩阵演示
    print("\n" + "=" * 80)
    print("2. 共现矩阵与早期方法演示")
    print("=" * 80)

    all_tokens = []
    for text in texts:
        all_tokens.extend(preprocessor.preprocess(text, remove_stop=True))

    cooccurrence = CooccurrenceMatrix(window_size=2)
    cooccurrence.build_cooccurrence_matrix(all_tokens)

    print(f"\n词汇表大小:{len(cooccurrence.vocab)}")
    print(f"示例词汇:{list(cooccurrence.vocab)[:10]}")

    if 'language' in cooccurrence.vocab and 'processing' in cooccurrence.vocab:
        sim = cooccurrence.compute_similarity('language', 'processing')
        print(f"\n'language'和'processing'的余弦相似度:{sim:.4f}")

    # 3. Word2Vec模拟器演示
    print("\n" + "=" * 80)
    print("3. Word2Vec词向量学习演示(Skip-gram with Negative Sampling)")
    print("=" * 80)

    w2v = Word2VecSimulator(embedding_dim=50, window_size=2, learning_rate=0.01, negative_samples=5)
    w2v.train(all_tokens, epochs=3, batch_size=32)

    # 词汇相似度
    print(f"\n'model'最相似的词汇:")
    if 'model' in w2v.word2idx:
        similar = w2v.most_similar('model', topn=5)
        for word, sim in similar:
            print(f"  {word}: {sim:.4f}")

    # 词汇类比演示
    print(f"\n词汇类比演示(learning:models = training:?):")
    if all(w in w2v.word2idx for w in ['learning', 'models', 'training']):
        analogy = w2v.analogy('learning', 'models', 'training', topn=5)
        for word, sim in analogy:
            print(f"  {word}: {sim:.4f}")

    # 4. 词袋模型与TF-IDF演示
    print("\n" + "=" * 80)
    print("4. 词袋模型与TF-IDF演示")
    print("=" * 80)

    documents = [preprocessor.preprocess(text, remove_stop=True) for text in texts]
    bow = BagOfWordsModel()

    tfidf_matrix = bow.tf_idf(documents)
    print(f"\n词汇表大小:{len(bow.vocab)}")
    print(f"文档数:{len(documents)}")
    print(f"TF-IDF矩阵形状:{tfidf_matrix.shape}")

    # 5. 情感分析演示
    print("\n" + "=" * 80)
    print("5. 情感分析演示(NLTK VADER)")
    print("=" * 80)

    sentiment_texts = [
        "I love this amazing product! It's absolutely wonderful!",
        "This is terrible and I hate it completely",
        "The weather is okay today",
        "Deep learning is fantastic and powerful technology",
        "I'm very disappointed with this poor quality"
    ]

    analyzer = SentimentAnalyzer()
    results = analyzer.analyze_batch(sentiment_texts)

    for result in results:
        print(f"\n文本:{result['text'][:50]}...")
        print(f"情感:{result['sentiment'].upper()}")
        print(f"复合分数:{result['compound']:.4f}")
        print(f"正向:{result['positive']:.4f}, 负向:{result['negative']:.4f}, 中性:{result['neutral']:.4f}")

    # 6. 文本分类演示
    print("\n" + "=" * 80)
    print("6. 文本分类演示(朴素贝叶斯)")
    print("=" * 80)

    # 创建训练数据
    train_texts = [
        "excellent amazing wonderful fantastic",
        "terrible awful horrible bad",
        "good nice great beautiful",
        "worst terrible awful disgusting",
        "love adore excellent amazing",
        "hate dislike terrible awful"
    ]
    train_labels = ['positive', 'negative', 'positive', 'negative', 'positive', 'negative']

    train_docs = [text.split() for text in train_texts]
    classifier = TextClassifier()
    classifier.train(train_docs, train_labels)

    # 测试
    test_text = "this product is excellent and amazing"
    test_tokens = test_text.split()
    predicted_label, probabilities = classifier.predict(test_tokens)

    print(f"\n测试文本:{test_text}")
    print(f"预测类别:{predicted_label}")
    print(f"类别概率:")
    for label, prob in probabilities.items():
        print(f"  {label}: {prob:.4f}")

    # 7. 搭配分析演示
    print("\n" + "=" * 80)
    print("7. 搭配分析演示(二元组和三元组)")
    print("=" * 80)

    sample_tokens = all_tokens[:200]
    bigram_analyzer = BigramAnalyzer()

    bigrams = bigram_analyzer.find_bigrams(sample_tokens)
    print(f"\n前10个二元组:")
    for bigram in list(Counter(bigrams).most_common(10)):
        print(f"  {bigram}")

    # 8. 分布假说与语境分析
    print("\n" + "=" * 80)
    print("8. 分布假说与语境分析演示")
    print("=" * 80)

    dist_semantics = DistributionalSemantics()
    windows = dist_semantics.extract_context_windows(all_tokens, window_size=2)

    print(f"\n提取的上下文窗口数:{len(windows)}")
    print(f"示例窗口(前3个):")
    for window in windows[:3]:
        print(f"  目标词:'{window['target']}', 上下文:{window['context']}")

    # 分析特定词的分布
    if 'learning' in all_tokens:
        dist_info = dist_semantics.analyze_distribution(all_tokens, 'learning')
        print(f"\n'learning'的分布分析:")
        print(f"  频率:{dist_info['frequency']}")
        print(f"  位置:{dist_info['positions'][:5]}...")
        print(
            f"  主要上下文词(前5个):{sorted(dist_info['context_distribution'].items(), key=lambda x: x[1], reverse=True)[:5]}")

    # 9. Transformer自注意力机制演示
    print("\n" + "=" * 80)
    print("9. Transformer自注意力机制演示")
    print("=" * 80)

    transformer = TransformerSimulator(dim=64, num_heads=4)
    demo_tokens = ["word", "embedding", "attention", "mechanism"]

    print(f"\n输入序列:{demo_tokens}")

    output = transformer.forward(demo_tokens)
    print(f"\n输出形状:{output.shape}")
    print(f"输出(前2个词,前5个维度):")
    print(f"  {output[:2, :5]}")

    # 10. 总结统计
    print("\n" + "=" * 80)
    print("10. 汇总统计")
    print("=" * 80)

    print(f"\n处理统计:")
    print(f"  文本数量:{len(texts)}")
    print(f"  总词元数:{len(all_tokens)}")
    print(f"  词汇表大小:{len(set(all_tokens))}")
    print(f"  平均句子长度:{np.mean([len(text.split()) for text in texts]):.2f}")

    print(f"\n模型统计:")
    print(f"  Word2Vec词汇表:{len(w2v.vocab)} 词")
    print(f"  词向量维度:{w2v.embedding_dim}")
    print(f"  文本分类器类别数:{len(classifier.classes)}")

    print("\n" + "=" * 80)
    print("演示完成!")
    print("=" * 80)


if __name__ == "__main__":
    main()
相关推荐
Elaine3368 小时前
【验证码识别算法性能对比实验系统——KNN、SVM、CNN 与多模态大模型的性能博弈与机理分析】
python·opencv·支持向量机·cnn·多模态·数字图像处理
SCBAiotAigc8 小时前
langchain1.x学习笔记(三):langchain之init_chat_model的新用法
人工智能·python·langchain·langgraph·deepagents
Blossom.1188 小时前
联邦迁移学习实战:在数据孤岛中构建个性化推荐模型
开发语言·人工智能·python·深度学习·神经网络·机器学习·迁移学习
yaoxin5211238 小时前
288. Java Stream API - 创建随机数的 Stream
java·开发语言
Blossom.1188 小时前
大模型自动化压缩:基于权重共享的超网神经架构搜索实战
运维·人工智能·python·算法·chatgpt·架构·自动化
superman超哥8 小时前
迭代器适配器(map、filter、fold等):Rust函数式编程的艺术
开发语言·rust·编程语言·rust map·rust filter·rust fold·rust函数式
yuanmenghao8 小时前
自动驾驶中间件iceoryx - 同步与通知机制(二)
开发语言·单片机·中间件·自动驾驶·信息与通信
天天睡大觉8 小时前
Python学习7
windows·python·学习
郝学胜-神的一滴8 小时前
Qt实现圆角窗口的两种方案详解
开发语言·c++·qt·程序人生