学习ELMo从文本中提取特征的分步NLP指南

Introduction

我从事不同的自然语言处理(NLP)问题(成为数据科学家的好处!)。 每个NLP问题都是以自己的方式面临的独特挑战。 这只是人类语言复杂,美丽和精彩的反映。

但有一点一直是NLP从业者心中的荆棘是无法(机器)理解句子的真正含义。 是的,我在谈论背景。 当被要求执行基本任务时,传统的NLP技术和框架非常棒。 当我们试图为这种情况添加背景时,事情很快就消失了。

NLP格局在过去18个月左右发生了重大变化。 像谷歌的BERT和Zalando’s Flair这样的NLP框架能够解析句子并掌握 他们写作的背景。

在这里插入图片描述

语言模型嵌入(ELMo)
在这方面取得的最大突破之一归功于ELMo,这是AllenNLP开发的最先进的NLP框架。 当你完成这篇文章的时候,你也将成为一个伟大的ELMo粉丝 - 就像我一样。

在本文中,我们将探索ELMo(嵌入语言模型)并使用它来构建一个令人兴奋的使用Python的NLP模型 关于真实世界的数据集。

注意:本文假设您熟悉不同类型的字嵌入和LSTM体系结构。 您可以参考以下文章来了解有关这些主题的更多信息:

An Intuitive Understanding of Word Embeddings
Essentials of Deep Learning : Introduction to Long Short Term Memory

Table of Contents

  • 什么是ELMo?
  • 了解ELMo的工作原理
  • ELMo与其他单词嵌入有何不同?
  • 实现:用于Python中文本分类的ELMo
    - 理解问题陈述
    • 关于数据集
    • 导入库
    • 阅读和检查数据
    • 文本清理和预处理
    • TensorFlow Hub简介
    • ELMo载体制备
    • 模型构建和评估
  • 我们还能用ELMo做些什么?

What is ELMo?

不,我们所指的ELMo不是芝麻街的角色! 背景重要性的典型例子。

在这里插入图片描述
ELMo是一种在矢量或嵌入中表示单词的新方法。 这些单词嵌入有助于在几个NLP任务中实现最先进的(SOTA)结果:
在这里插入图片描述

全球NLP科学家已开始将ELMo用于各种NLP任务,包括研究和行业。 您必须在此处查看原始的ELMo研究论文 - https://arxiv.org/pdf/1802.05365.pdf。 我通常不会要求人们阅读研究论文,因为他们经常会遇到沉重而复杂的问题,但我正在为ELMo做例外。 这是对ELMo如何设计的一个非常酷的解释。

Understanding how ELMo works

让我们直观地了解ELMo在我们在Python中实现它之前是如何工作的。 为什么这很重要?

好吧,想象一下。 您已成功将GitHub中的ELMo代码复制到Python中,并设法在自定义文本数据上构建模型。 您可以获得平均结果,因此您需要改进模型。 如果您不了解ELMo的架构,您将如何做到这一点? 如果你还没有研究过它,你会调整哪些参数?

这种思路适用于所有机器学习算法。 你不需要进入他们的推导,但你应该总是知道足够的玩它们并改进你的模型。

现在,让我们回到ELMo的工作原理。

正如我之前提到的,ELMo字向量是在双层双向语言模型(biLM)之上计算的。 这个biLM模型有两层堆叠在一起。 每层有2次传球 - 前传和后传:

在这里插入图片描述

  • 上述架构使用字符级卷积神经网络(CNN)将文本字符串的单词表示为原始单词向量
  • 这些原始单词向量充当第一层biLM的输入
  • 前向传递包含有关某个单词和该单词之前的上下文(其他单词)的信息
  • 后向传递包含有关该词及其后的上下文的信息
  • 来自前向和后向传递的这对信息形成中间词向量
  • 这些中间词向量被送入下一层biLM
  • 最终表示(ELMo)是原始单词向量和2个中间单词向量的加权和

由于biLM的输入是根据字符而不是单词计算的,因此它捕获了单词的内部结构。 例如,biLM将能够发现像美丽和美丽这样的术语在某种程度上是相关的,甚至没有看到它们经常出现的背景。听起来不可思议!

How is ELMo different from other word embeddings?

