NLP入门实战之——基于词频和TF-IDF,利用朴素贝叶斯机器学习方法新闻分类

2 篇文章 0 订阅
1 篇文章 0 订阅

基于词频和TF-IDF,利用朴素贝叶斯机器学习方法新闻文本分类(洗数据、sklearn新手练习)

本人是零基础的小白,现在从零开始学习NLP,这是学习的一些简单的笔记,如有错误请指正。
编译环境:Jupyter Notebook
Windows x64
本文数据处理主要分为两个板块:
一 是数据预处理(Data Preparation)从而获得所需要的特征(feature),如将数据层层处理(分词、停用词过滤、向量化),本文向量化内容由于使用sklearn库,放置第二板块讲解。
二 是利用模型(Modeling)解决具体的问题,本文主要采用朴素贝叶斯经典机器学习方法对文本进行分类。

一、理论基础

下面简单回顾一下理论部分(可以直接跳过到实战部分)

1.1 词频(TF)

词频(term frequency) 指的是某一个给定的词语在该文件中出现的频率。对于在某一文件里的词语 t i t_i ti来说,它的重要性可表示为:
t f i j = n i , j ∑ k n k , j {tf}_{ij}=\frac{n_{i,j}}{\sum_kn_{k,j}} tfij=knk,jni,j
其中, n i , j n_{i,j} ni,j是该词在文件 d j d_j dj中出现次数,而分母是文件 d j d_j dj中所有字词出现的次数总和。

1.2 逆向文本频率(IDF)

逆向文件频率(inverse document frequency) 是一个词语普遍重要性的度量。某一特定词语的idf,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取以10为底的对数得到,个人理解为:对词频向量的改进,原因在于:词语出现的越多,并不能代表它就越重要,相反,文档中出现的越多,其实它的重要性是降低的,所以TFIDF考虑了单词的重要性而做的对词频的改进,可表示为:
t f i d f ( w ) = t f ( d , w ) × i d f ( w ) tfidf(w)=tf(d,w)\times{idf(w)} tfidf(w)=tf(d,w)×idf(w)
(1)其中 t f ( d , w ) tf(d,w) tf(d,w) 代表文档d中w的词频
(2) i d f ( w ) = log ⁡ N N ( w ) idf(w)=\log\frac{N}{N(w)} idf(w)=logN(w)N N {N} N代表语料库中的文档总数, N ( w ) {N(w)} N(w)代表词语w出现在多少个文档中,出现在文档的次数越多, log ⁡ \log log值越小,故称为逆向文本频率

1.3 朴素贝叶斯(Naive Bayesian Model,NBM)

朴素贝叶斯的中心思想,在于利用各类别在训练样本中的分布以及类别中各特征元素的分布,计算后验概率,使用极大似然法判断测试样本所属,一般用于简单分类。
贝叶斯公式:
P ( B ∣ A ) = P ( A ∣ B ) P ( B ) P ( A ) P(B\mid{A})=\frac{P(A\mid{B})P(B)}{P(A)} P(BA)=P(A)P(AB)P(B)
对应分类任务则为:
P ( 类 别 ∣ 特 征 ) = P ( 特 征 ∣ 类 别 ) P ( 类 别 ) P ( 特 征 ) P(类别\mid{特征})=\frac{P(特征\mid{类别})P(类别)}{P(特征)} P()=P()P()P()




垃圾邮件分类(判别模型)举例:
P ( 特 征 ∣ 类 别 ) P(特征\mid{类别}) P() 相当于先验概率,也就是我们已知的概率,比如垃圾邮件分类里面,我们已有的数据中正常的类别邮件里面包含“购买”一词的概率,以及垃圾类别里面包含“购买”一次的概率等, P ( 类 别 ) P(类别) P() 就是正常或者垃圾邮件在数据集中的概率,这些概率都已知。
那么要判断邮件为正常还是垃圾,则要判断:

P ( 正 常 ∣ 内 容 ) P(正常\mid内容) P() P ( 垃 圾 ∣ 内 容 ) P(垃圾\mid内容) P() 的大小

