全文共7034字,預計學習時長14分鐘
本文將以BBC新聞文章分類為例,討論不同的文本分類技術。同時,本文將討論如何用不同向量空間模型代表文本數據。
為解決此問題,將使用到Python、Sci-kit-learn、Genism和Xgboost庫等工具。
獲取數據
本問題涉及到的數據可以在Kaggle上找到。本數據集包含BBC新聞文本及其雙欄CSV格式的分類列表,展示如下:
import pandas as pd
bbc_text_df = pd.read_csv('../data/bbc-text.csv')
bbc_text_df.head()
表中似乎含有許多長文本。後續章節將對其作詳細論述。現在的問題是:若給定一個「文本」,就需要預測其類別。這無疑是一個多累文本分類的問題。
數據探索及視覺化呈現
首先,我們需要知道有哪些類別。
%matplotlib inline
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(12,5))
sns.countplot(x=bbc_text_df.category, color='green')
plt.title('BBC text class distribution', fontsize=16)
plt.ylabel('Class Counts', fontsize=16)
plt.xlabel('Class Label', fontsize=16)
plt.xticks(rotation='vertical');
由圖可知,有五個類別。可將其稱為「類」。顯然,該類別的分配並無太多偏斜。
下一步是弄清數據集該「文本」領域中包含何種內容。因此需要先清理文本。
文本清理通常包含以下步驟:
1. 英文字母小寫化
2. 去除標點
3. 去除整數、數字
4. 去除多餘空格
5. 去除標籤(如<html>, <p>等)
6. 去除停用詞(如and、to、the等)
7. 詞幹提取(將單詞轉換至詞源形式)
此次使用了Python的「genism」庫進行文本清理工作。
from gensim import utils
import gensim.parsing.preprocessing as gsp
filters = [
gsp.strip_tags,
gsp.strip_punctuation,
gsp.strip_multiple_whitespaces,
gsp.strip_numeric,
gsp.remove_stopwords,
gsp.strip_short,
gsp.stem_text
]
def clean_text(s):
s = s.lower()
s = utils.to_unicode(s)
for f in filters:
s = f(s)
return s
可應用「clean_text」功能完成這項任務。
把記錄的文本領域的第一段內容列印出來。
bbc_text_df.iloc[2,1]
清理後
clean_text(bbc_text_df.iloc[2,1])
文本可能稍微缺乏語法邏輯,但這是為了便於理解。
接下來會編寫一個功能,將「文本」內容視覺化呈現為「文字雲」。
%matplotlib inline
from wordcloud import WordCloud
def plot_word_cloud(text):
wordcloud_instance = WordCloud(width = 800, height = 800,
background_color ='black',
stopwords=None,
min_font_size = 10).generate(text)
plt.figure(figsize = (8, 8), facecolor = None)
plt.imshow(wordcloud_instance)
plt.axis("off")
plt.tight_layout(pad = 0)
plt.show()
需要連結所有文本,並將其導入此功能。
texts = ''
for index, item in bbc_text_df.iterrows():
texts = texts + ' ' + clean_text(item['text'])
plot_word_cloud(texts)
會出現如下結果:
單詞越大,意味著其出現頻率越高。所以「年 (year) 」、「時間 (time) 」、「人 (peopl) 」是出現頻率最高的詞。
現在進行更深入的分析:在某一特定「類別」理,「文本」的「文字雲」。
為其編寫一項一般/通用功能。
def plot_word_cloud_for_category(bbc_text_df, category):
text_df = bbc_text_df.loc[bbc_text_df['category'] == str(category)]
texts = ''
for index, item in text_df.iterrows():
texts = texts + ' ' + clean_text(item['text'])
plot_word_cloud(texts)
比如,「技術」這一「類別」的「文字雲」
plot_word_cloud_for_category(bbc_text_df,'tech')
因此,「技術」這一類別中最常出現的詞是「人 (peopl)」、「techlog」、「遊戲 (game)」等。
而對於「運動」類別:
plot_word_cloud_for_category(bbc_text_df,'sport')
最常出現的詞是「plai」、「遊戲 (game) 」、「運動員 (player) 」、「勝利 (win) 」、「比賽 (match) 」、「英格蘭 (England) 」等。
對於「政治」類別:
plot_word_cloud_for_category(bbc_text_df,'politics')
最常出現的詞是「治理 (govern) 」、「人 (people) 」、「布萊爾 (blair) 」、「國家 (countri) 」、「部長 (Minist) 」等。
毫無疑問,每一個類別中都有自己獨有的詞彙。也可以這樣理解:每一個「文本」的內容都在暗示某個語境,從而決定其類別。
需要進行向量空間分析,並將其應用於模型,以證實以上推斷。
向量空間建模及構建管道
對於任何自然語言處理問題,都有必要進行向量空間建模。以兩個最常見的向量空間模型為例:Doc2Vec和Tf-Idf。首先,把數據分成特徵和類別。
df_x = bbc_text_df['text']
df_y = bbc_text_df['category']
Doc2Vec
使用「Genism」庫的Doc2Vec編寫一般/通用「Doc2VecTransfoemer」。
from gensim.models.doc2vec import TaggedDocument, Doc2Vec
from sklearn.base import BaseEstimator
from sklearn import utils as skl_utils
from tqdm import tqdm
import multiprocessing
import numpy as np
class Doc2VecTransformer(BaseEstimator):
def __init__(self, vector_size=100, learning_rate=0.02, epochs=20):
self.learning_rate = learning_rate
self.epochs = epochs
self._model = None
self.vector_size = vector_size
self.workers = multiprocessing.cpu_count() - 1
def fit(self, df_x, df_y=None):
tagged_x = [TaggedDocument(clean_text(row).split(), [index]) for index, row in enumerate(df_x)]
model = Doc2Vec(documents=tagged_x, vector_size=self.vector_size, workers=self.workers)
for epoch in range(self.epochs):
model.train(skl_utils.shuffle([x for x in tqdm(tagged_x)]), total_examples=len(tagged_x), epochs=1)
model.alpha -= self.learning_rate
model.min_alpha = model.alpha
self._model = model
return self
def transform(self, df_x):
return np.asmatrix(np.array([self._model.infer_vector(clean_text(row).split())
for index, row in enumerate(df_x)]))
應用該轉換器,可以看到「DocVec」如下:
doc2vec_trf = Doc2VecTransformer()
doc2vec_features = doc2vec_trf.fit(df_x).transform(df_x)
doc2vec_features
因此,這是一組文本數據的數位化呈現。可以把這個數字特徵應用到機器學習算法中。下面以LogisticRegression, RandomForest和XGBoost為例進行操作。
對於每一個案例,都應用數據集對模型進行五層/級交叉驗證並試運行。精確度得分將會是五個層級的平均分。
Doc2Vec和LogisticRegression管道
精確度變得非常低!!
再來看其它分類器。
Doc2Vec和RandomForest管道
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
pl_log_reg = Pipeline(steps=[('doc2vec',Doc2VecTransformer()),
('log_reg', LogisticRegression(multi_class='multinomial', solver='saga', max_iter=100))])
scores = cross_val_score(pl_log_reg, df_x, df_y, cv=5,scoring='accuracy')
print('Accuracy for Logistic Regression: ', scores.mean())
精確度也不是很高!!
Doc2Vec和XGBoost管道
import xgboost as xgb
pl_xgb = Pipeline(steps=[('doc2vec',Doc2VecTransformer()),
('xgboost', xgb.XGBClassifier(objective='multi:softmax'))])
scores = cross_val_score(pl_xgb, df_x, df_y, cv=5)
print('Accuracy for XGBoost Classifier : ', scores.mean())
精確度並未提高多少。
「Doc2Vec」運行狀況並不良好。
下面看「Tf-Idf」向量空間模型。
Tf-Idf
為「Tf-Idf」編寫一個相似的轉換器。
from sklearn.feature_extraction.text import TfidfVectorizer
class Text2TfIdfTransformer(BaseEstimator):
def __init__(self):
self._model = TfidfVectorizer()
pass
def fit(self, df_x, df_y=None):
df_x = df_x.apply(lambda x : clean_text(x))
self._model.fit(df_x)
return self
def transform(self, df_x):
return self._model.transform(df_x)
那麼,文本會變成什麼樣呢?
tfidf_transformer = Text2TfIdfTransformer()
tfidf_vectors = tfidf_transformer.fit(df_x).transform(df_x)
將其維數列印出來。
tfidf_vectors.shape
[「0」]標記總計出現18754次。
print(tfidf_vectors)
現在把此模型運用到實際機器學習模型中。
Tf-Idf & LogisticRegression
pl_log_reg_tf_idf = Pipeline(steps=[('tfidf',Text2TfIdfTransformer()),
('log_reg', LogisticRegression(multi_class='multinomial', solver='saga', max_iter=100))])
scores = cross_val_score(pl_log_reg_tf_idf, df_x, df_y, cv=5,scoring='accuracy')
print('Accuracy for Tf-Idf & Logistic Regression: ', scores.mean())
精確度很高!!
Tf-Idf & RandomForest
pl_random_forest_tf_idf = Pipeline(steps=[('tfidf',Text2TfIdfTransformer()),
('random_forest', RandomForestClassifier())])
scores = cross_val_score(pl_random_forest_tf_idf, df_x, df_y, cv=5,scoring='accuracy')
print('Accuracy for Tf-Idf & RandomForest : ', scores.mean())
Tf-Idf & XGBoost
pl_xgb_tf_idf = Pipeline(steps=[('tfidf',Text2TfIdfTransformer()),
('xgboost', xgb.XGBClassifier(objective='multi:softmax'))])
scores = cross_val_score(pl_xgb_tf_idf, df_x, df_y, cv=5)
print('Accuracy for Tf-Idf & XGBoost Classifier : ', scores.mean())
最後這一個精確度最高!!
毫無疑問,使用Tf-Idf & XGBoost結合模型能夠解決本案例的問題。
結果解讀
儘管在自然語言處理中,「DocVec」模型比「Tf-Idf」模型更高級,但我們的案例證明,後者效果更佳。我們分別使用了基於線性、袋狀以及推進型的分類器。
原因可以這麼理解。在我們的數據集中,每一個「文本」領域包含了一些決定其類別的高頻單詞/標記。因此,應用一個對語境/上下文敏感的模型會使問題更為複雜、(或者)混淆信息。某些文本類別包含一些高頻出現的標記,這些標記提供了大量數值以定義「Tf-Idf」模型。同時,「文本」是細分領域的。
比如,「布萊爾 (blair) 」一詞更可能出現在「政治」類別,而非「運動」類別。因此,像這樣的詞對「Tf-Idf」模型起了作用。
而且,「Doc2Vec」模型更適合應用於語法正確的文本中。
而我們的案例文本本質上過於粗糙。
「維基百科」文本就是一個語法正確的文本。
同時,大量案例和數據科學家的實驗證明,雖然「Tf-Idf」模型次於」DocVec」模型,但前者對於細分領域的文本分類更為有效。
結論
實驗結束。我們對所有分類器和向量空間模型的組合進行了測試。GitHub上有關於這一實驗的Jupyter筆記。
留言 點讚 關注
我們一起分享AI學習與發展的乾貨
歡迎關注全平臺AI垂類自媒體 「讀芯術」