数据挖掘之文本分类挖掘

文本分类是NLP领域最经典的使用场景之一,它涉及到自动将文本数据分配到预设的类别或标签中。这项技术在多个应用场景中扮演着重要角色,例如情感分析、新闻归类、主题识别以及垃圾邮件检测等。

文本分类任务可根据是否使用深度学习方法将文本分类主要分为以下两大类:

  • 基于传统机器学习的文本分类,如 TF-IDF文本分类
  • 基于深度学习的文本分类,如Facebook开源的FastText文本分类Text-CNN 文本分类Text-CNN 文本分类等。

在本实验中将通过传统机器学习和深度学习两个方面实现文本分类挖掘任务。

数据集介绍

选用大众点评评价数据,该数据集是一个中文的用于文本分类的二分类数据集,包含了用户对服务和产品的评价。实验的目的是通过文本分类来判断用户评价的情感倾向,即判断情感是正面的还是负面的。数据集链接为:大众点评评价数据 · 数据集 (modelscope.cn)

基于机器学习的文本分类挖掘

数据加载

首先读取数据集,并查看数据集的具体内容。训练集读取如下:

1
2
from modelscope.msdatasets import MsDataset
ds = MsDataset.load('DAMO_NLP/yf_dianping', subset_name='default', split='train')

查看数据集的格式,可以看到主要分为三列,一列是sentence表示评价内容;一列lable表示情感偏向;一列是dataset表示数据集来源。

1
next(iter(ds))

对验证集进行相同处理

1
2
3
from modelscope.msdatasets import MsDataset
ds_val = MsDataset.load('DAMO_NLP/yf_dianping', subset_name='default', split='validation')
next(iter(ds))

文本处理

然后将数据集转化为dataframe格式。

1
2
3
import pandas as pd
column_names = ['sentence', 'label', 'dataset']
df = pd.DataFrame(ds, columns = column_names)

之前观察到数据集一共有三列,由于dataset列内容相同且对分类结果没有影响,所以仅保留sentence和label列。

1
df = df.drop('dataset',axis=1)

然后判断label和sentence是否含有空值,删除含有空值的行。

1
2
df = df.dropna(subset=['label'])
df = df.dropna(subset=['sentence'])

将label列从float类型转化为int类型。

1
df['label'] = df['label'].astype(int)

查看处理后的数据:

对验证集进行相同的处理:

处理后的训练集有44984条数据,验证集有5016条数据。

数据分布

将训练集和验证集的数据拼接在一起,查看lable分布情况,可以看到1为27623条,0为27393条,数量接近。

1
2
3
4
import matplotlib.pyplot as plt
df = pd.concat([df,df_val])
lable_counts = df['label'].value_counts()
lable_counts

绘制柱形图直观展现正负标签分布情况,可以看到数据分布非常均匀

1
2
3
4
5
6
7
plt.figure(figsize=(10,6))
lable_counts.plot(kind='bar')
plt.title('Distribution of Positive and Negative Saples')
plt.xlabel('Lable')
plt.ylabel('Count')
plt.legend(title='Lable')
plt.show()

把文本转为向量形式

TF-IDF是一种用于信息检索与文本挖掘的常用加权技术。 TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF的主要思想是:如果某个单词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。

为了进行文本分类任务,需要将文本转化为向量,查看哪些词语的tfidf较高。

首先需要初始化并应用 TF-IDF 向量化器,查看转换后数据的维度

1
2
3
4
5
6
7
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
x = df['sentence']
y = df['label']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(x)
X.shape

查看TF-IDF值最高的词语

1
2
3
4
data1 = {'word': vectorizer.get_feature_names_out(),
'tfidf': X.toarray().sum(axis=0).tolist()}
df1 = pd.DataFrame(data1).sort_values(by='tfidf',ascending=False,ignore_index=True)
df1.head(10)

训练模型以及评估

划分训练集和测试集,查看划分后的数据情况

1
2
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,stratify=y,random_state=0)
X_train.shape,X_test.shape, y_train.shape, y_test.shape

