Datawhale 大模型算法全栈基础篇 202602第2次笔记

作业:

目录

[第一节 初级分词技术](#第一节 初级分词技术)

方案一:降低"九""头"词频,并添加"九头"词条

方案二:直接添加"九头虫"词条

准备测试代码

对比分析

[第三节 从主题模型到 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)来进一步提升效果。

相关推荐
样例过了就是过了2 小时前
LeetCode热题100 反转链表
数据结构·算法·leetcode·链表
weixin_448119942 小时前
Datawhale 大模型算法全栈基础篇 202602第3次笔记
笔记·rnn·算法
紫陌涵光2 小时前
538. 把二叉搜索树转换为累加树
c++·算法·leetcode
Zik----2 小时前
Leetcode35 —— 搜索插入位置(二分查找)
数据结构·算法·leetcode
ding_zhikai2 小时前
【Web应用开发笔记】Django笔记3:模版的用法-实现一个简单的网页
笔记·后端·python·django
yi.Ist2 小时前
牛客寒假训练营3
c++·学习·算法
24白菜头2 小时前
2026-2-23:LeetCode每日一题(动态规划专项)
笔记·学习·算法·leetcode·动态规划
土拨鼠烧电路2 小时前
笔记09:产品与研发:爆款、成分与上市生死时速
笔记