Twitter美国航空公司情绪数据集的监督或半监督ULMFit模型

时间:2022-07-24
本文章向大家介绍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