P ( 正 常 ∣ 内 容 ) = P ( 内 容 ∣ 正 常 ) P ( 正 常 ) P ( 内 容 ) P(正常\mid内容)=\frac{P(内容\mid正常)P(正常)}{P(内容)} P()=P()P()P()
P ( 垃 圾 ∣ 内 容 ) = P ( 内 容 ∣ 垃 圾 ) P ( 垃 圾 ) P ( 内 容 ) P(垃圾\mid内容)=\frac{P(内容\mid垃圾)P(垃圾)}{P(内容)} P()=P()P()P()
P ( 正 常 ) P(正常) P() P ( 垃 圾 ) P(垃圾) P() 均已知, P ( 内 容 ) P(内容) P()消去,剩下就是要比较 P ( 内 容 ∣ 正 常 ) P(内容\mid正常) P() P ( 内 容 ∣ 垃 圾 ) P(内容\mid垃圾) P()
P ( 内 容 ∣ 正 常 ) = P ( 购 买 , 物 品 , 广 告 , 产 品 ∣ 正 常 ) = P ( 购 买 ∣ 正 常 ) P ( 物 品 ∣ 正 常 ) P ( 广 告 ∣ 正 常 ) P ( 产 品 ∣ 正 常 ) P(内容\mid正常)\\=P(购买,物品,广告,产品\mid正常)\\ =P(购买\mid正常)P(物品\mid正常)P(广告\mid正常)P(产品\mid正常) P()=P(广)=P()P()P(广)P(),而这些先验概率前面都已算过,带入计算作比较大小即可。

二、数据预处理

数据预处理部分可谓是耗费了大部分的时间,参考了一些博客,但是感觉不是特别详细,其中也遇到了不少麻烦,下面一一讲解到位,非常适合小白参考。

2.1 数据下载及导入

首先下载搜狗实验室的文本数据(精简版347MB,tar.gz格式):
下载链接
在这里插入图片描述
在这里插入图片描述
解压后,得到如下128个txt文件
在这里插入图片描述
文件格式如下:
在这里插入图片描述
对于特定格式的文本,我们一般采用正则表达式来提取所需要的信息,代码如下:

import re
import os
import pandas as pd
import pickle
import numpy as np
import jieba;
# 定义正则表达式
patternURL = re.compile(r'<url>(.*?)</url>', re.S)
patternCtt = re.compile(r'<content>(.*?)</content>', re.S)
contents_total = []
urls_total=[]
labels = []
# os.listdir()返回文件夹里所有文件名
file = os.listdir("C:/Users/84747/Desktop/新建文件夹/SogouCS.reduced")
for i in range(len(file)):  
    file0=file[i]
    file_path = os.path.join("C:/Users/84747/Desktop/新建文件夹/SogouCS.reduced/", file0)
# os.path.join()将路径进行拼接,从而打开每一个txt文件
    text = open(file_path, 'rb').read().decode("gbk", 'ignore')
    # 正则匹配出url和content
    urls = patternURL.findall(text)
    contents = patternCtt.findall(text)

# 得到所有contents和urls
    urls_total=urls_total + urls
    contents_total = contents_total + contents
df=pd.DataFrame({'URL':urls_total,'content':contents_total})
#将目前处理的数据用dataframe可视化一下,方便查错
df.head()  # 显示dataframe的前五行

结果如下(有空值、内容也很乱),后面一步步处理:
在这里插入图片描述
下面我们再将URL内容再次正则一下,提取官方的分类label:

labels=[]
for i in range(0,len(urls_total)):
    patternClass = re.compile(r'http://(.*?).sohu.com', re.S)
    labels.append(patternClass.findall(urls_total[i]))
df=pd.DataFrame({'label':labels,'URL':urls_total,'content':contents_total}).dropna()
df.head() #如果想显示最后五行可用.tail()

其中传统dataframe中dropna() 函数删空值的方法在这里并不适用,结果如下,待会会处理,我们先把label里面的格式调整一下,调整的原因:目前的label格式为list of list,为了方便后面筛选label来替换中文等后续操作,先脱去一层list:
在这里插入图片描述

type(labels)
# print(labels[0:100])
labels2 = []
for index in range(len(labels)):
    labels2.append(' '.join(labels[index]))  #将list of list转换为list
labels2[0:100]  
df.label.unique()

在这里插入图片描述

df=pd.DataFrame({'label':labels2,'URL':urls_total,'content':contents_total})
df.tail()

在这里插入图片描述
好了,到这里label格式已经调好了,接下来需要对label进行中文替换,所以我们需要先把各类label筛选出来,总共有以下label:

print(df.label.unique()) #将所有不重复的label显示出来

Alt
将所需要的label对应的内容进行筛选查看(替换‘career’为各个label,查看相关内容),方便人为辨识类别
代码如下:

df.loc[df['label']== 'career'].tail(20)

在这里插入图片描述
接下来就是替换label,通过人为的观察上述各label所对应的分类,将中文替换到下列map映射之中,最后完成label替换:

label_mapping={'sports':'体育', 'house':'房屋','it':'科技', '2008':'奥运', 'women':'女人',\
               'auto':'汽车','yule':'娱乐', 'news':'时事','learning':'教育', 'business':'财经',\
               'mil.news':'军事', 'travel':'旅游', 'health':'健康', 'cul':'文化', 'career':'职场'}
df['label'] = df['label'].map(label_mapping) #将label进行替换
df.head()

在这里插入图片描述
回到刚刚提到的空值问题,明明有很多空值,但isnull()查阅后仍然显示false,原因在于:pandas里空值是指NA,包括numpy的np.nan,python的None,pandas对空值进行操作可以用isnull/notnull/isna/notna/fillna/dropna等等,但是,这些操作对空字符串均无效(此处参考链接)。
空字符串即“ ”(一个或多个空格),但在excel表格里其实是看不出来,pandas也把它当成有值进行操作。
代码如下:

df.content.replace(to_replace=r'^\s*$',value=np.nan,regex=True,inplace=True)
df.head()

在这里插入图片描述
这样一来,就将空值转换成了NaN,从而再可以使用dropna()。

df2=df.dropna(axis=0, how='any') # 对任意含有NaN的行(axis=0)进行删除
df2.head()

在这里插入图片描述
再将索引重新排列一下:

df3=df2.reset_index(drop=True)
df3.head()

在这里插入图片描述

2.2 结巴分词及停用词过滤

此处我没有用前面的数据进行处理(毕竟有42w行数据,作为新手使用小数据集练手足够,后面可能还会发42w行的运行结果,这里采用了前辈整理好的5000行数据进行处理),格式和我之前处理得到的基本一致,不影响大家参考。
样例数据导入:

import gensim
import numpy
import pandas as pd
import jieba
#python -m pip install --user gensim  (gensim包)
#pip install jieba
df_news = pd.read_table('./val.txt',names=['category','theme','URL','content'],encoding='utf-8')
print(df_news.head())
print(df_news.shape)  #数据类型

在这里插入图片描述
在这里插入图片描述

2.2.1 结巴分词:

分词之前首先我们要将dataframe的格式转换为list才能适应jieba库,代码如下:

content = df_news.content.values.tolist()    #将datafrmae中content转化为list
content_S = []            #对content中内容进行分词
for line in content:
    current_segment = jieba.lcut(line)
    if len(current_segment) > 1 and current_segment != '\r\n': #换行符
        content_S.append(current_segment)
df_content=pd.DataFrame({'content_S':content_S}) #### 将分完词的list转换为dataframe
df_content.head()

在这里插入图片描述

2.2.2 停用词过滤:

需要先下载好一份停用词表,网上有很多,此处提供前辈整理好的素材,很方便

topwords=pd.read_csv("stopwords.txt",index_col=False,sep="\t",quoting=3,names=['stopword'], encoding='utf-8')
stopwords.head(15)

Alt

def drop_stopwords(contents,stopwords):
    contents_clean = []
    all_words = []
    for line in contents:
        line_clean = []
        for word in line:
            if word in stopwords:
                continue
            line_clean.append(word)
        contents_clean.append(line_clean)
    return contents_clean,all_words
    #print (contents_clean)
        

contents = df_content.content_S.values.tolist()    #df转换为list
stopwords = stopwords.stopword.values.tolist()     #转换为list
contents_clean,all_words = drop_stopwords(contents,stopwords)

df_content=pd.DataFrame({'contents_clean':contents_clean})  #将分完词的list再转换为df
df_content.head()

Alt

三、模型(modeling)贝叶斯分类器

df_train=pd.DataFrame({'contents_clean':contents_clean,'label':df_news['category']})
df_train.tail() #tail()展示最后几个数据(一共是5000个数据)

在这里插入图片描述

df_train.label.unique()
#对label做映射
label_mapping = {"汽车": 1, "财经": 2, "科技": 3, "健康": 4, "体育":5, "教育": 6,"文化": 7,"军事": 8,"娱乐": 9,"时尚": 0}
df_train['label'] = df_train['label'].map(label_mapping) #将label进行替换
df_train.head()

在这里插入图片描述