模型构建,model1 是一个多项式朴素贝叶斯分类器的实例。model2 是一个逻辑回归分类器的实例,其中正则化强度C为10的10次方,迭代次数设置为10000。model3 是一个K近邻分类器的实例,设置邻居数为50。model4` 是一个决策树分类器的实例,随机状态设置为77。

1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.naive_bayes import MultinomialNB
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier

model1 = MultinomialNB()
model2 = LogisticRegression(C=1e10,max_iter=10000)
model3 = KNeighborsClassifier(n_neighbors=50)
model4 = DecisionTreeClassifier(random_state=77)

model_list=[model1,model2,model3,model4]
model_name=['NativeBayes','LogisticRegression','KNeighbors','DecisionTree']

查看模型准确率,可以看到贝叶斯模型的准确率最高,K近邻模型准确率最低,但是整体都在百分之六十多左右,相差不大。

1
2
3
4
5
6
7
8
scores=[]
for i in range(len(model_list)):
model_C=model_list[i]
name=model_name[i]
model_C.fit(X_train, y_train)
s=model_C.score(X_test,y_test)
scores.append(s)
print(f'{name}方法在测试集的准确率为{round(s,3)}')

建立一个多项式朴素贝叶斯模型,然后在测试数据上生成并显示该模型的接收器操作特征(ROC)曲线

1
2
3
4
5
6
7
8
9
import numpy as np
from sklearn.metrics import RocCurveDisplay
from sklearn.naive_bayes import MultinomialNB
model = MultinomialNB()
model.fit(X_train,y_train)
RocCurveDisplay.from_estimator(model,X_test,y_test)
x = np.linspace(0,1,100)
plt.plot(x,x,"k--",linewidth=1)
plt.title('ROC Curve(Test Set)')

然后查看数据集不平衡时的实验效果,选择标签为0的随机十分之一。

1
2
3
4
5
6
sample_size=len(df[df['label']==0]) // 10
sampled_df = df[df['label'] == 0].sample(n=sample_size)
df_one = df[df['label']==1]
df_new = pd.concat([sampled_df,df_one])
df_new = df_new.sample(frac=1)
print(df_new)

构建训练集和测试集,构建模型,查看准确率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x = df_new['sentence']
y = df_new['label']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(x)
X.shape
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,stratify=y,random_state=0)
X_train.shape,X_test.shape, y_train.shape, y_test.shape
model1 = MultinomialNB()
model_list=[model1]
model_name=['NativeBayes']
scores=[]
for i in range(len(model_list)):
model_C=model_list[i]
name=model_name[i]
model_C.fit(X_train, y_train)
s=model_C.score(X_test,y_test)
scores.append(s)
print(f'{name}方法在测试集的准确率为{round(s,3)}')

基于深度学习的文本分类挖掘

数据加载和文本处理与基于机器学习的文本分类挖掘任务相同。

分词并删除停用词

导入库并获取中文停用词

1
2
3
4
import jieba
from stopwordsiso import stopwords
stop_words = stopwords("zh")
stop_words

定义文本预处理函数,进行分词并过滤停用词。

1
2
3
4
def preprocess_text(text):
words = jieba.cut(text)
filtered_words = [word for word in words if word not in stop_words]
return ' '.join(filtered_words)

使用更新后的preprocess_text函数处理你的DataFrame中的文本列

1
2
df['sentence'] = df['sentence'].apply(preprocess_text)
df_val['sentence'] = df_val['sentence'].apply(preprocess_text)

查看处理后的数据

导入必要的库

1
2
3
4
5
import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Embedding, LSTM, Dense, Dropout

使用 Keras 的 Tokenizer 来对文本进行分词并构建词汇表。此外需求将文本转换为序列,然后对其进行填充以确保输入神经网络的文本长度一致。

1
2
3
4
5
6
7
8
9
10
11
12
# 设定 Tokenizer 参数
tokenizer = Tokenizer(num_words=25000, oov_token="<OOV>")
tokenizer.fit_on_texts(df['sentence'])

# 将文本转换为整数序列
train_sequences = tokenizer.texts_to_sequences(df['sentence'])
val_sequences = tokenizer.texts_to_sequences(df_val['sentence'])

# 填充序列以确保相同长度
max_length = 280
train_padded = pad_sequences(train_sequences, maxlen=max_length, padding='post', truncating='post')
test_padded = pad_sequences(val_sequences, maxlen=max_length, padding='post', truncating='post')

构建CNN网络,首先使用一个词嵌入层,其目的是将输入的词汇索引转换成固定大小的稠密向量,词汇表大小为25,000,输出维度为150。然后使用一维卷积层来处理嵌入向量,卷积层有128个过滤器,每个过滤器的宽度为5。卷积层后面是一个最大池化层,采用池化窗口大小为5。之后是全局最大池化层。后续是全连接层和Dropout层,全连接层有32个神经元,并使用ReLU激活函数。紧接着是一个Dropout层,dropout率设为0.5。最后,使用一个包含两个神经元的全连接层和softmax激活函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from keras.models import Sequential
from keras.layers import Embedding, Conv1D, MaxPooling1D, GlobalMaxPooling1D, Dense, Dropout

# 模型构建
model = Sequential([
# 词嵌入层:将输入的整数(代表词汇索引)转换为固定大小的稠密向量
Embedding(input_dim=25000, output_dim=150, input_length=max_length),
# 一维卷积层:用于捕捉局部关联特征
Conv1D(128, 5, activation='relu'),
# 最大池化层:减少参数数量,避免过拟合,同时提取重要特征
MaxPooling1D(5),
# 为了提取全局特征,增加一个全局最大池化层
GlobalMaxPooling1D(),
# 全连接层
Dense(32, activation='relu'),
# Dropout层以减少过拟合
Dropout(0.5),
# 输出层:使用 softmax 激活函数做二分类
Dense(2, activation='softmax')
])

# 编译模型
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# 打印模型摘要
model.summary()

模型训练,epoch为15

1
2
import tensorflow as tf
history = model.fit(train_padded, tf.keras.utils.to_categorical(df['label']), epochs=15, batch_size=128)

模型准确率

查看模型训练的loss损失

1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt

# 绘制训练损失和验证损失
plt.figure(figsize=(10, 6))
plt.plot(history.history['loss'], label='train loss')
plt.title('Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()