NLP(自然语言处理, Natural Language Processing)

发布于:2025-09-11 ⋅ 阅读:(22) ⋅ 点赞:(0)

让计算机能够理解、解释、操纵和生成人类语言,从而执行有价值的任务。
关注社区:Hugging Face、Papers With Code、GitHub 是现代NLP学习不可或缺的资源。许多最新模型和代码都在这里开源。

①、安装库

pip install numpy pandas matplotlib nltk scikit-learn tensorflow torch transformers

②、传统NLP方法

Ⅰ文本预处理

import nltk
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer, WordNetLemmatizer
import re

# 下载所需nltk数据(第一次运行需要)
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')

# 示例文本
text = "NLP is amazing! It involves analyzing and processing human language with computers."

# a) 小写化 & 去除标点/数字
text_clean = re.sub(r'[^a-zA-Z\s]', '', text.lower())
print("Cleaned Text:", text_clean)

# b) 分词
tokens = word_tokenize(text_clean)
print("Tokens:", tokens)

# c) 去除停用词
stop_words = set(stopwords.words('english'))
filtered_tokens = [word for word in tokens if word not in stop_words]
print("Filtered Tokens:", filtered_tokens)

# d) 词干提取
stemmer = PorterStemmer()
stemmed_tokens = [stemmer.stem(word) for word in filtered_tokens]
print("Stemmed Tokens:", stemmed_tokens)

# e) 词形还原(比词干提取更高级)
lemmatizer = WordNetLemmatizer()
lemmatized_tokens = [lemmatizer.lemmatize(word) for word in filtered_tokens]
print("Lemmatized Tokens:", lemmatized_tokens)

Ⅱ 文本表示(TF-IDF)与机器学习模型训练

from sklearn.feature_extraction.text import TfidfVectorizer #使用TF-IDF向量化文本
from sklearn.model_selection import train_test_split # 划分训练集和测试集
from sklearn.naive_bayes import MultinomialNB # 训练一个朴素贝叶斯分类器
from sklearn.metrics import accuracy_score

# 示例数据:电影评论和情感标签(0-负面, 1-正面)
corpus = [
    "This movie is great and I love it!",
    "The plot was terrible and boring.",
    "Amazing acting by the lead actor.",
    "Waste of time, horrible script.",
    "I enjoyed it a lot, highly recommend.",
    "The worst movie I have ever seen."
]
labels = [1, 0, 1, 0, 1, 0]

# 使用TF-IDF向量化文本(自动处理了大部分预处理)
vectorizer = TfidfVectorizer(stop_words='english', max_features=50)
X = vectorizer.fit_transform(corpus).toarray()
print("TF-IDF Matrix Shape:", X.shape)
print("Feature names:", vectorizer.get_feature_names_out())

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.33, random_state=42)

# 训练一个朴素贝叶斯分类器
model = MultinomialNB()
model.fit(X_train, y_train)

# 预测并评估
predictions = model.predict(X_test)
print("Predictions:", predictions)
print("True Labels:", y_test)
print("Accuracy:", accuracy_score(y_test, predictions))

# 预测新样本
new_review = ["This film is absolutely fantastic!"]
new_review_vec = vectorizer.transform(new_review)
prediction_new = model.predict(new_review_vec)
print(f"Review: '{new_review[0]}' -> Sentiment: {'Positive' if prediction_new[0] == 1 else 'Negative'}")

③、深度学习NLP代码 (使用Keras)

使用LSTM进行情感分析

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout

# 假设我们使用和上面一样的corpus和labels,但这里需要更大量的数据,仅作示例

# 构建词汇表并序列化文本
tokenizer = Tokenizer(num_words=500, oov_token="<OOV>")
tokenizer.fit_on_texts(corpus)
word_index = tokenizer.word_index
sequences = tokenizer.texts_to_sequences(corpus)

# 填充序列以保证相同长度
padded_sequences = pad_sequences(sequences, maxlen=10, padding='post', truncating='post')
print("Padded Sequences:\n", padded_sequences)

# 构建一个简单的LSTM模型
model = Sequential([
    Embedding(input_dim=500, output_dim=16, input_length=10), # 500词汇量,16维嵌入向量,输入长度10
    LSTM(units=32, dropout=0.2),
    Dense(1, activation='sigmoid') # 二分类输出
])

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()

# 训练模型 (这里数据量太小,仅为演示格式)
# model.fit(padded_sequences, labels, epochs=10, validation_split=0.2)

④、现代NLP代码 (使用Hugging Face Transformers)

from transformers import AutoTokenizer, TFAutoModelForSequenceClassification
from transformers import pipeline
import tensorflow as tf

# ------------------ 方法一:直接使用Pipeline(零样本学习,无需训练)------------------
# Hugging Face提供的pipeline API,开箱即用
classifier = pipeline('sentiment-analysis')
results = classifier("I really enjoyed this movie! The acting was superb.")
print(results) # 输出:[{'label': 'POSITIVE', 'score': 0.9998}]

# ------------------ 方法二:加载模型和分词器进行微调 ------------------
# 假设我们有一个更大的数据集,这里仅展示加载和准备步骤

