【实验】基于朴素贝叶斯的新闻分类

简介: 【实验】基于朴素贝叶斯的新闻分类

一、贝叶斯新闻分类任务


新闻数据集处理

爬取的新闻数据,需要我们对文本数据进行很多预处理才能使用

文本分词

通常我们处理的都是词而不是一篇文章

去停用词

停用词会对结果产生不好的影响,所以一定得把他们去剔除掉

构建文本特征

如何构建合适特征是自然语言处理中最重要的一步,这俩我们选择两种方案来进行对比

贝叶斯分类

基于贝叶斯算法来完成最终的分类任务

数据源:http://www.sogou.com/labs/resource/ca.php


1.1 读取数据

import pandas as pd
import jieba
#pip install jieba
df_news = pd.read_table('./data/data.txt',names=['category','theme','URL','content'],encoding='utf-8')
df_news = df_news.dropna()
df_news.tail()
# 查看数据规模
  df_news.shape

于原始数据都是由爬虫爬下来的,所以看起来有些不整洁,一会我们还需要清洗一番。这里我们自己定义个字段:

Category:当前新闻所属的类别,一会我们要进行分别任务,这就是标签了。

Theme:新闻的主题,这个咱们先暂时不用,大家在练习的时候也可以把它当作特征。

URL:爬取的界面的链接,方便检验爬取数据是不是完整的,这个咱们暂时也不需要。

Content:新闻的内容,这些就是一篇文章了,里面的内容还是很丰富的。

任务已经很明确了,根据文章的内容来进行类别的划分。那如何做这个事呢?咱们之前看到的数据都是数值的,直接传入到算法中求解参数就可以了。这份数据就有些特别了,都是文本,计算机可不认识这些文字呀,所以我们需要把这些文字转换成特征,比如说将一篇文章转换成一个向量,这样就可以玩起来了。 对于一篇文章来说,里面的内容还是有点太多了,如果直接把它转换成向量,有一串数字来表示这篇文章,一方面来说难度有些大,另一方面这些表示的效果也不尽如人意。通常的做法都是先把文章进行分词,然后在词的层面上去做文章。先拿到一篇文章,然后我们再看看其分词的结果:


1.2 中文分词


对于文本分类来说,直接对一篇完整的文章来建模有些难度,一般情况下我们都是先把文章进行分词,然后在词上做文章,先拿一篇新闻看看长什么样


content = df_news.content.values.tolist() #将每一篇文章转换成一个list
print (content[1000]) #随便选择其中一个看看
content_S = []
for line in content:
    current_segment = jieba.lcut(line) #对每一篇文章进行分词
    if len(current_segment) > 1 and current_segment != 'rn': #换行符
        content_S.append(current_segment) #保存分词的结果
# 查看其中一条的结果
content_S[1000]
df_content=pd.DataFrame({'content_S':content_S}) #专门展示分词后的结果
df_content.head()

完成了分词任务之后,我们要处理的对象就是其中每一个词了,但是这里我们得先考虑一个问题,一篇文章是什么主题应该是由其内容中的一些关键词来决定的,比如这里的‘车展’,‘跑车’,‘发动机’等,这些词我们一看就知道跟汽车相关的。但是另一类词,‘今天’,‘在’,‘3月份’等,这些词给我们的感觉好像既可以在汽车相关的文章中使用,也可以在其他类型的文章使用,就把它们称作停用词,也就是我们一会要过滤的目标。 首先需要选择一个合适的停用词库,这些网上有很多现成的,但是都没那么完整,所以当大家在进行数据清洗任务的时候还需要自己添加一些,先来看看停用词表长什么样子吧:


stopwords=pd.read_csv("stopwords.txt",index_col=False,sep="t",quoting=3,names=['stopword'], encoding='utf-8')
stopwords.head(20)
# 筛选停用词
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)
            all_words.append(str(word))
        contents_clean.append(line_clean)
    return contents_clean,all_words
contents = df_content.content_S.values.tolist()    
stopwords = stopwords.stopword.values.tolist()
contents_clean,all_words = drop_stopwords(contents,stopwords)
df_content=pd.DataFrame({'contents_clean':contents_clean})
df_content.head()

1.3 TF-IDF :提取关键词


下面我们再来看一下TF-IDF:关键词提取方法。在一篇文章中,我们经过清洗之后,剩下的都是稍微有价值的词,但是这些词的重要程度是一样的嘛?还是各有高低吧,假设我想从一篇文章找出最有价值的几个词该怎么办呢?如果只按照词频来统计,得到结果可能不会太好,因为词频高的可能都是一些套话并不是主题。这时候TF-IDF就派上用场了。 这里,借用一个经典的例子,我手里有一篇文章<中国的蜜蜂养殖>:


当我进行词频统计的时候,发现这篇文章中,‘中国’,‘蜜蜂’,‘养殖’这三个词出现的次数是一样的,比如都是10次,那这个时候如果判断其重要性呢?这一篇文章应该讲述的是都跟蜜蜂和养殖相关的技术,所以这俩词应当是重点了。而中国这个词,我们既可以说中国的蜜蜂,还可以说中国的篮球,中国的大熊猫,能派上用场的地方简直太多了,没有那么专一,所以在这篇文章中它应当不是那么重要的。 这里我们就可以给出一个合理的定义了,如果一个词在整个语料库中(可以当作是在所有文章中)出现的次数都很高(这篇也有它,另一片还有这个词),那么这个词的重要程度就不高,因为它更像一个通用词。如果另一个词在整体的预料库中的词频很低,但是在这一篇文章中的词频却很高,我们就有理由认为它在这篇文章中就很重要了。比如蜜蜂这个词,在篮球,大熊猫相关的文章中基本不可能出现,这里却大量出现了。

import jieba.analyse #工具包
index = 2400 #随便找一篇文章就行
content_S_str = "".join(content_S[index]) #把分词的结果组合在一起,形成一个句子
print (content_S_str) #打印这个句子
print ("  ".join(jieba.analyse.extract_tags(content_S_str, topK=5, withWeight=False)))#选出来5个核心词

简单过一遍文章可以发现,讲的大概就是足球比赛赞助商各自的发展策略,得到的关键词结果也是跟我们预计是一致的。关键词提取方法还是很实用的,想一想我们每天使用各种APP都能看到很多广告,大家的广告应该是各不相同的,比如我的基本都是跟游戏相关,因为我平时的关注点就在这些,我想这些APP已经给我打上的标签可能就是:王者荣耀,手机游戏,死肥宅。。。TF-IDF算法打标签中最容易的一种了,介绍了几种文本处理方法,接下来我们还需把重点放回到我们的分类任务中,也就是要进行文本特征提取。

df_train=pd.DataFrame({'contents_clean':contents_clean,'label':df_news['category']})
df_train.tail()
df_train.label.unique()
label_mapping = {"汽车": 1, "财经": 2, "科技": 3, "健康": 4, "体育":5, "教育": 6,"文化": 7,"军事": 8,"娱乐": 9,"时尚": 0}
df_train['label'] = df_train['label'].map(label_mapping) #构建一个映射方法
df_train.head()
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(df_train['contents_clean'].values, df_train['label'].values, random_state=1)
#x_train = x_train.flatten()
x_train[0][1]
words = []
for line_index in range(len(x_train)):
    try:
        #x_train[line_index][word_index] = str(x_train[line_index][word_index])
        words.append(' '.join(x_train[line_index]))
    except:
        print (line_index,word_index)
words[0] 
print (len(words))

1.4 制作词袋模型特征

from sklearn.feature_extraction.text import CountVectorizer
texts=["dog cat fish","dog cat cat","fish bird", 'bird'] #为了简单期间,这里4句话我们就当做4篇文章了
cv = CountVectorizer() #词频统计
cv_fit=cv.fit_transform(texts) #转换数据
print(cv.get_feature_names())
print(cv_fit.toarray())
print(cv_fit.toarray().sum(axis=0))


