磐创AI分享
作者 | Aadit Kapoor
编译 | VK
来源 | Towards Data Science
我们的任务是将ULMFit(Ruder等人,2018)等监督/半监督技术应用于Twitter美国航空公司情绪分析数据。
这个问题之所以是半监督的,是因为它首先是一种无监督的训练方法,然后通过在网络顶部添加一个分类器网络对网络进行微调。
❝我们使用Twitter美国航空公司的数据集(https://www.kaggle.com/crowdflower/twitter-airline-emotion) ❞
我们将从以下几点开始:
我们将首先研究数据集统计信息并执行所有必需的特征转换。
# 正在加载数据集
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 等词嵌入来表示单词作为向量表示。
通常,我们使用嵌入层作为模型的第一层,然后根据需要附加一个分类器。这使得系统很难训练,因为它需要大量的数据。这些语言模型是早期使用概率分布来表示单词的统计信息。
❝在更进一步之前,我们将看到语言模型和分类器的概述。 ❞
在任何机器学习实验之前,我们应该建立一个baseline,并将我们的结果与之进行比较。
为了建立baseline,我们将使用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流程概述
论文:https://arxiv.org/abs/1801.06146
不同类型的流程如下:
这些技术包括:
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))
模型结果
混淆矩阵
结果如下:
Colab Notebook:https://colab.research.google.com/drive/1eiSmiFjg1aeNgepSfSEB55BJccioP5PQ?usp=sharing
原文链接:https://towardsdatascience.com/natural-language-processing-nlp-dont-reinvent-the-wheel-8cf3204383dd