NLP从零开始------11文本进阶处理之新闻文本分类聚类示例

1. 新闻文本分类示例

        本小节将介绍运用朴素贝叶斯模型,分别采用自定义函数和调用 Python内置函数两种方法对新闻文本进行分类。新闻文本分类的流程包括以下步骤。
        (1)数据读取。读取原始新闻数据, 共有1000条数据。
        (2)文本预处理。对原始数据进行预处理, 对其进行去重、脱敏和分词等操作, 并分别统计教育、旅游的词频,随后绘制相应的词云图。由于数据分布不均, 对每个类别的数据各抽取400条, 共抽取800条数据进行训练模型及分类。
        (3)分类和预测。调用 Python内置函数实现朴素贝叶斯分类, 将最终结果与测试集进行比较, 得到模型的分类情况和准确率。
        (4)模型评价。使用处理好的测试集进行预测, 对比真实值与预测值, 获得准确率并进行结果分析。

1.1 数据读取

        加载库并读取数据, 如代码下所示。由代码运行结果可知, 数据中各类新闻均为500条。

 import os
 import re
 import jieba
 import numpy as np
 import pandas as pd
# from scipy. misc import imread
 import imageio
 import matplotlib. pyplot as plt
 from wordcloud import WordCloud
 from sklearn. naive _ bayes import MultinomialNB
 from sklearn. model _ selection import train _ test _ split
 from sklearn. feature _ extraction. text import CountVectorizer
 from sklearn. metrics import confusion _ matrix, classification _ report
 os. chdir('../ data/')
# 读取数据
 data = pd. read _ csv(' news. csv', encoding=' utf-8', header= None)
 data. columns = [' news', ' label']
 data. label. value _ counts()


1.2 文本预处理

        对文本数据进行预处理, 包括以下几个步骤。
        (1)单独抽取文本数据进行预处理, 查看数据发现不存在缺失值, 对其进行去重和脱敏操作。
        (2)由于原始数据中的敏感信息已统一用字符替换, 因此进行脱敏时只需减去相应的字符, 脱敏后共减少了100个字符。
        (3)采用 jieba库对文本数据进行分词, 由于在分词的过程中会切分部分有用信息,因此需要加载自定义字典newdic1. txt以避免过度分词, 文件中包含文本数据的几个重要词汇。
        (4)对分词后的结果过滤停用词, 去除停用词后共减少了2278148个字符。
        (5)经过处理的数据中存在一些无意义的空列表, 对其进行删除。可用 lambda()方法创建一个自定义函数, 并可以借助 apply()方法实现并返回相应的结果。


         apply()方法的使用格式如下。

kDataFrame. apply( func, axis=0, broadcast= False, raw= False,
     reduce= None, args=(), ** kwds)

        apply()方法有很多参数可供使用, 常用的参数及其说明如下表所示。

apply()方法常用的参数及其说明
名称含义
func 接收 functions表示应用于每行或每列的函数。无默认值
axis 接收0或1代表操作的轴向。默认为0
 broadcast 接收 bool表示是否进行广播。默认为 False
raw 接收 bool表示是否直接将ndarray对象传递给函数。默认为 False
 reduce 接收 bool或 None表示返回值的格式。默认为 None

        (6)通过自定义函数统计词频,对于各个类别的新闻文本, 留下词频大于20的词。并分别对各个类别的新闻文本绘制词云图, 查看文本数据分布情况。

        对文本数据进行上述步骤的代码处理示例如下:

# 数据预处理
temp = data.news
print(temp.isnull().sum())  # 打印数据中的空值数量

# 去重
data_dup = temp.drop_duplicates()  # 删除重复的行

# 脱敏
num_x = data_dup.astype(str).apply(lambda x: x.count('x')).sum()  # 计算 'x' 的总数量
data_qumin = data_dup.astype(str).apply(lambda x: re.sub('x', '', x))  # 用空格代替 'x'
print('减少了' + str(num_x - data_qumin.astype(str).apply(lambda x: x.count(' '))).sum() + '个字符')

