在机器学习算法的训练过程中,假设给定 N N N 个样本,每个样本有 M M M 个特征,这样组成了 N × M N×M N×M
的样本矩阵,然后完成算法的训练和预测。
但是在自然语言领域,上述方法却不可行:文本是不定长度的。文本表示成计算机能够运算的数字或向量的方法一般称为词嵌入(Word Embedding)方法。词嵌入将不定长的文本转换到定长的空间内,是文本分类的第一步。
这里的One-hot与数据挖掘任务中的操作是一致的,即将每一个单词使用一个离散的向量表示。具体将每个字/词编码一个索引,然后根据索引进行赋值。
One-hot表示方法的例子如下:
句子1:我 爱 北 京 天 安 门
句子2:我 喜 欢 上 海
首先对所有句子的字进行索引,即将每个字确定一个编号:
{
'我': 1, '爱': 2, '北': 3, '京': 4, '天': 5,
'安': 6, '门': 7, '喜': 8, '欢': 9, '上': 10, '海': 11
}
在这里共包括11个字,因此每个字可以转换为一个11维度稀疏向量:
我:[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
爱:[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
...
海:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
缺点:每个单词的one-hot编码维度是整个词汇表的大小,维度非常巨大,编码稀疏,会使得计算代价变大。而且词与词之间没有相关性
Bag of Words(词袋表示),也称为Count Vectors,每个文档的字/词可以使用其出现次数来进行表示。
句子1:我 爱 北 京 天 安 门
句子2:我 喜 欢 上 海
直接统计每个字出现的次数,并进行赋值:
句子1:我 爱 北 京 天 安 门 我
转换为 [2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
句子2:我 我 喜 欢 上 海
转换为 [2, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
在sklearn
中可以直接CountVectorizer
来实现这一步骤:
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
'This is the first document.',
'This document is the second document.',
'And this is the third one.',
'Is this the first document?',
]
vectorizer = CountVectorizer()
vectorizer.fit_transform(corpus).toarray()
array([[0, 1, 1, 1, 0, 0, 1, 0, 1],
[0, 2, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 0, 1, 1, 0, 1, 1, 1],
[0, 1, 1, 1, 0, 0, 1, 0, 1]], dtype=int64)
N-gram与Count Vectors类似,不过加入了相邻单词组合成为新的单词,并进行计数
如果N取值为2,则句子1和句子2就变为:
句子1:我爱 爱北 北京 京天 天安 安门
句子2:我喜 喜欢 欢上 上海
详细的N-gram介绍可以看这篇博客自然语言处理NLP中的N-gram模型
TF-IDF 分数由两部分组成:第一部分是词语频率(Term Frequency),第二部分是逆文档频率(Inverse Document Frequency)。其中计算语料库中文档总数除以含有该词语的文档数量,然后再取对数就是逆文档频率。
TF(t)= 该词语在当前文档出现的次数 / 当前文档中词语的总数
IDF(t)= log_e(文档总数 / 出现该词语的文档总数)
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('./train_set.csv', sep='\t', nrows=15000)
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:])
print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
0.7414710556201648
使用全部数据的结果
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('./train_set.csv', sep='\t', nrows=15000)
train_df = pd.read_csv('./train_set.csv', sep='\t')
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])
clf.fit(train_test[:150000], train_df['label'].values[:150000])
# val_pred = clf.predict(train_test[10000:])
val_pred = clf.predict(train_test[150000:])
# print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
print(f1_score(train_df['label'].values[150000:], val_pred, average='macro'))
跑全部数据速度太慢,跑完差不多用了10分钟,还是先拿15000条跑着玩吧
0.814299885456091
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score
train_df = pd.read_csv('./train_set.csv', sep='\t', nrows=15000)
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:])
print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
0.8721598830546126
我们要调整的参数:
ngram_range
:要提取的n-gram的n-values的下限和上限范围,在min_n <= n <= max_n区间的n的全部值。有时候我们觉得单个的词语作为特征还不足够,能够加入一些词组更好,就可以设置这个参数。ngram_range=(1, 3)表示统计三种类型的词:单个词,2个单词构成的词组,3个单词构成的词组。
max_features
:用多少个单词构建词汇表,按语料词频排序。在大规模语料上训练TFIDF会得到非常多的词语,如果再使用了上一个设置加入了词组,那么我们词表的大小就会爆炸。出于时间和空间效率的考虑,可以限制最多使用多少个词语,模型会优先选取词频高的词语留下。
同样地,我们仅使用15000条训练样本来尝试
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import RidgeClassifier
from sklearn.metrics import f1_score
train_df = pd.read_csv('./train_set.csv', sep='\t', nrows=15000)
for i in range(1, 4):
for j in [2000, 3000, 4000, 5000, 6000, 7000]:
tfidf = TfidfVectorizer(ngram_range=(1, i), max_features=j)
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:])
print(i, j, f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
1 2000 0.8609053798476228
1 3000 0.858329649339088
1 4000 0.8601916764212559
1 5000 0.8603325900148268
1 6000 0.8603325900148268
1 7000 0.8603325900148268
2 2000 0.8584782097110735
2 3000 0.8719465729628795
2 4000 0.8794638920291346
2 5000 0.886402018449437
2 6000 0.8877841442863286
2 7000 0.8875087923712194
3 2000 0.8603842642428617
3 3000 0.8721598830546126
3 4000 0.8753945850878357
3 5000 0.8850817067811825
3 6000 0.8901406771892212
3 7000 0.8920634181410882
之后又补测了两组
4 5000 0.8849155025217313
4 6000 0.8884095725793182
4 7000 0.891603038208734
4 8000 0.8929861900360884
4 9000 0.891819829251606
5 5000 0.8853177666563177
5 6000 0.8890428095306815
5 7000 0.8899856202435279
5 8000 0.891256263020867
5 9000 0.8901292428375412
在这些参数里,可以看到ngram_range=(1,4)
,max_features=8000
的结果是最好的
使用SVM和LR来进行训练和验证
SVM
这里使用的是SVC模型进行训练
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn import svm
from sklearn.metrics import f1_score
train_df = pd.read_csv('./train_set.csv', sep='\t', nrows=15000)
tfidf = TfidfVectorizer(ngram_range=(1,4), max_features=8000)
train_test = tfidf.fit_transform(train_df['text'])
clf = svm.SVC(C=1.0, gamma=0.1)
clf.fit(train_test[:10000], train_df['label'].values[:10000])
val_pred = clf.predict(train_test[10000:])
print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
0.8081017478499909
LR
使用LR进行训练
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score
train_df = pd.read_csv('./train_set.csv', sep='\t', nrows=15000)
tfidf = TfidfVectorizer(ngram_range=(1,4), max_features=8000)
train_test = tfidf.fit_transform(train_df['text'])
clf = LogisticRegression(C=2.0)
clf.fit(train_test[:10000], train_df['label'].values[:10000])
val_pred = clf.predict(train_test[10000:])
print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
0.8806360528882696