# a. 加载预训练模型和对应的分词器
model_name = "distilbert-base-uncased-finetuned-sst-2-english" # 一个在SST-2情感分析任务上微调过的DistilBERT模型
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = TFAutoModelForSequenceClassification.from_pretrained(model_name)

# b. 对输入文本进行编码(Tokenization)
inputs = tokenizer("This is a fantastic example of NLP!", return_tensors="tf", padding=True, truncation=True)
print("Input IDs:", inputs['input_ids'])
print("Attention Mask:", inputs['attention_mask'])

# c. 模型预测
outputs = model(inputs)
predictions = tf.nn.softmax(outputs.logits, axis=-1)
print("Predictions:", predictions)
# 输出类似:tf.Tensor([[0.0012 0.9988]], shape=(1, 2), dtype=float32) -> 第二类概率高,代表积极

# d. (可选)在自己的数据集上微调
# ... 需要准备数据集、定义优化器、训练循环等,步骤较为复杂,但Hugging Face提供了完整的示例代码。

构建一个新闻文本分类器,判断一篇新闻属于体育、科技还是娱乐类别

  1. 数据准备:加载并探索数据 成功加载了三个类别的20 Newsgroups数据集
  2. 文本预处理:清洗和标准化文本 转换为小写 移除标点符号、数字,移除停用词,进行词干提取或词形还原
  3. 文本表示:将文本转换为数值特征(TF-IDF) 将数据转换为易于操作的Pandas DataFrame格式
  4. 模型训练:使用机器学习算法训练分类器,使用MultinomialNB, SVC, LogisticRegression等模型在训练集上进行训练,并在测试集上进行预测。最后使用accuracy_score, classification_report, confusion_matrix(并配合seaborn和matplotlib进行可视化)来全面评估和比较各个模型的性能
  5. 评估与预测:评估模型性能并用其预测新数据,可能会使用cross_val_score或更高级的超参数调优工具(如GridSearchCV)来寻找最佳模型

一、安装必要的库并加载数据。我们将使用scikit-learn内置的新闻数据集

# 导入所需库
import numpy as np
import pandas as pd #用于数值计算和数据操作(如DataFrame)
from sklearn.datasets import fetch_20newsgroups  #sklearn (Scikit-learn):机器学习核心库,从20 Newsgroups数据集中加载数据
from sklearn.feature_extraction.text import TfidfVectorizer # 将文本数据转换为TF-IDF特征向量
from sklearn.naive_bayes import MultinomialNB #三个不同的分类器模型(多项式朴素贝叶斯、支持向量机、逻辑回归)
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score #用于划分训练集/测试集和进行交叉验证
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score #用于评估模型性能的指标
import matplotlib.pyplot as plt
import seaborn as sns #用于数据可视化和绘制图表(如混淆矩阵)
import nltk #自然语言处理工具库
from nltk.corpus import stopwords #常用停用词列表(如 “the”, “is”, “and”)
from nltk.stem import PorterStemmer, WordNetLemmatizer #用于词干提取和词形还原
import re #用于处理正则表达式和字符串操作,常用于文本清洗
import string

# 下载NLTK所需数据 下载NLTK模块运行所需的语料库和数据文件
nltk.download('stopwords') #下载停用词列表
nltk.download('wordnet') #下载WordNet词典,这是词形还原器(WordNetLemmatizer)所必需的
nltk.download('omw-1.4')#下载Open Multilingual WordNet,为WordNet提供多语言支持(对于英语处理同样重要)

# 从著名的20 Newsgroups数据集中加载一个子集      加载数据:我们选择3个类别  
categories = ['rec.sport.hockey', 'comp.graphics', 'sci.space'] #冰球、计算机图形学和太空科学
#分别加载官方的训练集和测试集
#确保数据在加载时被随机打乱,但同时通过固定随机种子(42)保证每次运行的结果是可复现的
#此时,newsgroups_train和newsgroups_test是包含数据、标签、类别名称等信息的Bunch对象
newsgroups_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=42)
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories, shuffle=True, random_state=42)

# 转换为DataFrame便于处理  将Scikit-learn的Bunch对象转换为Pandas DataFrame。这是一种非常常见的操作,因为DataFrame提供了更直观、更方便的数据查看和处理方式(例如,使用iloc, value_counts等)
df_train = pd.DataFrame({'text': newsgroups_train.data, 'target': newsgroups_train.target})#'text': 包含原始的新闻文本内容 (newsgroups_train.data)
df_test = pd.DataFrame({'text': newsgroups_test.data, 'target': newsgroups_test.target})#'target': 包含对应的类别标签(数字形式,newsgroups_train.target)

# 查看数据信息  输出训练集和测试集的样本数量(行数)和特征数量(列数,这里应该是2列:text和target)
print(f"训练集大小: {df_train.shape}")
print(f"测试集大小: {df_test.shape}")
print("\n类别分布:")
#查看训练集中每个类别的样本数量,检查数据是否平衡(即每个类别的样本数是否大致相同)。平衡的数据集通常有助于模型更好地学习
#输出数字标签(target)与实际类别名称的对应关系。例如,0可能对应'comp.graphics',1对应'rec.sport.hockey',2对应'sci.space'
print(df_train['target'].value_counts())
print("\n类别名称:", newsgroups_train.target_names)

