利用TF-IDF的机器学习方法对搜狗新闻数据进行文本分类

本文来自我的个人博客 https://www.zhangshenghai.com/posts/28687/

这篇文章分为两个部分,第一部分是叙述TF-IDF的计算过程,第二部分是基于sklearn利用TF-IDF建立多种机器学习模型进行文本分类。其中文本分类使用的数据集来自搜狗实验室提供的新闻数据,使用的是其中完整版648MB的数据。

TF-IDF的计算过程

TF-IDF(Term Frequency - Inverse Document Frequency)即词频-逆向文本频率,是一种用于信息检索和文本挖掘的常用加权技术。假设一个语料库包含多个文件,TF-IDF则用于评估一字词对于一个语料库中的其中一份文件的重要程度。字词的重要性会随着它在文件中出现的次数成正比增加,但同时也会随着它在语料库中出现的频率成反比下降。

词频(TF)

词频(term frequency)指的是某一个给定的词语在该文件中出现的频率。对于在某一文件里的词语t_i来说,它的重要性可表示为:
tf_{i,j} = \frac{n_{i,j}}{\sum_k{n_{k,j}}}
其中,n_{i,j}是该词在文件d_j中的出现次数,而分母是文件d_j中所有字词的出现次数总和。

逆向文本频率(IDF)

逆向文件频率(inverse document frequency)是一个词语普遍重要性的度量。某一特定词语的idf,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取以10为底的对数得到:
idf_i = lg \frac{|D|}{|j:t_i \in d_j|+1}
其中,|D|是语料库中的文件总数,|j:t_i \in d_j|是包含词语t_i的文件数目。如果没有一个文件包含词语t_i,那么导致分母为零,所以通常使用|j:t_i \in d_j|+1

词频-逆向文本频率(TF-IDF)

TF-IDF即将TF和IDF相乘。下面首先用个简单的例子介绍一下TF-IDF的计算过程。

假设给定一个包含4个文件的语料库:

from collections import Counter
import numpy as np

corpus = [
    'this is the first document',
    'this is the second second document',
    'and the third one',
    'is this the first document'
]

将其分词后加入数组corpus_list中:

# 分词
corpus_list = []
for i in range(len(corpus)):
    corpus_list.append(corpus[i].split(' '))
print(corpus_list)

然后对每个文件中的单词统计其在文件中的出现次数:

# 统计词频
count_list = []
for i in range(len(corpus_list)):
    count_list.append(Counter(corpus_list[i]))
print(count_list)

计算语料库中每个文件中每个单词的TF-IDF:

# 计算TF-IDF
def TFIDF(word, count, count_list):
    # 计算tf
    tf = count[word] / sum(count.values())
    # 计算idf
    num_contain = sum(1 for count in count_list if word in count)
    idf = np.log10(len(count_list) / (num_contain+1))
    # 计算tf-idf
    return tf*idf


for i, count in enumerate(count_list):
    # 创建字典,存储每个单词的TF-IDF
    score_dic = {word: TFIDF(word, count, count_list) for word in count}
    print(score_dic)

结果如下:

image

基于sklearn利用TF-IDF进行文本分类

数据清洗与准备

所谓数据分析,真的是一大半时间全部花在了数据的预处理上。将搜狗实验室的原始新闻数据下载下来后会有一个SogouCS.reduced文件夹,里面有128个txt文件,包含着所有新闻数据。

image

数据格式为:

<doc>

<url>页面URL</url>

<docno>页面ID</docno>

<contenttitle>页面标题</contenttitle>

<content>页面内容</content>

</doc>

我们要做的第一步就是将这些数据从txt文件中提取出来,这里用强大的正则表达式来提取。要注意的是,下载下来的数据是用gbk进行编码的,记得设置一下decode的方式是gbk。

import re
import os
import jieba.posseg as pseg
from collections import Counter

# 定义正则表达式
patternURL = re.compile(r'<url>(.*?)</url>', re.S)
patternCtt = re.compile(r'<content>(.*?)</content>', re.S)
contents_total = []
classes_total = []

for file in os.listdir("./SogouCS.reduced"):
    # 设置路径打开文件
    file_path = os.path.join("./SogouCS.reduced", file)
    text = open(file_path, 'rb').read().decode("gbk", 'ignore')

    # 正则匹配出url和content
    urls = patternURL.findall(text)
    contents = patternCtt.findall(text)
    classes = []
    for i in range(urls.__len__()):
        patternClass = re.compile(r'http://(.*?).sohu.com', re.S)
        classes.append(patternClass.findall(urls[i])[0])

    # 得到所有contents和classes
    contents_total = contents_total + contents
    classes_total = classes_total + classes

看看这些新闻的种类共有多少以及每个种类有多少篇新闻:

image

可以看到,有些奇奇怪怪的种类,不知道什么意思,这里将他们删除,同时删除字数小于100个字的新闻,提高分类的准确度:

# 去除几个不需要的种类,同时删除字数小于100字的新闻
for i in range(contents_total.__len__())[::-1]:
    if (len(contents_total[i]) < 100 or classes_total[i] == '2008' or classes_total[i] == 'auto'
            or classes_total[i] == 'cul' or classes_total[i] == 'mil.news' or classes_total[i] == 'career'):
        contents_total.pop(i)
        classes_total.pop(i)

删除之后,再看一下,发现每类新闻的数量不太均衡,于是从每个类别中抽取2000条数据:

