当前位置: 首页 > news >正文

藏语自然语言处理入门 - 5 文本归类

简单可用的文本分类器(TF-IDF + 逻辑回归)

本文要做的三件小事

  1. 准备标注:从 sentences.txt 抽 60–90 句,填上类别。
  2. 训练模型:分词 → TF-IDF 特征 → 逻辑回归。
  3. 评估 + 预测:看报告、混淆矩阵;用脚本预测新句子(含“低置信度→other”兜底),还能输出“为什么这么判”。

做完你会得到:

  • labels.csv(你的标注)
  • clf.joblib模型快照:含 TF-IDF 词表/IDF + 逻辑回归权重)
  • report.txt(precision/recall/F1 + 混淆矩阵)
  • predict.py(预测脚本,带阈值兜底)
  • predict_explain.py(可解释预测:列出“贡献最大的词”)

语料准备: sentences.txtlabel_template.csvlabels_sample.csv


0) 环境与输入

pip install botok pandas regex scikit-learn joblib

你需要的文件:

  • sentences.txt(一行一句)
  • stopwords.txt(可选;没有也能跑)
  • label_template.csv(给出了模板,或你自己抽样生成)

1) 生成标注模板

如果你要从自己的 sentences.txt 抽样一批来标,保存并运行:

# make_label_template.py
# 从 sentences.txt 随机抽 N 句,做成 label_template.csv(label 先留空)
import argparse, random
from pathlib import Path
import pandas as pddef main():p = argparse.ArgumentParser()p.add_argument("--n", type=int, default=60, help="抽几句来标注")args = p.parse_args()sents = [s.strip() for s in Path("sentences.txt").read_text(encoding="utf-8").splitlines() if s.strip()]idx = list(range(1, len(sents)+1))random.seed(42); random.shuffle(idx)pick = sorted(idx[:min(args.n, len(sents))])rows = [{"sent_id": sid, "sentence": sents[sid-1], "label": ""} for sid in pick]pd.DataFrame(rows, columns=["sent_id","sentence","label"]).to_csv("label_template.csv", index=False, encoding="utf-8")print(f"✅ 已输出 label_template.csv({len(pick)} 行),请在 label 列填写类别。")if __name__ == "__main__":main()

填好 label_template.csvlabel 一列,另存为 labels.csv
(若想先跑通,可直接用我提供的 labels_sample.csv。)

类别建议(可自定义):greeting / question / statement


2) 训练:TF-IDF + Logistic Regression

# train_classifier.py
# 输入:sentences.txt(全体句子),labels.csv(你标的少量样本),stopwords.txt(可选)
# 输出:clf.joblib(模型快照),report.txt(评估报告与混淆矩阵)from pathlib import Path
import pandas as pd
import regex as re
from botok import WordTokenizer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix
import joblib# 1) 数据
sents = [s.strip() for s in Path("sentences.txt").read_text(encoding="utf-8").splitlines() if s.strip()]
df_label = pd.read_csv("labels.csv")  # 需要 sent_id,sentence,label
stopwords = set(Path("stopwords.txt").read_text(encoding="utf-8").splitlines()) if Path("stopwords.txt").exists() else set()# 2) 预处理(与前几课一致)
wt = WordTokenizer()
def tokenize(text: str):toks = [t.text for t in wt.tokenize(text) if t.text and t.text.strip()]toks = [t for t in toks if not re.fullmatch(r"[།༎]+", t) and t not in stopwords]return " ".join(toks) if toks else ""df_label = df_label.dropna(subset=["sent_id","label"])
df_label["sent_id"] = df_label["sent_id"].astype(int)
df_label["text"] = df_label["sent_id"].apply(lambda i: sents[i-1])
df_label["seg"] = df_label["text"].apply(tokenize)X = df_label["seg"].tolist()
y = df_label["label"].astype(str).tolist()# 3) 划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)# 4) 管道:TF-IDF + 逻辑回归
clf = make_pipeline(TfidfVectorizer(token_pattern=r"[^ ]+", lowercase=False, sublinear_tf=True),LogisticRegression(max_iter=2000, class_weight="balanced", n_jobs=1)
)
clf.fit(X_train, y_train)# 5) 评估
y_pred = clf.predict(X_test)
report = classification_report(y_test, y_pred, digits=4)
cm = confusion_matrix(y_test, y_pred, labels=sorted(set(y)))
cm_df = pd.DataFrame(cm, index=[f"true_{c}" for c in sorted(set(y))], columns=[f"pred_{c}" for c in sorted(set(y))])# 6) 保存
joblib.dump(clf, "clf.joblib")
with open("report.txt","w",encoding="utf-8") as f:f.write("=== Classification Report ===\n"+report+"\n\n=== Confusion Matrix ===\n"+cm_df.to_string())print("✅ 已保存 clf.joblib / report.txt")
print("\n—— 摘要 ——\n", report)
print("\n混淆矩阵:\n", cm_df)