# 加载自定义字典
current_dir = os.path.abspath('.')  # 获取当前目录的绝对路径
print(current_dir)
dict_file = os.path.join(current_dir, 'newdict1.txt')  # 拼接自定义字典的路径
jieba.load_userdict(dict_file)  # 加载自定义字典

# 分词
data_cut = data_qumin.astype(str).apply(lambda x: list(jieba.cut(x)))  # 对数据进行分词
print(data_cut)  # 打印分词结果

# 去停用词
stopword = pd.read_csv('stopword.txt', sep=' ', encoding='gbk', header=None)
stopword = [' '] + stopword[0].tolist()  # 将停用词列表转换为包含空格的列表
data_qustop = data_cut.apply(lambda x: [i for i in x if i not in stopword])  # 删除停用词
print(data_qustop)  # 打印去除停用词后的结果

# 词频统计函数
def chipin(data_qustop, num=10):
    temp = [' '.join(x) for x in data_qustop]  # 将每一行的词用空格连起来
    temp1 = ' '.join(temp)  # 将所有行的句子用空格连起来
    temp2 = pd.Series(temp1.split()).value_counts()  # 对所有词进行计数
    print(temp2)  # 打印词频统计结果
    return temp2[temp2 > num]  # 返回词频大于 num 的词

# 根据标签筛选数据
label = [data.loc[i, 'label'] for i in data_qustop.index]  # 取出对应的标签
lab1 = pd.Series(label, index=data_qustop.index)  # 转换为 Series

# 筛选 '教育' 和 '旅游' 类的数据,并进行词频统计
data_t = chipin(data_qustop[lab1 == '教育'], num=20)
data_to = chipin(data_qustop[lab1 == '旅游'], num=20)

# 绘制词云图所需的库
# 确保背景图片路径正确,字体路径正确

# 绘制教育新闻文本词云图
wc = WordCloud(font_path='C:/Windows/Fonts/simkai.ttf', background_color='white', max_words=2000, mask=back_pic, max_font_size=200, random_state=1234)
wordcloud1 = wc.generate(' '.join(data_t))
plt.figure(figsize=(16, 8))
plt.imshow(wordcloud1)
plt.axis('off')
plt.show()

# 绘制旅游新闻文本词云图
wordcloud4 = wc.generate(' '.join(data_to))
plt.figure(figsize=(16, 8))
plt.imshow(wordcloud4)
plt.axis('off')
plt.show()

        运行上述代码后, 会得到的教育、旅游的新闻文本词云图。从上述代码的词云图中, 教育新闻文本中学校、孩子等词出现的频次较高,旅游新闻文本中旅游、游客等词出现的频次较高。
由于各类新闻数据量不一样,为了方便后续建模与分类, 可采用 sample函数实现简单随机抽样。 

        sample 函数的使用格式如下:

DataFrame. sample(n= None, frac= None, replace= False, weights= None, 
random _ state= None, axis= None)

         sample 函数常用的参数及其说明如表 4-4 所示。

sample函数常用的参数及其说明
名称含义
n 接收 int表示从轴返回的项目数。无默认值
replace 接收 bool表示允许或不允许对同一行进行多次采样。默认为 False
 random _ state 接收 int、 array- like、BitGenerator、 np. random. RandomState若接收的是前3种类型则表示随机数生成器的生成种子;若接收的是 np. random. RandomState 则表示使用的是numpy RandomState的对象。无默认值
axis 接收0或 index, 1 或 columns 表示采样的轴向。默认为 None

        对教育、旅游这两类的新闻数据各抽取400条,如下代码实现

 num = 400
 adata = data _ teaching. sample( num, random _ state=5, replace = True)
 ddata = data _ tour. sample( num, random _ state=5, replace = True)
 data _ sample = pd. concat([ adata, ddata])
# data _ sample = pd. concat([ adata, bdata, cdata, ddata])
 data = data _ sample. apply( lambda x: ' '. join(x))
 lab= pd. DataFrame(['教育'] * num + ['旅游'] * num, index= data. index)
 my _ data = pd. concat([ data, lab], axis=1)
 my _ data. columns = [' news', ' label']

        在上述代码中, sample函数设置了随机状态, 目的是使得每次运行程序时的抽样方式与上一次的相同。