image
# 每一类提取2000个新闻
X = []
y = []
d = {"business":0, "health":0, "house":0, "it":0, "learning":0, "news":0, "sports":0, "travel":0, "women":0, "yule":0}
for i in range(len(classes_total))[::-1]:
    if (d[classes_total[i]] < 2000):
        d[classes_total[i]] += 1
        X.append(contents_total[i])
        y.append(classes_total[i])

至此,初步的数据准备就完成了,得到两个一维数组X和y,X中包含所有的新闻,每条新闻是一个字符串,同样,y中包含所有新闻对应的类别。

中文分词

如果是英文语料,那就十分简单了,直接用空格进行分词就可以。但中文嘛......有点困难,不过好在有大神开发了中文分词工具,这里使用的是jieba分词工具,这是一个相当不错的中文分词工具了,推荐使用!

# 对所有语料进行分词
X_fenci = []
for line in X:
    words = pseg.cut(line)
    line0 = []
    for w in words:
        if 'x' != w.flag:
            line0.append(w.word)
    X_fenci.append(' '.join(line0))

分词的计算量有点大,感觉可慢了,如果数据量实在太大的话可以考虑多进程分词。将分词好的语料保存:

# 将分词好的语料保存
file_X_fenci = open("X_fenci.txt", 'w')
file_y = open("y.txt", 'w')
file_X_fenci.write('\n'.join(X_fenci))
file_y.write('\n'.join(y))
file_X_fenci.close()
file_y.close()

TF-IDF特征提取

第一步是将刚才分词好的数据导入,然后使用sklearn的接口提取文本的tf-idf特征,将文本转换为文档-词项矩阵。然后划分训练集和测试集,这里将前15000条数据划分为训练集,后5000条数据划分为测试集。

def tf_idf(contents):
    # 提取文本特征tf-idf
    vectorizer = CountVectorizer(min_df=1e-5)
    transformer = TfidfTransformer()
    tfidf = transformer.fit_transform(vectorizer.fit_transform(contents))
    return tfidf


# 导入数据
X_fenci = open("X_fenci.txt", 'rb').read().decode("gbk", "ignore").split('\n')
y = open("y.txt", 'rb').read().decode("gbk", "ignore").split('\n')

# 提取tf-idf特征以及数据划分
X_ifidf = tf_idf(X_fenci)
X_train, y_train = X_ifidf[:15000], y[:15000]
X_test, y_test = X_ifidf[15000:], y[15000:]

模型建立与测试

朴素贝叶斯模型

"""朴素贝叶斯模型"""
NB_model = MultinomialNB(alpha=0.01)
NB_model.fit(X_train, y_train)

preds = NB_model.predict(X_test)

num = 0
for i, pred in enumerate(preds):
    if pred == y_test[i]:
        num += 1
print("precision_score:" + str(float(num)/len(preds)))

朴素贝叶斯模型很简单,但同时也很有效,precision_score:0.7842

逻辑回归模型

"""逻辑回归模型"""
lr_model = LogisticRegressionCV(solver='newton-cg', multi_class='multinomial', cv=10, n_jobs=-1)
lr_model.fit(X_train, y_train)

preds = lr_model.predict(X_test)

num = 0
for i, pred in enumerate(preds):
    if pred == y_test[i]:
        num += 1
print("precision_score:" + str(float(num)/len(preds)))

选用十折交叉验证,训练时间有点长,precision_score:0.8412

线性支持向量机模型

"""线性支持向量机模型"""
svm_model = SVC(kernel="linear")
svm_model.fit(X_train, y_train)

preds = svm_model .predict(X_test)

num = 0
for i, pred in enumerate(preds):
    if pred == y_test[i]:
        num += 1
print("precision_score:" + str(float(num)/len(preds)))

precision_score:0.7958

K-近邻模型

"""K-近邻模型"""
for x in range(1, 15):
    knn_model = KNeighborsClassifier(n_neighbors=x)
    knn_model.fit(X_train, y_train)

    preds = knn_model.predict(X_test)

    num = 0
    for i, pred in enumerate(preds):
        if pred == y_test[i]:
            num += 1
    print("precision_score:" + str(float(num) / len(preds)))

在K-近邻模型中,经测试,当k取值为8时,模型在测试集上的表现最好,precision_score:0.734。

总结

尽管在这个样本集上,逻辑回归模型的表现最好,各个模型的表现差距也不是很明显。但机器学习方法五花八门,具体用什么方法还是得取决于具体的问题。这里的数据量有限,只是将每类的新闻抽取两千条,整个数据集也才两万条新闻,当数据量上升,模型的训练时间也是必须要考虑的问题,参数优化也是必不可少的。而且仅用precision_score作为单一的评价标准还是太简陋了,之后有时间的话再加入LSTM、CNN等深度学习模型以及更多的机器学习评价标准进行进一步的模型评估吧。

参考

https://zh.wikipedia.org/wiki/Tf-idf
https://blog.csdn.net/Shuang_Mo/article/details/81916214
https://blog.csdn.net/Techmonster/article/details/74905668
https://zhuanlan.zhihu.com/p/26729228
https://www.libinx.com/2018/text-classification-classic-ml-by-sklearn/
https://blog.csdn.net/pnnngchg/article/details/86601168
https://blog.csdn.net/sadfassd/article/details/80568321
https://www.libinx.com/2018/text-classification-cnn-by-tensorflow/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容