在sklearn的feature_extraction.text模块中导入了CountVectorizer,也就是我们词袋模型要用的模块,这里还有很多丰富的文本处理方法,感兴趣的同学也可以来尝试一下其他方法。为了简单起见,构造了4个句子,我们暂且当做4篇文章就好。观察可以发现,这四篇文章中总共包含了4个词:'bird', 'cat', 'dog', 'fish'。所以词袋模型的向量长度就是4了,在结果中我们打印了get_feature_names()得到了特征中各个位置的含义,例如第一个句子"dog cat fish"得到的向量为:[0 1 1 1],它的意思就是首先看第一个位置'bird'在这句话中有没有出现,出现了几次,结果为0;接下来同样看 'cat',发现出现了1次,那向量的第二个位置就为1;同理 'dog', 'fish'在这句话中也各出现了1次,最终的结果就得到了。 词袋模型是自然语言处理中最基础的一种特征提取方法了,说白了它就是看每一个词出现几次,来统计词频就可以了,再把所有出现的词组成特征的名字,依次统计其个数就可以得到文本特征了。这里给我们的感觉有点过于简单了,只考虑词频而不考虑词出现的位置以及先后顺序,那能不能稍微再改进一些呢?这里我们还可以通过设置ngram_range来控制特征的复杂度,比如我们不光可以考虑单单一个词,还可以考虑两个词连在一起,甚至更多的词连在一起。

from sklearn.feature_extraction.text import CountVectorizer
texts=["dog cat fish","dog cat cat","fish bird", 'bird']
cv = CountVectorizer(ngram_range=(1,4)) #设置ngram参数,让结果不光包含一个词,还有2个,3个的组合
cv_fit=cv.fit_transform(texts)
print(cv.get_feature_names())
print(cv_fit.toarray())
print(cv_fit.toarray().sum(axis=0))

这里我们只加入了ngram_range=(1,4)参数,其它保持不变,观察结果中的特征名字可以发现,这回就不单单是一个词了,而且有两个组合,三个组合在一起。例如'cat cat'表示文本中出现'cat'词后面又跟了一个'cat'词出现的个数。跟之前的单个词来对比,这回我们得到的特征更复杂了一些,特征的长度也明显变多了。可以考虑到上下文的前后关系了,但是这只是我们举的一个简单小例子,看起来还没什么问题。如果实际文本中出现词的个数成千上万了呢?那使用ngram_range=(1,4)参数,得到的词向量的长度就太长了,用起来就很麻烦了。所以通常情况下,ngram参数设置基本为2就够了,再多计算起来就成累赘了。

from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer(analyzer='word',lowercase = False)
feature = vec.fit_transform(words)
feature.shape
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer(analyzer='word', max_features=4000,  lowercase = False)
feature = vec.fit_transform(words)
feature.shape

在构建过程中,我们还额外加入了一个限制条件max_features=4000,表示我们的特征最大长度为4000,这就会自动过滤掉一些词频较小的词语了。如果不进行限制的话,最终得到的向量长度为85093,大家也可以去掉这个参数来自己观察下,这会使得特征长度过大,而且里面很多都是词频很低的词语,也会导致特征过于稀疏,这些对我们建模来说都是不利的,所以还是非常有必要加上这样一个限制参数。


1.5 使用词袋模型的特征来建模,观察结果

from sklearn.naive_bayes import MultinomialNB #贝叶斯模型
classifier = MultinomialNB() 
classifier.fit(feature, y_train)
test_words = []
for line_index in range(len(x_test)):
    try:
        #
        test_words.append(' '.join(x_test[line_index]))
    except:
         print (line_index,word_index)
test_words[0]
classifier.score(vec.transform(test_words), y_test)

在贝叶斯模型中,我们选择了MultinomialNB,这里它额外做了一些平滑处理主要目的就在我们求解先验概率和条件概率的时候避免其值为0。词袋模型的效果看起来还凑合,能不能再改进一些呢?在这份特征中我们是均等的对待每一个次,说白了就是完全看这个词出现的个数,而不管它是什么词,这看起来还是有点问题的,因为对于不同主题来说有些词可能更重要一些,有些词就没什么太多价值。咱们刚刚还讲了tf-idf算法,能不能将其应用在特征之中呢,当然是可以的,还是通过一个小例子来看一下吧:


1.6 制作TF-IDF特征