# 查看前3条数据 定性地查看原始数据的样子
for i in range(3):
	#根据数字标签获取对应的类别名称
    print(f"\n--- 样本 {i} (类别: {newsgroups_train.target_names[df_train['target'].iloc[i]]}) ---")
    print(df_train['text'].iloc[i][:200] + "...")  # 只显示前200个字符  获取第i个样本的文本内容,并只打印前200个字符。这有助于了解数据的原始格式(通常包含邮件头、发件人、主题等)、语言风格和需要进行哪些清洗工作。

二、文本预处理

  • 清洗、分词、去停用词、词形还原
  • 减少噪声、标准化词汇、降低特征空间的维度,从而帮助模型专注于真正有信息量的词汇上,提升其泛化能力和性能
# 初始化工具
stemmer = PorterStemmer()# 初始化波特词干提取器。词干提取(Stemming)是一种粗粒度地还原单词到其词根形式的方法,通常会直接砍掉词缀,可能产生不是真正单词的结果(如 "running" → "run", "happily" → "happili")
lemmatizer = WordNetLemmatizer() #初始化词形还原器。词形还原(Lemmatization)是一种更精细的方法,它使用词典将单词还原为其字典形式( lemma),并且会考虑单词的词性(如 "running" → "run", "better" (adj.) → "good")
stop_words = set(stopwords.words('english'))#获取英语停用词列表,并将其转换为集合(set)。集合的查找速度(O(1))远快于列表(O(n)),这对于处理大量文本时的性能提升至关重要。停用词是那些极其常见但携带信息量很少的词(如 "the", "a", "an", "in")

def preprocess_text(text):
    """
    文本预处理函数
    步骤: 小写 -> 去标点/数字 -> 分词 -> 去停用词 -> 词干提取/词形还原
    """
    # 1. 转换为小写
    text = text.lower()
    
    # 2. 移除标点符号和数字 用正则表达式清理文本中的非字母字符
    text = re.sub(r'[^a-zA-Z\s]', '', text)
    
    # 3. 分词 将连续的文本字符串分割成单个单词(或称为"词元/tokens")的列表
    tokens = text.split() #对于更复杂的语言(如中文)或需要保留特定结构的情况,会使用更高级的分词器(如NLTK的word_tokenize)
    
    # 4. 去除停用词 过滤掉无意义的信息和噪声
    #for word in tokens: 遍历上一步得到的所有词元
    #if word not in stop_words: 只保留不在停用词集合中的词。
    #and len(word) > 2: 同时只保留长度大于2的词。这一步可以过滤掉很多残留的噪音(如单个字母、缩写等),这些短词通常信息量也很低
    tokens = [word for word in tokens if word not in stop_words and len(word) > 2]
    
    # 5. 词形还原 (比词干提取更常用) 将单词的各种屈折形式还原为其基本形式(lemma)
    # 例如,"running" → "run", "mice" → "mouse", "is" → "be"。这可以进一步减少特征的维度,将相关词归并为同一个特征,帮助模型更好地学习
    tokens = [lemmatizer.lemmatize(word) for word in tokens]
    
    # 6. 重新组合为文本 将处理后的词元列表重新连接成一个干净的字符串 下一步使用的TfidfVectorizer的输入通常是字符串序列(每个文档是一个字符串),而不是词元列表。
    return ' '.join(tokens)

# 应用预处理
print("原始文本示例:")
print(df_train['text'].iloc[0][:200], "...") #将preprocess_text函数应用到df['text']列的每一个元素上。

df_train['cleaned_text'] = df_train['text'].apply(preprocess_text)
df_test['cleaned_text'] = df_test['text'].apply(preprocess_text)

print("\n清洗后文本示例:")
print(df_train['cleaned_text'].iloc[0][:200], "...")

三、文本表示(TF-IDF)

将文本转换为机器学习模型可以理解的数值特征(将文本数据数值化),使用TF-IDF将文本转换为数值向量

  • 初始化:通过配置TfidfVectorizer的参数,设定了特征提取的规则,旨在保留信息量最大的词汇,剔除噪声。
  • 拟合:在训练集上学习这些规则,建立从文本到特征向映射关系。
  • 转换:应用学到的规则,分别将训练集和测试集文本转换为数值矩阵(X_train_tfidf, X_test_tfidf)
  • 输出:生成的X_train_tfidf和X_test_tfidf就是可以直接输入到后续分类模型(如MultinomialNB, SVC)中的特征矩阵。它们的每一行代表一个文档,每一列代表一个特征词(或n-gram)的TF-IDF权重
