查看原文
其他

贝叶斯新闻分类实战项目详解

(给Python开发者加星标,提升Python技能

来源:Be_melting

https://blog.csdn.net/lys_828/article/details/108990366

【导读】:今日头条、UC头条等,只要是打开以后,各种新闻就已经分好类了,根据自己喜好查看相应的新闻内容。社会、娱乐、电影等各种分类,数以亿计的新闻,通过机器学习完成分类。那么我们自己能不能自己做一个新闻分类的应用呢?答案是肯定的,本文将带领大家学习基于贝叶斯方法的新闻分类实战,快来学习吧。


--- 以下是正文 ---

贝叶斯方法简介


关于贝叶斯要解决的问题(贝叶斯方法源于他生前为解决“逆概”问题写的一篇文章):

正向概率:假设袋里面有N个白球,M个黑球,伸手进去摸一把,摸出黑球的概率有多
逆向概率:如果我们事先并不知道袋子里面黑白球的比例,而是闭着眼睛摸出一个(或者好几个球),观察这些取出来球的颜色后,那么我们可以就此对袋子里面的黑白球的比例作出什么样的推测,还是举个具体的例子进行理解逆向概率,如下:

假设学校男生占60%,女生占40%,男生总是喜欢穿长裤,而女生则是一半穿长裤一半穿裙子

正向概率:随机选取一个学生,其穿长裤的概率和穿裙子的概率有多大?
逆向概率:迎面走来了一个穿长裤的学生,你只看见其穿的是长裤,而无法确定其性别,那么可以推断其是女生的概率有多大?

贝叶斯方法解决的问题就是上面举例中的逆向概率的求解。新闻分类也是一样,根据一篇新闻的内容,推断其最有可能哪个类别。下面就开始新闻分类的实战之旅。

新闻分类实战

1. 文本分词

就是将获取到的新闻数据,转化成有一个个词语组成的样式。新闻的数据是由搜狗实验室提供,可以根据链接进行获取,这里使用的数据是已经经过清洗后的数据,直接进行加载(关于文章中使用的数据,包括处理后的新闻数据和停用词数据,已经上传至资源,可以自行下载)。

1.1 数据加载

#既然涉及到分词,最常用,也是最成熟的一个包就是jieba了import pandas as pdimport jiebaimport osos.chdir(r'C:\Users\86177\Desktop\机器学习案例\新闻分类')df_news = pd.read_table('./data/data.txt',names=['category','theme','URL','content'],encoding='utf-8')df_news = df_news.dropna()df_news.tail()

输出的结果为:(Category:当前新闻所属的类别,一会我们要进行分别任务,这就是标签了。Theme:新闻的主题,这个咱们先暂时不用,大家在练习的时候也可以把它当作特征。URL:爬取的界面的链接,方便检验爬取数据是不是完整的,这个咱们暂时也不需要。Content:新闻的内容,这些就是一篇文章了,里面的内容还是很丰富的。)

既然数据导入之后,就看一下数据的规模。

df_news.shape#(5000, 4)#数据量这里就只导入了5000条,特征的话就是4个

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

content = df_news.content.values.tolist() #将每一篇文章转换成一个listprint (content[4999]) #随便选择其中一个看看,就是最后一篇新闻

输出的结果为:(在进行分词前要将多篇新闻数据放在一个列表中,方便后序的遍历。)

1.2 jieba分词基本步骤

使用jieba分词筛选结果的模板(标准方式),共五步:

  • 在遍历数据之前创建一个空列表,用于存放数据。

  • 然后在依次遍历存放新闻数据的列表,获取每一篇新闻数据。

  • 使用jieba.lcut()方法对文本进行分词。

  • 接着就是剔除掉字符个数小于等于1个的词语,还有顺带去掉换行符(也可以按照自己的格式要求进行剔除)。

  • 最后就是将满足的结果添加到最初创建的空列表中。

content_S = []for page in content: current_segment = jieba.lcut(page) #对每一篇文章进行分词 if len(current_segment) > 1 and current_segment != '\r\n': #换行符        content_S.append(current_segment) #保存分词的结果
content_S[4999]#['全球','最美','女人','合成图',':',':', '国','整形外科','教授' '李承哲',',','在','国际', '学术','杂志','美容','整形外科',#'学会','学报','发表','了','考虑','种族','和','民族','脸型','多样性','的','黑人','、','白人','、','中国',#'人','、','日本','人','女性','“','最具','魅力','的','脸型','”','的','论文','。','李承哲','合成','这些','脸型','时','采用',#'的','演艺人','脸型','有',',','黑人','1','3','名','、','白','1','6','名','、','中国','人','2','0','名','、','日本',#'人','1','4','名','等','共','6','3','名','的','脸型','。','M','贾凶','宰笙','蛴','乙来','挝',':','黑人','美女','、',#'白人','美女','、','中国','美女','、','日本','美女','和','韩国','美女','。']

因为分词后的内容是列表,可以直接使用字典列表的形式创建DataFrame,而字典的键就是分词的结果的列名。

df_content=pd.DataFrame({'content_S':content_S}) #专门展示分词后的结果df_content.head()

输出结果为:

创建的DataFrame数据可以用于绘制词云图,从分词后的结果可以看出,虽然剔除了部分数据,但是还是有很多不是我们想看到的内容,比如冒号空格,句号空格等。因此绘制词云图之前需要先剔除一些常见的不是很重要的词语,前面的是初加工筛选,这里就要精加工筛选了,为了避免一个个手动剔除常见的一些不重要的词汇,所以就有了停用词字典这个概念(就是把日常的词汇收集在一起,这个字典不是指python的字典数据类型,而是指新华字典,用于存放收集的数据的地方),凡是分词结果后的词语在这个停用词范围中的,都将被舍弃。

1.3 加载停用词字典数据进行数据清洗

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

输出结果为:(前面的一些停用词都是标点符号,后面的都是常见的汉字词语,文件已经上传到我的资源。)

为了方便日后的使用,这里直接封装函数,只需要传入相对应格式的数据即可(数据就是新闻数据的列表,和停用词的列表。)

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)