1.3 分类和预测

        朴素贝叶斯分类可以通过调用MultinomialNB 函数实现。首先划分训练集和测试集,分别输入数据集的文本数据和标签、测试集所占比例以及随机状态, 接着利用训练集生成词库,使用CountVectorizer 函数分别构建训练集和测试集的向量矩阵, 最后利用内置朴素贝叶斯函数预测分类。CountVectorizer函数的使用格式如下。

 class sklearn. feature _ extraction. text. CountVectorizer(*, input=' content',
 encoding=' utf-8', decode _ error=' strict',
 strip _ accents= None, lowercase= True, preprocessor= None, 
 tokenizer= None,  stop _ words= None, token _ pattern='(?u)\b\w\w+\b',
 ngram _ range=(1, 1), analyzer=' word', max _ df=1.0, min _ df=1, 
 max _ features= None,vocabulary= None, binary= False,
 dtype=< class' numpy. int64'>

        CountVectorizer 函数有许多参数, 常用的参数及其说明如下表所示。

CountVectorizer 函数常用的参数及其说明
名称含义
 input 接收 str表示传入对象的类型,可选 filename、 file、 content。默认为 content
 encoding 接收 str表示对传入对象进行解码的编码。默认为 utf-8
decode _ error 接收 str表示若给出分析的字节序列包含没有给定编码的字符该如何操作, 可选  strict、 ignore、 replace。默认为 strict
strip _ accents 接收 str表示在预处理步骤中删除重音并执行其他字符归一化,可选 ascii、 unicode。默认为 None
vocabulary 接收 dict表示映射,其中键是项, 值是特征矩阵中的索引或可迭代的项。默认为  None

         MultinomialNB 函数的使用格式如下。
 

 class sklearn. naive _ bayes. MultinomialNB(*, alpha=1.0,
 fit _ prior= True, class _ prior= None)

        MultinomialNB 函数常用的参数及其说明如下表所示。
 

MultinomialNB 函数常用的参数及其说明
名称含义
 alpha 接收 float表示加法平滑参数(0表示不平滑)。默认为 1.0
 fit _ prior 接收 bool表示是否学习类的先验概率。默认为 True
class _ prior 接收 array- like表示类的先验概率。默认为 None

        调用CountVectorizer函数构建向量矩阵, 调用 MultinomialNB 函数进行分类和预测,如下代码所示。

# 划分训练集和测试集
 x_ train, x_ test, y_ train, y_ test= train _ test _ split(
 my _ data. news, my _ data. label, test _ size=0.2, random _ state=1234)# 构建词频
向量矩阵
# 训练集
 cv= CountVectorizer() # 将文本中的词语转化为词频矩阵
 train _ cv= cv. fit _ transform(x_ train) # 拟合数据, 再将数据转化为标准格式
 train _ cv. toarray()
 train _ cv. shape # 查看数据大小
 cv. vocabulary _ #_查看词库内容
# 测试集
 cv1 = CountVectorizer( vocabulary= cv. vocabulary _)
 test _ cv=cv1. fit _ transform(x_ test)
 test _ cv. shape
# 朴素贝叶斯
 nb= MultinomialNB() # 朴素贝叶斯分类器
 nb. fit( train _ cv, y_ train) # 训练分类器
 pre = nb. predict( test _ cv) # 预测分类器

1.4 模型评价

        分类和预测完成后,对模型进行评价, 如下代码所示。

# 评价
 cm = confusion _ matrix(y_ test, pre)
 cr = classification _ report(y_ test, pre)
 print( cm)
 print( cr)

        代码运行结果如下。

[[88 1]
[ 0 71]]
precision  recall  fl- score  support
教育 1.00 0.99° 0.99 89
旅游 0.99 1.00 0.99 71
accuracy 0.99 160
macro avg 0.99 0.99 0.99 160
weighted avg 0.99 0.99 0.99 160

        结果显示测试集中正确分类的共有159条数据, 错误分类的只有1条数据。其中, 教育、旅游新闻信息被正确分类的数据分别有88、71条, 教育新闻信息被预测为旅游新闻信息的有1条。模型的分类准确率为0.99, F1值基本一致, 分类效果非常好。精确度和召回率分别表示模型对不同类别新闻信息的识别能力, F1值是两者的综合, 值越高说明模型越健壮。这里模型总体的精确度、召回率和 F1 值大致都为 0.99, 模型较为健壮。但是过于健壮的模型可能存在过拟合的风险。


2. 新闻文本聚类示例

        新闻文本聚类的流程包括如下步骤。
        (1)数据读取。读取文件列表中的新闻文本并给定标签,划分训练集与测试集,读入的每条新闻作为一行,方便后续数据处理及词频矩阵的转化。
        (2)文本预处理。对每个新闻文本使用 jieba库进行分词和去除停用词处理,去除文本中无用的停用词,降低处理维度,加快计算速度。
        (3)特征提取。使用scikit-learn库调用CountVectorizer将文本转为词频矩阵, 使用TfidfTransformer函数计算TF-IDF值并转化为矩阵。
        (4) 聚类。根据导入数据类型标签个数,定义聚类个数,导入训练集后通过调用sklearn.cluster函数训练模型, 并保存聚类模型。
        (5)模型评价。使用处理好的测试集进行预测,对比真实值与预测值,获得准确率并进行结果分析。


2.1 数据读取

        通过获取文件列表信息, 使用 pandas库的 read _ csv函数逐一读取数据, 在获得文本内容的同时去除文本中的换行符、制表符等特殊符号。 read_ csv函数的使用格式如下。

pandas. read _ csv( filepath _ or _ buffer, sep=< object object>,
header=' infer', names= None, engine= None, encoding= None, 
delim _ whitespace= False, low _ memory= True, memory _ map= False, 
float _ precision= None, storage _ options= None)

        read _ csv函数有许多参数, 常用的参数及其说明如下表所示。

read _ csv函数常用的参数及其说明
名称含义
filepath _ or _ buffer 接收 str表示文件的路径对象。无默认值
 sep 接收 str表示使用的定界符。默认为“,”
header 接收 int、 list of int表示用作列名的行号以及数据的开头。默认为 infer
 names 接收 array- like表示使用的列名列表。无默认值
engine 接收 str表示使用的解析器引擎, 可选 c 和 python。无默认值
 encoding 接收 str

表示读/写时的编码。无默认值

        读取数据过程如下代码所示。

import re
import json
import jieba
import pandas as pd
from sklearn. cluster import KMeans
import joblib.
from sklearn. feature _ extraction. text import TfidfTransformer
from sklearn. feature _ extraction. text import CountVectorizer
import random
from sklearn import metrics
# 数据读取
data = pd. read _ csv('../ data/ news. csv', encoding=' utf-8', header= None)
data. columns = [' news', ' label']

2.2 文本预处理

        通过文本预处理,方便对训练集与测试集进行相关的处理。首先需对读取的文本数据进行缺失值处理。而使用 read _ csv函数读取文件时默认将空格符去除, 于是在加载停用词后需补上被自动去除的空格符号。读取数据中的每个新闻文本,并使用 jieba库进行分词处理并去除停用词。去除文本中的非中文字符,且仅保留长度大于1的文本。将预处理的文本数据前1600条划分为训练集, 剩余部分作为测试集, 如下代码所示。

# 去重
data_dup = data.drop_duplicates()

# 脱敏,用空格代替 'x'
data_qumin = data_dup.astype(str).apply(lambda x: re.sub('x', ' ', x))

# 读取停用词
stopword = pd.read_csv('../data/stopword.txt', sep=' ', encoding='gbk', header=None)
stopword = [''] + stopword[0].tolist()

# 加载自定义词典
dict_file = pd.read_csv('../data/newdicl.txt', encoding='utf-8')
jieba.load_userdict(dict_file)

# 分词
data_cut = data_qumin.copy()
data_cut['news'] = data_qumin['news'].astype(str).apply(lambda x: list(jieba.cut(x)))

# 删除停用词
data_cut['news'] = data_cut['news'].apply(lambda x: [i for i in x if i not in stopword])

# 提取中文字符
data_cut['news'] = data_cut['news'].apply(lambda x: ''.join(re.findall('[\u4e00-\u9fa5]', i)))

# 过滤长度大于1的词语
data_cut['news'] = data_cut['news'].apply(lambda x: [i for i in x if len(i) > 1])

# 去除空列表
data_qustop = pd.DataFrame()
ind = [len(i) > 1 for i in data_cut.iloc[:, 0]]
data_qustop = data_cut.loc[ind, :]

# 标签映射
reps = {'教育': '1', '体育': '2', '健康': '3', '旅游': '4'}
data_qustop['label'] = data_qustop['label'].map(lambda x: reps.get(x))

# 构建语料库
corpus = []
for i in data_qustop.iloc[:, 0]:
    temp = ' '.join(i)
    corpus.append(temp)

# 划分训练集和测试集
train_corpus = corpus[:1600]
test_corpus = corpus[1600:]


 2.3 特征提取

        特征提取环节调用CountVectorizer函数将文本中的词语转换为词频矩阵, 矩阵中的元素 a[i][j]表示j词在i类文本下的词频;调用 TfidfTransformer 函数计算 TF-IDF 权重并转化为矩阵, 矩阵中的元素w[i][j]表示j词在i类文本中的 TF-IDF 权重。TfidfTransformer 函数的使用格式如下。

 class sklearn. feature _ extraction. text. TfidfTransformer(*,
 norm='12', use _ idf= True, smooth _ idf= True, sublinear _ tf= False)

        TfidfTransformer 函数有许多参数, 常用的参数及其说明如下表所示。

TfidfTransformer函数常用的参数及其说明
名称含义
 norm 接收 str表示每个输出行将具有单位范数, 可选11、12。默认为12
use _ idf 接收 bool表示启用反向文档频率重新加权。默认为 True
 smooth _ idf 接收 bool表示通过在文档频率上增加一个来平滑TF-IDF 权重。默认为 True
sublinear _ tf 接收 bool表示应用亚线性 TF 缩放。默认为 False

        使用CountVectorizer 函数和TfidfTransformer函数进行特征提取如下代码所示。
 

# 将文本中的词语转换为词频矩阵, 矩阵元素a[i][j]表示j 词在i类文本下的词频
 vectorizer = CountVectorizer()
# 统计每个词语的TF-IDF 权重
 transformer = TfidfTransformer()
# 第一个 fit _ transform用于计算 TF-IDF权重, 第二个 fit _ transform用于将文本转为矩阵
 train _ tfidf = transformer. fit _ transform( vectorizer. fit _ transform( train _ corpus))
 test _ tfidf = transformer. fit _ transform( vectorizer. fit _ transform( test _ corpus))
# 将TF-IDF矩阵抽取出来, 元素w[i][j]表示 j词在i类文本中的TF-IDF 权重
 train _ weight = train _ tfidf. toarray()
 test _ weight = test _ tfidf. toarray()

2.4 聚类

        由于之前选取了4个数据集, 因此, 选用4个中心点。随后进行模型的训练, 调用 fit函数将数据输入聚类器中, 训练完成后保存模型, 并查看模型在训练集上的调整兰德系数( Adjusted Rand Index, ARI)、调整互信息( Adjusted Mutual Information, AMI)、调和平均(V- measure )。
        ARI 取值范围为[-1,1],值越大越好, 用于反映两种划分的重叠程度, 使用该度量指标需要数据本身有类别标签。其计算公式如下式所示。

                                              ARI= \frac {RI-E[RI]}{ \max (RI)-E[RI]}
        上式中RI为兰德系数, max(RI)表示兰德系数的最大值, E[RI]表示兰德系数的期望。
        AMI也用于衡量两个数据分布的吻合程度。假设U与V 是对 N个样本标签的分配情况, 其计算公式如下式所示。
                ​​​​​​​                ​​​​​​​        ​​​​​​​AMI= \frac {MI-E[M \Pi ]}{ \max (H(U),H(V))-E[MI]}
        上式中MI为互信息, max(H(U),H(V))为信息熵的最大值, E[MI]为互信息的期望。
        v是同质性h( homogeneity, 即每个群集只包含单个类的成员)和完整性c( completeness, 即给定类的所有成员都分配给同一个群集)的调和平均。其计算公式如下式所示。
        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​   \nu =2 \times \frac {h \times c}{h+c}
        训练聚类模型并查看 ARI、AMI、调和平均如下代码所示。
 

 clf = KMeans(n_ clusters=4, algorithm=' elkan') # 选择4个中心点
# clf. fit(X)可以将数据输入分类器里
 clf. fit( train _ weight)
# 4个中心点
 print('4个中心点为:' + str( clf. cluster _ centers _))
# 保存模型
 joblib. dump( clf, ' km. pkl')
 train _ res = pd. Series( clf. labels _). value _ counts()
# 预测的簇
 labels _ pred = clf. labels _
# 真实的簇
 labels _ true = data _ qustop[' label'][: 1600]
 print('\n 训练集 ARI 为: _+。 str( metrics. adjusted _ rand _ score( labels _ true, labels _ pred)))
 print('\n训练集 AMI为: '+ str( metrics. adjusted _ mutual _ info _ score( labels _ true, labels _ pred)))
 print('\n 训 练集调和平均为: ' + str( metrics. v_ measure _ score( labels _ true, labels _ pred)))
 print('每个样本所属的簇为')
 for i in range( len( clf. labels _)):
     print(i , ' ', clf. labels _[i])

上述代码运行后, 得到的结果如下。

4个中心点为:[[-6.77626358e-21 1.89735380e-19-6.77626358e-21… 5.42101086e-20
3.38813179e-21 -2.03287907e-19]
[-6.77626358e-21 -1.62630326e-19 1.69406589e-20 … 4.74338450e-20
3.38813179e-20 -1.49077799e-19]
[-6.77626358e-21 2.56453789e-04 1.69406589e-20 … 2.03287907e-20
-6.77626358e-21 1.35525272e-20]
[2.74667615e-05 2.05646617e-042.56174527e-05 …4.79056635e-05
2.93372653e-05 1.05062623e-04}]
训练集 ARI 为: 0.2352928109807351
训练集 AMI 为: 0.45133131936936033
训练集调和平均为: 0.4526106487682168
每个样本所属的簇为
0 3
1 3
2 1
3 3
...
1596 2
1597 3
1598 3
1599 3
1600 3

        第一个输出结果为聚类的中心点, 表示4个类别的聚类中心点; 第二、三、四个输出分别为ARI、AMI、调和平均; 第五个输出为每个数据样本的簇, 即类别标签。


2.5 模型评价

        输入测试数据进行模型预测, 计算测试数据的ARI、AMI、调和平均,如下代码所示。

# 预测的簇
 labels _ pred = clf. fit _ predict( test _ weight)
# 真实的簇
 labels _ true = data _ qustop['lɪbel'111600:1
 print('\n 测试集 ARI 为: ' + strimetrice, adjusted _ rand _ ecore( labele _tree, labels _ pred)))
 print('\n测试集 AMI为: '+ str( metrics, adjusted _mutuial _ info _ more( labels _ true. labels _ pred))
 print('\n 测试集调和平均为: ) + str( metrics,v_ measus的_ ecore( labels _ true, labels _ pred)))

        运行代码后,得到的结果如下。

测试集 ARI为: 0.32951365267813887
测试集 AMI为: 0.4899627160105715
测试集调和平均为: 0.4962776583574683

        模型测试集的ARI约为0.3295, AMI约为0.4899,调和平均约为0.4962, 读者可以尝试通过不同的提取特征方式得到不同的结果。

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值