# 初始化TF-IDF向量化器 TfidfVectorizer对象,并设置了几个关键参数来控制和优化特征提取过程
# max_features: 只考虑最常见的5000个词
# min_df: 忽略出现次数少于5次的词
# max_df: 忽略出现在90%以上文档中的词(如"the", "and")
vectorizer = TfidfVectorizer(max_features=5000,  #限制特征词汇表的大小,只考虑整个语料库中TF-IDF权重最高的5000个词,降维
                            min_df=5,  #忽略那些在所有文档中出现次数少于5次的词 去除稀有词。出现次数极少的词很可能是拼写错误、非常专业的术语或噪音,它们对于大多数文档的分类没有帮助,反而会增加特征空间的噪声
                            max_df=0.9, #忽略那些出现在超过90%的文档中的词,去除常见词。虽然代码之前已经用NLTK的停用词列表过滤过一次,但此参数作为一个额外的安全网。有些词可能不在标准停用词列表中,但在特定数据集中出现得过于频繁(例如,在这个新闻数据集里可能常见的"writes"、"article"等),这些词携带的类别区分信息非常少,类似于停用词。
                            stop_words='english',#指定使用内置的英语停用词列表进行过滤,这里与之前NLTK的预处理是冗余的。因为cleaned_text已经移除了停用词,所以此处的参数可能不会起到额外作用。通常,只需在一处做停用词移除即可
                            ngram_range=(1, 2))  # 同时使用单个词和双词组合 #指定不仅要提取单个词(unigrams),还要提取相邻的两个词组合(bigrams)捕获上下文信息。有些概念由多个词组成,例如:"machine" (1-gram) 和 "learning" (1-gram) 单独出现时含义广泛。 "machine learning" (2-gram) 作为一个整体则是一个信息量高度集中的特定术语。

# 在训练集上拟合并转换 fit():学习训练数据的特征。具体来说,它基于df_train['cleaned_text']
# 构建词汇表(所有不同的词) 计算每个词的文档频率(DF)(即有多少个文档包含这个词) 根据max_features, min_df, max_df等参数过滤词汇表,确定最终要使用的特征词
X_train_tfidf = vectorizer.fit_transform(df_train['cleaned_text'])
# 在测试集上转换(注意:只transform,不fit)#应用刚才学到的规则,转换训练数据。它使用学习到的词汇表和DF值,为每一篇训练文档计算TF-IDF特征向量,最终生成一个稀疏矩阵 X_train_tfidf。
X_test_tfidf = vectorizer.transform(df_test['cleaned_text'])

y_train = df_train['target']
y_test = df_test['target']

print(f"训练集特征维度: {X_train_tfidf.shape}")#输出稀疏矩阵的形状,格式为(样本数量, 特征数量)
print(f"测试集特征维度: {X_test_tfidf.shape}")

# 查看特征词 获取最终向量化器所采用的特征名称(即词汇表)
feature_names = vectorizer.get_feature_names_out() 
print(f"\n前20个特征词: {feature_names[:20]}")

四、模型训练与评估

  • 使用准确率、分类报告、混淆矩阵
# 初始化不同模型 创建一个字典,以便于循环遍历和比较多个不同的模型
models = {
    'Naive Bayes': MultinomialNB(),# (朴素贝叶斯): 非常适合文本分类的经典模型,特别适用于像TF-IDF这样的离散特征。它计算高效,通常在文本任务上有一个不错的基线表现。
    'SVM': SVC(kernel='linear', random_state=42),#(支持向量机)kernel='linear': 指定使用线性核。对于高维文本数据(如TF-IDF)random_state=42: 设置随机种子,确保结果可复现
    'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42)#(逻辑回归):增加最大迭代次数。逻辑回归使用迭代优化算法,对于大型数据集,默认的100次迭代可能不足以收敛。这个参数防止算法因未收敛而发出警告
}

# 训练并评估每个模型
results = {}
for name, model in models.items():
    print(f"\n=== 训练 {name} ===")
    
    # 训练模型 在训练数据上训练模型。模型学习TF-IDF特征(X_train_tfidf)和对应标签(y_train)之间的关系,并调整其内部参数
    model.fit(X_train_tfidf, y_train)
    
    # 预测 使用训练好的模型对从未见过的测试集(X_test_tfidf)进行预测,生成预测标签(y_pred)
    y_pred = model.predict(X_test_tfidf)
    
    # 评估 计算准确率:(预测正确的样本数) / (总样本数)
    accuracy = accuracy_score(y_test, y_pred)
    results[name] = accuracy
    
    print(f"准确率: {accuracy:.4f}")
    print("\n分类报告:")
    #提供更详细的、按类别划分的评估指标
    print(classification_report(y_test, y_pred, target_names=newsgroups_train.target_names))

# 比较模型性能
print("\n=== 模型性能比较 ===")
for name, acc in results.items():
    print(f"{name}: {acc:.4f}")

# 选择最佳模型(这里假设Naive Bayes最好)
best_model = MultinomialNB()
best_model.fit(X_train_tfidf, y_train)

五、可视化与深入分析