运行:

python train_classifier.py

打开 report.txt:先看 F1;再看 混淆矩阵(哪两类最容易混)。

示例输出:

✅ 已保存 clf.joblib / report.txt—— 摘要 ——precision    recall  f1-score   supportgreeting     0.6000    1.0000    0.7500         3question     1.0000    0.7143    0.8333         7statement     0.8889    0.8889    0.8889         9accuracy                         0.8421        19macro avg     0.8296    0.8677    0.8241        19
weighted avg     0.8842    0.8421    0.8465        19混淆矩阵:pred_greeting  pred_question  pred_statement
true_greeting               3              0               0
true_question               1              5               1
true_statement              1              0               8

简要解读:

  • 整体:准确率 84.2%(16/19 正确)。三类都能用,但还有提升空间。
  • greeting召回 1.00(真问候都抓到了),但精确率 0.60(有 2 条“非问候”被错判成问候:来自 question 1 条、statement 1 条)。→ 问题:过度判为问候
  • question召回 0.714(7 条真问句只找回 5 条),精确率 1.00(一旦判为问句就基本没错)。→ 问题:漏判(2 条问句被错成 1 条问候、1 条说明)。
  • statement:precision/recall 均 0.889,较稳;只错收 1 条问句,漏掉 1 条说明。

混淆矩阵(看错在哪儿)

  • 真问候:3 全判对。
  • 真问句:被错成 问候 1说明 1
  • 真说明:被错成 问候 1

怎么改:

  1. 提高问句召回:多加含疑问结构的样本;停用词里别误删问句线索;必要时在预测时降低阈值(更敢判问句)。
  2. 抑制“过度问候”:给“问候”多收集更纯的套语样本,同时在预测时提高问候阈值或加入简单词典规则(如必须含常见问候词再判为问候)。

3) clf.joblib 是什么?里面装了啥?

  • 它是模型快照文件:下次不用重训,直接加载就能预测。

  • 内容(来自 scikit-learn Pipeline):

    1. TF-IDF 向量器:词表(vocabulary)、IDF 权重、参数等
    2. 逻辑回归分类器:各类权重 coef_、偏置 intercept_、类别名 classes_
  • 安全提醒:只加载你自己训练或可信来源的 .joblib/.pkl 文件。


4) 预测脚本怎么“判”的?(流程 + 代码)

流程

  1. 加载 clf.joblib(里头含 TF-IDF 和分类器)。
  2. 用训练时相同的规则分词、去停用词,拼成“空格分词串”。
  3. 送进 TF-IDF 向量器 → 得到稀疏向量;
  4. 逻辑回归对每个类算概率(softmax);
  5. 最大概率 < 阈值(默认 0.45)就判 other,防止“硬判错类”。

predict.py(可直接用)

# predict.py
# 用法:python predict.py --q "ཁྱེད་ལ་བཀྲ་ཤིས།" --threshold 0.45
import argparse, joblib, regex as re
from pathlib import Path
from botok import WordTokenizer
import numpy as npdef load_stopwords():p = Path("stopwords.txt")return set(p.read_text(encoding="utf-8").splitlines()) if p.exists() else set()def main():p = argparse.ArgumentParser()p.add_argument("--q", type=str, required=True, help="输入一句藏文")p.add_argument("--threshold", type=float, default=0.45, help="最低置信度阈值,低于则判 other")args = p.parse_args()clf = joblib.load("clf.joblib")stop = load_stopwords()wt = WordTokenizer()toks = [t.text for t in wt.tokenize(args.q) if t.text and t.text.strip()]toks = [t for t in toks if not re.fullmatch(r"[།༎]+", t) and t not in stop]seg = " ".join(toks) if toks else args.q.strip()# 若管道末端支持 predict_proba(逻辑回归支持)if hasattr(clf[-1], "predict_proba"):proba = clf.predict_proba([seg])[0]classes = clf[-1].classes_idx = int(np.argmax(proba))label, score = classes[idx], float(proba[idx])if score < args.threshold:print(f"预测: other  (置信度 {score:.3f}, 最可能类 {label})")else:print(f"预测: {label}  (置信度 {score:.3f})")else:label = clf.predict([seg])[0]print(f"预测: {label}  (此分类器不支持概率输出)")if __name__ == "__main__":main()

试试:

python predict.py --q "ཁྱེད་ལ་བསམ་ཚུལ་ག་རེ་ཡིན།"

示例输出:

预测: question  (置信度 0.555)

5) 想知道“为什么这么判”?——可解释预测版

把下面保存为 predict_explain.py:在给出预测的同时,列出贡献最大的若干词(谁把分数“抬”上去了)。

# predict_explain.py
import argparse, joblib, regex as re, numpy as np
from pathlib import Path
from botok import WordTokenizerdef tokenize_q(q, stop):wt = WordTokenizer()toks = [t.text for t in wt.tokenize(q) if t.text and t.text.strip()]toks = [t for t in toks if not re.fullmatch(r"[།༎]+", t) and t not in stop]return " ".join(toks) if toks else q.strip()def main():p = argparse.ArgumentParser()p.add_argument("--q", required=True)p.add_argument("--threshold", type=float, default=0.45)args = p.parse_args()clf = joblib.load("clf.joblib")stop = set(Path("stopwords.txt").read_text(encoding="utf-8").splitlines()) if Path("stopwords.txt").exists() else set()seg = tokenize_q(args.q, stop)vec = clf.named_steps["tfidfvectorizer"]lr  = clf.named_steps["logisticregression"]Xq = vec.transform([seg])                 # [1, V]classes = lr.classes_z = lr.decision_function(Xq).ravel()      # 线性打分 z = w·x + bproba = lr.predict_proba(Xq)[0]best = int(np.argmax(proba))print(f"预测: {classes[best]}  (置信度 {proba[best]:.3f})")if proba[best] < args.threshold:print(f"→ 低于阈值 {args.threshold:.2f},建议判为 other")# 贡献最大的词terms = vec.get_feature_names_out()x = Xq.tocoo()contrib = []for i, tfidf_val in zip(x.col, x.data):w = lr.coef_[best, i]                 # 该类对该词的权重contrib.append((terms[i], tfidf_val * w, tfidf_val, w))contrib.sort(key=lambda t: t[1], reverse=True)print("\n【贡献最大的词】(词, 贡献值, TF-IDF, 该类权重)")for term, score, tfidf_val, w in contrib[:10]:print(f"{term}\t{score:.4f}\t(tfidf={tfidf_val:.3f}, w={w:.3f})")if __name__ == "__main__":main()

直接去看“贡献最大的词”,就能明白模型根据什么做了判断。