from sklearn.feature_extraction.text import TfidfVectorizer
X_test = ['卡尔 敌法师 蓝胖子 小小','卡尔 敌法师 蓝胖子 痛苦女王']
tfidf=TfidfVectorizer()
weight=tfidf.fit_transform(X_test).toarray()
word=tfidf.get_feature_names()
print (weight)
for i in range(len(weight)):  
    print (u"第", i, u"篇文章的tf-idf权重特征")
    for j in range(len(word)):
        print (word[j], weight[i][j])


我们简单写了两句话,就是要分别构建它俩的特征。一共出现了5个词,所以特征的长度依旧为5这和词袋模型是一样的,接下来我们得到的特征就是每一个词的tf-idf权重值了,把它们组合在一起就形成我们的特征矩阵了。观察可以发现,两篇文章当中,唯一不同就是'小小'和'痛苦女王',其他词都是一致的,所以要论区分程度的话,还是它俩更重要一些,其权重值自然就更大了。在结果中也分别进行了打印方便大家观察。TfidfVectorizer()函数中我们还可以加入很多参数来控制特征,比如过滤停用词,最大特征个数,词频最大,最小比例限制等,这些都会对结果产生不同的影响,建议大家使用的时候还是先参考其API文档,价值还是蛮大的,并且还有示例代码.

from sklearn.feature_extraction.text import TfidfVectorizer
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)

效果比之前的词袋模型有所提高,这也是我们预料之中,那还有没有其它更好的特征提取方法了呢?这里给大家再简介介绍一下word2vec词向量模型,它是基于神经网络来实现的,这里我们对其细节就不做过多阐述了。它的强大之处在于,不光对词进行了向量话,还会给其一个实际的含义,咱们之前的词袋模型或者tf-idf都是基于词频去做的,并没有实际的含义只是一个数字而已。但是word2vec中每一个词都有实际的意义,只不过只有计算机能读懂它罢了,这样计算机能把词语进行理解,而不单单看其出现的位置和次数了。如果大家对词向量模型感兴趣,可以参考gensim这个工具包,里面提供了非常简洁的函数帮助我们构建特征,不光如此工具包中还包含了很多自然语言处理相关算法的实现,之后大家肯定都会用上的。

目录
相关文章
|
11月前
|
机器学习/深度学习 自然语言处理
(路透社数据集)新闻分类:多分类问题实战
(路透社数据集)新闻分类:多分类问题实战
|
机器学习/深度学习 自然语言处理 算法
机器学习算法(五):基于企鹅数据集的决策树分类预测
机器学习算法(五):基于企鹅数据集的决策树分类预测
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用神经网络完成新闻分类
使用神经网络完成新闻分类
151 0
使用神经网络完成新闻分类
|
数据处理 索引
使用朴素贝叶斯对电影评论分类
使用朴素贝叶斯对电影评论分类
142 0
使用朴素贝叶斯对电影评论分类
|
数据处理 索引
决策树IMDB数据集电影评测分类
决策树IMDB数据集电影评测分类
196 0
决策树IMDB数据集电影评测分类
|
机器学习/深度学习 自然语言处理 算法
朴素贝叶斯进行--垃圾邮件分类、新闻分类、个人广告获取区域倾向的解读
朴素贝叶斯进行--垃圾邮件分类、新闻分类、个人广告获取区域倾向的解读
146 0
朴素贝叶斯进行--垃圾邮件分类、新闻分类、个人广告获取区域倾向的解读
|
算法 Serverless
基于朴素贝叶斯算法对新闻文本进行分类
基于朴素贝叶斯算法对新闻文本进行分类
168 0
基于朴素贝叶斯算法对新闻文本进行分类
|
机器学习/深度学习 数据采集 算法
不平衡数据集分类实战:成人收入数据集分类模型训练和评估(一)
不平衡数据集分类实战:成人收入数据集分类模型训练和评估(一)
384 0
不平衡数据集分类实战:成人收入数据集分类模型训练和评估(一)
|
机器学习/深度学习 算法 测试技术
不平衡数据集分类实战:成人收入数据集分类模型训练和评估(二)
不平衡数据集分类实战:成人收入数据集分类模型训练和评估(二)
198 0
不平衡数据集分类实战:成人收入数据集分类模型训练和评估(二)