将数据切分为训练集(x_train,y_train)和测试集(x_test,y_test)

from sklearn.model_selection import train_test_split
#将数据集切分为训练和测试集,x代表content,y代表label
x_train, x_test, y_train, y_test = train_test_split(df_train['contents_clean'].values, df_train['label'].values, random_state=1)
print(len(x_train),len(x_test),len(y_train),len(y_test))

在这里插入图片描述

3.1 文本数据向量化

数据向量化之前,我们先要将类型转换为list以适合CountVectorizer(词频)/TfidfVectorizer(逆向文本频率IDF)

#将x_train(numpy.array型转换为list类型,
#以适合CountVectorizer/TfidfVectorizer向量化操作)
words = []
for line_index in range(len(x_train)):
	words.append(' '.join(x_train[line_index]))  #numpy.array转换为list

test_words = []
for line_index in range(len(x_test)):
	test_words.append(' '.join(x_test[line_index]))
3.1.1 基于词频向量化

导入sklearn机器学习库中的CountVectorizer词频向量化函数

from sklearn.feature_extraction.text import CountVectorizer

vec = CountVectorizer(analyzer='word', max_features=4000,  lowercase = False) #建立向量
vec.fit(words)

导入贝叶斯

from sklearn.naive_bayes import MultinomialNB  #导入贝叶斯
classifier = MultinomialNB()
classifier.fit(vec.transform(words), y_train)
classifier.score(vec.transform(test_words), y_test)  #基于词频向量构造的结果

在这里插入图片描述

3.1.2 基于TFIDF向量化
from sklearn.feature_extraction.text import TfidfVectorizer  #基于TF-IDF向量

vectorizer = TfidfVectorizer(analyzer='word', max_features=4000,  lowercase = False)
vectorizer.fit(words)
# 导入贝叶斯
from sklearn.naive_bayes import MultinomialNB
classifier = MultinomialNB()
classifier.fit(vectorizer.transform(words), y_train)
# 计算分类器精度
classifier.score(vectorizer.transform(test_words), y_test)

在这里插入图片描述
相比之下,TFIDF向量化的结果会偏高一点点,当然,这里采用的是很小的数据集(才5000行),精度很低,如果将42w的数据进行训练,精度应该会提升不少。到此为止,整个搜狗新闻文本分类任务就完成了。

本文到这里就全部结束了,如果有错误或者引用不当,还请指出,我会加以改进!欢迎大家评论留言,相互学习和进步!(前辈整理的数据集后面会上传到csdn上,如有需要可以联系)



参考文章:
https://blog.csdn.net/weixin_43269174/article/details/88634129
https://blog.csdn.net/sadfassd/article/details/80568321
https://www.jianshu.com/p/edad714110fb
https://blog.csdn.net/maotianyi941005/article/details/84315965
https://www.cnblogs.com/datou-swag/articles/10060532.html

tf-idf是一种机器学习中常用的特征提取方法,它可以用来评估一个词在文本中的重要程度。tf-idf表示词频-逆文档频率(term frequency-inverse document frequency)。tf代表词频,指的是某个词在文本中出现的频率。idf代表逆文档频率,指的是词在整个语料库中出现的频率的倒数。通过计算tf和idf的乘积,可以得到一个词的tf-idf值,这个值越大,则表示这个词在文本中越重要。 在NLP中,tf-idf广泛应用于文本分类、信息检索和文本挖掘等任务。它的主要思想是,通过提取关键词的tf-idf值来代表文本的特征,然后使用这些特征训练分类器或进行文本检索。tf-idf能够帮助我们识别并区分不同文本中的重要词汇,从而提高分类和检索的准确性。 朴素贝叶斯分类算法则是一种基于贝叶斯定理和特征条件独立性假设的分类算法。它假设每个特征之间相互独立,并通过先验概率和条件概率来计算后验概率。朴素贝叶斯算法在文档分类和垃圾邮件过滤等领域有广泛应用。 在文本分类任务中,可以结合tf-idf朴素贝叶斯算法来进行文本分类。首先,使用tf-idf提取文本的特征向量,然后使用这些特征向量训练朴素贝叶斯分类器。在实际应用中,可以使用混淆矩阵等评估指标来评估分类器的性能。 总结起来,tf-idf是一种用于提取文本特征的方法,而朴素贝叶斯是一种基于贝叶斯定理和特征条件独立性假设的分类算法。它们可以结合使用来进行文本分类任务。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值