在当今信息爆炸的时代,大约80%的信息是非结构化的,而文本数据是其中最常见的一种。由于其混乱的特性,分析、理解、组织和排序文本信息成为了复杂且耗时的任务。自然语言处理(NLP)和文本分类技术在此背景下应运而生。文本分类是一种机器学习技术,它能够将文本自动分类到不同的类别中。企业可以利用分类器模型自动结构化各种文本,如电子邮件、法律文件、社交媒体帖子、聊天机器人消息、调查结果等,从而节省分析信息的时间,自动化业务流程,并做出数据驱动的业务决策。
import pandas as pd
import fasttext
from sklearn.model_selection import train_test_split
import re
from gensim.parsing.preprocessing import STOPWORDS
from gensim.parsing.preprocessing import remove_stopwords
pd.options.display.max_colwidth = 1000
数据集是一系列新闻文章标题及其讽刺(来自新闻媒体“The Onion”)和非讽刺(来自“HuffPost”)的注释。数据集链接:https://www.kaggle.com/rmisra/news-headlines-dataset-for-sarcasm-detection
数据变量包括:
加载数据并检查变量和观测值的数量:
df_headline.shape
# (26709, 3)
df_headline.head(3)
df_headline.is_sarcastic.value_counts()
0 14985
1 11724
df_headline.is_sarcastic.value_counts(normalize=True)
0 0.561047
1 0.438953
以下是一些讽刺和非讽刺标题的例子:
df_headline[df_headline['is_sarcastic']==1].head(3)
df_headline[df_headline['is_sarcastic']==0].head(3)
为了提高模型性能,首先进行简单的文本预处理是至关重要的。在开始构建分类器之前,需要准备文本:将所有单词转换为小写,去除标点符号、特殊字符和数字。为此,让创建一个清理函数并将其应用于标题变量。
def clean_text(text):
text = text.lower()
text = re.sub(r'[^a-zA-Z0-9 @ []]', '', text) # 去除标点符号
text = re.sub(r' w * d + w *', '', text) # 去除数字
text = re.sub(' s {2,}', "", text) # 去除不必要的空格
return text
df_headline['headline'] = df_headline['headline'].apply(clean_text)
在开始训练模型之前,需要按照以下方式分割数据。通常,80%的信息用于训练模型(根据数据量,样本大小可能会有所不同),20%用于测试(准确性验证)。
train, test = train_test_split(df_headline, test_size=0.2)
接下来,需要准备txt格式的文件。默认文件格式应包括__label__。在数据集中,__label__0表示没有讽刺,但__label__1表示讽刺。
with open('train.txt', 'w') as f:
for every_text, every_lbl in zip(train['headline'], train['is_sarcastic']):
f.writelines(f '__label__ {every_lbl} {every_text} \n')
with open('test.txt', 'w') as f:
for every_text, every_lbl in zip(test['headline'], test['is_sarcastic']):
f.writelines(f '__label__ {every_lbl} {every_text} \n')
要训练模型,需要设置fastText的输入文件及其名称:
model1 = fasttext.train_supervised('train.txt')
def print_results(sample_size, precision, recall):
precision = round(precision, 2)
recall = round(recall, 2)
print(f'{sample_size=}')
print(f'{precision=}')
print(f'{recall=}')
print_results(*model1.test('test.txt'))
sample_size=5342
precision=0.85
recall=0.85
结果虽然不是完美的,但看起来很有希望。
手动寻找最佳超参数可能会很耗时。默认情况下,fastText模型在训练过程中每个训练样本只包含五次,考虑到数据集中只有12,000个样本,这个数字相当小。可以通过手动优化epoch来增加每个样本的查看次数(也称为epoch):
model2 = fasttext.train_supervised('train.txt', epoch=25)
print_results(*model2.test('test.txt'))
sample_size=5342
precision=0.83
recall=0.83
正如看到的,模型的准确性并没有提高。改变过程速度的另一种方法是增加(或减少)算法的学习率。这对应于模型在处理每个样本后的变化量。学习率为0意味着模型根本不会变化,因此不会学到任何东西。好的学习率范围在0.1-1.0之间。也可以手动优化这个超参数,使用参数lr:
model3 = fasttext.train_supervised('train.txt', epoch=10, lr=1.0)
print_results(*model3.test('test.txt'))
sample_size=5342
precision=0.83
recall=0.83
最后,可以通过使用词的二元组而不是仅仅使用单个字来提高模型的性能。这在分类任务中尤为重要,例如分析情感、定义批评、讽刺等,其中词序很重要。为此,将在模型中包含一个参数wordNgrams等于2。
model4 = fasttext.train_supervised('train.txt', epoch=10, lr=1.0, wordNgrams=2)
print_results(*model4.test('test.txt'))
sample_size=5342
precision=0.86
recall=0.86
通过这一系列的步骤,能够将准确性从86%提高:
还可以通过添加参数autotune Metric通过评估特定标签来适应超参数的搜索:
model5 = fasttext.train_supervised('train.txt', autotuneValidationFile='test.txt')
print_results(*model5.test('test.txt'))
sample_size=5342
precision=0.87
recall=0.87
fastText自动调整功能优化超参数以获得最高的F1。为此,需要包含模型参数autotune ValidationFile和测试数据集:
model6 = fasttext.train_supervised('train.txt', autotuneValidationFile='test.txt', autotuneMetric="f1:__label__1")
print_results(*model6.test('test.txt'))
sample_size=5342
precision=0.87
recall=0.87
让保存模型结果并创建一个函数来对新数据进行分类:
model6.save_model('optimized.model')
model.quantize(input='train.txt', retrain=True)
fastText还能够压缩模型以产生一个更小的文件,通过量化牺牲很少的性能。
还可以模拟新数据并测试模型对真实标题的效果。这将使用Kaggle上的新闻聚合数据集:https://www.kaggle.com/uciml/news-aggregator-dataset
df_headline_test = pd.read_csv('uci-news-aggregator.csv')
df_headline_test.TITLE.head(3)
让对新标题应用文本分类函数,并创建具有预测标签及其概率的变量:
df_headline_test['TITLE'] = df_headline_test['TITLE'].apply(clean_text)
def predict_sarcasm(text):
return model.predict(text, k=1)
df_headline_test['predict_score'] = df_headline_test.TITLE.apply(predict_sarcasm)
df_headline_test['predict_score'] = df_headline_test['predict_score'].astype(str)
df_headline_test[['label','probability']] = df_headline_test.predict_score.str.split(" ",expand=True)
df_headline_test['label'] = df_headline_test['label'].str.replace("(", '')
df_headline_test['label'] = df_headline_test['label'].str.replace(")", '')
df_headline_test['label'] = df_headline_test['label'].str.replace("__", ' ')
df_headline_test['label'] = df_headline_test['label'].str.replace(",", '')
df_headline_test['label'] = df_headline_test['label'].str.replace("'", '')
df_headline_test['label'] = df_headline_test['label'].str.replace("label", '')
df_headline_test['probability'] = df_headline_test['probability'].str.replace("array", '')
df_headline_test['probability'] = df_headline_test['probability'].str.replace("(", '')
df_headline_test['probability'] = df_headline_test['probability'].str.replace(")", '')
df_headline_test['probability'] = df_headline_test['probability'].str.replace("[", '')
df_headline_test['probability'] = df_headline_test['probability'].str.replace("]", '')
df_headline_test = df_headline_test.drop(columns=['predict_score'])
df_headline_test.label.value_counts(normalize=True)
输出结果:
0 0.710827
1 0.289173