# 绘制混淆矩阵 创建一个可视化函数来显示模型的详细分类性能,超越简单的准确率
def plot_confusion_matrix(y_true, y_pred, class_names):
    cm = confusion_matrix(y_true, y_pred) #计算混淆矩阵。这是一个N×N的矩阵(N为类别数),其中:行代表真实标签 列代表预测标签 对角线上的值表示正确分类的样本数 非对角线上的值表示误分类的样本数

    plt.figure(figsize=(8, 6))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',  #使用Seaborn绘制热力图 annot=True: 在每个单元格中显示数值 fmt='d': 将数值格式化为整数(decimal)cmap='Blues': 使用蓝色系颜色映射(颜色越深表示数值越大)xticklabels=class_names, yticklabels=class_names: 使用类别名称而不是数字作为坐标轴标签
                xticklabels=class_names, 
                yticklabels=class_names)
    plt.title('混淆矩阵')
    plt.ylabel('真实标签')
    plt.xlabel('预测标签')
    plt.show()

# 使用最佳模型的预测结果 
y_pred_best = best_model.predict(X_test_tfidf)
plot_confusion_matrix(y_test, y_pred_best, newsgroups_train.target_names)

#使用最佳模型对测试集进行预测,然后调用上面的函数绘制混淆矩阵
# 分析最重要的特征(词) 可视化每个类别中最具判别性的词汇,帮助理解模型是如何区分不同类别的
def plot_top_features(model, vectorizer, class_names, n=10):
    """
    可视化每个类别最重要的特征词
    """
    fig, axes = plt.subplots(1, len(class_names), figsize=(15, 5))
    
    if len(class_names) == 1:
        axes = [axes]
    
    for i, class_label in enumerate(class_names):
        # 获取该类别的特征重要性(对数概率)
        feature_importance = model.feature_log_prob_[i]
        
        # 获取最重要的特征索引
        top_indices = np.argsort(feature_importance)[-n:][::-1]
        top_features = [feature_names[idx] for idx in top_indices]
        top_scores = [feature_importance[idx] for idx in top_indices]
        
        axes[i].barh(range(n), top_scores[::-1])
        axes[i].set_yticks(range(n))
        axes[i].set_yticklabels(top_features[::-1])
        axes[i].set_title(f'Top features for: {class_label}')
    
    plt.tight_layout()
    plt.show()

# 绘制特征重要性(适用于Naive Bayes)
plot_top_features(best_model, vectorizer, newsgroups_train.target_names, n=8)

六、使用模型进行新预测

# 准备新的新闻文本进行预测 创建三条新的新闻文本,分别对应于之前定义的三个类别(太空、冰球、计算机图形学)
#NASA, Mars, orbit, space → 应该预测为 sci.space
#hockey, championship, goal, game → 应该预测为 rec.sport.hockey
#computer graphics, rendering, 3D → 应该预测为 comp.graphics
new_news = [
    "The NASA spacecraft successfully entered Mars orbit today after a seven-month journey through space.",
    "The hockey team won the championship with an amazing final goal in the last seconds of the game.",
    "New computer graphics technology allows for real-time rendering of complex 3D environments with stunning detail."
]

# 预处理新文本 对新文本应用与训练数据完全相同的预处理流程
#使用列表推导式,对new_news中的每个文本调用之前定义的preprocess_text()函数。
cleaned_news = [preprocess_text(text) for text in new_news]

# 转换为TF-IDF特征 transform():使用之前从训练集学习到的词汇表和IDF权重来转换新数据。
#这意味着:如果新文本中包含训练时未见过的词,它们会被忽略;如果包含训练时见过的词,会使用训练时计算好的IDF值。
new_news_tfidf = vectorizer.transform(cleaned_news)

# 预测 predict():返回每个样本的预测类别(数字标签)
predictions = best_model.predict(new_news_tfidf)
#predict_proba():返回每个样本属于各个类别的概率值。这是一个形状为(3, 3)的数组(3个样本,3个类别)
prediction_proba = best_model.predict_proba(new_news_tfidf)

# 显示预测结果
print("=== 新新闻预测结果 ===")
for i, (text, pred, proba) in enumerate(zip(new_news, predictions, prediction_proba)):
    predicted_class = newsgroups_train.target_names[pred]
    print(f"\n新闻 {i+1}:")
    print(f"文本: {text[:80]}...")
    print(f"预测类别: {predicted_class}")
    print(f"各类别概率: {dict(zip(newsgroups_train.target_names, np.round(proba, 3)))}")

预测输出案例:

=== 新新闻预测结果 ===

新闻 1:
文本: The NASA spacecraft successfully entered Mars orbit today after a seven-month journe...
预测类别: sci.space
各类别概率: {'comp.graphics': 0.001, 'rec.sport.hockey': 0.002, 'sci.space': 0.997}

新闻 2:
文本: The hockey team won the championship with an amazing final goal in the last seconds...
预测类别: rec.sport.hockey
各类别概率: {'comp.graphics': 0.003, 'rec.sport.hockey': 0.995, 'sci.space': 0.002}

新闻 3:
文本: New computer graphics technology allows for real-time rendering of complex 3D envir...
预测类别: comp.graphics
各类别概率: {'comp.graphics': 0.996, 'rec.sport.hockey': 0.002, 'sci.space': 0.002}

使用LSTM进行情感分析(分类任务)

一、环境准备与数据加载

import tensorflow as tf #TensorFlow/Keras: 主深度学习框架
from tensorflow.keras.models import Sequential #Sequential: 顺序模型,用于堆叠神经网络层
#Embedding: 词嵌入层,将单词转换为密集向量
#LSTM: 长短期记忆网络,处理序列数据
#Bidirectional: 双向LSTM,从两个方向处理序列
#Dense: 全连接层
#Dropout: 防止过拟合的正则化层
from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout, Bidirectional
from tensorflow.keras.preprocessing.text import Tokenizer #Tokenizer: 文本分词和向量化
from tensorflow.keras.preprocessing.sequence import pad_sequences #pad_sequences: 将序列填充到相同长度
from tensorflow.keras.callbacks import EarlyStopping#EarlyStopping: 提前停止训练回调

import numpy as np #numpy: 数值计算
import pandas as pd #pandas: 数据处理和分析
from sklearn.model_selection import train_test_split #train_test_split: 数据集划分
from sklearn.metrics import classification_report, confusion_matrix#classification_report: 分类性能报告 #confusion_matrix: 混淆矩阵

import matplotlib.pyplot as plt#matplotlib/seaborn: 数据可视化
import seaborn as sns #stopwords: 停用词列表
import re
from nltk.corpus import stopwords #nltk: 自然语言处理工具包
import nltk

nltk.download('stopwords') #停用词下载: nltk.download('stopwords') - 获取常见无意义词汇

# 设置随机种子确保结果可重现
tf.random.set_seed(42)
np.random.seed(42)

二、加载和预处理IMDb电影评论数据集

  • 加载原始数据并限制词汇表大小
  • 创建词汇表映射以便查看原始文本
  • 将变长序列填充/截断为固定长度
  • 准备用于训练神经网络的数据格式
# 加载IMDb数据集
from tensorflow.keras.datasets import imdb

# 只保留数据集中最常用的10000个词
vocab_size = 10000 #vocab_size = 10000: 限制词汇表大小,只使用数据集中最常用的10,000个单词
max_length = 200  # 每条评论最大长度 max_length = 200: 设置每条评论的最大长度,超过此长度的将被截断,不足的将被填充

# 加载数据 num_words=vocab_size: 只保留出现频率最高的10,000个单词
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=vocab_size)

# 获取词汇表
word_index = imdb.get_word_index()#获取单词到整数的映射字典
reverse_word_index = {value: key for key, value in word_index.items()} #创建整数到单词的反向映射字典,用于将数字序列解码回文本

# 解码一条评论看看 这个函数将整数序列转换回可读的文本
def decode_review(encoded_review):
	#i - 3: 因为IMDb数据集中的索引偏移了3(0、1、2分别用于填充、序列开始和未知词)
	#reverse_word_index.get(i - 3, '?'): 如果找不到对应的单词,使用'?'代替
    return ' '.join([reverse_word_index.get(i - 3, '?') for i in encoded_review])

print("示例评论:")
print(decode_review(X_train[0]))
print(f"情感标签: {y_train[0]} (0=负面, 1=正面)")

# 填充序列到相同长度 pad_sequences: 将所有序列填充/截断到相同长度
#axlen=max_length: 设置序列的最大长度为200
#padding='post': 在序列末尾进行填充(而不是开头)
#truncating='post': 从序列末尾截断(而不是开头)
X_train = pad_sequences(X_train, maxlen=max_length, padding='post', truncating='post')
X_test = pad_sequences(X_test, maxlen=max_length, padding='post', truncating='post')

#处理后,训练集和测试集都变为形状为(样本数量, 200)的二维数组
#例如:(25000, 200)表示25,000条评论,每条评论长度为200
print(f"\n训练集形状: {X_train.shape}")
print(f"测试集形状: {X_test.shape}")

三、 构建LSTM模型

  • 输入: 形状为(batch_size, 200)的整数序列
  • 嵌入层: 转换为(batch_size, 200, 128)的词向量矩阵
  • 双向LSTM: 处理序列,输出(batch_size, 128)的特征向量(64×2,双向)
  • Dropout: 随机丢弃50%的连接
  • 全连接层: 进一步提取特征,32维输出
  • Dropout: 随机丢弃30%的连接
  • 输出层: 输出0-1之间的情感概率
# 构建LSTM模型
embedding_dim = 128  # 词向量维度   词向量的维度,每个单词将被表示为128维的密集向量
lstm_units = 64      # LSTM单元数量 LSTM层中隐藏单元的数量,控制模型的容量和复杂度

