作业:
目录
[第一节 初级分词技术](#第一节 初级分词技术)
[第三节 从主题模型到 Word2Vec](#第三节 从主题模型到 Word2Vec)
[1. 数据加载部分](#1. 数据加载部分)
[2. 特征提取部分](#2. 特征提取部分)
[3. 转换为 PyTorch 张量](#3. 转换为 PyTorch 张量)
[4. 模型结构](#4. 模型结构)
[5. 训练设备](#5. 训练设备)
[6. 训练过程](#6. 训练过程)
[7. 测试集评估](#7. 测试集评估)
[8. 推理示例](#8. 推理示例)
第一节 初级分词技术
user_pos_dict.txt
九 10000000 n
头 1000000 n
奔波儿灞 nr
每行格式:词语 词频 词性
当前"九"的词频是 10000000,"头"的词频是 1000000,数值很大。这导致 jieba 在分词时,认为"九"和"头"单独出现的概率非常高,所以会把"九头虫"拆成"九""头""虫"。
方案一:降低"九""头"词频,并添加"九头"词条
将文件内容修改为:
九 1 n
头 1 n
九头 100000 n
奔波儿灞 nr
修改说明:
把"九"的词频从 10000000 降到 1,概率变得极低。
把"头"的词频从 1000000 降到 1。
新增一行"九头 100000 n",表示"九头"作为一个词,词频较高(10万)。这样动态规划会倾向于把"九头"连起来,而不是分开。
方案二:直接添加"九头虫"词条
如果你想看"九头虫"作为一个整体,可以这样修改:
九 1 n
头 1 n
九头虫 100000 n
奔波儿灞 nr
准备测试代码
test_pos.py
import jieba
import jieba.posseg as pseg
# 加载词典(确保路径正确)
jieba.load_userdict("./user_pos_dict.txt")
# 测试句子
text = "九头虫让奔波儿灞把唐僧师徒除掉"
# 分词并打印词性标注结果
words = pseg.lcut(text, HMM=False) # HMM=False 强制只使用词典和动态规划
print(words)
当user_pos_dict.txt如下时:
九 10000000 n
头 1000000 n
奔波儿灞 nr
输出:
pair('九', 'n'), pair('头', 'n'), pair('虫', 'n'), pair('让', 'v'), pair('奔波儿灞', 'nr'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')
方案一时输出:
pair('九头', 'n'), pair('虫', 'n'), pair('让', 'v'), pair('奔波儿灞', 'nr'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')
方案二时输出:
pair('九头虫', 'n'), pair('让', 'v'), pair('奔波儿灞', 'nr'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')
对比分析
-
原理回顾:jieba 动态规划会计算每种切分路径的 log 概率之和,选择最大的。词频高的词 log 概率大,更容易被选中。
-
在方案一中,"九头"词频高,所以路径
九头+虫的 log 概率和大于九+头+虫,因此被选中。 -
在方案二中,"九头虫"整体词频高,路径
九头虫的 log 概率大于九头+虫或拆分,所以被选中。
code/C2/01_jieba.py
python
import jieba
text1 = "我在梦里收到清华大学录取通知书"
seg_list = jieba.lcut(text1, cut_all=False) # cut_all=False 表示精确模式
print(seg_list)
# 未加载词典前的错误分词
text2 = "九头虫让奔波儿灞把唐僧师徒除掉"
print(f"精准模式: {jieba.lcut(text2, cut_all=False)}")
# 加载自定义词典
jieba.load_userdict("./user_dict.txt")
print(f"加载词典后: {jieba.lcut(text2, cut_all=False)}")
text3 = "我在Boss直聘找工作"
# 开启HMM(默认)
seg_list_hmm = jieba.lcut(text3, HMM=True)
print(f"HMM开启: {seg_list_hmm}")
# 关闭HMM
seg_list_no_hmm = jieba.lcut(text3, HMM=False)
print(f"HMM关闭: {seg_list_no_hmm}")
import jieba.posseg as pseg
words = pseg.lcut(text2, HMM=False)
print(f"默认词性输出: {words}")
jieba.load_userdict("./user_pos_dict.txt")
dic_words = pseg.lcut(text2, HMM=False)
print(f"加载词性词典后: {dic_words}")
输出:
(base) PS E:\Datawhale 2026\base-llm202602> & D:/Users/app/miniconda3/envs/base-llm/python.exe "e:/Datawhale 2026/base-llm202602/01_jieba.py"
D:\Users\app\miniconda3\envs\base-llm\lib\site-packages\jieba\_compat.py:18: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\app\AppData\Local\Temp\jieba.cache
Prefix dict has been built successfully.
'我', '在', '梦里', '收到', '清华大学', '录取', '通知书'
精准模式: ['九头', '虫', '让', '奔波', '儿', '灞', '把', '唐僧', '师徒', '除掉']
加载词典后: ['九头虫', '让', '奔波儿灞', '把', '唐僧', '师徒', '除掉']
HMM开启: ['我', '在', 'Boss', '直聘', '找', '工作']
HMM关闭: ['我', '在', 'Boss', '直', '聘', '找', '工作']
默认词性输出: [pair('九头虫', 'x'), pair('让', 'v'), pair('奔波儿灞', 'x'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')]
加载词性词典后: [pair('九', 'n'), pair('头', 'n'), pair('虫', 'n'), pair('让', 'v'), pair('奔波儿灞', 'nr'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')]
方案一:
(base) PS E:\Datawhale 2026\base-llm202602> & D:/Users/app/miniconda3/envs/base-llm/python.exe "e:/Datawhale 2026/base-llm202602/01_jieba.py"
D:\Users\app\miniconda3\envs\base-llm\lib\site-packages\jieba\_compat.py:18: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\app\AppData\Local\Temp\jieba.cache
Loading model cost 0.491 seconds.
Prefix dict has been built successfully.
'我', '在', '梦里', '收到', '清华大学', '录取', '通知书'
精准模式: ['九头', '虫', '让', '奔波', '儿', '灞', '把', '唐僧', '师徒', '除掉']
加载词典后: ['九头虫', '让', '奔波儿灞', '把', '唐僧', '师徒', '除掉']
HMM开启: ['我', '在', 'Boss', '直聘', '找', '工作']
HMM关闭: ['我', '在', 'Boss', '直', '聘', '找', '工作']
默认词性输出: [pair('九头虫', 'x'), pair('让', 'v'), pair('奔波儿灞', 'x'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')]
加载词性词典后: [pair('九头', 'n'), pair('虫', 'n'), pair('让', 'v'), pair('奔波儿灞', 'nr'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')]
方案二:
(base) PS E:\Datawhale 2026\base-llm202602> & D:/Users/app/miniconda3/envs/base-llm/python.exe "e:/Datawhale 2026/base-llm202602/01_jieba.py"
D:\Users\app\miniconda3\envs\base-llm\lib\site-packages\jieba\_compat.py:18: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\app\AppData\Local\Temp\jieba.cache
Loading model cost 0.498 seconds.
Prefix dict has been built successfully.
'我', '在', '梦里', '收到', '清华大学', '录取', '通知书'
精准模式: ['九头', '虫', '让', '奔波', '儿', '灞', '把', '唐僧', '师徒', '除掉']
加载词典后: ['九头虫', '让', '奔波儿灞', '把', '唐僧', '师徒', '除掉']
HMM开启: ['我', '在', 'Boss', '直聘', '找', '工作']
HMM关闭: ['我', '在', 'Boss', '直', '聘', '找', '工作']
默认词性输出: [pair('九头虫', 'x'), pair('让', 'v'), pair('奔波儿灞', 'x'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')]
加载词性词典后: [pair('九头虫', 'n'), pair('让', 'v'), pair('奔波儿灞', 'nr'), pair('把', 'p'), pair('唐僧', 'nr'), pair('师徒', 'n'), pair('除掉', 'v')]
第三节 从主题模型到 Word2Vec
安装所需库
pip install scikit-learn torch numpy
python text_classifier.py
python
"""
text_classifier.py
基于 20newsgroups 数据集和全连接网络的文本分类示例
"""
# ==================== 1. 导入所需库 ====================
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import TensorDataset, DataLoader
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
# ==================== 2. 加载数据 ====================
print("正在加载 20newsgroups 数据...")
# 加载训练集和测试集
train_data = fetch_20newsgroups(subset='train', shuffle=True, random_state=42)
test_data = fetch_20newsgroups(subset='test', shuffle=True, random_state=42)
print(f"训练样本数: {len(train_data.data)}")
print(f"测试样本数: {len(test_data.data)}")
print(f"类别数: {len(train_data.target_names)}")
print("类别名称:", train_data.target_names)
# ==================== 3. 文本特征提取 (TF-IDF) ====================
print("\n正在提取 TF-IDF 特征...")
# 创建 TF-IDF 向量化器,限制最大特征数以控制内存和维度
# 如果内存不足,可以减小 max_features 的值(例如 5000)
max_features = 10000
vectorizer = TfidfVectorizer(max_features=max_features, stop_words='english')
# 对训练集进行拟合和转换
X_train = vectorizer.fit_transform(train_data.data)
# 对测试集只进行转换
X_test = vectorizer.transform(test_data.data)
# 标签
y_train = train_data.target
y_test = test_data.target
print(f"训练集特征矩阵形状: {X_train.shape}") # (样本数, max_features)
print(f"测试集特征矩阵形状: {X_test.shape}")
# ==================== 4. 转换为 PyTorch 张量 ====================
print("\n正在转换为 PyTorch 张量...")
# 注意:toarray() 会将稀疏矩阵转为稠密矩阵,可能占用大量内存。
# 如果内存不足,可考虑减小 max_features 或使用分批转换。
X_train_dense = torch.tensor(X_train.toarray(), dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.long)
X_test_dense = torch.tensor(X_test.toarray(), dtype=torch.float32)
y_test_tensor = torch.tensor(y_test, dtype=torch.long)
# ==================== 5. 定义全连接网络模型 ====================
class TextClassifier(nn.Module):
def __init__(self, input_dim, hidden_dim1, hidden_dim2, num_classes):
super(TextClassifier, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim1)
self.fc2 = nn.Linear(hidden_dim1, hidden_dim2)
self.fc3 = nn.Linear(hidden_dim2, num_classes)
self.dropout = nn.Dropout(0.5) # 防止过拟合
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = F.relu(self.fc2(x))
x = self.dropout(x)
x = self.fc3(x) # 输出 logits,不加 softmax
return x
# 定义模型参数
input_dim = X_train.shape[1] # 等于 max_features
hidden_dim1 = 128
hidden_dim2 = 64
num_classes = len(train_data.target_names)
model = TextClassifier(input_dim, hidden_dim1, hidden_dim2, num_classes)
print("\n模型结构:")
print(model)
# ==================== 6. 准备数据加载器 ====================
batch_size = 64
train_dataset = TensorDataset(X_train_dense, y_train_tensor)
test_dataset = TensorDataset(X_test_dense, y_test_tensor)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# ==================== 7. 定义损失函数和优化器 ====================
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# ==================== 8. 训练模型 ====================
num_epochs = 10
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
print(f"\n使用设备: {device}")
print("开始训练...")
for epoch in range(num_epochs):
model.train()
total_loss = 0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")
# ==================== 9. 评估模型 ====================
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f"\n测试集准确率: {accuracy:.2f}%")
# ==================== 10. 推理函数与示例 ====================
def predict_text(text, model, vectorizer, device):
"""对单个文本进行预测,返回预测的类别索引"""
model.eval()
# 文本向量化
X = vectorizer.transform([text])
X_tensor = torch.tensor(X.toarray(), dtype=torch.float32).to(device)
with torch.no_grad():
outputs = model(X_tensor)
_, predicted = torch.max(outputs, 1)
return predicted.item()
# 用测试集中的第一个文本进行演示
sample_text = test_data.data[0]
true_label = test_data.target[0]
pred_label = predict_text(sample_text, model, vectorizer, device)
print("\n推理示例:")
print(f"文本预览: {sample_text[:200]}...")
print(f"真实类别: {test_data.target_names[true_label]}")
print(f"预测类别: {test_data.target_names[pred_label]}")
# 可选:保存模型
# torch.save(model.state_dict(), "text_classifier.pth")
# print("\n模型已保存为 text_classifier.pth")
输出:
(base-llm) PS E:\Datawhale 2026\base-llm202602> python text_classifier.py
正在加载 20newsgroups 数据...
D:\Users\app\miniconda3\envs\base-llm\lib\site-packages\sklearn\datasets\_base.py:1518: UserWarning: Retry downloading from url: https://ndownloader.figshare.com/files/5975967
warnings.warn(f"Retry downloading from url: {remote.url}")
训练样本数: 11314
测试样本数: 7532
类别数: 20
类别名称: ['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
正在提取 TF-IDF 特征...
训练集特征矩阵形状: (11314, 10000)
测试集特征矩阵形状: (7532, 10000)
正在转换为 PyTorch 张量...
模型结构:
TextClassifier(
(fc1): Linear(in_features=10000, out_features=128, bias=True)
(fc2): Linear(in_features=128, out_features=64, bias=True)
(fc3): Linear(in_features=64, out_features=20, bias=True)
(dropout): Dropout(p=0.5, inplace=False)
)
使用设备: cuda
开始训练...
Epoch [1/10], Loss: 2.6843
Epoch [2/10], Loss: 1.3785
Epoch [3/10], Loss: 0.7894
Epoch [4/10], Loss: 0.5311
Epoch [5/10], Loss: 0.3786
Epoch [6/10], Loss: 0.2989
Epoch [7/10], Loss: 0.2285
Epoch [8/10], Loss: 0.1875
Epoch [9/10], Loss: 0.1520
Epoch [10/10], Loss: 0.1311
测试集准确率: 81.05%
推理示例:
文本预览: From: v064mb9k@ubvmsd.cc.buffalo.edu (NEIL B. GANDLER)
Subject: Need info on 88-89 Bonneville
Organization: University at Buffalo
Lines: 10
News-Software: VAX/VMS VNEWS 1.41
Nntp-Posting-Host: ubvmsd....
真实类别: rec.autos
预测类别: misc.forsale
输出翻译与解释
1. 数据加载部分
text
正在加载 20newsgroups 数据...
D:\Users\app\miniconda3\envs\base-llm\lib\site-packages\sklearn\datasets\_base.py:1518: UserWarning: Retry downloading from url: https://ndownloader.figshare.com/files/5975967
warnings.warn(f"Retry downloading from url: {remote.url}")
训练样本数: 11314
测试样本数: 7532
类别数: 20
类别名称: ['alt.atheism', 'comp.graphics', ...]
-
训练样本数 11314:训练集共有 11,314 篇新闻文档。
-
测试样本数 7532:测试集有 7,532 篇文档。
-
类别数 20:数据集包含 20 个不同的新闻组类别。
-
类别名称 :列出了所有类别,例如
alt.atheism(无神论)、comp.graphics(计算机图形学)等,涵盖科技、体育、政治、宗教等多个领域。
2. 特征提取部分
text
正在提取 TF-IDF 特征...
训练集特征矩阵形状: (11314, 10000)
测试集特征矩阵形状: (7532, 10000)
-
TF-IDF 特征:将文本转换为数值向量,每个维度代表一个词(或词组)的 TF-IDF 值。
-
形状 (11314, 10000) :训练集有 11,314 个样本,每个样本被表示为一个 10,000 维的向量(因为我们设置了
max_features=10000,只保留最重要的 1 万个词)。 -
测试集形状 (7532, 10000):测试集也有相同的 10,000 维特征(特征空间与训练集一致)。
3. 转换为 PyTorch 张量
text
正在转换为 PyTorch 张量...
- 这一步将 NumPy 数组(稠密矩阵)转换为 PyTorch 的张量(
torch.tensor),以便输入神经网络。
4. 模型结构
text
模型结构:
TextClassifier(
(fc1): Linear(in_features=10000, out_features=128, bias=True)
(fc2): Linear(in_features=128, out_features=64, bias=True)
(fc3): Linear(in_features=64, out_features=20, bias=True)
(dropout): Dropout(p=0.5, inplace=False)
)
-
fc1:第一个全连接层,输入 10,000 维,输出 128 维(即隐藏层 1)。
-
fc2:第二个全连接层,输入 128 维,输出 64 维(隐藏层 2)。
-
fc3:第三个全连接层,输入 64 维,输出 20 维(对应 20 个类别,输出的是每个类别的得分 logits)。
-
dropout:在训练时随机丢弃 50% 的神经元,防止过拟合。
5. 训练设备
text
使用设备: cuda
- 你的电脑有 NVIDIA GPU,并且 PyTorch 检测到了它,因此使用 GPU 加速训练(cuda)。如果没有 GPU,这里会显示
cpu。
6. 训练过程
text
开始训练...
Epoch [1/10], Loss: 2.6843
Epoch [2/10], Loss: 1.3785
Epoch [3/10], Loss: 0.7894
Epoch [4/10], Loss: 0.5311
Epoch [5/10], Loss: 0.3786
Epoch [6/10], Loss: 0.2989
Epoch [7/10], Loss: 0.2285
Epoch [8/10], Loss: 0.1875
Epoch [9/10], Loss: 0.1520
Epoch [10/10], Loss: 0.1311
-
Epoch:模型完整遍历一次训练集称为一个 epoch。
-
Loss:损失值(交叉熵损失),衡量模型预测与真实标签的差距。数值越小表示模型拟合得越好。
-
观察:损失从 2.68 稳步下降到 0.13,说明模型在学习,并且效果逐渐变好。
7. 测试集评估
text
测试集准确率: 81.05%
- 模型在从未见过的 7,532 篇测试新闻上,正确分类的比例为 81.05%。这是一个不错的准确率,说明模型学到了有意义的特征,能够区分不同类别的新闻。
8. 推理示例
text
推理示例:
文本预览: From: v064mb9k@ubvmsd.cc.buffalo.edu (NEIL B. GANDLER)
Subject: Need info on 88-89 Bonneville
Organization: University at Buffalo
Lines: 10
News-Software: VAX/VMS VNEWS 1.41
Nntp-Posting-Host: ubvmsd....
真实类别: rec.autos
预测类别: misc.forsale
-
文本预览:这是测试集中第一篇新闻的部分内容,看起来是关于一辆 88-89 年庞蒂亚克 Bonneville 汽车的咨询。
-
真实类别 :
rec.autos(汽车主题的讨论组),与实际内容相符。 -
预测类别 :
misc.forsale(买卖信息组),模型预测错了。 -
分析:准确率 81% 意味着模型在 100 次预测中大约有 19 次会出错,所以这里预测错误是正常现象。可能的原因是:这则咨询帖虽然主题是汽车,但可能包含一些与买卖相关的词语(如"info"可能被模型误解为"广告"),或者模型对这类边界模糊的样本判断不准。
总结
-
成功:完整地运行了一个基于 TF-IDF 和全连接网络的文本分类项目,从数据加载到训练、评估、推理全部走通。
-
性能:模型达到了 81% 的测试准确率,证明了方法的有效性。
-
局限:从推理示例看,模型仍有错误,这提示我们可以通过调参、增加数据、使用更复杂的模型(如 CNN、BERT)来进一步提升效果。