任何分類問題, 都需要從數據中挖掘有用的特徵, 文本分類也不例外. 這裏會介紹幾種從文本中提取特徵的方式. 也是處理文本最基礎的方法.

文本表示方法

在機器學習算法的訓練過程中,假設給定$N$個樣本,每個樣本有$M$個特徵,這樣組成了$N×M$的樣本矩陣,然後完成算法的訓練和預測。同樣的在計算機視覺中可以將圖片的像素看作特徵,每張圖片看作hight×width×3的特徵圖,一個三維的矩陣來進入計算機進行計算。

但是在自然語言領域,上述方法卻不可行:文本是不定長度的。文本表示成計算機能夠運算的數字或向量的方法一般稱爲詞嵌入(Word Embedding)方法。詞嵌入將不定長的文本轉換到定長的空間內,是文本分類的第一步。

One-hot獨熱標籤

one-hot通常被用來編碼不同類別, 一個編碼的每一位對應一個類別, 且只有其中一位是1, 其餘均爲0. 按照相同的思想, 我們也可以用one-hot編碼來表示每一個單詞. 比如下面兩句話

句子1:我 愛 北 京 天 安 門

首先會統計兩句話中的所有字的類別, 並將每個類別編號

{

'我': 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]

這種思路看似是合理的, 但存在明顯的2個問題

  1. 對於一個稍複雜的語料數據, 就已經包含龐大數量的詞, 並且一個詞還會有多種形式, 如果每個詞都用一個one-hot編碼向量表示, 會導致維度爆炸.
  2. one-hot向量無法建模單詞之間(one-hot向量相互正交)的關係, 然而這種信息是文本中重要的特徵.

Bag of Words

bag of words(BoW)也叫詞袋模型, 是一種從文本中提取特徵用於建模的方法.

詞袋模型是一種描述一個文檔中的單詞出現的文本表示,它主要包括

  • 一個已有單詞的詞典.
  • 已有單詞表示的度量.

之所以被稱爲詞袋, 因爲BoW只關心已知單詞在文檔中是否出現, 並不關心它在文檔中出現的順序和結構信息.它將每個詞在文檔中的計數作爲特徵.

構建一個BoW模型包括以下幾個步驟

  1. 收集數據

    比如

    It was the best of times,

    it was the worst of times,

    it was the age of wisdom,

    it was the age of foolishness,

  2. 設計詞典

    可以將文檔庫(收集的數據)中自己認爲重要的單詞加入到詞典中, 詞典的形式如下

    “it”

    “was”

    “the”

    “best”

    “of”

    “times”

    “worst”

    “age”

    “wisdom”

    “foolishness”

  3. 創建文檔向量

    這一步的目的是將每個文檔(可以理解成包含不定長度單詞的句子)轉換爲一個固定長度的向量, 向量的長度爲詞典中單詞的個數.

    那麼如何將文檔轉換爲單個向量呢, 最簡單的方式就是, 使用一個布爾值來表示詞典中每個詞是否在文檔中是否出現, 出現了即爲1, 否則爲0

    比如上面的一個文檔得到的向量爲

    “it” = 1

    “was” = 1

    “the” = 1

    “best” = 1

    “of” = 1

    “times” = 1

    “worst” = 0

    “age” = 0

    “wisdom” = 0

    “foolishness” = 0

    對應向量爲:

    [1, 1, 1, 1, 1, 1, 0, 0, 0, 0]

在sklearn中, 我們可以利用自帶的工具快速的實現BoW的功能

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?',
]
# 將每個單詞在詞典中出現的次數作爲特徵
counter = CountVectorizer()
vectors = counter.fit_transform(corpus)

N-gram

簡單來說, N-gram模型根據前N-1個已有的單詞來預測一個單詞出現的概率, 但N=2(N-1=1)時, 即一個單詞出現的概率僅由它的前一個單詞決定.

那麼如何根據N-1個已出現的單詞來預測一個單詞的出現呢?

首先, 我們需要一個語料庫(corpus), 包含了大量的句子. 假設現在語料庫包含了如下的句子

1.He said thank you.

2.He said bye as he walked through the door.

3.He went to San Diego.

4.San Diego has nice weather.

5.It is raining in San Francisco.

假設我們設置N爲2, 即只根據它前一個詞來進行預測單詞出現的概率.通常而言, 概率的計算方式如下

$\frac{count(wp wn)}{count(wp)}$, wp表示上一個單詞, wn表示當前單詞, count爲計數函數.

比如我們要得到 you 出現在 thank 之後的概率 P(you|thank) ,它等同於

occurence times of "thank you" / occurence times of "thank"

= 1 / 1

= 1

我們可以說, 無論什麼時候出現了 thank , you 都會出現在它後面.

TF-IDF

TF-IDF(term frequency-inverse document frequency), 它是一種統計度量的方法, 用來評估一個單詞對於文檔庫中的一個文檔的相關程度. 它在信息檢索和文本挖掘經常被使用.

對於一個文檔中的一個單詞, 它的TF-IDF可以通過乘以兩個不同的指標來得到

  • term-frequency(TF): $TF(t) = \frac{count(t)}{ total \quad terms} =\frac{單詞t在當前文檔出現的次數}{當前文檔中總的單詞數}$
  • inverse document frequency(IDF): $IDF(t)=In(\frac{count(document)}{count(document\quad which\quad contain\quad term\quad t)})=In(\frac{總的文檔數目}{包含單詞t的文檔數目})$

比如一個文檔中包含100個詞, 單詞 cat 出現了3次, 則TF( cat )=3/100=0.03, 假設我們有1e7個文檔, cat 在其中的1e3箇中出現了, 則IDF( cat )=log(1e7/1e3)=4, 因此TF_IDF權重爲: 0.03 * 4 = 0.12.

現在回到競賽的數據中去, 嘗試使用TF-IDF來構建特徵進行分類

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics import  f1_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import RidgeClassifier

root_dir = '/content/drive/My Drive/competitions/NLPNews'

# 內存有限, 這裏只讀取10000行
train_df = pd.read_csv(root_dir+'/train.csv', sep='\t', nrows=10000)

# max_features表示詞典的大小, 包含詞頻最高的max_features個詞
tfidf = TfidfVectorizer(ngram_range=(1, 3), max_features=3000)
train_test = tfidf.fit_transform(train_df['text'])

# 構建分類器
clf = RidgeClassifier()

# 切分數據集
x_train, x_test, y_train, y_test = train_test_split(train_test, train_df['label'], test_size=0.1, random_state=0)

# 訓練模型
clf.fit(x_train, y_train)

# 執行預測
y_pred = clf.predict(x_test)

# 輸出宏平均f1-score
print(f1_score(y_test, y_pred, average='macro'))

0.8802400152512864

總結

通過本次的學習, 對於文本的表示方法以及文本數據集的特徵構建有了一個基本的瞭解.

Reference

[1]Datawhale零基礎入門NLP賽事 - Task3 基於機器學習的文本分類)

[2] A Gentle Introduction to the Bag-of-Words Model

[3] An Introduction to N-grams: What Are They and Why Do We Need Them?

[4] what does tf-idf mean?

相關文章