Task03:基于机器学习的文本分类

学习目标

  • 学会文本的离散表示的原理
  • 使用sklearn的机器学习模型完成文本分类

文本表示

在自然语言领域,需要对文本进行向量表示:文本是不定长度的。文本表示成计算机能够运算的数字或向量的方法一般称为词嵌入(Word Embedding)方法。
词嵌入是指:一个维数为所有词的数量的高维空间嵌入到一个维数低得多的连续向量空间,它是文本分类的第一步。

文本的表示方法分为离散表示和分布式表示:

  • 离散表示(离散、高维、稀疏):
    one-hot、count vector、TF-IDF、n-gram 等,
  • 分布式表示(连续、低维、稠密):
    经典模型是word2vec、Glove、ELMO、GPT、BERT。

1. One-hot:

每个字使用一个离散的向量表示。维度为字典大小,文字对应的索引处标为1,其余为0。

One-hot表示方法的例子如下:

句子1:我 爱 北 京 天 安 门
句子2:我 喜 欢 上 海

在这里共包括11个字,因此每句话可以转换为一个11维度稀疏向量:

句子1:我 爱 北 京 天 安 门
转换为 [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
句子2:我 喜 欢 上 海
转换为 [1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]

one-hot的问题:维度爆炸,稀疏sparse,字与字之间无关系,无法表示上下文关系等等……

2. Bag of Words:

也称count vector,使用字出现的次数来表示文本。维度是字典的大小,文字对应的索引处标为他在这条文本中出现的次数。

句子1:我 爱 北 京 天 安 门 门
句子2:我 喜 欢 上 海 我

直接统计每个字出现的次数,并进行赋值:

句子1:我 爱 北 京 天 安 门 门
转换为 [1, 1, 1, 1, 1, 1, 2, 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

bag of words的问题:仅考虑了字数的出现次数,问题与onehot类似。

3. N-gram:

N-gram与Count Vectors类似,不过加入了相邻单词组合成为新的单词,并进行计数。

如果N取值为2,则句子1和句子2就变为:

句子1:我爱 爱北 北京 京天 天安 安门
句子2:我喜 喜欢 欢上 上海

操作起来也很简单:我的代码是在原文的基础上再加上bigram

train=["我 爱 北 京 天 安 门","我 喜 欢 上 海"]
train_bigram=[]
for value in train:
    lentext=len(value.split())
    value=value.split()
    for i in range(lentext-1):
        value.append("".join(value[i:i+2]))
    train_bigram.append(" ".join(value))
# output
  ['我 爱 北 京 天 安 门 我爱 爱北 北京 京天 天安 安门', '我 喜 欢 上 海 我喜 喜欢 欢上 上海']

4. TF-IDF:

TF-IDF 分数由两部分组成:第一部分是词语频率(Term Frequency),第二部分是逆文档频率(Inverse Document Frequency)。其中计算语料库中文档总数除以含有该词语的文档数量,然后再取对数就是逆文档频率。

我们的层级关系为:语料库>文档>词语。
\text{TF}_w(t)= \frac{该词语(w)在当前文档(t)出现的次数 }{ 当前文档(t)中词语的总数} \\ \text{IDF}_w(t)= ln (\frac{语料库中的文档总数 }{出现过该词语(w)的文档总数+ \epsilon })\\ \text{TF-IDF}_w(t) = \text{TF}_w(t)*\text{IDF}_w(t)

  • TF (term frequency)词频:
    指的是某一个给定的词语在该文档中出现的次数。
    这个数字通常会被归一化(一般是词频 除以文档总词数 ), 以防止它偏向长的文档。(同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否。)
  • IDF (inverse document frequency) 逆向文件频率:
    主要思想是:如果包含词条t的文档越少, IDF越大,则说明词条具有很好的类别区分能力。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再取对数得到。

某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤常见的词语,保留重要的词语。

from sklearn.feature_extraction.text import TfidfVectorizer

由于TfidfVectorizerTfidf+CountVectorizer,所以有如下参数,
参数ngram_range: tuple(min_n, max_n):控制n-gram的范围,要提取的n-gram的n-values的下限和上限范围,在min_n <= n <= max_n区间的n的全部值。
参数max_features: 如果不为None,构建一个词汇表,仅考虑max_features--按语料词频排序,如果词汇表不为None,这个参数被忽略。也就是说这个参数可以加快运算速度。

TF-IDF的缺点主要还是稀疏的,维度也比较高
它使用sparse matrix进行保存数据的,简单的来说,就是三元组记录(i,j,k)表示 i 行 j 列的元素是 k 。

下面进行一下TF-IDF中元素的可视化展示:

首先是对象的类型:我们可以看到的确是sparse matrix,储存了53151659个元素

<200000x1000 sparse matrix of type ''
    with 53151659 stored elements in Compressed Sparse Row format>

矩阵train_test有什么特点? 计算一下它的稀疏度,达到了0.734,非常多的0啊。

# TODO: 
sparsity = 1- len(np.nonzero(train_test)[0])/(train_test.shape[0]*train_test.shape[1])
print (sparsity)  # 打印出稀疏度(sparsity)
#output
0.7342417050000001

我们取了每篇文章的前40个词语,为0不展示,非0输出。

for i in range(0,2):#打印每类文本的tf-idf词语权重,第一个for遍历所有文本,第二个for便利某一类文本下的词语权重
    print (u"-------这里输出第",i+1,u"类文本的词语tf-idf权重------")
    for j in range(min(40,len(tfidf.get_feature_names()))):
        if train_test[i,j]!=0:
            print (tfidf.get_feature_names()[j],":",train_test[i,j])

output: 可以看到,前两篇文章只有不到10个是非0,可见tf-idf相当的稀疏啊

-------这里输出第 1 类文本的词语tf-idf权重------
1031 : 0.013883565340423655
1031 761 : 0.016471070579437433
1099 : 0.11356808782732254
1141 : 0.02594605663786779
1214 : 0.018628590967992242
1219 : 0.09588359370454744
1241 : 0.015350624386514937
1271 : 0.02999935826444165
1279 : 0.027819888128774638
1279 2975 : 0.01707749756960527
1324 : 0.00921777596903975
-------这里输出第 2 类文本的词语tf-idf权重------
1277 : 0.02127605710091801
1308 : 0.03284026507040861

5. 分布式表示:

关于文本的分布式表示留到下一次分享。

机器学习模型

1. RidgeClassifier:

这是教材中使用的模型为RidgeClassifier,属于sklearn.linear_model

对他和正则化的LogisticRegression的区别有一些疑问,后来查询资料后得知,他与正则化的LogisticRegression的区别是损失函数不同。

正则化的LogisticRegression的损失函数为cross-entropy,而RidgeClassifie 用了 Ridge的回归模型来建立一个分类器,损失函数为 RMSE + l2 penality。对于两分类,大于0为正例,小于0为负例。对于多分类,会使用One-Vs-Rest model对每一个分类进行预测,然后再合起来进行多分类的预测。

2. LogisticRegression:

关于LogisticRegression正好也写过一篇博客

本章作业

1. 尝试改变TF-IDF的参数,并验证精度

发现DSW压根跑不动全部数据集,自己的电脑有点心疼,还是取小点把,总之,tf-idfmaxfeature太大跑不动,太小没精度,真是个糟糕的词嵌入啊!

tfidf = TfidfVectorizer(ngram_range=(1,3), max_features=1000)
train_test = tfidf.fit_transform(train['text'])

2.尝试使用其他机器学习模型,完成训练和验证

对于这么大的稀疏数据,根据CTR模型中的GBDT+LR模型的理论,LR模型对于稀疏数据的感觉会好一点,这里单选LR模型,不然DSW跑不动了。

X = train_test
y = np.array(train['label'])
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,random_state=666)
model = LogisticRegression(C=2.0)
model.fit(X_train,y_train)
y_predict = model.predict(X_test)
print(f1_score(y_predict, y_test, average='macro'))
# output 
0.8957692040204028

这里只是做了一些小尝试,也算是理解了文本表示中的离散模型,但是本人对tf-idf并不感兴趣,不想投入过多的算力在其中(抱紧自己的小电脑),期望在分布式表示中继续学习。

其他:

我们可以学习一个天池上面的baseline,来加深tf-idf+machine-learning的理解。
学习一个文本分类的baseline

你可能感兴趣的:(Task03:基于机器学习的文本分类)