使用BERT升级你的初学者NLP项目

2021-08-05 10:16:28 浏览数 (1)

介绍

当我开始学习数据科学时,我认为我可以做一个深度学习或其他项目。

随着强大的模型越来越容易访问,我们可以轻松地利用深度学习的一些力量,而不必优化神经网络或使用GPU。

在这篇文章中,我们将研究嵌入。这是将单词表示为向量的方式。我们可以部分地生成嵌入,并在上面使用一些常规(scikit-learn)模型,以获得一些结果!

我将单独解释每个方法,使用图来表示为什么它工作,并演示如何在Python中实现这些技术。

目录

  • 先决条件
  • 词向量
  • 词袋法
  • Count Vectoriser
  • TF-IDF
  • 词嵌入
  • Word2Vec
  • GLoVe
  • Doc2Vec
  • 基于Transfromer的模型
  • Universal Sentence Encoder
  • BERT
  • 结论

先决条件

  • 你应该了解机器学习的基本知识。
  • 为了最大限度地利用这一点,你应该知道如何在scikit-learn中安装模型,并且已经有了适合NLP的数据集。
  • 对于那些已经有了一个NLP项目,并希望升级它并尝试深度学习的人来说,本教程是理想的选择。本文中的每个模型都增加了复杂性。本文将解释基本原理和如何使用该技术。

数据集

为了说明每个模型,我们将使用Kaggle NLP的灾难Tweets数据集。这是大约10000条推特,这些推特是根据关键词(例如ablaze)选择的,然后标记它们是否是关于真正的灾难。

你可以阅读有关比赛的信息,并在此处查看结果:

https://www.kaggle.com/c/nlp-getting-started

代码如下

https://github.com/AdamShafi92/Exploring-Embeddings

可视化

我们将使用2个维度可视化来探索每个模型。

可视化单词

句子的UMAP表示。UMAP是一种降维方法,它允许我们仅在2维中查看高维的单词表示。

降维是将数据从高维空间转换为低维空间,使低维表示保留原始数据的一些有意义的性质,理想的接近于其内在维数。

这对于可视化主题簇非常有用,但如果你以前没有遇到过降维,可能会感到困惑。本质上,我们是在寻找我们的词汇如何被分割成簇,在这些簇中,具有相似主题的Tweets在空间上彼此接近。明确区分蓝色(非灾难)和橙色(灾难)的文本,因为这意味着我们的模型能够很好地对这些数据进行分类。

我们按照下面的指标评估模型性能…

一组5张图表。从左到右:

  1. ROC AUC。这是一个典型的评分,允许我们比较模型。它考虑到预测的概率
  2. 精确率/召回率。另一个典型的度量。
  3. 特征重要性。这是为了比较我们从每个方法中得到的结果。这对BERT来说不会有什么意义,但有助于说明解释性
  4. 预测概率。这允许我们可视化模型如何很好地区分这两个类。
  5. 混淆矩阵。我们可视化假阳性与假阴性。

定义

  • 向量:向量的经典描述是一个数,它既有大小,也有方向(例如,西5英里)。在机器学习中,我们经常使用高维向量。
  • 嵌入:用向量作为一种表示词(或句子)的方法。
  • 文档:单个文本。
  • 语料库:一组文本。

表示单词作为向量

为了建立一个基于单词的模型,我们必须将这些单词转换成一个数字。最简单的方法是对每个单词进行one-hot编码并告诉我们的模型,例如

  • 句子1有单词1,单词12和单词13。
  • 句子2有单词6、单词24和单词35。

词袋和TDF-IDF以这种方式表示单词,在此基础上,包括一些单词出现频率的度量。

Bag of Words,词袋方法通过简单地为每个单词创建一列并用数字指示单词所在的位置,将单词表示为向量。向量的大小将与语料库中单词的数量相同。

这对于某些方法来说是好的,但是我们会丢失关于在同一个句子中具有不同含义的单词的信息,或者上下文信息。

把单词变成数字或向量,就是词嵌入。我们可以把一组单词描述成嵌入向量。

我们对词汇进行向量化的目的是以一种能够捕获尽可能多信息的方式来表示这些词汇……

我们怎样才能告诉一个模型一个词和另一个词相似?它怎么知道完全不同的词意味着同一件事?或者另一个词是如何改变后面这个词的意思的呢?或者一个词在同一个句子中有多个意思

深度学习使各种技术得以发展,这些技术在回答这些问题中起到了很大的作用。

词袋法

这是表示单词的最简单的方法。我们将每个句子表示为一个向量,取语料库中的所有单词,根据是否出现在句子中给每个单词一个1或0。

你可以看到,随着单词数量的增加,这个数字会变得非常大。一个问题是我们的向量开始变得稀疏。如果我们有很多短句和广泛的单词,我们的数据集中会有很多0。稀疏性可以成倍地增加我们的计算时间。

我们可以通过计算每个单词的数量来“升级”词袋的表示,而不仅仅是1或0。当我们进行计数时,我们也可以删除在语料库中出现不多的单词,例如,我们可以删除每一个出现少于5次的单词。

另一种改进词袋的方法是使用n-grams。这只需要n个单词而不是1个单词。这有助于捕捉句子中更多的上下文。

Count Vectoriser

直觉

这是将语言向量化的最简单方法。我们只是简单地计算句子中的每个单词。在大多数情况下,建议删除非常常见的词和非常罕见的词。

实现

代码语言:javascript复制
from sklearn.feature_extraction.text import CountVectorizer

bow = CountVectorizer(min_df=5,max_df=.99, ngram_range=(1, 2)) 
# 删除带有df参数的稀有词和常用词
# 包括单个和2个单词对

X_train_vec = bow.fit_transform(X_train[‘text’])
X_test_vec = bow.transform(X_test[‘text’])

cols = bow.get_feature_names() #if you need feature names

model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

这个四千维向量降维后的二维表示不是很好。对于我们的模型来说,没有一个明确的方法来聚类或分离数据。

不管怎样,我们的模型都表现得很好,它能够区分一些tweet。但是,从特征的重要性我们可以看出,它主要是通过url来实现的。这是发现灾难微博的有效方法吗?

TF-IDF

直觉

使用词袋的一个问题是,频繁使用的单词(如)在不提供任何附加信息的情况下开始占据特征空间。可能有一些特定领域的词更为重要,但由于它们不那么频繁,因此会丢失或被模型忽略。

TF-IDF代表词频-逆文档概率

  • 词频:当前文档中该词的词频。
  • 逆文档概率:对单词在语料库中的罕见程度进行评分。

在TF-IDF中,我们使用词频对单词进行评分,就像在词袋中一样。然后,我们将惩罚所有文档中频繁出现的任何单词(如the, and, or)。

我们也可以使用n-grams和TF-IDF。

实现

代码语言:javascript复制
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf= TfidfVectorizer(min_df=5,max_df=.99, ngram_range=(1, 2)) 
# 删除带有df参数的稀有词和常用词
# 包括单个和2个单词对

X_train_vec = tfidf.fit_transform(X_train[‘text’])
X_test_vec = tfidf.transform(X_test[‘text’])

cols = tfidf.get_feature_names() #if you need feature names

model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

TF-IDF与此数据集上的计数向量器没有太大区别。灾难微博和非灾难微博还有很多重叠。

我们看到使用TF-IDF在模型性能上有一个小的提升。一般来说,这确实表现得更好,因为我们减少了不附带信息的常见词汇。

词嵌入

词袋模型有三个关键问题:

  1. 相似的词彼此不相关。例如模型不知道badterrible的词是相似的,只是这些都与消极情绪有关。
  2. 文字不在上下文中,例如not bad将不会被有效的学习。词袋模型不能捕获具有双重含义的单词。
  3. 使用大语料库会产生非常大的稀疏向量。这使得在规模上计算困难。

通过深度学习,我们从表示方式转变为嵌入。与以前的方法不同,深度学习模型通常输出一个固定长度的向量,而不必与语料库中的单词数相同。现在,我们正在为数据集中的每个单词或句子创建一个唯一的向量表示。

Word2Vec

Word2Vec是一种生成嵌入的深度学习方法,发表于2013年。它可以相对容易地在你的语料库上进行训练,但是本教程的目的是使用预训练的方法。我将简要地解释一下模型是如何训练的。

这个模型有两种训练方法。

  • Skip-gram:模型循环在句子中的每个单词,并试图预测相邻的单词。
  • Continuous Bag of Words:模型循环每个单词,并使用周围的n个单词来预测它。

要深入研究这个模型,请看JayAlammer的这篇精彩文章,https://jalammar.github.io/illustrated-word2vec/

实现

为了实现Word2Vec,我们将使用Gensim在Google新闻数据集上训练的版本。该模型为每个单词输出300大小的向量。理论上,相似词应该具有相似的向量表示。

Word2Vec和GloVe的一个问题是我们不能轻易地生成一个句子嵌入。

要生成一个包含Word2Vec或GloVe的句子,我们必须为每个单词生成一个300大小的向量,然后平均它们。问题是,尽管相似的句子应该有类似的句子向量,但我们丢失了任何关于单词顺序的信息。

代码语言:javascript复制
import gensim
import gensim.models as g
import gensim.downloader
from spacy.tokenizer import Tokenizer
from spacy.lang.en import English


def vectorize_sentence(sentence,model):
    nlp = English()
    tokenizer = Tokenizer(nlp.vocab)
    a = []
    for i in tokenizer(sentence):
        try:
            a.append(model.get_vector(str(i)))
        except:
            pass

    a=np.array(a).mean(axis=0)
    a = np.zeros(300) if np.all(a!=a) else a
    return a

word2vec = gensim.downloader.load('word2vec-google-news-300') #1.66 gb

# 将数据向量化

X_train_vec = pd.DataFrame(np.vstack(X_train['text'].apply(vectorize_sentence, model=word2vec)))
X_test_vec = pd.DataFrame(np.vstack(X_test['text'].apply(vectorize_sentence, model=word2vec)))

# Word2Vec没有特征名

model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

乍一看,Word2Vec似乎比以前的方法更好地表示了我们的数据。有明显的蓝色区域和橙色的独立区域。左上角的簇似乎主要是大写字母的词,在其他地区,也有关于天气的微博。

不幸的是,乍一看,这与模型性能无关。准确度得分明显低于TF-IDF。然而,如果我们看一下混淆矩阵,我们可以看到,这个模型在识别灾难推特方面做得更好。

这里的一个大问题是,我们现在不知道是什么推动了这些更好的预测。有一个特征显然是模型使用最多的,但是如果不做额外的工作,我们就无法找出它代表了什么。

GloVe

直觉

GloVe代表Global Vectors。

GloVe类似于Word2Vec,因为它是一种早期的嵌入方法,已经在2014年发布。然而,GloVe的关键区别在于,GloVe不只是依赖于附近的单词,而是结合全局统计数据——跨语料库的单词出现情况,来获得词向量。

GloVe训练的方法是通过计算语料库中每个单词的共现矩阵来实现。然后,在矩阵上进行某种类型的维数约简,将其缩小为固定大小,为每个句子留下一个向量。我们可以很容易地访问这个模型的预处理版本。如果你想知道更多关于它是如何工作的,请看这里:https://towardsdatascience.com/light-on-math-ml-intuitive-guide-to-understanding-glove-embeddings-b13b4f19c010。

实现

我们使用的是Wikipedia语料库上训练的GloVe“Gigaword”模型。你会注意到,这个模型的大小比Word2Vec模型小得多,因为它可能是用较少的单词训练的。这是一个问题,因为GLoVe在我们的数据集中无法识别单词,它会返回一个错误。

代码语言:javascript复制
import gensim
import gensim.models as g
import gensim.downloader
from spacy.tokenizer import Tokenizer
from spacy.lang.en import English

def vectorize_sentence(sentence,model):
    nlp = English()
    tokenizer = Tokenizer(nlp.vocab)
    a = []
    for i in tokenizer(sentence):
        try:
            a.append(model.get_vector(str(i)))
        except:
            pass

    a=np.array(a).mean(axis=0)
    a = np.zeros(300) if np.all(a!=a) else a
    return a

gv = gensim.downloader.load('glove-wiki-gigaword-300') #376mb

# 将数据向量化

X_train_vec = pd.DataFrame(np.vstack(X_train['text'].apply(vectorize_sentence, model=gv)))
X_test_vec = pd.DataFrame(np.vstack(X_test['text'].apply(vectorize_sentence, model=gv)))

# GloVe没有特征名称

model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

GloVe向量很有趣,右上角的区域是带有大写字母的每条单词的tweet。其他地方蓝色和橙色之间有很多重叠。

我们的GloVe模型的性能比其他的要差得多。最可能的原因是这个模型不理解我们语料库中的许多单词。为了解决这个问题,你必须自己在语料库(或Twitter数据)上训练这个模型。

Doc2Vec

直觉

GloVe和Word2Vec的关键问题是我们只是在句子中平均。Doc2Vec对句子进行了预训练,应该能更好地表示我们的句子。

实现

Doc2Vec不是Gensim库的一部分,所以我在网上找到了一个版本,它已经做了预处理,但是我不确定是什么版本。

代码语言:javascript复制
# 模型来自 https://ai.intelligentonlinetools.com/ml/text-clustering-doc2vec-word-embedding-machine-learning/
#https://ibm.ent.box.com/s/3f160t4xpuya9an935k84ig465gvymm2

# 加载解压缩模型,保存在本地
model="../doc2vec/doc2vec.bin"
m = g.Doc2Vec.load(model)

# 实例化SpaCyTokenizer
nlp = English()
tokenizer = Tokenizer(nlp.vocab)

# 循环文本并创建向量

a=[]
for text in tqdm(X_train['text']):
    a.append(m.infer_vector([str(word) for word in tokenizer(text)]))

X_train_vec = pd.DataFrame(np.array(a))

a=[]
for text in tqdm(X_test['text']):
    a.append(m.infer_vector([str(word) for word in tokenizer(text)]))

X_test_vec = pd.DataFrame(np.array(a))

# Doc2Vec没有特征名称

model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

我本来期待着这个模型能带来大的改变,但它没有实现。最左边的区域是带有@的tweet,而最右边则主要是url。很好,这个模型能够理解这些(尽管编码了完整的句子),但是我们正在寻找比这更细微的差别。

我的上述评论反映在模型中,表现和GloVe一样糟糕。

基于Transformer的模型

我不会在这里详细介绍,但值得理解基于Transformer的模型,因为自从2017年谷歌的论文发布以来,这种模型架构已经引起了我们在过去几年中看到的各种最先进的NLP模型。

尽管这些模型最近发布并在大型数据集上接受了训练,但我们仍然可以使用高级python库访问它们。是的,我们可以利用最先进的、深度学习的模型,只需使用几行代码。

Universal Sentence Encoder

https://amitness.com/2020/06/universal-sentence-encoder/

谷歌的通用句子编码器包括一个Transformer结构和深度平均网络。在发布时,它取得了最新的结果,因为传统上,句子嵌入在整个句子中平均。在通用的句子编码器中,每个单词都有影响。

使用此选项的主要好处是:

  1. Tensorflow Hub非常容易使用。该模型自动生成一个完整句子的嵌入。
  2. 该模型比Word2Vec更好地捕获单词顺序和上下文。

有关详细信息,请参阅此说明(https://amitness.com/2020/06/universal-sentence-encoder/)和谷歌的下载页面(https://tfhub.dev/google/universal-sentence-encoder/4)。

实现

这是最容易实现的模型之一。

代码语言:javascript复制
import tensorflow_hub as hub

def embed_document(data):
    model = hub.load("../USE/")
    embeddings = np.array([np.array(model([i])) for i in data])
    return pd.DataFrame(np.vstack(embeddings))

# 将数据向量化
X_train_vec = embed_document(X_train['text'])
X_test_vec = embed_document(X_test['text'])

# USE 没有功能名称
model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

现在这很有趣。橙色和蓝色之间有很好的分离。在微博上徘徊,很明显,语义相似的微博彼此接近。

如果运行代码,你还将注意到,这个模型嵌入句子非常快,这是一个很大的好处,因为NLP工作可能由于数据量大而缓慢。

正如预期的那样,该模型的性能非常好。虽然精度仅略高于TF-IDF,但我所观察的每一个度量都有提升。

解释性仍然是一个问题。一个特征看起来比其他特征更重要,但是它对应的是什么?

BERT

https://github.com/UKPLab/sentence-transformers

BERT代表Transformer的双向编码器表示。它是一个具有Transformer结构的深度学习模型。该模型通过在句子中间屏蔽一些单词,并使模型预测这些单词,以类似于Word2Vec的方式进行训练。它还接受训练,以预测下一句,给出一个输入句。

BERT接受了来自英国维基百科和图书语料库数据集的300多个单词的训练。

有两个关键概念:

嵌入:单词的向量表示,其中相似的单词彼此“接近”。BERT使用“Wordpiece”嵌入(3万单词)和句子嵌入(句子嵌入)来显示单词在哪个句子中,以及表示每个单词在句子中的位置的位置嵌入(位置嵌入)。然后可以将文本输入BERT。

注意:核心思想是每次模型预测输出词时,它只使用输入的部分,其中最相关的信息集中而不是整个序列。简单地说,它只注意一些输入词。

然而,我们并不需要为此担心,因为我们有一些方法可以使用几行代码生成嵌入。

实现

BERT的语言表达非常有力。当对模型进行微调时,该模型能够很好地捕捉语义差异和词序。

sentence-transformers允许我们利用预训练的BERT模型,这些模型已经在特定任务(如语义相似度或问答)上训练过。这意味着我们的嵌入是专门针对特定任务的。这也使得生成一个完整句子的嵌入非常容易。。

在这个例子中,我使用RoBERTa,它是Facebook优化的BERT版本。

代码语言:javascript复制
from sentence_transformers import SentenceTransformer

bert = SentenceTransformer('stsb-roberta-large') #1.3 gb

# 将数据向量化

X_train_vec = pd.DataFrame(np.vstack(X_train['text'].apply(bert.encode)))
X_test_vec = pd.DataFrame(np.vstack(X_test['text'].apply(bert.encode)))

# BERT没有特征名

model = RandomForestClassifier(n_estimators=500, n_jobs=8)
model.fit(X_train_vec, y_train)
model.score(X_test_vec, y_test)

可视化

很难说这是否比通用的句子编码器版本好。我的直觉是,这个模型在区分灾难和非灾难微博方面做得更糟,但可能已经更好地对类似主题进行了聚类。

该模型客观上比universal sentence encoder差。一个特征比其他特征更重要,我希望这与URL相对应,也许模型对这些权重太大,但无法从其他1023向量中提取细节。

结论

我们探索了将单词转换为数字的多种方法。在这个数据集上,谷歌的通用句子编码器性能最好。对于大多数应用程序来说,这是值得尝试的,因为它们的性能非常好。我认为Word2Vec现在有点过时,但是使用这样的方法非常快和强大。

我们中的许多人第一次学习NLP的方式是通过做一个情绪分析项目,用词袋来表示文本。这是一个很好的学习方式,但我觉得它带走了很多NLP的兴奋。词袋和one-hot编码数据之间没有太大区别。制作出来的模型并不是特别有效,也很少能捕捉到文本中的任何细微差别。我们可以很容易地使用BERT嵌入,这通常会带来巨大的性能提升。

作为最后一点,模型的可解释性和可解释性总是值得考虑的。通过词袋法,我们可以清楚地说出哪些词会影响模型。在BERT模型中,我们可以很容易地说向量中的哪个位置影响模型,但是要准确地说每个向量的含义需要相当大的努力(可能几乎不可能)。

了解更多

https://towardsdatascience.com/simple-logistic-regression-a9735ed23abd

https://adam-shafi.medium.com/tech-skills-2021-5805848851c6

0 人点赞