本次内容:用一堆的法子去处理二十个不同主题下的文档。(数据集:newsgroup posts)
前文为关于安装的balabala,反正我直接下了个anaconda套装。
加载数据集
数据集名称为“Twenty Newsgroups”,关于该数据集的描述:
20 Newsgroups数据集包含了近20000篇新闻文稿,被分成20个不同的主题,最早由Ken Lang为他自己的论文所收集,如今经常被拿来测试机器学习模型,如文本分类或聚类
该数据集可手动下载,然后用sklearn.datasets.load_files函数导入,不过这里我们直接加载它
from sklearn.datasets import fetch_20newsgroups
为了加快训练速度,第一个例子中,只取其中四个主题的文档
categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med']
#取训练集,并随机排序,另。。时间稍长
twenty_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=42)
之所以要重新排序,是方便你希望拿一个小样本出来快速验证一下自己的模型时,不至于从头一取大部分都是同一类里的。
返回值是一个类似于字典的objects
print twenty_train.target_names
print len(twenty_train.data),len(twenty_train.filenames)
#还可以看看第一个,全部打印就算了。。
print "\n".join(twenty_train.data[0].split("\n")[:3])
#看看第一条数据的类别
print twenty_train.target_names[twenty_train.target[0]]
提取特征
要让电脑可以去处理文本,那就得把每个文本表示成一个可计算可比较可量化的东西,也就是数值类型的特征向量。对哪一类的数据都是如此。
词袋模型
这应该是最基本的一种方式了,
初始化一个m*n维的X矩阵,就是模型输入值。m为文档的数量,n为下文中词典的长度。
1. 统计任一文档中出现的词,组合成一个词典b,其实就是这所有文档的词汇量汇总了,并且为每个词编号。
2. 对每个文档,查看词典b中的第j个词在文档中是否出现,出现了,则把出现的次数记录在X[i, j]中
由此带来的一个问题是,这个矩阵往往会非常大,如果是20000篇文档中有10000个不同的词,则所占空间为20000*10000*4bytes为8GB RAM。这个一般个人电脑玩不转的。
但是所幸这个矩阵里有很多零值,因为单就一篇文档而言,词汇量还是很有限的。于是大家们就引入了稀疏矩阵的存储方式来解决这个问题。关于此处可见scipy.sparse.
最简单的一个例子:
import numpy as np
from scipy.sparse import csr_matrix
# row记录行号,col记录列号,data为值。如都看数组的第一个元素,0行1列的值为1,以此类推
row = np.array([0, 1, 1, 2, 3, 3])
col = np.array([1, 0, 2, 3, 0, 1])
data = np.array([1, 2, 1, 1, 2, 3])
X = csr_matrix((data, (row, col)), shape=(4, 4))
print X
标记文本
首先,sklearn中有CountVectorizer函数可以直接得到上述文档的稀疏模型
from sklearn.feature_extraction.text import CountVectorizer
count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(twenty_train.data)
#看algorithm里词汇表的长度
print count_vect.vocabulary_.get(u'algorithm')
根据词典库以及文档中词语的分布我们可以判断矩阵X的哪个位置是有值的了,不过值取多少却要视情形而定。
- 二值特征,即只关心有还是没有,非0即1。听着不太合理,但事实说,有时候效果还不错。
- 出现了几次值为几。这就牵涉到了一个问题,如果某一文档本身较长,那它啥词都会显的更多,因此改用词出现的频率,就是出现的次数除以文档总词数啦。也就是常称的tf(term frequencies).
但这也不算完,所谓能体现主题的关键词。那当然是露面次数较少的。这样的词信息量才大。什么时候都说好其实就相当于什么都没说。照如此想,似乎又跟熵的理念比较接近。好吧,回到正题,因此针对这种情况,我们还可以给tf加个权重,即总文档数量与该词出现的文档数量之比。也就是常说的idf(inverse document frequency),当然这个idf的算法又有很多改进,比如取对数约束一下,或者分母加1防止为0的情形,或者光滑一下等等。但是本质上意思还是这么个意思
from sklearn.feature_extraction.text import TfidfTransformer
tf_transformer = TfidfTransformer(use_idf=False)
X_train_tf = tf_transformer.fit_transform(X_train_counts)
#use_idf默认为True
tfidf_transformer = TfidfTransformer()
X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)
上分类器
有了特征以后,就可以用模型对数据进行分类了,这里选用的是简单高效好用的朴素贝叶斯方法。
from sklearn.naive_bayes import MultinominalNB
clf = MultinomialNB().fit(X_train_tfidf, twenty_train.target)
接下来要对测试文档用transform方法,目的是使测试文档的特征能和训练时所用的特征匹配起来。
docs_new = ['God is love', 'openGL on the GPU is fast']
X_new_counts = count_vect.transform(docs_new)
X_new_tfidf = tfidf_transformer.transform(X_new_counts)
predicted = clf.predict(X_new_tfidf)
for doc, category in zip(docs_new, predicted):
print '%r => %s'%(doc, twenty_train.target_names[category])
#利用数据集中提供的测试集来看看准确率
twenty_test = fetch_20newsgroups(subset='test', categories=categories, shuffle=True, random_state=42)
docs_test = twenty_test.data
X_test = count_vect.transform(docs_test)
X_test_tfidf = tfidf_transformer.transform(X_test)
predicted = clf.predict(X_test_tfidf)
accuracy = np.mean(predicted == twenty_test.target)
print accuracy
当然也是可以用前一节说的Gridsearch来帮助选取参数。不过都是纯代码的东西,不想翻了,另外,困了zzz。。