elmo实战

1. 基于tensorflow

利用tensorflow hub加载已经训练好的elmo模型
本案例使用train_2kmZucJ.csv、test_oJQbWVk.csv数据集,可搜索下载数据。开始为数据预处理模块,可以忽略不看(最后给出了预处理后的结果)

import pandas as pd
import numpy as np
import spacy
from tqdm import tqdm
import re
import time
import pickle
import tensorflow_hub as hub # tensorflow hub是一个允许迁移学习的库 
import tensorflow as tf

# 读取数据
train = pd.read_csv(r'D:\002study\data\train_2kmZucJ.csv', encoding='ANSI')
test = pd.read_csv(r'D:\002study\data\test_oJQbWVk.csv', encoding='ANSI')
# label:0表示非负面推文;1表示负面推文

# 数据清洗
# 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))


# # 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()))

# 文本标准化:将produces、production等改成product,使用spacy库
# import spaCy's language model
nlp = spacy.load('en_core_web_sm', 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

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

train.head()

在这里插入图片描述
再继续使用elmo获得词向量之前,先对tensorflow hub中的elmo模块进行详细讲解。

  • 第一、加载模型
elmo = hub.Module("https://tfhub.dev/google/elmo/2", trainable=True)
  • 将训练预料传给模型
embeddings = elmo(
    ["the cat is on the mat", "dogs are in the fog"],
    signature="default",
    as_dict=True)
    # 可以看到,整个文本在一个列表里,其词与词之间用空格隔开
  • 上例中参数signature=default,该参数有两个备选值,default、tokens,如果是tokens,传入的训练预料需要是分好词的,如下:
tokens_input = [["the", "cat", "is", "on", "the", "mat"],
                ["dogs", "are", "in", "the", "fog", ""]]
tokens_length = [6, 5]
embeddings = elmo(
    inputs={
        "tokens": tokens_input,
        "sequence_len": tokens_length
    },
    signature="tokens",
    as_dict=True)

当参数为tokens时,一个列表为一句话,且每句话是分好词的。此外所有句子的列表长度需要一致,不够的用""补充。且此时必须制定sequence_len,即每句话的长度(本例中第一句话6个词,第二句话5个词,当然也可以分别制定为3个词和2个词等等)

  • 结果:
    • {‘default’: <tf.Tensor ‘module_12_apply_default/truediv:0’ shape=(2, 1024) dtype=float32>, 前面得到的均为word级别的向量,这个选项给出了简单使用mean-pooling求的句子级别的向量,即将上述elmo的所有词取平均,方便后续下游任务。
    • ‘elmo’: <tf.Tensor ‘module_12_apply_default/aggregation/mul_3:0’ shape=(2, 9, 1024) dtype=float32>,对应文章中的公式1,每个词的输入层(word_emb),第一层LSTM输出,第二层LSTM输出的线性加权之后的最终的词向量,shape为[batch_size, max_length, 1024],此外这个线性权重是可训练的。
    • ‘lstm_outputs1’: <tf.Tensor ‘module_12_apply_default/concat:0’ shape=(2, ?, 1024) dtype=float32>,ELMo中的第一层LSTM的隐状态输出,shape同样为[batch_size, max_length, 1024]
    • ‘lstm_outputs2’: <tf.Tensor ‘module_12_apply_default/concat_1:0’ shape=(2, ?, 1024) dtype=float32>,ELMo中的第二层LSTM的隐状态输出,shape同样为[batch_size, max_length, 1024]
    • ‘sequence_len’: <tf.Tensor ‘module_12_apply_default/Sum:0’ shape=(2,) dtype=int32>,输入中每个句子的长度
    • ‘word_emb’: <tf.Tensor ‘module_12_apply_default/bilm/Reshape_1:0’ shape=(2, 9, 512) dtype=float32>},ELMo的最开始一层的基于character的word embedding, shape为[batch_size, max_length, 512]

一般情况使用embeddings [‘elmo’]可得到每个词的ELMo 词向量,(因为最终结果是一个字典)即可用于后续的任务,比如分类等。(即使用output[“elmo”])获得词向量.
现在针对下面案例再做一次具体说明

embeddings = elmo(
    ["the cat is on the mat", "dogs are in the fog"],
    signature="default",
    as_dict=True)["elmo"]
    # 可以看到,整个文本在一个列表里,其词与词之间用空格隔开

最终结果embeddings的shape为[batch_size, max_length, 1024]。其中batch_size为批次的样本个数,本例为2;max_length为一个batch中句子最长的token个数。本例中共两句话,第一句有6个token,第二句有5个token,因此max_length为6,;1024即词向量的维度,即预训练模型的维度。从这个结果可以看出,elmo直接得到了text中每个句子的向量化表示,而不需要像word2vec一样得到的是一个个token的词向量(想要使用还需要进一步操作将文本变成向量)。此外,如果elmo中多个句子出现同一个词,这多个句子的同一个词会有不同的向量表示。最终结果是max_length,那么句子中token数小于max_length时,会向后补充,如本例第一句话缺少一个词,会在结果第一维补充1行(如果补充多行,则多行的词向量都是一样的),如果多个句子都缺少词,则都会向后补充,且这几个句子补充的向量都是一样的。
对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 sess.run(tf.reduce_mean(embeddings, 1))

如果使用上述代码来一次性处理所有推文,可能会耗尽所有内存。通过将训练集和测试集分割成一系列容量为100条的样本来避免这个问题,然后将他们相继传递给elmo_vectors()函数。(多个batch)

# 如果使用上述代码来一次性处理所有推文,你可能会耗尽所有内存。
# 我们可以通过将训练集和测试集分割成一系列容量为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)]
# 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)
# 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)

构建模型并评估:

xtrain, xvalid, ytrain, yvalid = train_test_split(elmo_train_new, train['label'], random_state=42, test_size=0.2)
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score
lreg = LogisticRegression()
lreg.fit(xtrain, ytrain)
preds_valid = lreg.predict(xvalid)
f1_score(yvalid, preds_valid)
preds_test = lreg.predict(elmo_test_new)
sub = pd.DataFrame({'id':test['id'], 'label':preds_test})
# sub.to_csv("sub_lreg.csv", index=False)

以上是tensorflow hub关于elmo的案例,很遗憾的是只有英文版的模型,且只有一个,并没有基于中文预训练好的demo。
tensorflow搜索:tensorflow官网modules-text embedding

2.基于pytorch

pytorch方法基于的是allennlp库,首先需要配置环境:

  1. 在conda中创建allennlp环境:conda create -n allennlp python=3.6
  2. 安装pytorch:具体代码可去官网查询
  3. 安装allennlp:pip install allennlp
    从官网可以看到,有好几个已经预训练好的模型
    在这里插入图片描述
    以上是基于英语,可以发现allennlp还有其他语言和领域的预训练模型,如下:
    在这里插入图片描述
    遗憾的是仍然没有基于中文的。
    下面就根据英文的model进行一次小小的试验:
  • 第一步:下载模型(包括参数下载.hdf5和模型下载.json)
  • 第二步:获得词向量
from allennlp.commands.elmo import ElmoEmbedder
options_file = "/files/elmo_2x4096_512_2048cnn_2xhighway_options.json"
weight_file = "/files/elmo_2x4096_512_2048cnn_2xhighway_weights.hdf5"

elmo = ElmoEmbedder(options_file, weight_file)

# use batch_to_ids to convert sentences to character ids
context_tokens = [['I', 'love', 'you', '.'], ['Sorry', ',', 'I', 'don', "'t", 'love', 'you', '.']] #references
elmo_embedding, elmo_mask = elmo.batch_to_embeddings(context_tokens)

# 输出两个Variable, 第一个是2*3*8*256的embedding信息, 第二个是mask。
# embeddings信息中2是batch_size, 3是两层biLM的输出加一层CNN对character编码的输出, 8是最长list的长度(对齐),1024是每层输出的维度; 
# mask的输出2是batch_size, 8实在最长list的长度,

以上只是简单举例。allennlp中ElmoEmbedder是一个大类,需要传入参数option_file和weight_file。该类下面有诸多函数,如下所示:

  • batch_to_embeddings:参数为list[list[str]]即上例所示,返回值为tensor的元组。the first representing activations (batch_size, 3, num_timesteps, 1024) and the second a mask (batch_size, num_timesteps).
  • embed_batch:参数为list[list[str]]。返回值为tensor的列表,each representing the ELMo vectors for the input sentence at the same index.
  • embed_file(见下图):
    在这里插入图片描述
    其余应用可参见下图:
    在这里插入图片描述
发布了111 篇原创文章 · 获赞 113 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43178406/article/details/102522853