Twitter美国航空公司情绪数据集的监督或半监督ULMFit模型
磐创AI分享
作者 | Aadit Kapoor
编译 | VK
来源 | Towards Data Science
我们的任务是将ULMFit(Ruder等人,2018)等监督/半监督技术应用于Twitter美国航空公司情绪分析数据。
这个问题之所以是半监督的,是因为它首先是一种无监督的训练方法,然后通过在网络顶部添加一个分类器网络对网络进行微调。
❝我们使用Twitter美国航空公司的数据集(https://www.kaggle.com/crowdflower/twitter-airline-emotion) ❞
我们将从以下几点开始:
- 探索数据集,对其进行预处理并为模型做准备
- 探索一点情感分析的历史
- 探讨语言模型及其重要性
- 设置baseline模型
- 文本分类技术探讨
- ULMFit简介
- ULMFIT在Twitter美国航空公司数据中的应用
- 结果与预测
- 结论与未来方向
数据集
我们将首先研究数据集统计信息并执行所有必需的特征转换。
- 由于这是一个多类分类问题,我们将对目标变量进行编码。
- 我们将更改列的顺序
- 我们将进行基本统计,以便从数据中获得一些见解
- 最后,我们将新的数据帧分成df_train、df_val、df_test
# 正在加载数据集
df = pd.read_csv(DATA_DIR)
# LabelEncoder将积极、消极和中性更改为数字
labelEncoder = LabelEncoder()
def cleanAscii(text):
"""
从数据集中删除非ASCII字符。
Arguments:
text: str
"""
return ''.join(i for i in text if ord(i) < 128)
def gather_texts_and_labels(df=None, test_size=0.15,random_state=42):
"""
从数据集收集文本和相应的标签,并将其拆分。
Arguments:
df: Pandas DataFrame
test_size: 表示测试集大小
random_state: 随机状态
Returns:
(x_train, x_test, y_train, y_test, new_df)
"""
# 文本
texts = df["text"].values
# 编码标签 (positive, neutral, negative)
df['airline_sentiment'] = labelEncoder.fit_transform(df['airline_sentiment'])
labels = df['airline_sentiment'].values
# 更改fastai标记器的顺序捕获数据。
new_df = pd.DataFrame(data={"label":labels, "text":texts})
df_train, df_test = train_test_split(new_df, stratify = new_df['label'], test_size=test_size, random_state = random_state)
df_train, df_val = train_test_split(df_train, stratify = df_train['label'], test_size = test_size,
random_state = random_state)
print("Training: {}, Testing: {}, Val: {}".format(len(df_train), len(df_test), len(df_val)))
return df_train, df_test, df_val,new_df
def describe_dataset(df=None):
"""
描述数据集
Arguments:
df: Pandas Dataframe
"""
print(df["airline_sentiment"].value_counts())
print(df["airline"].value_counts())
print("nMean airline_sentiment_confidence is {}".format(df.airline_sentiment_confidence.mean()))
# 可选
def add_negativereason_to_text(df=None):
# 如果negativereason是NaN就改成""
df['negativereason'] = df['negativereason'].apply(lambda x: "" if pd.isna(x) else x)
# 添加negativereason
df['text'] = df['text'] + df['negativereason']
add_negativereason_to_text(df)
df['text'] = df['text'].apply(cleanAscii)
describe_dataset(df)
df_train, df_test, df_val, new_df = gather_texts_and_labels(df)
我们将依靠不同的指标来衡量模型的性能(精确度、召回率、F1分数)。
历史
在ULMFit(2018)或NLP中的迁移学习之前,我们使用word2Vec或GLove 等词嵌入来表示单词作为向量表示。
通常,我们使用嵌入层作为模型的第一层,然后根据需要附加一个分类器。这使得系统很难训练,因为它需要大量的数据。这些语言模型是早期使用概率分布来表示单词的统计信息。
- ULMfit,BERT,Universal sentence encoder,OpenAI GPT-2使用一种称为神经语言模型的东西来以分布式方式表示单词,并允许对一个大的预训练的语言模型进行微调,以帮助我们完成任务。
- 具体来说,ULMfit(2018)引入了三种新技术来微调预训练的语言模型
- 微调是计算机视觉中比较流行的一种方法,在NLP上进行了试验,结果表明也是成功的。
❝在更进一步之前,我们将看到语言模型和分类器的概述。 ❞
设定baseline
在任何机器学习实验之前,我们应该建立一个baseline,并将我们的结果与之进行比较。
为了建立baseline,我们将使用word2vec嵌入矩阵来尝试预测情绪。
- 为了加载我们的word2vec,我们将使用嵌入层,然后使用基本前馈神经网络来预测情绪。
「我们也可以加载一个预训练过的word2vec或GLOVE嵌入,以将其输入到我们的嵌入层中」。
「我们可以在嵌入层之后使用LSTM或CNN,然后再使用softmax激活函数」。
# word2vec需要句子作为列表列表。
texts = df['text'].apply(cleanAscii).values
tokenizer = keras.preprocessing.text.Tokenizer(num_words=5000, oov_token='<OOV>')
# 拟合
tokenizer.fit_on_texts(texts)
vocab_size = len(tokenizer.word_index) + 1
# 填充的最大长度 (batch_size, 100)
max_length = 100
train_text = tokenizer.texts_to_sequences(df_train['text'].values)
test_text = tokenizer.texts_to_sequences(df_test['text'].values)
# 获取填充长度
padded_train_text = keras.preprocessing.sequence.pad_sequences(train_text, max_length, padding='post')
padded_test_text = keras.preprocessing.sequence.pad_sequences(test_text, max_length, padding='post')
labels_train = keras.utils.to_categorical(df_train['label'].values, 3)
labels_test = keras.utils.to_categorical(df_test['label'].values, 3)
metrics = [
keras.metrics.Accuracy()
]
net = Sequential()
# 返回50维的嵌入表示,input_length为100
net.add(keras.layers.Embedding(vocab_size, 50, input_length=max_length))
net.add(keras.layers.Flatten())
net.add(keras.layers.Dense(512, activation='relu'))
net.add(keras.layers.Dense(3, activation='softmax'))
net.compile(optimizer='adam', loss=keras.losses.categorical_crossentropy, metrics=metrics)
net.summary()
# word2vec需要句子作为列表列表。
texts = df['text'].apply(cleanAscii).values
tokenizer = keras.preprocessing.text.Tokenizer(num_words=5000, oov_token='<OOV>')
# 拟合
tokenizer.fit_on_texts(texts)
vocab_size = len(tokenizer.word_index) + 1
# 填充的最大长度 (batch_size, 100)
max_length = 100
train_text = tokenizer.texts_to_sequences(df_train['text'].values)
test_text = tokenizer.texts_to_sequences(df_test['text'].values)
# 获取填充长度
padded_train_text = keras.preprocessing.sequence.pad_sequences(train_text, max_length, padding='post')
padded_test_text = keras.preprocessing.sequence.pad_sequences(test_text, max_length, padding='post')
labels_train = keras.utils.to_categorical(df_train['label'].values, 3)
labels_test = keras.utils.to_categorical(df_test['label'].values, 3)
metrics = [
keras.metrics.Accuracy()
]
net = Sequential()
# 返回50维的嵌入表示,input_length为100
net.add(keras.layers.Embedding(vocab_size, 50, input_length=max_length))
net.add(keras.layers.Flatten())
net.add(keras.layers.Dense(512, activation='relu'))
net.add(keras.layers.Dense(3, activation='softmax'))
net.compile(optimizer='adam', loss=keras.losses.categorical_crossentropy, metrics=metrics)
net.summary()
# 测试baseline模型
def test_baseline_sentiment(text):
"""
测试baseline模型
Arguments:
text:str
"""
padded_text = keras.preprocessing.sequence.pad_sequences(tokenizer.texts_to_sequences([text]), max_length, padding='post')
print(net.predict(padded_text).argmax(axis=1))
net.evaluate(padded_test_text, labels_test)
preds = net.predict(padded_test_text).argmax(axis=1)
❝结果显示,用一个简单的前馈神经网络和一个嵌入层,我们很难达到12%的准确率 ❞
加载语言模型并进行微调
FastAI为我们提供了一个易于使用的语言模型(AWD)。
我们将从加载LM数据开始,并用所需的数据初始化它。
data_lm = TextLMDataBunch.from_df(train_df = df_train, valid_df = df_val, path = "")
# 将数据保存为备份
data_lm.save("data_lm_twitter.pkl") # 保存作为备用
# 加载语言模型(AWD_LSTM)
learn = language_model_learner(data_lm, AWD_LSTM, drop_mult=0.3)
print(learn)
正如你所看到的,fastai库使用了一个标识器,因此我们不执行任何数据预处理,除非删除ascii字符。ULMFit的作者对标识化过程进行了很好的经验测试。
训练
# 寻找最佳学习率
learn.lr_find(start_lr=1e-8, end_lr=1e2)
learn.recorder.plot()
# 使用fit_one_cycle
learn.fit_one_cycle(1, 1e-2)
# 所有层都可以训练
learn.unfreeze()
# fit_one_cycle使用10个epoch
learn.fit_one_cycle(10, 1e-3, moms=(0.8,0.7))
# 保存编码器
learn.save_encoder('fine_tuned_enc') # 我们需要编码器,会用于分类器
文本分类
我们在网络下面创建添加我们的分类器(微调)。这是将指定的任务分类器添加到预训练的语言模型中的最后一步
# 准备分类器数据
data_clas = TextClasDataBunch.from_df(path = "", train_df = df_train, valid_df = df_val, test_df=df_test, vocab=data_lm.train_ds.vocab)
# 构建分类器
learn = text_classifier_learner(data_clas, AWD_LSTM, drop_mult=0.5)
# 加载保存的编码器
learn.load_encoder('fine_tuned_enc') # 从LM加载编码器
# 倾斜学习率调度器
# 微调整个网络
learn.fit_one_cycle(3, 1e-2, moms=(0.8,0.7)) # 你可以多次训练
# 一层一层地对网络进行微调,尽可能多地保留信息。
learn.freeze_to(-2) # 倒数第2层解冻
learn.fit_one_cycle(2, slice(1e-2/(2.6**4),1e-2), moms=(0.8,0.7))
learn.freeze_to(-3) # 倒数第3层解冻
learn.fit_one_cycle(2, slice(5e-3/(2.6**4),5e-3), moms=(0.8,0.7))
learn.freeze_to(-4) # 倒数第4层解冻
learn.fit_one_cycle(2, slice(5e-3/(2.6**4),5e-3), moms=(0.8,0.7))
learn.freeze_to(-5) # 倒数第5层解冻
learn.fit_one_cycle(2, slice(5e-3/(2.6**4),5e-3), moms=(0.8,0.7))
# 解冻所有的层
learn.unfreeze()
learn.fit_one_cycle(3, slice(1e-3/(2.6**4),1e-3), moms=(0.8,0.7))
我们达到了94%的准确率
ULMFit简介
ULMfit流程概述
论文:https://arxiv.org/abs/1801.06146
不同类型的流程如下:
- LM 预训练:这是我们遵循无监督学习的步骤,以获取大型语料库的语义和概率表示。
- LM 微调:这是我们使用某些新技术对LM进行微调的步骤。由于AWD-LSTM(pretrained model)的每一层都捕捉到关于语料库的不同信息,我们首先对最后一层进行微调,因为它包含的信息量最少,而所有其他层都被冻结。然后我们解冻所有其他层,用指定的任务重新训练模型。这样,我们就不会丢失信息。训练采用斜三角学习率(slanted triangular learning rate)进行。
- 最后一步是分类器的微调,分类器模型附着在模型的顶部,采用逐步解冻的方法进行训练,通过逐层解冻对模型进行训练。
这些技术包括:
- 微调
- 斜三角学习率
- 逐渐解冻
ULMFit在美国航空公司推特的情绪分析
def get_sentiment(text:str):
"""
获取文本的情感。
Arguments:
text: 要预测的文本情感
"""
index = learn.predict("This was a great movie!")[2].numpy().argmax()
print("Predicted sentiment: {}".format(mapping[index]))
def evaluate():
"""
评估网络
Arguments:
None
Returns:
accuracy: float
"""
texts = df_test['text'].values
labels = df_test['label'].values
preds = []
for t in texts:
preds.append(learn.predict(t)[1].numpy())
acc = (labels == preds).mean() * 100
print("Test Accuracy: {}".format(acc))
return preds, labels
get_sentiment("This is amazing")
preds, labels = evaluate()
print(classification_report(labels, preds, labels=[0,1,2]))
print(confusion_matrix(labels, preds))
模型结果
混淆矩阵
- 正如你所看到的,我们的模型是好的,但可以通过调节超参数来改进。
- 混淆矩阵显示我们的模型正确地分类了大多数类。
- 黑色代表0,从图中,我们得到的大部分预测都是黑色的
结论与未来方向
结果如下:
- 我们使用美国航空公司的tweet数据库训练一个模型来预测一条推文的情绪。
- 我们使用ULMFit(Ruder等人,2018年)用上述新技术训练我们的模型。
- 我们使用流行的fastai库来训练模型,因为它包含AWD-LSTM的预训练权重。
- 我们达到了94的测试准确度,由于我们的数据集是不平衡的,我们使用诸如F1分数的指标。
- 我们得到的F1分数是89。
- 我们使用混淆矩阵进一步检查模型的性能。
- 为了建立更好的模型,我们还可以使用其他语言模型和技术,如BERT、use、Transformers、XLNet等。
Colab Notebook:https://colab.research.google.com/drive/1eiSmiFjg1aeNgepSfSEB55BJccioP5PQ?usp=sharing
原文链接:https://towardsdatascience.com/natural-language-processing-nlp-dont-reinvent-the-wheel-8cf3204383dd
- 用线性判别分析 LDA 降维
- Bagging 简述
- 机器学习中常用评估指标汇总
- 用 Grid Search 对 SVM 进行调参
- PCA 的数学原理和可视化效果
- 用 Pipeline 将训练集参数重复应用到测试集
- 什么是 ROC AUC
- SSE(Server-sent events)技术在web端消息推送和实时聊天中的使用
- 详解 Stacking 的 python 实现
- RESTful接口设计原则和优点
- 用 Doc2Vec 得到文档/段落/句子的向量表达
- 手把手用 IntelliJ IDEA 和 SBT 创建 scala 项目
- 项目中记录影响性能的缓慢数据库查询
- memory_profiler的使用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP字符串与数组处理函数用法小结
- 详解Flask前后端分离项目案例
- Laravel5.1 框架表单验证操作实例详解
- 通过实例了解Python异常处理机制底层实现
- header函数设置响应头解决php跨域问题实例详解
- Linux采用双网卡bond、起子接口的方式
- PHP实现字母数字混合验证码功能
- php+pdo实现的购物车类完整示例
- CentOS7怎么执行PHP定时任务详解
- Linux下PHP+Apache的26个必知的安全设置
- linux中ssh免密通信的实现
- 怎么修改CentOS服务器时间为北京时间
- Laravel5.1 框架控制器基础用法实例分析
- Laravel5.1 框架模型软删除操作实例分析
- Laravel 手动开关 Eloquent 修改器的操作方法