model = Sequential([
    # 嵌入层:将整数索引转换为密集向量  将每个单词的整数索引转换为密集的词向量表示
    Embedding(input_dim=vocab_size, #词汇表大小(10,000)
              output_dim=embedding_dim,  #输出向量维度(128维)
              input_length=max_length),#输入序列长度(200)
    
    # 双向LSTM层:从两个方向捕捉上下文信息 捕捉文本中的双向上下文信息,提高对语义的理解
    #lstm_units=64: LSTM单元数量
    #return_sequences=False: 只返回最后一个时间步的输出(不是整个序列)
    #Bidirectional(): 包装器,使LSTM从正向和反向两个方向处理序列
    Bidirectional(LSTM(lstm_units, return_sequences=False)),
    
    # Dropout层:防止过拟合 作用:正则化技术,防止模型过拟合训练数据
    Dropout(0.5),#rate=0.5: 丢弃50%的神经元连接
    
    # 全连接层 
    Dense(32, activation='relu'), #units=32: 32个神经元  activation='relu': 使用ReLU激活函数
    Dropout(0.3), #第二个Dropout层:丢弃30%的连接,进一步防止过拟合
    
    # 输出层:二分类 units=1: 1个神经元(二分类问题) activation='sigmoid': Sigmoid激活函数,输出0-1之间的概率值
    Dense(1, activation='sigmoid')
])

# 编译模型
model.compile(
    optimizer='adam',#使用Adam优化器,适合大多数深度学习任务
    loss='binary_crossentropy', #二元交叉熵损失函数,适合二分类问题
    metrics=['accuracy', 'precision', 'recall'] #评估指标 accuracy: 准确率  precision: 精确率(预测为正例中真正正例的比例) recall: 召回率(真正正例中被预测为正例的比例)
)

# 显示模型结构 显示每层的类型、输出形状、参数数量 帮助理解模型结构和参数规模 便于调试和优化模型
model.summary()

四、训练模型

  • 数据分割:训练开始时,自动将X_train的20%作为验证集
  • 训练循环:每个epoch使用64个样本的批次进行训练
  • 验证评估:每个epoch结束后在验证集上评估性能
  • 早停监控:持续监控验证集损失,决定是否提前停止
  • 权重恢复:训练结束后自动恢复到最佳性能的权重
# 设置早停回调 在训练过程中监控验证集性能,当性能不再提升时自动停止训练
#训练结束后恢复最佳模型的权重(而不是最后一个epoch的权重)
#防止过拟合:避免在训练集上表现过好但在验证集上性能下降
#节省时间:自动停止不必要的训练轮次
#获得最佳模型:通过restore_best_weights确保得到泛化能力最好的模型
early_stopping = EarlyStopping(
    monitor='val_loss',#监控验证集损失值(validation loss)
    patience=3, #允许性能不提升的轮次数。如果连续3个epoch验证损失没有改善,则停止训练
    restore_best_weights=True #训练结束后恢复最佳模型的权重(而不是最后一个epoch的权重)
)

# 训练模型 包含训练过程中所有指标的历史记录
history = model.fit(
    X_train, y_train, #训练数据(填充后的整数序列)  训练标签(0或1的情感标签)
    epochs=15, #最大训练轮次为15  实际可能因早停而提前结束
    batch_size=64, #每个批次的样本数量 将25,000个训练样本分成每批64个的小批次 每epoch需要约391个批次(25000/64≈391)
    validation_split=0.2, # 从训练集中划分20%作为验证集
    callbacks=[early_stopping],#使用早停回调函数 可以添加多个回调函数,如[early_stopping, checkpoint]
    verbose=1 #显示训练进度条 0: 不显示输出  1: 显示进度条  2: 每个epoch显示一行信息
)

五、评估模型

# 评估模型
test_loss, test_accuracy, test_precision, test_recall = model.evaluate(X_test, y_test, verbose=0)
print(f"测试集准确率: {test_accuracy:.4f}")
print(f"测试集精确率: {test_precision:.4f}")
print(f"测试集召回率: {test_recall:.4f}")

# 绘制训练历史
def plot_training_history(history):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
    
    # 绘制准确率
    ax1.plot(history.history['accuracy'], label='训练准确率')
    ax1.plot(history.history['val_accuracy'], label='验证准确率')
    ax1.set_title('模型准确率')
    ax1.set_xlabel('Epoch')
    ax1.set_ylabel('Accuracy')
    ax1.legend()
    
    # 绘制损失
    ax2.plot(history.history['loss'], label='训练损失')
    ax2.plot(history.history['val_loss'], label='验证损失')
    ax2.set_title('模型损失')
    ax2.set_xlabel('Epoch')
    ax2.set_ylabel('Loss')
    ax2.legend()
    
    plt.tight_layout()
    plt.show()

plot_training_history(history)

# 预测并生成分类报告
y_pred_proba = model.predict(X_test)
y_pred = (y_pred_proba > 0.5).astype(int)

print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=['负面', '正面']))

# 绘制混淆矩阵
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
            xticklabels=['负面', '正面'], 
            yticklabels=['负面', '正面'])
plt.title('混淆矩阵')
plt.ylabel('真实标签')
plt.xlabel('预测标签')
plt.show()

六、对新文本进行预测

