以BBC新闻文章为例:应用XGBoost等算法进行文本分类

全文共7034字,预计学习时长14分钟


图片来源:unsplash.com/@knightwill


本文将以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. 去除标签(如,

等)


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垂类自媒体 “读芯术”


打开APP阅读更多精彩内容