与传统的单词嵌入(如word2vec和GLoVe)不同,分配给标记或单词的ELMo向量实际上是包含该单词的整个句子的函数。因此,相同的单词在不同的上下文中可以具有不同的单词向量。

我可以想象你在问 - 知道如何帮助我处理NLP问题?让我用一个例子解释一下。

假设我们有几句话:

  • 1我昨天读了这本书。
  • 2你现在能看完这封信吗?

花点时间思考这两者之间的区别。第一句中的动词“read”是过去时。同一个动词在第二句中转换成现在时。这是一词多义词,其中一个词可以有多种含义或感官。

语言是如此复杂的事情。

传统的单词嵌入在两个句子中为“read”一词提供了相同的向量。因此,系统将无法区分多义词。这些单词嵌入只是无法掌握使用该单词的上下文。
ELMo字向量成功解决了这个问题。 ELMo单词表示将整个输入句子转换为计算单词嵌入的等式。 因此,术语“读取”在不同的上下文中将具有不同的ELMo向量。

实现:用于Python中文本分类的ELMo
现在你等待的那一刻 - 用Python实现ELMo! 让我们一步一步来。

在这里插入图片描述

1. Understanding the Problem Statement

处理任何数据科学挑战的第一步是定义问题陈述。 它构成了我们未来行动的基础。

对于本文,我们已经掌握了问题陈述:

情感分析仍然是自然语言处理(NLP)广泛应用的关键问题之一。 这一次,鉴于客户关于制造和销售手机,电脑,笔记本电脑等各种技术公司的推文,我们的任务是确定这些推文是否对这些公司或产品产生负面情绪。

它显然是一个二进制文本分类任务,其中我们必须从提取的推文中预测情绪。

2. About the Dataset

这是我们所拥有的数据集的细分:

  • 列车集包含7,920条推文
  • 测试集包含1,953条推文

您可以从此页面下载数据集。 请注意,您必须注册或登录才能这样做。

警告:推文中大多数亵渎和粗俗的术语已被“$&@ *#”取代。 但请注意,数据集可能仍包含可能被视为亵渎,粗俗或冒犯的文本。

好吧,让我们开启我们最喜欢的Python IDE并进行编码!

3. Import Libraries

Import the libraries we’ll be using throughout our notebook:

import pandas as pd
import numpy as np
import spacy
from tqdm import tqdm
import re
import time
import pickle
pd.set_option('display.max_colwidth', 200)

4. Read and Inspect the Data

# read data
train = pd.read_csv("train_2kmZucJ.csv")
test = pd.read_csv("test_oJQbWVk.csv")

train.shape, test.shape

Output: ((7920, 3), (1953, 2))

训练组有7,920条推文,而测试组只有1,953条。 现在让我们检查训练组中的类分布:

train['label'].value_counts(normalize = True)

Output:

0 0.744192
1 0.255808
Name: label, dtype: float64

这里,1表示负推文,而0表示非负推文。

让我们快速浏览一下训练组的前5行:

train.head()

在这里插入图片描述

我们有三列可供使用。 列’tweet’是独立变量,而列’label’是目标变量。

5. Text Cleaning and Preprocessing

我们将拥有一个干净,结构化的数据集,可以在理想的世界中使用。 但是NLP中的事情并不那么简单(尚)。

我们需要花费大量时间来清理数据,以便为模型构建阶段做好准备。 从文本中提取特征变得容易,甚至特征包含更多信息。 您会发现模型性能有了显着改善,数据质量会越好。

所以,让我们清理我们给出的文本并进行探索。

推文中似乎有很多URL链接。 他们没有告诉我们很多(如果有的话)关于推文的情绪,所以让我们删除它们。

# remove URL's from train and test
train['clean_tweet'] = train['tweet'].apply(lambda x: re.sub(r'http\S+', '', x))

test['clean_tweet'] = test['tweet'].apply(lambda x: re.sub(r'http\S+', '', x))

我们使用正则表达式(或RegEx)删除了URL。

注意:您可以在文章中了解有关Regex的更多信息。

我们现在就开始做一些常规的文本清理工作。

# remove punctuation marks
punctuation = '!"#$%&()*+-/:;<=>?@[\\]^_`{|}~'

train['clean_tweet'] = train['clean_tweet'].apply(lambda x: ''.join(ch for ch in x if ch not in set(punctuation)))
test['clean_tweet'] = test['clean_tweet'].apply(lambda x: ''.join(ch for ch in x if ch not in set(punctuation)))

# convert text to lowercase
train['clean_tweet'] = train['clean_tweet'].str.lower()
test['clean_tweet'] = test['clean_tweet'].str.lower()

# remove numbers
train['clean_tweet'] = train['clean_tweet'].str.replace("[0-9]", " ")
test['clean_tweet'] = test['clean_tweet'].str.replace("[0-9]", " ")

# remove whitespaces
train['clean_tweet'] = train['clean_tweet'].apply(lambda x:' '.join(x.split()))
test['clean_tweet'] = test['clean_tweet'].apply(lambda x: ' '.join(x.split()))

我还想标准化文本,也就是说,执行文本规范化。 这有助于将单词缩减为其基本形式。 例如,“生产”,“生产”和“生产”等词的基本形式是“产品”。 经常发生的是,同一个单词的多种形式并不那么重要,我们只需要知道该单词的基本形式。

我们将利用流行的spaCy库对文本进行引理(标准化)。

# import spaCy's language model
nlp = spacy.load('en', disable=['parser', 'ner'])

# function to lemmatize text
def lemmatization(texts):
    output = []
    for i in texts:
        s = [token.lemma_ for token in nlp(i)]
        output.append(' '.join(s))
    return output

Lemmatize tweets in both the train and test sets:

train['clean_tweet'] = lemmatization(train['clean_tweet'])
test['clean_tweet'] = lemmatization(test['clean_tweet'])

Let’s have a quick look at the original tweets vs our cleaned ones:

train.sample(10)

在这里插入图片描述

仔细查看上面的列。 'clean_tweet’列中的推文似乎比原始推文更易读。

但是,我觉得还有很多空间来清理文本。 我鼓励您尽可能多地探索数据,并在文本中找到更多的见解或不正确之处。

6. Brief Intro to TensorFlow Hub

等等,TensorFlow与我们的教程有什么关系?

TensorFlow Hub是一个支持转移学习的库 通过允许使用许多机器学习模型来完成不同的任务,从而对预先训练的模型进行微调。 ELMo就是这样一个例子。 这就是我们在实现中通过TensorFlow Hub访问ELMo的原因。
在这里插入图片描述
在我们做任何其他事情之前,我们需要安装TensorFlow Hub。 您必须安装或升级TensorFlow软件包至少1.7才能使用TensorFlow Hub:

$ pip install "tensorflow>=1.7.0"
$ pip install tensorflow-hub

7. Preparing ELMo Vectors

我们现在将导入预训练的ELMo模型。 请注意 - 该型号的尺寸超过350 mb,因此下载此版本可能需要一段时间。

import tensorflow_hub as hub
import tensorflow as tf

elmo = hub.Module("https://tfhub.dev/google/elmo/2", trainable=True)

我将首先向您展示如何为句子获取ELMo向量。 您所要做的就是在对象elmo中传递一个字符串列表。

# just a random sentence
x = ["Roasted ants are a popular snack in Columbia"]

# Extract ELMo features 
embeddings = elmo(x, signature="default", as_dict=True)["elmo"]

embeddings.shape

Output: TensorShape([Dimension(1), Dimension(8), Dimension(1024)])

输出是三维张量的形状(1,8,1024):

  • 此张量的第一个维度表示训练样本的数量。 在我们的案例中,这是1
  • 第二个维度表示输入字符串列表中最长字符串的最大长度。 由于我们的输入列表中只有1个字符串,因此第二个维度的大小等于字符串的长度–8
  • 第三维等于ELMo向量的长度
    因此,输入句子中的每个单词都有一个大小为1024的ELMo向量。

让我们继续为列车和测试数据集中已清理的推文提取ELMo向量。 但是,为了得到整个推文的向量表示,我们将采用构成术语或推文标记的ELMo向量的均值。

让我们定义一个执行此操作的函数:

def elmo_vectors(x):
  embeddings = elmo(x.tolist(), signature="default", as_dict=True)["elmo"]

  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(tf.tables_initializer())
    # return average of ELMo features
    return sess.run(tf.reduce_mean(embeddings,1))

如果使用上述函数一次性提取推文的嵌入,则可能会耗尽计算资源(内存)。 作为一种解决方法,将列车和测试装置分成每批100个样品。 然后,将这些批次顺序传递给函数elmo_vectors()。

我会将这些批次保存在列表中:

list_train = [train[i:i+100] for i in range(0,train.shape[0],100)]
list_test = [test[i:i+100] for i in range(0,test.shape[0],100)]

现在,我们将遍历这些批次并提取ELMo向量。 警告!这需要很长时间。

# Extract ELMo embeddings
elmo_train = [elmo_vectors(x['clean_tweet']) for x in list_train]
elmo_test = [elmo_vectors(x['clean_tweet']) for x in list_test]

一旦我们拥有了所有向量,我们就可以将它们连接回一个数组:

elmo_train_new = np.concatenate(elmo_train, axis = 0)
elmo_test_new = np.concatenate(elmo_test, axis = 0)

我会建议你保存这些数组,因为我们花了很长时间才得到它们的ELMo向量。 我们将它们保存为pickle文件:

# save elmo_train_new
pickle_out = open("elmo_train_03032019.pickle","wb")
pickle.dump(elmo_train_new, pickle_out)
pickle_out.close()

# save elmo_test_new
pickle_out = open("elmo_test_03032019.pickle","wb")
pickle.dump(elmo_test_new, pickle_out)
pickle_out.close()

使用以下代码加载它们:

# load elmo_train_new
pickle_in = open("elmo_train_03032019.pickle", "rb")
elmo_train_new = pickle.load(pickle_in)

# load elmo_train_new
pickle_in = open("elmo_test_03032019.pickle", "rb")
elmo_test_new = pickle.load(pickle_in)

8. Model Building and Evaluation

让我们用ELMo构建我们的NLP模型!

我们将使用训练数据集的ELMo向量来构建分类模型。 然后,我们将使用该模型对测试集进行预测。 但在此之前,将elmo_train_new拆分为训练和验证集,以便在测试阶段之前评估我们的模型。

from sklearn.model_selection import train_test_split

xtrain, xvalid, ytrain, yvalid = train_test_split(elmo_train_new, 
                                                  train['label'],  
                                                  random_state=42, 
                                                  test_size=0.2)

由于我们的目标是设置基线分数,我们将使用ELMo向量作为特征构建一个简单的逻辑回归模型:

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score

lreg = LogisticRegression()
lreg.fit(xtrain, ytrain)

预测时间! 首先,在验证集上:

preds_valid = lreg.predict(xvalid)

我们将根据F1得分指标评估我们的模型,因为这是比赛的官方评估指标。

f1_score(yvalid, preds_valid)

Output: 0.789976

验证集上的F1分数非常令人印象深刻。 现在让我们继续并对测试集进行预测:

# make predictions on test set
preds_test = lreg.predict(elmo_test_new)

准备我们将在比赛页面上传的提交文件:

# prepare submission dataframe
sub = pd.DataFrame({'id':test['id'], 'label':preds_test})

# write predictions to a CSV file
sub.to_csv("sub_lreg.csv", index=False)

这些预测在公共排行榜上给出了0.875672的分数。 鉴于我们只进行了相当基本的文本预处理并使用了一个非常简单的模型,这是非常令人印象深刻的。 想象一下,使用更先进的技术可以得分。 在你的最后尝试,让我知道结果!

What else we can do with ELMo?

我们刚刚看到了ELMo对文本分类的有效性。 如果再加上更复杂的模型,它肯定会提供更好的性能。 ELMo的应用不仅限于文本分类的任务。 只要您需要对文本数据进行矢量化,就可以使用它。

以下是我们可以使用ELMo的一些NLP任务:

  • 机器翻译
  • 语言建模
  • 文本摘要
  • 命名实体识别
  • 问答系统

End Notes

ELMo无疑是NLP的重大进步,并且将继续存在。 鉴于NLP研究的进展速度非常快,最近几个月还出现了其他新的最先进的词汇嵌入,如Google BERT和Falando’s Flair。 NLP从业者的激动人心的时刻!

我强烈建议您在其他数据集上使用ELMo,并亲自体验性能提升。 如果您有任何问题或希望与我和社区分享您的经验,请在下面的评论部分中进行。

猜你喜欢

转载自blog.csdn.net/weixin_41697507/article/details/89419975