这篇文章主要是记录在实验中的实验过程及部分代码,完整代码及
数据集请参考 我的主页另一篇文章。如下
http://t.csdnimg.cn/rP6qxhttp://t.csdnimg.cn/rP6qx
完整代码参考主页
http://t.csdnimg.cn/ZjtEEhttp://t.csdnimg.cn/ZjtEE
一、实验目的
(1)通过TF和TF-IDF特征,评估不同文本表示方法在分类任务中的有效性。
(2)探索文本预处理和特征选择对分类性能的优化作用。
(3)训练朴素贝叶斯、逻辑回归和XGBoost模型,使用TF特征,比较它们的表现。
(4)使用准确率、F1分数和AUC值评估TF特征在分类任务中的性能。
(5)对比TF与TF-IDF特征在相同模型下的性能差异。
(6)应用LSTM模型进行文本分类,探索其在小数据集上的表现。
二、实验内容与步骤
(1)实验任务
1. 对比TF, TF-IDF提取的特征在分类任务上的表现
1.1 读取搜狗文本分类数据集,导入标签
1.2 对文本数据进行预处理,分词,去停用词等
1.3 分别提取文档的TF特征,并进行必要的特征选择
1.4 数据集划分
1.5 使用朴素贝叶斯、LOGIT、XGBOOST算法,在训练集上使用TF作为特征,结合真实标签进行训练
1.6 在测试集上预测文本标签并评价使用TF作为特征时的准确率、F1和AUC值
1.7 重复3-6的操作,只是这里将TF-IDF作为输入特征,得到基于TF-IDF在同一测试集上的准确率、F1和AUC值
1.8 给出对比表格与分析
2.使用LSTM进行文本分类,并评价其性能(所用数据集同上)
(2)实验内容
1.文本数据集
数据集有五类,分别是:体育、健康、军事、教育、汽车,每一个类别有1000条文本数据。其中文本内容已经提取出来了,所以不需要进行提取新闻内容。
2.实验流程图
(3)实验步骤
1. 修改dataset.py以读取搜狗文本分类数据集
在已有的dataset.py文件中添加如下代码,读取搜狗文本分类数据集,并处理标签
或是直接利用嵌套的for循环遍历包含所有类别标签的列表categories和每个类别目录下的文件,使用file.read()读取文件的全部内容,并将其存储在变量text中,并将文件内容及其对应的类别标签添加到一个Pandas DataFrame中,从而构建起用于文本分类任务的数据集。
2、对文本数据进行预处理
导入“百度停用词表.txt”读取停用词,对DataFrame中的文本数据进行预处理,使用jieba库进行中文分词和去除停用词。
3、TF-IDF 与TF的特征提取
- TF
词频表示一个词语在文档中出现的次数,不考虑其在整个文档集合中的分布情况。
利用 Scikit-learn提供的库CountVectorizer,用于将文本转换为词频矩阵。在特征提取时,使用fit_transform方法,CountVectorizer学习训练数据集的词汇(计算每个词的出现次数),然后生成一个词频矩阵X_tf。在这个矩阵中,每个元素表示特定文档中特定词语的出现次数。
②TF-IDF
TF-IDF(Term Frequency-Inverse Document Frequency)是一种衡量词语重要性的统计方法,它会根据词语在文档中出现的频率(TF)以及在整个文档集合中的文档频率(IDF)来计算权重。高频出现在某文档中的词语但低频出现在整个文档集合中的词语会获得较高的TF-IDF值。
采取直接利用Scikit-learn库中的一个类,将文本数据转换为TF-IDF
,在进行特征提取时,通过调用fit_transform方法,模型首先学习训练数据集(df['text'])的词汇统计信息(即“fit”阶段),然后基于学习到的信息将文本数据转换为TF-IDF特征向量(即“transform”阶段)。结果X_tfidf是一个稀疏矩阵,矩阵的每一行代表一个文档,每一列对应一个特征(词语),矩阵中的值是相应词语在对应文档中的TF-IDF值。
- 两者对比
TF仅计算词语在文档中出现的频次,而TF-IDF在此基础上还考虑了词语的文档频率,降低了在多篇文档中普遍出现的词语的权重,从而增强了对文档独特信息的体现。TF-IDF相比TF更能有效区分和突出文档之间的差异,尤其适用于信息检索、文本分类等任务。
4、进行标签编码和数据集划分
标签编码部分直接利用sklearn的库,将非数字的类别标签(如字符串)转换为整数编码,先创建一个le对象,准备用于编码工作,再进行调用fit_transform()进行编码转换,构建了一个映射关系,将每个类别名映射到一个整数。
数据集划分:按照80%(训练集)和20%(测试集)的比例随机划分数据集,每一次调用train_test_split都会返回四个变量,分别是训练集特征、测试集特征、训练集标签、测试集标签。在第二次调用时,由于只需要特征矩阵用于后续模型训练,所以后两个返回的测试集标签被忽略(用_代替)。
random_state=42:为什么选择42,这并没有特殊的数学或科学意义。在编程和科幻文化中,42是一个著名的梗,源自道格拉斯·亚当斯的《银河系漫游指南》中提到的“生命、宇宙以及一切的答案”。 意在表达一个观念:即便有了所有问题的答案,如果不知道问题本身是什么,那么这个答案也是毫无意义的。
5、模型训练与评估
①创建了一个字典models并设置逻辑回归的最大迭代次数为1000,以防止因默认迭代次数不足导致的收敛问题。后续还可以新增SVM进行多方面的比较
②定义训练及评估函数并调用
遍历models字典中的每个模型,使用模型名称作为键,模型实例作为值。使用predict方法在测试集上进行预测,得到预测标签y_pred。使用predict_proba方法获取测试集上每个类别的预测概率,并计算每个模型的评估指标返回results字典
6、打印结果
(3)代码优化
1、增加对文本数据的可视化
可以了解每一类文本数据的大体分布,采用词云Word Cloud每个类别生成一个词云,并通过matplotlib库显示出来。结果如下
2、利用prettytable来美化表格
第一次实验结果如下,不利于进行对比分析。
使用了 PrettyTable 类来创建一个表格,并设置了列标题。然后,它遍历 results_tf 和 results_tfidf 字典中的每个模型,将对应的评估结果作为行添加到表格中。最后,打印出格式化的表格。结果如下。
(4)LSTM文本分类
1、LSTM介绍(Long Short-Term Memory):
LSTM的核心是它的门控机制,包括:
遗忘门(Forget Gate):决定哪些信息应该从细胞状态中被遗忘。
输入门(Input Gate):决定哪些新的信息将被存储在细胞状态中。
输出门(Output Gate):决定下一个隐藏状态的输出是什么。
LSTM广泛应用于各种序列建模任务,包括语言模型、文本生成、机器翻译、语音识别、时间序列预测等领域。
2、选择的原因
相比于BERT来说,LSTM能够处理序列数据,捕捉长距离依赖关系,并且需要的计算资源较少,针对搜狗文本的简单的分类任务,文本数据集为5000个样本可能足够训练一个基础的模型,LSTM或逻辑回归。但是像BERT这样的复杂模型,5000个样本可能不足以训练一个非常高精度的模型,因为这些模型通常需要大量的数据来学习有效的特征表示并避免过拟合。
LSTM也存在缺点:
- 对于非常长的序列,可能仍然会遇到困难(尽管称为“长短期”记忆,但实际应用中记忆能力有限)。
- 需要手动特征工程,比如设计词嵌入。
3、代码流程图
对于LSTM模型,通常不直接使用像TF-IDF这样的稀疏矩阵特征表示,而是直接处理词索引序列,并利用嵌入层来学习词向量表示,最后输出打印准确率、F1和AUC值。
4、实验结果
根据结果显示,准确率44%,F1=0.4341,AUC=0.7847,说明模型在当前的文本分类任务上的表现一般,模型正确预测的比例不高。根据训练损失随epoch变化的曲线看,在epoch=8时,loss达到最低,结合右图的准确率,同样epoch=8时结果也是最好的,修改代码,尝试将epoch改为8再观察结果。
结果如下所示,准确率86%,F1=0.8634,AUC=0.9723,说明目前的模型在任务上表现得非常好。
5、代码优化
由于模型准确率已经很高,可以进行错误分析,检查模型预测错误的样本。了解模型在哪些类型的数据上出错,发现数据集中的特定模式或异常值。
首先,需要识别模型预测错误的样本,即预测标签与真实标签不一致的那些数据点。然后,可以进一步分析这些错误样本的特征,以寻找错误模式。结果如下所示:错误分类样本为75个,其中还出现了很多数字、 符号、空格
再进行文本清洗:去除文本中的无关字符、特殊符号和多余的空格,并统一文本中的日期、时间等格式化表达。使用Python的正则表达式(regex)库re,在预处理之后进行,结果如下所示,文本的空格及特殊符号被去除了,文本变得更简洁,但实际分类错误样本增加到了153。
三、实验代码
(给出主代码,以及主代码里每一个函数的功能,输入和输出)
1、代码
# 设置中文字体和解决负号显示问题
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 读取数据
data_path = 'CH10data/搜狗文本分类语料库迷你版'
categories = ['体育', '健康', '军事', '教育', '汽车']
df = pd.DataFrame()
for idx, category in enumerate(categories):
for filename in os.listdir(os.path.join(data_path, category)):
with open(os.path.join(data_path, category, filename), 'r', encoding='utf-8') as file:
text = file.read()
new_row = pd.DataFrame({'text': [text], 'label': [category]})
df = pd.concat([df, new_row], ignore_index=True)
# 加载停用词表
stopwords_path = '百度停用词表.txt'
with open(stopwords_path, 'r', encoding='utf-8') as file:
stopwords = set([line.strip() for line in file.readlines()])
# 清洗文本函数
def clean_text(text):
text = re.sub(r'[<> ]', '', text) # 去除特殊字符
text = re.sub(r'\s+', ' ', text).strip() # 去除多余空格
return text
# 应用清洗函数
df['text'] = df['text'].apply(clean_text)
# 预处理(分词和去停用词)
df['text'] = df['text'].apply(lambda x: ' '.join([word for word in jieba.cut(x) if word not in stopwords]))
# 特征提取
vectorizer_tfidf = TfidfVectorizer()
X_tfidf = vectorizer_tfidf.fit_transform(df['text'])
# 标签编码
le = LabelEncoder()
y = le.fit_transform(df['label'])
# 划分数据集
X_train_tfidf, X_test_tfidf, y_train, y_test = train_test_split(X_tfidf, y, test_size=0.2, random_state=42)
# 构建 LSTM 模型
tokenizer = Tokenizer(num_words=5000, oov_token="<OOV>")
tokenizer.fit_on_texts(df['text'])
sequences = tokenizer.texts_to_sequences(df['text'])
max_length = 100
X = pad_sequences(sequences, maxlen=max_length, padding='post', truncating='post')
# 将标签编码为 one-hot 编码
labels = pd.get_dummies(df['label']).values
# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.2, random_state=42)
# 定义 LSTM 模型
embedding_dim = 100
lstm_model = Sequential([
Embedding(input_dim=len(tokenizer.word_index)+1, output_dim=embedding_dim, input_length=max_length),
SpatialDropout1D(0.2),
LSTM(128, dropout=0.2, recurrent_dropout=0.2),
Dense(labels.shape[1], activation='softmax')
])
lstm_model.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
# 训练模型
history = lstm_model.fit(X_train, y_train, epochs=9, batch_size=64, validation_split=0.2)
# 评估模型
_, accuracy = lstm_model.evaluate(X_test, y_test)
print(f"Test Accuracy: {accuracy * 100:.2f}%")
# 预测最可能的类别
y_pred_classes = np.argmax(lstm_model.predict(X_test), axis=1)
y_true_classes = np.argmax(y_test, axis=1)
# 计算 F1 分数
f1_macro = f1_score(y_true_classes, y_pred_classes, average='macro')
print(f"Macro F1 Score: {f1_macro:.4f}")
# 计算 AUC
roc_auc = roc_auc_score(y_test, np.max(lstm_model.predict(X_test), axis=1), multi_class='ovr')
print(f"AUC: {roc_auc:.4f}")
# 绘制训练损失和准确率
plt.figure(figsize=(12, 4))
# 绘制训练损失
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
# 绘制训练准确率
plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
# 显示图表
plt.show()
# 错误样本分析
incorrect_count = np.sum(y_pred_classes != y_true_classes)
print(f"分类错误的样本个数: {incorrect_count}")
# 收集错误样本信息
incorrect_samples = [(idx, le.inverse_transform([true_label_idx])[0], le.inverse_transform([pred_label_idx])[0], df.iloc[idx]['text']) for idx, (true_label_idx, pred_label_idx) in enumerate(zip(y_true_classes, y_pred_classes)) if true_label_idx != pred_label_idx]
# 错误样本审查函数
def review_incorrect_samples(incorrect_samples):
print("错误样本及其标签:")
for sample in incorrect_samples:
print(f"样本索引: {sample[0]}, 真实标签: {sample[1]}, 预测标签: {sample[2]}")
print(f"样本文本: {sample[3]}")
print("-" * 60)
# 调用审查函数
review_incorrect_samples(incorrect_samples)
2、函数介绍
(1)train_and_evaluate_models(models, X_train, X_test, y_train, y_test)
功能:训练给定的模型列表,并评估每个模型的性能。
输入:models: 字典。X_train, X_test: 训练集和测试集的特征矩阵。y_train, y_test: 训练集和测试集的标签向量。
输出:每种模型的准确率、F1分数和AUC值。
(2)tokenizer.fit_on_texts(df['text'])和sequences = tokenizer.texts_to_sequences(df['text'])
功能:使用Keras的Tokenizer进行文本向量化
输入:文本列表。
输出:fit_on_texts无直接输出,但会在tokenizer内部构建词汇表;texts_to_sequences输出为整数序列列表,表示文本中的单词被映射到的索引。
(3)lstm_model.compile(optimizer=Adam(learning_rate=0.001),loss='categorical_crossentropy', metrics=['accuracy'])
功能:配置LSTM模型的优化器、损失函数和评价指标。
输入:优化器类型及参数、损失函数类型、模型评估使用的指标。
(4)history = lstm_model.fit(X_train, y_train, epochs=9, batch_size=64, validation_split=0.2)
功能:训练LSTM模型,使用训练数据,并在验证集上评估。
输入:训练数据集、训练标签、迭代轮数、批量大小、验证集划分比例。
输出:训练历史,包含每个epoch的训练和验证损失及精度。
(5)review_incorrect_samples(incorrect_samples)
功能:打印出预测错误的样本信息,索引、文本内容、真实标签与预测标签。
输入:错误样本的列表,每个元素是包含样本信息的字典。
输出:直接在控制台打印错误样本的详细信息。
四、实验结果及分析
(1)错误分析及解决办法
①wordcloud 库在渲染词云时需要使用一个支持绘制文本的字体,如果字体文件损坏或者格式不被支持,就可能出现这个错误。
解决:强制 wordcloud 使用 matplotlib 的内置字体
无法解决,重新下载中文字体导入路径
(2)词云无法显示
重新下载中文字体导入路径
(2)实验结果
①TF与TF-IDF
分析:在对使用TF和TF-IDF特征的文本分类模型进行评估后,我们发现所有模型均展现出较高的准确率和F1分数,AUC分数接近完美,显示出模型具有优秀的分类性能和排序能力。
②数据本文词云可视化结果
③LSTM结果
准确率86%,F1=0.8634,AUC=0.9723,说明目前的模型在任务上表现得非常好。