1. 程式人生 > >自然語言處理系列-2-文字分類-傳統機器學習方法

自然語言處理系列-2-文字分類-傳統機器學習方法

文件分類是指給定文件p(可能含有標題t),將文件分類為n個類別中的一個或多個,本文以人機寫作為例子,針對有監督學習簡單介紹傳統機器學習方法。 文件分類的常見應用:

新聞分類: 也就是給新聞打標籤,一般標籤有幾千個,然後要選取k個標籤,多分類問題,可見2017知乎看山杯比賽該比賽是對知乎的問題打標籤; 人機寫作判斷: 判斷文章是人寫的還是機器寫的,二分類問題,可見CCF2017的360人機大戰題目; 情感識別: 例如判斷豆瓣影評中的情感是正向、負向、中立,這個問題很常見而且應用場景很廣泛;

使用傳統機器學習方法解決文件分類問題一般分為:

  1. 文件預處理;
  2. 特徵提取;
  3. 分類器選取;
  4. 模型訓練及驗證;

1. 文字預處理

分詞:中文任務分詞必不可少,一般使用jieba分詞,工業界的翹楚。

詞性標註:在分詞後判斷詞性(動詞、名詞、形容詞、副詞…),在使用jieba分詞的時候設定引數就能獲取。

WORD—EMBEDDING:通過詞與上下文、上下文與詞的關係,有效地將詞對映為低維稠密的向量,可以很好的表示詞,一般是把訓練和測試的語料都用來做word-embedding。可以把word-embedding作為傳統機器學習演算法的特徵,同時也是深度學習方法必不可少的步驟(深度學習中單字和詞的embedding都需要)。 本文使用Word2Vector實現Word Embedding,引數設定情況如下:

size=256 : Word Embedding的維度,如果是詞的話一般設定為256,字的話設定為100就差不多,畢竟漢字數量為9w左右常用字7000左右; window=5: 滑動視窗的大小,詞一般設定為5左右,表示當前詞加上前後詞數量為5,如果為字的話可以設定大一點; min_count=5: 最小詞頻,超過該詞頻的才納入統計,字的話詞頻可以設定高一點; workers=15: 執行緒數量,加速處理;

分詞、Word Embedding訓練的程式碼如下,推薦使用pickle進行中間資料儲存:

import pickle
import codecs
import jieba
import multiprocessing
import codecs
import pandas as pd
from gensim.models.word2vec import Word2Vec

train_file = "train.csv"
test_file = "test.csv"
train_file = codecs.open(train_file, 'r', 'utf-8')
train_lines =
train_file.readlines() test_file = codecs.open(test_file, 'r', 'utf-8') test_lines = test_file.readlines() label = [] train_title = [] train_content = [] train_title_cut = [] train_content_cut = [] test_id = [] test_title = [] test_content = [] test_title_cut = [] test_content_cut = [] print("Segment train title/content...") for i in range(len(train_lines)): if i % 10000 == 0: print(i) if len(train_lines[i].split('\t')) != 4: continue article_id, title, content, l = train_lines[i].split('\t') if 'NEGATIVE' in l: label.append(0) else: label.append(1) train_title.append(title) train_content.append(content) train_title_cut.append(' '.join(jieba.cut(title.strip('\n'), cut_all=False))) train_content_cut.append(' '.join(jieba.cut(content.strip('\n'), cut_all=False))) print("Segment train completed.") print("Segment test title/content...") for i in range(len(test_lines)): if i % 10000 == 0: print(i) if len(test_lines[i].split('\t')) != 3: continue article_id, title, content = test_lines[i].split('\t') test_id.append(article_id) test_title.append(title) test_content.append(content) test_title_cut.append(' '.join(jieba.cut(title.strip('\n'), cut_all=False))) test_content_cut.append(' '.join(jieba.cut(content.strip('\n'), cut_all=False))) print("Segment test completed.") pickle.dump(label, open('train_label.p', 'wb')) pickle.dump(train_title, open('train_title.p', 'wb')) pickle.dump(train_content, open('train_content.p', 'wb')) pickle.dump(train_title_cut, open('train_title_cut.p', 'wb')) pickle.dump(train_content_cut, open('train_content_cut.p', 'wb')) pickle.dump(test_id, open('test_id.p', 'wb')) pickle.dump(test_title, open('test_title.p', 'wb')) pickle.dump(test_content, open('test_content.p', 'wb')) pickle.dump(test_title_cut, open('test_title_cut.p', 'wb')) pickle.dump(test_content_cut, open('test_content_cut.p', 'wb')) corpus = train_title_cut + train_content_cut + test_title_cut + test_content_cut class CorpusData: def __init__(self, corpus): self.corpus = corpus def __iter__(self): for doc in corpus: origin_words = doc.split(' ') yield origin_words print("Train word to vector...") corpus_data = CorpusData(corpus) model = Word2Vec(corpus_data, size=256, window=5, min_count=5, workers=15) model.save('w2v_model_s256_w5_m5.save') print("Train w2v completed.")

2. 特徵提取

