程序员的机器学习入门笔记(五):文本分类的入门介绍

来源:互联网 发布:网络用语py是什么意思 编辑:程序博客网 时间:2024/05/17 09:08

背景说明

可以说在分析机器学习的数据源中最常见的知识发现主题是把数据对象或事件转换为预定的类别,再根据类别进行专门的处理,这是分类系统的基本任务。

文本分类也如此:其实就是为用户给出的每个文档找到所属的正确类别(主题或概念)。想要实现这个任务,首先需要给出一组类别,然后根据这些类别收集相应的文本集合,构成训练数据集,训练集既包括分好类的文本文件也包括类别信息。

今天,在互联网的背景下自动化的文本分类被广泛的应用于,包括文本检索,垃圾邮件过滤,网页分层目录,自动生成元数据,题材检测,以及许多其他的应用领域,是文本挖掘最基础也是应用最广范的核心技术。

目前, 有两种主要的文本分类方法,一是基于模式系统(通过运用知识工程技术),二是分类模型(通过使用统计和/或机器学习技术)。专家系统的方法是将专家的知识以规则表达式的形式编码成分类系统。机器学习的方法是一个广义归纳过程,采用由一组预分类的例子,通过训练建立分类。由于文件数量以指数速度的增加和知识专家的可用性变得越来越小,潮流趋势正在转向机器学习 - 基于自动分类技术。

文本分类操作的基本步骤

不同语言的文本分类系统的现实过程可能存在一定的差异,但是大的流程基本是相同的,下面主要说明中文分类系统涉及到的几个主要的步骤

样本的预处理

去除样本中的噪声数据,例如:如果样本数据来源于网页,需要去处掉网页上的标签等操作。

文本处理的核心任务要把 非结构化和半结构 化的文本转化为结构 化的形式, 即向量空间模型。这之前,必须要对不同类型的文 本进行预处理。在大多 数文本挖掘 任务中,文本预处理的步骤都是相似的,基本步骤如下

确定需要处理的文本数据范围

有时间语料库里面的文件可能会很大,根据处理目的的不同,对文本的处理范围可能也不同,例如,如果需要使用语料库的训练结果,来做文本的分类预测,就需要处理语料库中文件的全部内容,如果只是用来做情感分析,可能只要分析每个文件的摘要部分

样本库的建立