示例输出:
预测: question  (置信度 0.555)【贡献最大的词】(词, 贡献值, TF-IDF, 该类权重)
ག་རེ་   0.3929  (tfidf=0.570, w=0.689)
ཡིན     0.1595  (tfidf=0.545, w=0.292)
ཁྱེད་   0.1590  (tfidf=0.428, w=0.371)
ལ་      -0.2585 (tfidf=0.441, w=-0.586)

6) 指标怎么看 & 怎么迭代

  • F1/准确率:先跑通流程,别苛求极限分数。
  • 混淆矩阵:看最容易混的两类,补样本、完善停用词。
  • 阈值:谨慎场景→调高 --threshold;宁可多判也不错过→调低。
  • 持续改进:每周多标一点,“少数类”多收集,模型自然变好。

7) 常见坑位(快速排雷)

  • 训练与预测分词不一致 → 必须用同一套分词/停用词规则(脚本里已经统一)。
  • 类别极不平衡 → 已启用 class_weight="balanced";同时尽量补齐弱势类。
  • 版本兼容 → 建议保存依赖版本:pip freeze > requirements.txt;加载 .joblib 用相近版本更稳。
  • 误杀关键词 → 停用词放宽一点,先看 tfidf_keywords_top50.csv 再决定。

8) 本课产出

  • labels.csv(你的标注)
  • clf.joblib(模型快照:TF-IDF + LR)
  • report.txt(评估指标与混淆矩阵)
  • predict.py(预测脚本,带阈值兜底)
  • predict_explain.py(可解释预测)

http://www.dtcms.com/a/437940.html

相关文章:

  • Stanford CS336 assignment1 | Transformer Language Model Architecture
  • 告别人工出题!PromptCoT 2.0 让大模型自己造训练难题,7B 模型仅用合成数据碾压人工数据集效果!
  • Prompt Programming - 用文字重构AI智能体系
  • 基于提示学习的多模态情感分析系统:从MULT到PromptModel的华丽升级
  • Node.js 图形渲染库对比:node-canvas 与 @napi-rs/canvas
  • 【LangChain】P10 LangChain 提示词模板深度解析(一):Prompt Template
  • C# TCP 服务端开发笔记(TcpListener/TcpClient)
  • 180课时吃透Go语言游戏后端开发6:Go语言的循环语句
  • wordpress+vps建站关键词语有哪些
  • 网站建设基本标准野花高清中文免费观看视频
  • hadoop-hdfs
  • VB6.0找不到该引用word,excel“Microsoft Excel 16.0 Object Library”解决方法
  • 读者-写者问题实现真正的写优先
  • 北京人力资源网站县区网站集约化建设
  • 从零开始,用WPS和DeepSeek打造数字人科普视频
  • netgear r6220 路由器,刷openwrt后,系统备份还原
  • 特价流量网站什么情况自己建设网站
  • 昂瑞微IPO前瞻:技术破局高端射频模组,国产替代第二波浪潮下的硬科技突围
  • 开源 全平台 哔哩哔哩缓存视频合并 Github地址:https://github.com/molihuan/hlbmerge_flutter
  • EPOLLONESHOT事件类型:多线程I/O中的“一次触发“机制
  • Github卡顿问题解决方案
  • 智慧园区数字孪生建设方案(WORD)
  • GitHub 热榜项目 - 日榜(2025-10-03)
  • 【QT常用技术讲解】自定义支持多选项的下拉框
  • 网址注册了怎么做网站小说网站自主建设
  • 基于PyTorch实现的MNIST手写数字识别神经网络笔记
  • 基于STM32单片机智能手表手环GSM短信上报GPS定位校时
  • 平台开发多少钱seo专员是什么意思
  • DAY23 单例设计模式、多例设计模式、枚举、工厂设计模式、动态代理
  • 在云服务器搭建部署私人饥荒联机版游戏服务器 [2025.10.3][ubuntu 24.04][腾讯云2核2G服务器]