将处理后的结果转化为DataFrame数据:

df_content=pd.DataFrame({'contents_clean':contents_clean})df_content.tail()

输出结果为:(这里的数据是将每篇新闻分词后清洗的结果重新放置在列表中。)

df_all_words=pd.DataFrame({'all_words':all_words})df_all_words.tail()

输出结果为:(这里的数据是将所有分词的结果以单个数据的形式都保存起来了。)

1.4 词云图绘制

那么接下来就可以使用数据绘制词云图,既可以选择按照每篇新闻分词的数据,也可以使用全部词语的数据,下面就使用所有词语的数据进行绘制,基本步骤就是四步:

  • 首先截取要呈现的目标数据(一般20,50或者100,根据需要选定)。

  • 词云图对象初始化(这里可以对词云图的参数进行设置)。

  • 将选取的数据传入到fit.words()方法中。

  • 最后就是将词云图展示出来。

from wordcloud import WordCloudimport matplotlib.pyplot as plt%matplotlib inlineimport matplotlibmatplotlib.rcParams['figure.figsize'] = (10.0, 5.0)woordcloud_data = df_all_words.all_words.value_counts()[:100]wordcloud=WordCloud(font_path="./data/simhei.ttf",background_color="white",max_font_size=80)wordcloud=wordcloud.fit_words(woordcloud_data)plt.imshow(wordcloud)

输出的结果为:(可以发现所有数据的词云中,一个个单词还是很多的。)

2. TF-IDF:提取关键词

解释一下TF-IDF是一个什么东西,还是进行举例子,假设一篇文章为《中国的蜜蜂养殖》

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

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个核心词

输出的结果为:(根据汇总出的五个关键词可以发现,基本上可以概括出这篇新闻的内容,主要围绕着赞助的问题在比赛球衣上进行宣传。)
3. 词袋模型

由上面的分类看出,通过jieba可以直接提取文本的特征,但是要进行文本的分类,对于计算机来讲的话,它并不认识这些汉字,它只认识数值,有没有可能性将汉字都转化为数字,用数字进行表示呢?那么词袋模型就可以解决这个问题。

既然是进行文本分类,首先要做的就是进行标签制作,这里就按照常见的是个新闻标签进行对应新闻的匹配。

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.tail()

输出结果为:(这样就实现标签的映射了。)

标签处理好了之后就是进行数据特征的处理,那么就有个问题,每篇的新闻的字数是不一样的,有的是200词,有的是300词,有的甚至更多,但是在机器学习中要求输入的数据的维度是统一的,因此就得把数据的维度化为统一,那么就需要找一个可以进行衡量的支点,而这个支点就是语料库,即使每篇文章的数据的长短不一样,但是由所有文章组成的语料库的容量是一定了,所以判断每篇文章中数据是否在语料库中既可以对应上,也就转化为同一维度了,为了方便理解还是进行举例说明。

from sklearn.feature_extraction.text import CountVectorizertexts=["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))


输出的结果为:(语料库就是有四个单词组成的,可以通过cv.get_feature_names()获取所有文章组成的语料库,这个语料库中包含所有的词汇,cv_fit.toarray()就可以将每篇的新闻数据转化为array数组,实现字母/汉字与数值的对应,当出现相同的内容时候,数值会加1,最后也可以进行多篇新闻数据的单词频数汇总。)

['bird', 'cat', 'dog', 'fish'][[0 1 1 1] [0 2 1 0] [1 0 0 1] [1 0 0 0]][2 3 2 2]

经过结果的分析,可以归纳出词袋模型的特征:说白了它就是看每一个词出现几次,来统计词频就可以了,再把所有出现的词组成特征的名字,依次统计其个数就可以得到文本特征了。这里给我们的感觉有点过于简单了,只考虑词频而不考虑词出现的位置以及先后顺序,那能不能稍微再改进一些呢?这里我们还可以通过设置ngram_range来控制特征的复杂度,比如我们不光可以考虑单单一个词,还可以考虑两个词连在一起,甚至更多的词连在一起。

from sklearn.feature_extraction.text import CountVectorizertexts=["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())

输出的结果为:(可以发现,设置了参数保留1-4个词汇特征的时候,原来语料库中只有四个单词的,现在直接扩充2.5倍,这种方式是可以标记对于文本中出现的词汇顺序,但是如果一篇文章就有很多单词,统计5000篇就可能超过10w,那么再设置ngram_range参数很大的话,就可能导致语料库达到几千万的容量,因此这里可以考虑单词出现的顺序,设置为2就可以了,然后在创建语料库的时候加一些限制,过滤掉词频较小的词汇。)

['bird', 'cat', 'cat cat', 'cat fish', 'dog', 'dog cat', 'dog cat cat', 'dog cat fish', 'fish', 'fish bird'][[0 1 0 1 1 1 0 1 1 0] [0 2 1 0 1 1 1 0 0 0] [1 0 0 0 0 0 0 0 1 1] [1 0 0 0 0 0 0 0 0 0]]

那么就开始拿新闻数据进行实践,首先就是划分训练集和测试集。

from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split(df_train['contents_clean'].values, df_train['label'].values, random_state=1)

接着进行把新闻数据的类型由列表全部转化为指定的格式(列表中嵌套着字符串数据。)

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)print(type(test_words))test_words[0]

如果不进行词频较小的特征过滤,看一下生成的语料库的,然后再对比一下进行过滤后的。

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

输出结果为:(可以发现如果不进行限制的话,3750篇文章共创建的语料库的容量为8w多,而限制保留指定的特征后,就会选取词汇频数最大的4000个进行保留。可以想象一下加入一篇文章有一千个字,那么对应8w,生成数组的结果肯定会有很多很多0,导致特征过于稀疏,所以有必要对创建的语料库容器进行限制。)

(3750, 85093)(3750, 4000)

最后一步就是实现词袋模型的特征建模,导入贝叶斯模型。

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[-1]

输出结果为:(至此,模型也训练好了,测试数据也化为统一格式了,那么接下来就可以进行模型得分的评测。)

classifier.score(vec.transform(test_words), y_test)

输出结果为:0.8032

那么就可以用这个模型进行预测一下,看看文本分类的结果。

classifier.predict(vec.transform(test_words)[-1])

输出结果为:

array([7], dtype=int64)

而直接查看一下对应的标签,并对应上最初的映射结果。

print(y_test[-1])print(['{}:{}'.format(key,value) for key,value in filter(lambda x:7 == x[1], label_mapping.items())])

输出的结果为:(根据上面的新闻数据的输出可知,是进行中国京剧的介绍的,归类为文化是没有问题的。)

7['文化:7']

4. TF-IDF制作特征

前面已经讲解到了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])

输出的结果为:(和之前的 CountVectorizer类似,之前计算的是出现的频次,这里除了考虑词频TF,还有IDF逆文本频率,最后按照权重进行赋值。)

[[0.44832087 0.63009934 0.44832087 0. 0.44832087] [0.44832087 0. 0.44832087 0.63009934 0.44832087]]第 0 篇文章的tf-idf权重特征卡尔 0.44832087319911734小小 0.6300993445179441敌法师 0.44832087319911734痛苦女王 0.0蓝胖子 0.44832087319911734第 1 篇文章的tf-idf权重特征卡尔 0.44832087319911734小小 0.0敌法师 0.44832087319911734痛苦女王 0.6300993445179441蓝胖子 0.44832087319911734

接下来就可以使用TF-IDF特征建模来观察结果,和之前的词袋模型的代码很类似,就是换了几个单词。

vectorizer = TfidfVectorizer(analyzer='word', max_features=4000, lowercase = False)vectorizer.fit(words)classifier = MultinomialNB()classifier.fit(vectorizer.transform(words), y_train)classifier.score(vectorizer.transform(test_words), y_test)

输出结果为:0.8136 (比之前的模型得分要好一点)

至此关于贝叶斯新闻分类实战项目梳理就到此完结了,撒花撒花✿✿ヽ(°▽°)ノ✿

总结

本文是对贝叶斯新闻分类实战项目进行梳理,使用贝叶斯新闻分类深度讲解。重点在于一下的几个知识点:

  • jieba分词的步骤。

  • 加载停用词进行分词后文本数据的清洗。

  • 词袋模型的理解。

  • TF-IDF提取和制作特征。

  • 贴标签和根据标签找文本数据。


- EOF -

推荐阅读  点击标题可跳转

1、平凡而又神奇的贝叶斯方法

2、码农不识贝叶斯,虽知数据也枉然

3、基于概率论的分类方法:朴素贝叶斯


觉得本文对你有帮助?请分享给更多人

关注「Python开发者」加星标,提升Python技能

点赞和在看就是最大的支持❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存