可以說提取的特徵決定了整個任務分數的上限,強的或者說敏感的特徵對文件分類有及其大的影響,而弱特徵的組合有時候也能發揮意向不到的效果,提取過程一般是選取文件的常規特徵、針對具體任務設計的特徵、對特徵的強度計算和篩選。

2.1 常規特徵

TF-IDF:詞頻-逆文件頻率,用以評估詞對於一個文件集或一個語料庫中的其中一個文件的重要程度,計算出來是一個DxN維的矩陣,其中D為文件的數量,N為詞的個數,通常會加入N-gram,也就是計算文件中N個相連詞的的TF-IDF。一般用sklearn的庫函式計算,具體用法詳見sklearn.feature_extraction.text.TfidfVectorizer。在人機寫作判斷的問題來看,TF-IDF是很強的一個特徵。 LDA(文件的話題):可以假設文件集有T個話題,一篇文件可能屬於一個或多個話題,通過LDA模型可以計算出文件屬於某個話題的概率,這樣可以計算出一個D * T的矩陣。LDA特徵在文件打標籤等任務上表現很好。 LSI(文件的潛在語義):通過分解文件-詞頻矩陣來計算文件的潛在語義,和LDA有一點相似,都是文件的潛在特徵。 詞性的TD—IDF:以詞的詞性表示詞,再次計算其tf-idf,由於詞性種類很有限,矩陣比較小。

2.2 針對具體任務設計特徵

本文是以人機寫作判斷為例子,為此設計了以下特徵,其中每種特徵都選取最大值、最小值、平均值、中位數、方差:

句子長度:文件短句之後,統計句子長度; 標點數:文件斷句之後,每個句子中的標點個數; jaccard相似度:分詞後的每個句子與分詞後的標題的jaccard距離; 重複句子:文件中是否有重複句子; 英文、數字個數:斷句後句子中的英文、數字個數;

2.3 特徵的強度計算和篩選

我們要儘可能選取任務敏感的特徵,也就是特徵足夠強可以影響分類的結果,一般用樹模型判斷特徵的重要程度,xgboost的get_fscore就可以實現這一功能。計算特徵強度之後,選取較強的特徵,摒棄弱特徵。可以嘗試組合不同的特徵來構造新的特徵,然後測試新特徵的強弱,反覆如此獲取更多的強特徵。

3. 分類器選取

特徵提取相當於構造了一個D * F的矩陣,其中D為文件數量,F為特徵數量,前人給我留下了諸如樸素貝葉斯、邏輯迴歸、SVM、決策樹、神經網路等分類演算法,sklearn等機器學習庫將其封裝為簡單容易上手的API,供我們選擇。

樸素貝葉斯:以前很多人拿到資料就會馬上用樸素貝葉斯驗證一下資料集,但是往往由於各個特徵不是獨立同分布的所以樸素貝葉斯的效果一般不好,但是也可以嘗試一下,如果效果不錯作為最後模型融合的一個模型。針對人機寫作判斷的任務來說效果太差了,就沒有記錄。

邏輯迴歸:用來解決迴歸問題的方法,分類問題可以看做迴歸問題的特例,也能嘗試用此方法。但是結果和樸素貝葉斯差不多的樣子。

SVM:在神經網路火起來之前,可以說SVM撐起了一半分類問題的解決方案,SVM的想法很質樸:找出兩類點之間的最大間隔(也包括軟間隔,即間隔中有樣本點)把樣本點分類。但是在人機寫作判斷的問題上,SVM的表現也不好。

樹模型:樹結構很適合文件分類,使用LightGBM模型對文件進行分類,其中調參的經驗是樹的深度不要太深、讓樹儘可能矮寬,這樣分類比較充分,一般深度為4~6就行。樹模型的調參是一件很痛苦的事情,推薦基於貝葉斯優化的調參方法。有熟悉XGBoost的同學也可以嘗試,但是總體來說LGB比XGB速度要快好幾倍更適合驗證特徵以及調參。

4. Adaboost多次訓練

這是原文針對人機寫作判斷問題,根據Adaboost演算法設計的一個小Trick。如果每次分類結果中,把大量的負樣本分為正樣本,或者大量負樣本分為正樣本,就根據正樣本和負樣本的錯誤率調整正負樣本的權重,例如正負樣本的錯誤率分別為P(正)、P(負),當前權重分別為W(正)、W(負),則根據以下方式調整

W(正) = P(正)+ [1 - abs(P(正) + P(負)) / 2] W(負) = P(負)+ [1 - abs(P(正) + P(負)) / 2]

根據這種動態調整權重的方法,可以充分發揮樹模型在分類問題上的優勢。

小結

深度學習的模型,明顯優於傳統機器學習模型。而且傳統機器學習方法中的特徵提取環節太過費時費力,而且經常很不討好。建議剛接觸文件分類或者其他機器學習任務,可以嘗試傳統的機器學習方法(主要是統計學習方法),可以體驗一下這幾個過程,但是如果想取得好成績最好還是嘗試深度學習。

參考連結: