本章将使用传统机器学习算法来完成新闻分类的过程。
1.学会TF-IDF的原理和使用
2.使用sklearn的机器学习模型完成文本分类
机器学习是对能通过经验自动改进的计算机算法的研究。机器学习通过历史数据训练出模型对应人类对经验进行归纳的过程;机器学习利用模型对新数据进行预测对应于人类利用总结的规律对新问题进行预测的过程。
机器学习有很多分支,初学者应该优先掌握机器学习算法的分类,然后对其中一种机器学习算法进行学习。作为一名机器学习初学者,你应该要知道:
1.机器学习能解决一定的问题,但它并不是万能的;
2.机器学习算法有很多种,它们有各自的偏好,需要根据具体问题选择具体算法。
在机器学习算法的训练过程中,假设给定N个样本,每个样本有M个特征,组成N×M的样本矩阵,然后完成算法的训练和预测。例如在计算机视觉领域,可以将图片的像素看作特征,每张图片看作hight×width×3的特征图,对一个三维的矩阵来进行计算。
但是在自然领域,上述方法却不可行,因为文本是不定长度的。我们需要将文本表示为计算机能够运行的数字或者向量,这种方法称为词嵌入(Word Embedding)方法。词嵌入将不定长的文本转换到定长的空间,是文本分类的第一步。
独热编码,即将每一个单词使用一个离散的向量表示。具体操作就是将每个字/词编码一个索引,然后根据索引进行赋值。
例子:
对所有句子的字进行索引,给每个字确定一个编号:
一共有11个字,可以转换为一个11维度的稀疏向量:
Bag of Words(词袋表示),也称为Count Vectors,使用每个字 /词出现的次数来进行表示。
sklearn中可以使用CountVectorizer来实现。
N-gram与Count Vectors类似,是将相邻字/词组合成新的字词,并进行计数。
如果N取2,则句子1和句子2变为:
CountVectorizer中ngram_range参数改为(2,2)则为2-gram。
TF-IDF分数由两部分组成,第一部分是词语频率(Term Frequency),第二部分是逆文档频率(Inverse Document Frequency)。其中计算语料库中文档总数除以含有该词语的文档数量,然后再去对数就是逆文档频率。
TF(t)= 该词语在当前文档出现的次数 / 当前文档中词语的总数。
IDF(t)= log_e(⽂档总数 / 出现该词语的文档总数)。
词频TF很好理解,就是文本中每个词出现的频率,但是逆文本频率IDF如何理解呢?我们知道有些词的词频可能很高,但是它的重要性却可能没那么高(比如‘的’,‘地’这些字),IDF就是来帮助我们来反应这个词的重要性的,进而修正仅仅用词频表示的词特征值。
概括来讲, IDF反应了一个词在所有文本中出现的频率,如果一个词在很多的文本中出现,那么它的IDF值应该低。而反过来如果一个词在比较少的文本中出现,那么它的IDF值应该高。一个极端的情况,如果一个词在所有的文本中都出现,那么它的IDF值应该为0。
sklearn中可以使用TfidfVectorizer来实现。
对比不同文本表示算法的精度,通过本地构建验证集计算F1得分。
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score
train_df = pd.read_csv('data/train_set.csv', sep='\t', nrows=15000)
#Count Vectors + RidgeClassifier
vectorizer = CountVectorizer(max_features=3000)
train_test = vectorizer.fit_transform(train_df['text'])
clf = RidgeClassifier()
clf.fit(train_test[:10000], train_df['label'].values[:10000])
val_pred = clf.predict(train_test[10000:])
f1_score(train_df['label'].values[10000:], val_pred, average='macro')
结果:0.7404817207081769
#TF-IDF + RidgeClassifier
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score
tfidf = TfidfVectorizer(ngram_range=(1, 3) ,max_features=3000)
train_test = tfidf.fit_transform(train_df['text'])
clf = RidgeClassifier()
clf.fit(train_test[:10000], train_df['label'].values[:10000])
val_pred = clf.predict(train_test[10000:])
f1_score(train_df['label'].values[10000:], val_pred, average='macro')
结果:0.8721598830546126
fit_transform学习到一个字典,并返回Document-term的矩阵(即词典中的词在该文档中出现的频次),行是文档个数,列是特征词的个数。
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score
#2-gram + RidgeClassifier
vectorizer = CountVectorizer(ngram_range=(2, 2), max_features=3000)
train_test = vectorizer.fit_transform(train_df['text'])
clf = RidgeClassifier()
clf.fit(train_test[:10000], train_df['label'].values[:10000])
val_pred = clf.predict(train_test[10000:])
f1_score(train_df['label'].values[10000:], val_pred, average='macro')
结果:0.7628933215959407
介绍了基于机器学习的文本分类方法,对比结果发现TF-IDF的转换方法f1_score最高。
1.尝试改变TF-IDF的参数,并验证精度
#TF-IDF + RidgeClassifier
tfidf = TfidfVectorizer(ngram_range=(1, 3) ,max_features=3000, sublinear_tf=True)
修改参数sublinear_tf=True,应用线性缩放TF,例如,使用1+log(tf)覆盖tf,f1_score提高为0.8847366264933348。
tfidf = TfidfVectorizer(ngram_range=(1, 3) ,max_features=3000,stop_words=['3750', '900', '648'], sublinear_tf=True)
Task2中我们知道编号’3750’, ‘900’, '648’可能为标点符号,可是尝试直接过滤,效果提高了一点点:0.8877121780871436。
tfidf = TfidfVectorizer(ngram_range=(1, 3) ,max_features=3000,stop_words=['3750', '900', '648'], sublinear_tf=True, max_df=0.6)
我们还可以使用max_df=0.6过滤掉出现次数超过60%的词,结果提高到:0.8899145557347227。
2.尝试使用其他机器学习模型,完成训练和验证。
使用KNN分类器:
#TF-IDF + KNN Classifier
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import f1_score
train_df = pd.read_csv('data/train_set.csv', sep='\t', nrows=15000)
tfidf = TfidfVectorizer(ngram_range=(1, 3) ,max_features=3000,stop_words=['3750', '900', '648'], sublinear_tf=True, max_df=0.6)
train_test = tfidf.fit_transform(train_df['text'])
clf = KNeighborsClassifier()
clf.fit(train_test[:10000], train_df['label'].values[:10000])
val_pred = clf.predict(train_test[10000:])
f1_score(train_df['label'].values[10000:], val_pred, average='macro')
结果:0.8626093007762307
参考资料.