实验名称
逻辑回归用于讽刺文本检测
实验目的
- 了解训练分类模型的基本原理;
- 掌握模型解释及模型改进人流程;
- 熟悉讽刺文本检测的逻辑回归方法;
实验背景
逻辑回归是统计学中经典的分类方法,是一种判别学习模型。本实验旨在解决网络评论中的讽刺性信 息。因此,本节实验针对这一现象,提出了一种基于逻辑回归用于讽刺文本检测模型,提取页面文字的 特征为样本进行词法分析。以词频和加权的方法,再通过分类逻辑回归模型进行分类,来甄别该页面是 否存在讽刺性内容。
实验原理
一、数据可视化探索
首先,使用条形图可视化讽刺和正常文本长度,这里利用 np.log1p 对数据进行平滑处理,压缩到一定 区间范围内。接下来,挑战需要利用 WordCloud 绘制讽刺文本和正常文本关键词词云图。然后计算出 不同子板块评论的总数。由于讽刺评论的标签为 1,正常评论为 0,所以通过 sum 求和操作就可以直接 求出讽刺评论的计数。同理,mean 即代表讽刺评论所占比例。
二、逻辑回归模型分类
接下来,我们训练讽刺评论分类预测模型。这里,我们使用 tf-idf 提取文本特征,并建立逻辑回归模 型。开始训练模型,我们可以发现,讽刺评论通常都喜欢使用 yes, clearly 等带有肯定意味的词句。接下 来,我们期望模型能得到进一步改进,所以再补充一个 subreddit 特征,同样完成切分。注意,这里 切分时一定要选择同一个 random_state,保证能和上面的评论数据对齐。改进后分类结果准确率应该 更高些。
实验步骤
一、环境准备
本实验在jupyter notebook进行开发。
二、代码编写
1、准备数据
本次挑战使用论文 A Large Self-Annotated Corpus for Sarcasm 提供的语料数据。
2、导包
#导入相关包
import os
import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
#导入逻辑回归包
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
import seaborn as sns
from matplotlib import pyplot as plt
#导入警告过滤包
import warnings
warnings.filterwarnings('ignore')
然后,加载语料并预览。
train_df = pd.read_csv('train-balanced-sarcasm.csv')
train_df.head()
label | comment | author | subreddit | score | ups | downs | date | created_utc | parent_comment | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | NC and NH. | Trumpbart | politics | 2 | -1 | -1 | 2016-10 | 2016-10-16 23:55:23 | Yeah, I get that argument. At this point, I'd ... |
1 | 0 | You do know west teams play against west teams... | Shbshb906 | nba | -4 | -1 | -1 | 2016-11 | 2016-11-01 00:24:10 | The blazers and Mavericks (The wests 5 and 6 s... |
2 | 0 | They were underdogs earlier today, but since G... | Creepeth | nfl | 3 | 3 | 0 | 2016-09 | 2016-09-22 21:45:37 | They're favored to win. |
3 | 0 | This meme isn't funny none of the "new york ni... | icebrotha | BlackPeopleTwitter | -8 | -1 | -1 | 2016-10 | 2016-10-18 21:03:47 | deadass don't kill my buzz |
4 | 0 | I could use one of those tools. | cush2push | MaddenUltimateTeam | 6 | -1 | -1 | 2016-12 | 2016-12-30 17:00:13 | Yep can confirm I saw the tool they use for th... |
查看数据集变量类别信息。
train_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1010826 entries, 0 to 1010825
Data columns (total 10 columns):
label 1010826 non-null int64
comment 1010773 non-null object
author 1010826 non-null object
subreddit 1010826 non-null object
score 1010826 non-null int64
ups 1010826 non-null int64
downs 1010826 non-null int64
date 1010826 non-null object
created_utc 1010826 non-null object
parent_comment 1010826 non-null object
dtypes: int64(4), object(6)
memory usage: 77.1+ MB
comment的数量小于其他特征数量,说明存在缺失值。这里直接将这些缺失数据样本删除
train_df.dropna(subset=['comment'], inplace=True)
输出数据标签,看一看类别是否平衡
train_df['label'].value_counts()
0 505405
1 505368
Name: label, dtype: int64
最后,将数据切分为训练和测试集
train_texts, valid_texts, y_train, y_valid = train_test_split(train_df['comment'], train_df['label'], random_state=17)
3、数据可视化探索
首先,使用条形图可视化讽刺和正常文本长度,这里利用 np.log1p 对数据进行平滑处理,压缩到一定 区间范围内。
#讽刺文本平滑压缩处理 train_df.loc[train_df['label'] == 1, 'comment'].str.len().apply( np.log1p).hist(label='sarcastic', alpha=.5)#正常文本平滑压缩处理 train_df.loc[train_df['label'] == 0, 'comment'].str.len().apply( np.log1p).hist(label='normal', alpha=.5)plt.legend()
<matplotlib.legend.Legend at 0x2010ecd5860>
可以看的,二者在不同长度区间范围(横坐标)的计数分布比较均匀。接下来,挑战需要利用 WordCloud 绘制讽刺文本和正常文本关键词词云图。
问题:参考 WordCloud 官方文档绘制两类评论文本词云图,可自定义样式效果
#导入词云包 from wordcloud import WordCloud, STOPWORDS#设置背景颜色是黑色,设置停用词为STOPWORDS,设置背景图片,设置最大现实字数2000,设置字体最大值 150,设置有30种随机生成状态,界面宽大小为400,高大小为800 wordcloud = WordCloud(background_color='black', stopwords=STOPWORDS, max_words=200, max_font_size=100, random_state=17, width=800, height=400)#图片输出尺寸设置 plt.figure(figsize=(16, 12)) #生成词云并注释为1 wordcloud.generate(str(train_df.loc[train_df['label'] == 1, 'comment'])) #显示词云 plt.imshow(wordcloud)
<matplotlib.image.AxesImage at 0x2010e99fbe0>
#图片输出尺寸设置 plt.figure(figsize=(16, 12)) #生成词云并注释为0 wordcloud.generate(str(train_df.loc[train_df['label'] == 0, 'comment'])) #显示词云 plt.imshow(wordcloud)
<matplotlib.image.AxesImage at 0x2010ebc9898>
词云非常好看,但往往看不出太多有效信息。
subreddit表示评论归属于 Reddit 论坛子板块信息。下面,我们使用 groupby 来确定各子板块讽刺评 论数量排序。
#计算出评论板块正常评论数,讽刺评论比例和讽刺评论数 sub_df = train_df.groupby('subreddit')['label'].agg([np.size, np.mean, np.sum]) #取出讽刺评论区前10 sub_df.sort_values(by='sum', ascending=False).head(10)
size | mean | sum | |
---|---|---|---|
subreddit | |||
AskReddit | 65674 | 0.401453 | 26365 |
politics | 39493 | 0.605348 | 23907 |
worldnews | 26376 | 0.642516 | 16947 |
leagueoflegends | 21034 | 0.542312 | 11407 |
pcmasterrace | 18987 | 0.566651 | 10759 |
news | 16891 | 0.603457 | 10193 |
funny | 17939 | 0.451474 | 8099 |
pics | 16152 | 0.484336 | 7823 |
todayilearned | 14159 | 0.547567 | 7753 |
GlobalOffensive | 13738 | 0.552045 | 7584 |
上面的代码中,np.size可以计算出不同子板块评论的总数。由于讽刺评论的标签为 1,正常评论为 0, 所以通过 sum求和操作就可以直接求出讽刺评论的计数。同理,mean即代表讽刺评论所占比例。这是 一个分析处理小技巧。
问题:沿用以上数据,输出子板块评论数大于 1000 且讽刺评论比例排名前 10 的信息。下面我们使用 代码:
#取出评论数大于 1000 且讽刺评论比例排名前 10 的信息 sub_df[sub_df['size'] > 1000].sort_values(by='mean', ascending=False).head(10)
size | mean | sum | |
---|---|---|---|
subreddit | |||
creepyPMs | 5466 | 0.784303 | 4287 |
MensRights | 3355 | 0.680775 | 2284 |
ShitRedditSays | 1284 | 0.661994 | 850 |
worldnews | 26376 | 0.642516 | 16947 |
Libertarian | 2562 | 0.640125 | 1640 |
atheism | 7377 | 0.639555 | 4718 |
Conservative | 1881 | 0.639553 | 1203 |
TwoXChromosomes | 1560 | 0.632692 | 987 |
fatlogic | 2356 | 0.623090 | 1468 |
facepalm | 1268 | 0.617508 | 783 |
同理,可以从用户的维度去分析讽刺评论的比例分布。下面就需要分析得出不同用户 author发表评论的 数量、讽刺评论的数量及比例。
问题:输出发表评论总数大于 300,且讽刺评论比例最高的 10 位用户信息。下面我们使用代码:
#计算出评论板块正常评论数,讽刺评论比例和讽刺评论数 sub_df = train_df.groupby('author')['label'].agg([np.size, np.mean, np.sum]) #取出评论数大于 300 且讽刺评论比例排名前 10 的信息 sub_df[sub_df['size'] > 300].sort_values(by='mean', ascending=False).head(10)
size | mean | sum | |
---|---|---|---|
author | |||
NeonDisease | 422 | 0.500000 | 211 |
ShyBiDude89 | 404 | 0.500000 | 202 |
ivsciguy | 342 | 0.500000 | 171 |
mad-n-fla | 318 | 0.500000 | 159 |
mindlessrabble | 302 | 0.500000 | 151 |
pokemon_fetish | 432 | 0.500000 | 216 |
Biffingston | 845 | 0.499408 | 422 |
3、训练分类模型
接下来,我们训练讽刺评论分类预测模型。这里,我们使用 tf-idf 提取文本特征,并建立逻辑回归模 型。
# 使用 tf-idf 提取文本特征 tf_idf = TfidfVectorizer(ngram_range=(1, 2), max_features=50000, min_df=2) # 建立逻辑回归模型 logit = LogisticRegression(C=1, n_jobs=4, solver='lbfgs', random_state=17, verbose=1)# 使用 sklearn pipeline 封装 2 个步骤 ttfidf_logit_pipeline = Pipeline([('tf_idf', tf_idf), ('logit', logit)])
下面就可以开始训练模型了。由于数据量较大,代码执行时间较长,请耐心等待。
问题:训练讽刺文本分类预测模型,并得到测试集上的准确度评估结果。下面我们使用代码
#拟合训练模型 tfidf_logit_pipeline.fit(train_texts, y_train)#逻辑回归预测 valid_pred = tfidf_logit_pipeline.predict(valid_texts) #准确打分 accuracy_score(y_valid, valid_pred)
[Parallel(n_jobs=4)]: Using backend LokyBackend with 4 concurrent workers.[Parallel(n_jobs=4)]: Done 1 out of 1 | elapsed: 11.5s finished
0.7209866478824191
4、模型解释
接下来,挑战构建一个混淆矩阵的函数 plot_confusion_matrix。
#构建混淆矩阵函数(实际,预测,类别,不归一化,设置标题,设置图片大小,输出相近颜色,保存路径) def plot_confusion_matrix(actual, predicted, classes, normalize=False, title='Confusion matrix', figsize=(7, 7), cmap=plt.cm.Blues, path_to_save_fig=None): #导入迭代函数 import itertools #导入混淆矩阵函数 from sklearn.metrics import confusion_matrix #导入实际和预测过的结果 cm = confusion_matrix(actual, predicted).T if normalize:#如果归一化 cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]#转为浮点型并归一化 plt.figure(figsize=figsize)#生成图片 plt.imshow(cm, interpolation='nearest', cmap=cmap)#显示预测结果图片 plt.title(title)#设置标题 plt.colorbar()#设置颜色条 tick_marks = np.arange(len(classes))#从头到尾返回标记序列 plt.xticks(tick_marks, classes, rotation=90)#返回X坐标位置并旋转90度 plt.yticks(tick_marks, classes)#返回Y坐标位置 fmt = '.2f' if normalize else 'd'#归一化保留两位小数,否则是整型 thresh = cm.max() / 2.#取最大值的一半 for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): plt.text(j, i, format(cm[i, j], fmt), horizontalalignment="center",color="white" if cm[i, j] > thresh else "black")#输出图形信息,格式 化,水平对齐,颜色配置 plt.tight_layout() plt.ylabel('Predicted label')#预测标签 plt.xlabel('True label')#真实标签 if path_to_save_fig:#图像保存 plt.savefig(path_to_save_fig, dpi=300, bbox_inches='tight')
应用 plot_confusion_matrix绘制出测试数据原始标签和预测标签类别的混淆矩阵。
#绘制出测试数据原始标签和预测标签类别的混淆矩阵 plot_confusion_matrix(y_valid, valid_pred, tfidf_logit_pipeline.named_steps['logit'].classes_,figsize=(8, 8))
实际上,这里利用 eli5 可以输出分类器在预测判定是文本特征的权重。下面我们使用eli5包并输出文 本特征权重:
先在终端输入:
import eli5 #输出文本特征权重 eli5.show_weights(estimator=tfidf_logit_pipeline.named_steps['logit'], vec=tfidf_logit_pipeline.named_steps['tf_idf'])
我们可以发现,讽刺评论通常都喜欢使用 yes, clearly 等带有肯定意味的词句。
5、模型改进
接下来,我们期望模型能得到进一步改进,所以再补充一个 subreddit特征,同样完成切分。注意,这 里切分时一定要选择同一个 random_state,保证能和上面的评论数据对齐。
subreddits = train_df['subreddit'] #保证能和上面的评论数据对齐 train_subreddits, valid_subreddits = train_test_split( subreddits, random_state=17)
接下来,同样使用 tf-idf 算法分别构建 2 个 TfidfVectorizer用于 comment和 subreddits的特征提取。
#特征参数设置 tf_idf_texts = TfidfVectorizer( ngram_range=(1, 2), max_features=50000, min_df=2)#构建子目录 tf_idf_subreddits = TfidfVectorizer(ngram_range=(1, 1))
问题:使用构建好的 TfidfVectorizer完成特征提取。下面我们使用代码:
#提取文本特征 X_train_texts = tf_idf_texts.fit_transform(train_texts) X_valid_texts = tf_idf_texts.transform(valid_texts) X_train_texts.shape, X_valid_texts.shape
((758079, 50000), (252694, 50000))
#提取子目录特征 X_train_subreddits = tf_idf_subreddits.fit_transform(train_subreddits) X_valid_subreddits = tf_idf_subreddits.transform(valid_subreddits) X_train_subreddits.shape, X_valid_subreddits.shape
((758079, 13255), (252694, 13255))
然后,将提取出来的特征拼接在一起。
from scipy.sparse import hstack #将训练文本特征与训练子目录特征拼接起来 X_train = hstack([X_train_texts, X_train_subreddits]) #将有效文本特征与有效子目录特征拼接起来 X_valid = hstack([X_valid_texts, X_valid_subreddits])X_train.shape, X_valid.shape
((758079, 63255), (252694, 63255))
最后,同样使用逻辑回归进行建模和预测。
问题:使用新特征训练逻辑回归分类模型并得到测试集上的分类准确度。下面我们使用代码:
#逻辑回归训练 logit.fit(X_train, y_train) #逻辑回归预测 valid_pred = logit.predict(X_valid) #打分结果 .accuracy_score(y_valid, valid_pred)
[Parallel(n_jobs=4)]: Using backend LokyBackend with 4 concurrent workers.[Parallel(n_jobs=4)]: Done 1 out of 1 | elapsed: 20.1s finished
0.7237805408913548
实验总结
以上我们介绍了使用条形图可视化讽刺和正常文本长度,挑战需要利用 WordCloud 绘制讽刺文本和正 常文本关键词词云图。然后计算出不同子板块评论的总数,同理,mean 即代表讽刺评论所占比例。我 们训练讽刺评论分类预测模型,使用 tf-idf 提取文本特征,并建立逻辑回归模型,训练模型,发现讽刺评 论通常都喜欢使用 yes, clearly 等带有肯定意味的词句。模型改进,再补充一个subreddit 特征,同样完成切分,保证能和上面的评论数据对齐,改进后分类结果准确率应该更高些。