样本的来源主要包括两种
- 已经分类好且存在的样本
如果出于研究目的,对样本的要求不高,可以使用一些公开的样本库,可以使用复旦大学谭松波中文文本分类语料(下载地址: http://www.threedweb.cn/thread-1292-1-1.html)和搜狗新闻分类语料库(下载地址: http://www.sogou.com/labs/dl/c.html)等。如果对于样本的质量要求较高,只能根据业务的要求对样本的质量逐步的进行改进

  • 未分类的样本库
    互联网上很多的综合性的新闻网站,每个网站都会将自己的内容进行分类,可以根据开发的需求,通过爬虫从这些新闻网站上对内容进行爬取,并使用网站自身提供的分类标签对爬到的数据打上标签

中文分词

使用分词器对文本进行分词,并去处停用词,例如 “的”,”也”,”但是”等词

中文分词 (Chinese Word Segmentation) 指的是将一个汉字序列(句子)切分成一 个一个单独的词。分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。 我们知道,在英文的行文中, 单词之间是以空格作 为自然分界符的,而中 文只是字、 句和段能通过明显的分界符来 简单划界,唯独词没 有一个形式上的分界符 ,虽然英文 也同样存在短语的划分问题, 不过在词这一层上, 中文比之英文要复杂的 多、困难的 多。中文分词,不仅是中文文 本分类的一大问题, 也是中文自然语言处理 的核心问题 之一。

在一定意义上,中文分词不完 全是个技术问题。 中文分词的难点也不 完全是算法 问题。因为关于到底什么是词 这个概念在中国理论 界已经争论了很多年。 一个著名的 例子就是:“鸡蛋”、“牛肉”、“下雨”是词吗,如果是那么“鸭蛋”、“驴肉”、 “下雪”、“鸟蛋”、“鱼肉 ”、“下雾”也应该 是词,按照这样规则组 合下去会产 生很多让人费解的结论。如果不是,这些字符串在我们日常生活中使用的频率非常高, 而且都有独立的意义。
分词是自然语言处 理中最基本、最底层 的模块,分 词精度对后续应用模块影响很 大,纵观整个自然语 言处理领域,文本或句 子的结构化 表示是语言处理最核心的任务 。
目前,文本的结构化表示简单分为四大类 :词向量空 间模型、主题模、主题模型、依存句法的树表示、RDF 的图表示。以上这四种文本表示都以分 词为基础的。

一般专业化大型的文本分类系统,为了提高精度,常常订制开发自己的分词系统。 一般算法都使用 CRF,语料资源则各有不同。现在开放出来的、比较成熟的、有商用 价值的分词工具有:理工大学张华平博士开发的中文分词系统:http://ictclas.nlpir.org/ (一年免费试用权);哈工大的语言云系统:http://w ww .ltp-c loud.c om/intro/(开源),Ansj的中文分词系统:http://www.nlpcn.org/(开源)等等。这类分词系统完整性强,稳定性、精度也不错。即使如此也不能满足搜索引擎 类公司对超大规模 分词的需求,一般像 新浪和百度这些公司的分词系统,仅扩展词汇要到五百多万,估计基础词汇不少于五十万。
以上这些分词系统与 Python 整合都比较麻烦,占用资源也大。因为分词不是本书讲解的重点,为了方便说明原理, 本文后面的例子使用 jieba 分词,它是专门使用 Python 语言开发的分词系统,占用资源 较小,常识类文档的分词精度较高。对于非专业文档绰绰有余。下载最新的jibes,分词算法使用的是 CRF,编写语言是 Python,基础词库 349046 个。

构建词的向量空间

主要是统计文本中词的频率
文本分类的结构化方法就是向量空间模型,虽然越来越多的实践已经证明,这种
模型存在着的局限,但是迄今为止,它仍是在文本分类中应用最广泛、最为流行的数据结构,也是很多相关技术的基础,例如:推荐系统、搜索引擎等。

向量空间模型把文本表示为一个向量,其中该向量的每个特征表示为文本中出现
的词。通常,把训练集中出现的每个不同的字符串都作为一个维度,包括常用词、专有词、词组和其他类型模式串,如电子邮件地址和URL。

目前,大多数文本挖掘系统,都把文本存储为向量空间的表示,因为它便于运用机器学习算法。这类算法适用并能有效处理高维空间的文本情况。但是,对于大规模文本分类,这会导致极高维的空间,即使是中等大小的文本文件集合,向量的维度也很轻易就达到数十万维。

由于文本在存储为向量空间时,维度比较高。 为节省存储空间和提高搜索效率,
在文本分类之前会自动过滤掉某些字或词,这些字或词即被称为停用词。 这类词一般都是意义模糊的常用词,还有一些语气助词,通常它们对文本起不了分类特征的意义。

在设计系统的时候,可以逐步建立自己的停词库,在对样本进行训练时,将其作为参数,从而降低处理数据的复杂程度

使用TF-IDF构建权重策略

通过TF—IDF方法选出能代表文本特征的一些词
计算文本的权重向量,应该选择一个有效的权重方案。最流行的方案是对 TF-IDF
权重的方法。 TF-IDF的含义是词频–逆文档频率,其含义是如果某个词或短语在一篇文章中出现的频率 TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。

TF-IDF 的假设是,高频率词应该具有高权重,除非它也是高文档频率。“ my”这个词在文本中是最经常出现的词汇之一。它不仅很多次发生在单一的文本中,但几乎也发生在每个文档中。逆文档频率就是使用词条的文档频率来抵消该词的词频对权重的影响,而得到一个较低的权重。

样本训练与预测

使用算法,对样本进行训练,构建出分类模型
最常用的文本分类方法有 kNN 最近邻算法,朴素贝叶斯算法和支持向量机算法。
这三类算法一般而言 kNN最近邻算法的原理最简单,分类精度尚可,但是速度最慢;

朴素贝叶斯对于短文本分类的效果最好,精度很高;支持向量机的优势是支持线性不可分的情况,精度上取中.

Scikit-Learn 的算法库中包含了一些常用算法的实现,如果不打算研究算法的细节,可以通过调用对应算法的API,传入TF-IDF产生的训练集词袋,产生向量空间模型,并用该模型对测试数据进行预测。详细的代码实现,将在后面的章节详细展示

优化

如果预测的结果不准确,则需要调整算法的参数重新进行样本训练以及预测,每种算法的优化参数都不同,具体优化方法要看算法的要求

例如,如果是贝叶斯算法,可以通过调整alpha达到更高的精度,

常用算法以及原理说明

朴素贝叶斯

朴素贝叶斯分类是一种十分简单的分类算法,叫它朴素是因为其思想基础的简单性: 就文本分类而言,它认为词袋中的两两词之间的关系是相互独立的,即一个对象的特征向量中每个维度都是相互独立的。例如,黄色是苹果和梨共有的属性,但苹果和梨是相互独立的。这是朴素贝叶斯理论的思想基础。该思想主要基于以下理论
已知某条件概率,如何得到两个事件交换后的概率,也就是在已知P(A|B)的情况下如何求得 P(B|A):
这里写图片描述

算法步骤

  • 设 x={a1,a2,…,am}为一个待分类项,而每个 a 为 x 的一个特征属性。
  • 有类别集合 C={y1,y2,…,yn}。
  • 计算 P( y1|x) ,P( y2|x),…, P( yn|x)。
  • 如果 P( yk|x) =max{P( y1|x),P( y2|x),…, P( yn|x)},则 x∈yk。

代码示例

# -*- coding: utf-8 -*-__author__ = 'eric.sun'import sysimport osimport jiebaimport picklefrom sklearn.datasets.base import Bunchfrom sklearn.feature_extraction.text import TfidfTransformerfrom sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.naive_bayes import MultinomialNBreload(sys)sys.setdefaultencoding('utf-8')ori_path='/others/ml_test_data/text_classification/fudan/small_ori'#ori_path='E:\\others\\ml_test_data\\text_classification\\fudan\\small_ori'seg_path='/others/ml_test_data/text_classification/fudan/small_seg'train_data= '/others/ml_test_data/text_classification/train_set.dat'#test_file 是用来被预测的数据,拷贝自sport450/10201.txt 以及medicine204/7173.txt,# 以及一篇来自qq体育的新闻:http://sports.qq.com/a/20160828/014734.htm(放在medicine下)#    一篇来自里约奥运会的新闻:# ,结构如下# .# ├── medicine204# │   ├── 7173.txt# │   └── qq_news.txt# └── sport450#     ├── 10201.txt#     └── rio_olympic.txttest_file_path='/others/ml_test_data/text_classification/test_txt'test_data= '/others/ml_test_data/text_classification/test_set.dat'test_space_path= '/others/ml_test_data/text_classification/test_space.dat'tf_idf_space_data='/others/ml_test_data/text_classification/tf_idf_space.dat'stop_words_path='/others/ml_test_data/text_classification/stop_word.txt'#seg_path='E:\\others\\ml_test_data\\text_classification\\fudan\\small_seg'def save_file(path,content):    fp=open(path,'wb')    fp.write(content)    fp.close()def read_file(path):    fp=open(path,'rb')    content=fp.read()    fp.close()    return contentdef save_object(path,content):    fp=open(path,'wb')    pickle.dump(content,fp)    fp.close()def read_object(path):    fp=open(path,'rb')    object=pickle.load(fp)    fp.close()    return object#(1)对词库数据文件进行分词def segment():    """    将ori_path的内容,通过jieba进行分词操作    """    for ori_dir in os.listdir(ori_path):        seg_dir=os.path.join(seg_path,ori_dir)        ori_subdir_path=os.path.join(ori_path,ori_dir)        for ori_file in os.listdir(ori_subdir_path):            if not os.path.exists(seg_dir):                os.mkdir(seg_dir)            ori_content=read_file(os.path.join(ori_subdir_path,ori_file))            #对换行符进行替换            ori_content.replace('\r\n','').strip()            ori_content.replace(',','')            seg_result=jieba.cut(ori_content)            save_file(os.path.join(seg_dir,ori_file),' '.join(seg_result))    print 'segment finished'#将分词数据转换成文本向量def segment_bunch():    bunch=Bunch(target_name=[],label=[],filenames=[],contents=[])        catelist=os.listdir(seg_path)    bunch.target_name.extend(catelist)    for class_dir in catelist:        for class_file in os.listdir(os.path.join(seg_path,class_dir)):            #print class_file            class_file_full_path=os.path.join(seg_path,class_dir,class_file)            bunch.label.append(class_dir)            bunch.filenames.append(class_file_full_path)            bunch.contents.append(read_file(class_file_full_path).strip())    save_object(train_data, bunch)#停词对象def load_stop_words():    return read_file(stop_words_path).splitlines()#(2)生成tf_idf 词向量空间def gen_tf_idf_space():    bunch=read_object(train_data)    tf_idf_space=Bunch(target_name=bunch.target_name,label=bunch.label,filenames=bunch.filenames,vocabulary={})    vectorizer=TfidfVectorizer(stop_words=load_stop_words(),sublinear_tf = True,max_df = 0.5)    transformer=TfidfTransformer()    tf_idf_space.tdm=vectorizer.fit_transform(bunch.contents)    tf_idf_space.vocabulary=vectorizer.vocabulary_    save_object(tf_idf_space_data,tf_idf_space)    # print tf_idf_space#(3)生成测试数据的data文件def gen_test_data():    bunch = Bunch(target_name=[], label=[], filenames=[], contents=[])    catelist = os.listdir(test_file_path)    bunch.target_name.extend(catelist)    for class_dir in catelist:        for class_file in os.listdir(os.path.join(test_file_path, class_dir)):            # print class_file            class_file_full_path = os.path.join(test_file_path, class_dir, class_file)            bunch.label.append(class_dir)            bunch.filenames.append(class_file_full_path)            bunch.contents.append(read_file(class_file_full_path).strip())    save_object(test_data, bunch)#(4)使用多项贝叶斯进行预测def execute_NM_predict():    test_bunch=read_object(test_data)    test_space=Bunch(target_name=test_bunch.target_name, label=test_bunch.label, filenames=test_bunch.filenames, tdm=[], vocabulary = {})    tf_idf_bunch=read_object(tf_idf_space_data)    vectorizer = TfidfVectorizer(stop_words=load_stop_words(), sublinear_tf=True, max_df=0.5,vocabulary=tf_idf_bunch.vocabulary)    transformer = TfidfTransformer()    test_space.tdm = vectorizer.fit_transform(test_bunch.contents)    test_space.vocabulary = tf_idf_bunch.vocabulary    clf=MultinomialNB(alpha=0.001).fit(tf_idf_bunch.tdm,tf_idf_bunch.label)    #预测结果    predicted=clf.predict(test_space.tdm)    #对结果进行更加友好的打印    for label,file_name,excect_cate in zip(test_bunch.label,test_bunch.filenames,predicted):        print file_name,' 实际类别:',label,' 预测类别:',excect_cate    # print predictedif __name__ == '__main__':    segment()    segment_bunch()    load_stop_words()    gen_tf_idf_space()    gen_test_data()    execute_NM_predict()

kNN 算法

kNN 算法,一种基于向量间相似度的分类
算法。

k 最近邻( k-Nearest Neighbor)算法是比较简单的机器学习算法。它采用测量不同特征值之间的距离方法进行分类。它的思想很简单:如果一个样本在特征空
间中的 k 个最邻近(最相似)的样本中的大多数都属于某一个类别,则该样本也属
于这个类别

算法步骤

算法的详细步骤如下:
- 第一阶段: 首先我们事先定下 k 值(就是指最近邻居的个数)。 一般是个奇数, 我们因为测试样本有限取 k 值为 3;

  • 第二阶段: 确定的距离度量公式—文本分类一般使用夹角余弦,得出待分类数据点和所有已知类别的样本点中, 选择距离最近的 k 个样本。

  • 第三阶段: 统计这 k 个样本点中,各个类别的数量。根据 k 个样本中,数量最多的样本是什么类别,我们就把这个数据点定为什么类别。

代码示例

# -*- coding: utf-8 -*-from numpy import *import numpy as npimport matplotlib.pyplot as pltimport operatorimport sysreload(sys)sys.setdefaultencoding('utf-8')train_data_set=[[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]train_data_label=['A','A','B','B']test_data_point=[0.1,0.0]#前面的4个点已经分好类了,且各自有自己的标签,最后一个点用来判断离哪个类别更近一点def get_train_data_set():    test_data=np.array(train_data_set)    labels=train_data_label    return test_data,labels# 夹角余弦距离公式def cosdist(vector1,vector2):    return dot(vector1,vector2)/(linalg.norm(vector1)*linalg.norm(vector2))# kNN 分类器# 测试集: testdata;训练集: trainSet;类别标签: listClasses; k:k 个邻居数def classify(testdata, trainSet, listClasses, k):    dataSetSize = trainSet.shape[0] # 返回样本集的行数    distances = array(zeros(dataSetSize))    for indx in xrange(dataSetSize): # 计算测试集与训练集之间的距离:夹角余弦        distances[indx] = cosdist(testdata,trainSet[indx])        # 根据生成的夹角余弦按从大到小排序,结果为索引号        sortedDistIndicies = argsort(-distances)    classCount={}    for i in range(k): # 获取角度最小的前 k 项作为参考项        # 按排序顺序返回样本集对应的类别标签        voteIlabel = listClasses[sortedDistIndicies[i]]        # 为字典 classCount 赋值,相同 key,其 value 加 1        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1    # 对分类字典 classCount 按 value 重新排序    # sorted(data.iteritems(), key=operator.itemgetter(1), reverse=True)    # 该句是按字典值排序的固定用法    # classCount.iteritems():字典迭代器函数    # key:排序参数; operator.itemgetter(1):多级排序    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)    return sortedClassCount[0][0] # 返回序最高的一项def print_data(dataset,labels,test_data_point):    fig=plt.figure()    ax=fig.add_subplot(111)    #print test data point    ax.scatter(test_data_point[0],test_data_point[1],c='red',marker='^',linewidths=0,s=300)    for point,label in zip(dataset,labels):        #add point        ax.scatter(point[0],point[1],c='blue',marker='o',linewidths=0,s=300)        #add point common        plt.annotate("("+str(point[0])+","+str(point[1])+' '+label+")",xy = (point[0],point[1]))    #print figure    plt.show()def print_test():    train_data,train_label=get_train_data_set()    print_data(train_data,train_label,test_data_point)if __name__ =='__main__':    #打印测试    print_test()    print classify(np.array(test_data_point),np.array(train_data_set),train_data_label,1)
0 0