# 对新评论进行情感分析
def predict_sentiment(text):
    # 文本预处理函数
    def preprocess_text(text):
        text = text.lower()
        text = re.sub(r'[^a-zA-Z\s]', '', text)
        tokens = text.split()
        return ' '.join(tokens)
    
    # 将文本转换为序列
    processed_text = preprocess_text(text)
    words = processed_text.split()
    
    # 将单词转换为索引
    sequence = []
    for word in words:
        if word in word_index and word_index[word] < vocab_size:
            sequence.append(word_index[word] + 3)  # +3 是因为IMDb数据集的偏移
    
    # 填充序列
    padded_sequence = pad_sequences([sequence], maxlen=max_length, padding='post', truncating='post')
    
    # 预测
    prediction = model.predict(padded_sequence)[0][0]
    sentiment = "正面" if prediction > 0.5 else "负面"
    confidence = prediction if prediction > 0.5 else 1 - prediction
    
    return sentiment, confidence, prediction

# 测试新评论
test_reviews = [
    "This movie was absolutely fantastic! The acting was superb and the story was engaging.",
    "I hated this film. It was boring and poorly made. Waste of time.",
    "The movie was okay, not great but not terrible either. Some good scenes but overall mediocre.",
    "An amazing masterpiece that deserves all the awards. Brilliant direction and performances!"
]

print("=== 新评论情感分析 ===")
for i, review in enumerate(test_reviews):
    sentiment, confidence, score = predict_sentiment(review)
    print(f"\n评论 {i+1}:")
    print(f"内容: {review[:80]}...")
    print(f"预测: {sentiment} (置信度: {confidence:.3f}, 原始分数: {score:.3f})")

使用LSTM进行文本生成(序列生成任务)

一、准备文本数据

# 下载莎士比亚文本数据
import tensorflow as tf
import numpy as np

path_to_file = tf.keras.utils.get_file(
    'shakespeare.txt', 
    'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt'
)

# 读取数据
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')
print(f'文本长度: {len(text)} 字符')
print(f'文本前500字符:\n{text[:500]}')

二、文本预处理

# 创建词汇表
vocab = sorted(set(text))
print(f'唯一字符数: {len(vocab)}')

# 创建字符到索引和索引到字符的映射
char2idx = {char: idx for idx, char in enumerate(vocab)}
idx2char = {idx: char for idx, char in enumerate(vocab)}

# 将文本转换为数字序列
text_as_int = np.array([char2idx[char] for char in text])

print(f'文本:\n"{text[:50]}"')
print(f'\n对应的索引:\n{text_as_int[:50]}')

# 创建训练样本
seq_length = 100  # 输入序列长度
examples_per_epoch = len(text) // (seq_length + 1)

# 创建训练数据集
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)

# 将字符序列转换为输入-目标对
sequences = char_dataset.batch(seq_length + 1, drop_remainder=True)

def split_input_target(chunk):
    input_text = chunk[:-1]
    target_text = chunk[1:]
    return input_text, target_text

dataset = sequences.map(split_input_target)

# 批处理和打乱数据
BATCH_SIZE = 64
BUFFER_SIZE = 10000

dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)

三、构建文本生成模型

# 模型参数
vocab_size = len(vocab)
embedding_dim = 256
rnn_units = 1024

# 构建模型
def build_model(vocab_size, embedding_dim, rnn_units, batch_size):
    model = tf.keras.Sequential([
        tf.keras.layers.Embedding(vocab_size, embedding_dim,
                                batch_input_shape=[batch_size, None]),
        tf.keras.layers.LSTM(rnn_units,
                            return_sequences=True,
                            stateful=True,
                            recurrent_initializer='glorot_uniform'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.LSTM(rnn_units,
                            return_sequences=True,
                            stateful=True,
                            recurrent_initializer='glorot_uniform'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(vocab_size)
    ])
    return model

model = build_model(vocab_size, embedding_dim, rnn_units, BATCH_SIZE)
model.summary()

四、训练模型

# 定义损失函数
def loss(labels, logits):
    return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)

# 编译模型
model.compile(optimizer='adam', loss=loss)

# 配置检查点
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}")

checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_prefix,
    save_weights_only=True)

# 训练模型(为了演示,只训练少量轮次)
EPOCHS = 10

history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])

五、文本生成函数

# 为了生成文本,我们需要重建模型用于单批次输入
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)
model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))
model.build(tf.TensorShape([1, None]))

# 文本生成函数
def generate_text(model, start_string, num_generate=1000, temperature=1.0):
    # 将起始字符串转换为数字
    input_eval = [char2idx[s] for s in start_string]
    input_eval = tf.expand_dims(input_eval, 0)
    
    # 空列表用于存储结果
    text_generated = []
    
    # 重置模型状态
    model.reset_states()
    
    for i in range(num_generate):
        predictions = model(input_eval)
        # 移除批次维度
        predictions = tf.squeeze(predictions, 0)
        
        # 用温度参数调整预测分布
        predictions = predictions / temperature
        predicted_id = tf.random.categorical(predictions, num_samples=1)[-1, 0].numpy()
        
        # 将预测的字符和之前的输入一起传递给模型作为下一个输入
        input_eval = tf.expand_dims([predicted_id], 0)
        
        text_generated.append(idx2char[predicted_id])
    
    return (start_string + ''.join(text_generated))

# 生成文本
print(generate_text(model, start_string=u"ROMEO: ", temperature=0.8))

网站公告

今日签到

点亮在社区的每一天
去签到