在自然语言处理领域,文本分类是基础而重要的任务之一。本文将介绍如何使用FastText构建一个高效的中文意图分类系统,适用于聊天机器人、客服系统等应用场景。我在互联网上翻到的相关文章,大多数都是仅仅给一个简单的训练代码,但最终训练出来的模型效果都差强人意。本文主要详细描述了一个二分类的入门版本调优过程,方便大家逐步学习。
FastText是大神Tomas Mikolov在2016年开发的一个高效文本分类和词向量训练库。算是Word2Vec的“弟弟”(出生更晚,性能更高)。其主要优势包括:
本文使用点评的二分类数据(对店铺和餐饮的正面或负面的评价,可以简单的认为是文本内容情绪的分类),通过FastText训练出一个模型,可以预测一段文本的情绪。全流程包括:
下载点评评价数据的二分类数据。
点击下载后,可以获得train.csv文件,文件内容大致为:
可见数据格式和FastText要求不一致,需要进行数据清洗。
清洗代码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
本脚本用于将大众点评格式的CSV数据(如train_dianping.csv)转换为FastText训练所需的格式。
输入: train_dianping.csv(第1列为文本内容,第2列为情感标签:0=负面,1=正面)
输出: fasttext_data_dianping.txt(每行为: __label__negative/positive + 空格 + 文本内容)
"""
import csv # 导入csv模块用于处理CSV文件
import os # 导入os模块用于文件路径和存在性判断
def process_dianping_to_fasttext(input_file, output_file):
"""
读取大众点评格式的CSV文件,转换为FastText格式并写入输出文件。
参数:
input_file (str): 输入的CSV文件路径
output_file (str): 输出的FastText格式文件路径
返回:
bool: 处理是否成功
"""
processed_count = 0 # 统计已处理的样本数
try:
# 以utf-8编码分别打开输入和输出文件,准备读写
with open(input_file, 'r', encoding='utf-8') as infile, \
open(output_file, 'w', encoding='utf-8') as outfile:
csv_reader = csv.reader(infile) # 创建CSV读取器
# 跳过表头(如果有表头可取消注释)
# next(csv_reader, None)
for row in csv_reader:
# 确保每行至少有两列(文本和标签)
if len(row) >= 2:
text = row[0].strip() # 获取文本内容并去除首尾空白
sentiment = row[1].strip() # 获取情感标签并去除首尾空白
# 跳过空文本
if not text:
continue
# 将情感标签转换为FastText格式的label
if sentiment == '0':
label = '__label__negative'
elif sentiment == '1':
label = '__label__positive'
else:
# 遇到未知标签时警告并跳过
print(f"Warning: Unknown sentiment value '{sentiment}' in row {processed_count + 1}")
continue
# 清理文本内容:去除多余空格和换行
text = ' '.join(text.split())
# 按FastText要求写入:标签+空格+文本
outfile.write(f"{label} {text}\n")
processed_count += 1
# 每处理10000条输出一次进度
if processed_count % 10000 == 0:
print(f"Processed {processed_count} records...")
except FileNotFoundError:
# 输入文件不存在时的异常处理
print(f"Error: Input file '{input_file}' not found.")
return False
except Exception as e:
# 其他异常处理
print(f"Error processing file: {e}")
return False
# 输出处理结果
print(f"Successfully processed {processed_count} records.")
print(f"Output saved to: {output_file}")
return True
def main():
"""
主程序入口:指定输入输出文件路径,检查输入文件是否存在,调用处理函数,输出处理进度和部分结果。
"""
# 指定输入和输出文件名
input_file = "train_dianping.csv"
output_file = "fasttext_data_dianping.txt"
# 检查输入文件是否存在,避免后续出错
if not os.path.exists(input_file):
print(f"Error: Input file '{input_file}' not found in current directory.")
print("Please make sure the file exists and run the script from the correct directory.")
return
print(f"Processing {input_file}...")
print(f"Output will be saved to {output_file}")
# 调用处理函数进行格式转换
success = process_dianping_to_fasttext(input_file, output_file)
if success:
print("\nProcessing completed successfully!")
# 成功后展示输出文件的前5行,方便用户快速查看结果格式
try:
with open(output_file, 'r', encoding='utf-8') as f:
print("\nFirst 5 lines of output:")
for i, line in enumerate(f):
if i >= 5:
break
print(f"{i+1}: {line.strip()}")
except Exception as e:
print(f"Error reading output file: {e}")
else:
print("Processing failed!")
# 脚本入口判断,支持直接运行
if __name__ == "__main__":
main()
使用上述代码,清洗完成后,文件内容大致为:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import fasttext
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import os
import time
def prepare_data():
"""
准备中文意图分类的训练数据
返回:
train_file (str): 训练数据文件路径
test_file (str): 测试数据文件路径
"""
# 从文件读取数据
data = []
with open('fasttext_data_dianping.txt', 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line: # 确保不是空行
# 分割标签和文本,只分割第一个空格
if ' ' in line:
label, text = line.split(' ', 1)
data.append((label.strip(), text.strip()))
print(f"原始数据集大小: {len(data)}")
# 创建DataFrame
df = pd.DataFrame(data, columns=["label", "text"])
# 统计各个类别的数据量
label_counts = df['label'].value_counts()
print("\n各类别数据量统计:")
for label, count in label_counts.items():
print(f" {label}: {count}条")
# 对于超过1000条数据的分类,只选择1000条
balanced_data = []
for label in label_counts.index:
label_data = df[df['label'] == label]
if len(label_data) > 100000:
print(f" 将{label}类别数据从{len(label_data)}条减少到100000条")
label_data = label_data.sample(100000, random_state=1)
balanced_data.append(label_data)
# 合并平衡后的数据
balanced_df = pd.concat(balanced_data)
print(f"平衡后的数据集大小: {len(balanced_df)}")
# 分割训练集和测试集
train_df, test_df = train_test_split(balanced_df, test_size=0.1, random_state=42)
# 保存为FastText格式的临时文件
train_file = "fasttext_train.txt"
test_file = "fasttext_test.txt"
# 写入训练数据
with open(train_file, 'w', encoding='utf-8') as f:
for _, row in train_df.iterrows():
f.write(f"{row['label']} {row['text']}\n")
# 写入测试数据
with open(test_file, 'w', encoding='utf-8') as f:
for _, row in test_df.iterrows():
f.write(f"{row['label']} {row['text']}\n")
return train_file, test_file
def train_model(train_file, model_path="fasttext_model.bin"):
"""
训练FastText分类器
参数:
train_file (str): 训练数据文件路径
model_path (str): 模型保存路径
返回:
model: 训练好的FastText模型
"""
start_time = time.time()
model = fasttext.train_supervised(
input=train_file,
lr=0.05, # 学习率
epoch=100, # 训练轮数
wordNgrams=2, # n-gram特征的最大长度
dim=128, # 词向量维度
loss='softmax', # 损失函数
bucket=200000, # 词典大小
lrUpdateRate=50, # 学习率更新频率
minCount=3, # 最小词频
verbose=2, # 输出详细信息
thread=8, # 使用的线程数
ws=5, # 窗口大小
minn=3, maxn=6 # n-gram特征的最小和最大长度
)
# 保存模型
model.save_model(model_path)
training_time = time.time() - start_time
print(f"模型训练完成,耗时: {training_time:.2f}秒")
print(f"模型已保存至 {model_path}")
return model
def evaluate_model(model, test_file):
"""
在测试数据上评估模型
参数:
model: 训练好的FastText模型
test_file (str): 测试数据文件路径
"""
# 评估模型
result = model.test(test_file)
print(f"\n测试样本数量: {result[0]}")
print(f"精确率: {result[1]:.4f}")
print(f"召回率: {result[2]:.4f}")
print(f"F1分数: {2 * result[1] * result[2] / (result[1] + result[2]):.4f}")
# 获取测试数据的预测结果
y_true = []
y_pred = []
with open(test_file, 'r', encoding='utf-8') as f:
for line in f:
label = line.split(' ')[0]
text = ' '.join(line.split(' ')[1:]).strip()
y_true.append(label)
pred_label = model.predict(text)[0][0]
y_pred.append(pred_label)
# 打印分类报告
print("\n详细分类报告:")
print(classification_report(y_true, y_pred))
def predict_example(model, text):
"""
对单个示例进行预测
参数:
model: 训练好的FastText模型
text (str): 待分类的文本
"""
labels, probs = model.predict(text)
print(f"\n文本: {text}")
for label, prob in zip(labels, probs):
print(f"预测标签: {label.replace('__label__', '')}")
print(f"置信度: {prob:.4f}")
def main():
# 准备数据
print("准备数据中...")
train_file, test_file = prepare_data()
# 训练模型
print("\n训练模型中...")
model = train_model(train_file)
# 评估模型
print("\n评估模型中...")
evaluate_model(model, test_file)
# 示例预测
print("\n进行示例预测...")
examples = [
"您好,请问有什么可以帮您的?",
"我考试考砸了,我妈妈骂了我",
"播放一首周杰伦的七里香",
"太感谢了!!!!",
"我可以用中文和你对话吗?",
"请帮我预定明天上午10点飞往北京的机票",
"这个产品有什么优惠活动吗?"
]
for example in examples:
predict_example(model, example)
print("\n训练和评估完成!")
if __name__ == "__main__":
main()
训练完成后,可以获得一个fasttext_model.bin模型文件。
在自然语言处理任务中,数据质量直接影响模型性能。我们的实现包含以下关键步骤:
# 数据平衡处理
balanced_data = []
for label in label_counts.index:
label_data = df[df['label'] == label]
if len(label_data) > 100000:
print(f" 将{label}类别数据从{len(label_data)}条减少到100000条")
label_data = label_data.sample(100000, random_state=1)
balanced_data.append(label_data)
这种方法解决了类别不平衡问题,防止模型偏向数据量大的类别。由于点评的数据集正面数据和负面数据都在2.2w左右,因此这里不会影响到训练数据。
我们使用了以下关键参数配置:
model = fasttext.train_supervised(
input=train_file,
lr=0.05, # 学习率
epoch=100, # 训练轮数
wordNgrams=2, # n-gram特征的最大长度
dim=128, # 词向量维度
loss='softmax', # 损失函数
bucket=200000, # 词典大小
lrUpdateRate=50, # 学习率更新频率
minCount=3, # 最小词频
verbose=2, # 输出详细信息
thread=8, # 使用的线程数
ws=5, # 窗口大小
minn=3, maxn=6 # n-gram特征的最小和最大长度
)
参数说明:
我们不仅计算整体准确率,还提供详细的分类报告:
print(classification_report(y_true, y_pred))
该报告包括精确率(precision)、召回率(recall)和F1分数,帮助开发者全面了解模型在各类别上的表现。
model = fasttext.train_supervised(
input=train_file,
)
由于第一轮负类样本的召回率仅为0.03,导致97%的负类样本被误判为正类,且整体准确率仅为50%,相当于随机猜测。这表明模型未能学习到有效的分类特征。针对特征提取不足的问题,调整了wordNgrams参数为2,以引入bigram特征,从而增强上下文语义的捕捉能力。为了解决模型欠拟合的问题,将epoch增加到50,并降低学习率至0.01,以实现更稳定的收敛。此外,将词向量维度扩展到128,以增强语义表达能力。在损失函数选择上,虽然目前使用'softmax',但未来可考虑'ova'以提高性能。
model = fasttext.train_supervised(
input=train_file,
lr=0.01, # 学习率
epoch=50, # 训练轮数
wordNgrams=2, # n-gram特征的最大长度
dim=128, # 词向量维度
loss='softmax', # 损失函数
)
效果变差
第二次训练结果显示,负类召回率显著提升(从0.03提升至0.68),但正类召回率急剧下降(从0.98降至0.32),导致整体F1分数仅为0.49,模型效果反而恶化。为解决这一问题,我们进行了参数调整:首先,通过启用3至5字符的子词特征(minn=3, maxn=5),增强模型对词形变化的感知能力,以解决原模型无法捕捉形态特征的问题;其次,将哈希桶数量从约200万下降至20万(bucket=200000),以减少由哈希冲突导致的特征丢失;第三,调整学习率更新策略(lrUpdateRate=100),每100个词更新一次,提高梯度下降的平稳性。此外,为加速训练和便于诊断,我们引入多线程支持(thread=4)和更详细的训练日志输出(verbose=2)。这些调整旨在优化子词特征和哈希桶配置,增强训练稳定性,解决模型对正类样本特征学习不足的问题。从整体策略来看,我准备调整聚焦于改善特征工程和训练稳定性,以系统性解决参数敏感性问题。
model = fasttext.train_supervised(
input=train_file,
lr=0.01, # 学习率
epoch=50, # 训练轮数
wordNgrams=2, # n-gram特征的最大长度
dim=128, # 词向量维度
loss='softmax', # 损失函数
bucket=200000, # 词典大小
lrUpdateRate=100, # 学习率更新频率
verbose=2, # 输出详细信息
thread=4, # 使用的线程数
minn=3, maxn=5 # n-gram特征的最小和最大长度
)
在第三次训练结果中,模型性能显著提升,F1从0.49提升至0.68,但仍有优化空间。核心优化策略包括:增加训练轮数(epoch从50增加至100)和提高学习率(lr从0.01提高到0.05),以应对训练不充分的问题;调整上下文窗口大小以更好捕捉长距离依赖;通过将minCount设置为3来过滤低频噪声词,并扩展子词特征的最大长度(maxn从5到6),提升特征质量。此外,固定学习率的更新频率从100调整到50,加速后期收敛;线程从4增至8,提高训练效率。目标是将平均损失减少到低于0.5,将训练时间缩短至少于30秒,F1分数提升至0.75以上。这些调整有望在现有有效架构上,通过扩大训练规模和优化特征选择,突破当前性能瓶颈,预计F1分数将突破0.70,训练时间减少约40%,并显著净化特征集。
model = fasttext.train_supervised(
input=train_file,
lr=0.05, # 学习率
epoch=100, # 训练轮数
wordNgrams=2, # n-gram特征的最大长度
dim=128, # 词向量维度
loss='softmax', # 损失函数
bucket=200000, # 词典大小
lrUpdateRate=50, # 学习率更新频率
minCount=3, # 最小词频
verbose=2, # 输出详细信息
thread=8, # 使用的线程数
ws=5, # 窗口大小
minn=3, maxn=6 # n-gram特征的最小和最大长度
)
在本轮的模型优化中,性能确实取得了一些显著的进步。具体来说,F1分数从0.68提升到了0.70,并且正负类的平衡性得到了改善,均达到约0.70。准确率也已接近70%,达到69.55%。在训练效率方面,通过将词汇量从93,459减少至740(利用minCount=3有效过滤了99%的低频噪声),损失显著降低,从0.69降至0.12,展示了模型拟合能力的增强。另外,窗口大小设置为5以及子词特征范围设置在3到6之间的组合,成功捕捉了上下文语义。
其中也应关注现存的问题。首先,词汇量从93k骤降至740,意味着过滤掉了99.2%的词汇,这可能导致某些重要语义特征的丢失,尤其是特定领域的术语。另外,尽管性能有所提升,训练时间却翻倍,从61秒增加至145秒(由于epoch设置为100),同时F1分数仅提升了0.015,收益不明显。此外,训练损失显著降低至0.12,与测试F1 0.70的差距暗示了过拟合的风险。在模型性能方面,负类的精度(0.71)高于召回率(0.68),说明分类偏保守;而正类的召回率(0.71)高于精度(0.68),则表明存在误判问题,需要进一步优化。
FastText在中文意图分类任务中表现出显著优势:
该技术可应用于多种业务场景:
FastText为中文意图分类提供了一种高效实用的解决方案。通过本文介绍的方法,开发者可以逐步调整参数,训练模型,构建高性能的文本分类系统。其简洁的API接口和出色的性能表现,使其成为工业级应用的首选之一。
随着NLP技术的不断发展,FastText仍将在实际业务场景中发挥重要作用,特别是在需要快速部署和高效运行的场景中。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
这次台风叫什么 | 橘子是什么季节 | 物上代位性是什么意思 | 芒果什么时候成熟 | 抽筋缺什么维生素 |
家有蝙蝠是什么兆头 | 宫保鸡丁属于什么菜系 | 阳刃是什么意思 | 甲状腺炎是什么引起的 | 牡蛎是什么 |
玉树临风什么意思 | 人生价值是什么 | 精神什么意思 | 杏仁有什么作用和功效 | 梦见自己掉头发是什么征兆 |
袁崇焕为什么杀毛文龙 | 大便次数多什么原因 | 渐冻症是什么病 | 脑血管堵塞吃什么药最好 | 为什么不能叫醒梦游的人 |
叩齿是什么意思hcv9jop1ns6r.cn | 流产什么样的症状表现hcv8jop2ns5r.cn | 女人什么时候容易怀孕hcv8jop4ns6r.cn | 补血吃什么药最快最好hcv8jop5ns1r.cn | 三个六代表什么意思hcv8jop0ns0r.cn |
什么是碳水食物有哪些hcv8jop3ns7r.cn | 什么叫生僻字hcv8jop5ns4r.cn | 舌苔很厚很白什么原因hcv7jop7ns1r.cn | 喉咙痛吃什么药效果最好hkuteam.com | hbv病毒是什么意思hcv8jop5ns3r.cn |
呵护是什么意思hcv9jop3ns9r.cn | 频繁流鼻血是什么病的前兆hcv9jop5ns6r.cn | 靳东妹妹叫什么名字hcv8jop7ns9r.cn | 竹节麻是什么面料hcv8jop9ns6r.cn | 属马的和什么属相最配hcv9jop1ns3r.cn |
不正常的人有什么表现hcv9jop4ns6r.cn | 荷叶又什么又什么hcv9jop2ns9r.cn | 子宫肌腺症是什么病hcv9jop2ns6r.cn | 一模一样的意思是什么hcv7jop5ns0r.cn | pv是什么意思hcv9jop0ns1r.cn |