之前我写过一篇文章,利用bert来生成token级向量(对于中文语料来说就是字级别向量),参考我的文章:《使用BERT模型生成token级向量》。但是这样做有一个致命的缺点就是字符序列长度最长为512(包含[cls]和[sep])。其实对于大多数语料来说已经够了,但是对于有些语料库中样本的字符序列长度都比较长的情况,这就有些不够用了,比如我做一个法院文书领域预测任务,里面的事实部分许多都大于1000字,我做TextCharCNN的时候定义的最大长度为1500(能够涵盖百分之95以上的样本)。

这个时候怎么办呢,我想到了一个办法,就是用句子序列来代表他们。比如一段事实有1500个字,如果按照句号划分,则有80个句子。那么每一个句子,我们可以利用bert得到句子向量,我们可以把一个句子中包含的字符的最大长度人为定义为128(实际上这样一个句子得到的结果的shape是(128, 768),可以参考我文章开头提到的那篇文章。我的做法是首先要用bert模型在我们的数据集任务上进行微调,然后用微调过的模型去生成这样一个结果,然后取出第0个分量,也就是说,一句话有128个字符,第0个字符是[cls]字符,我们就取第0个字符代表这句话的向量表示,这也就是为什么我在前面提到一定要在我们的任务中微调过模型再拿来用,要不然这个[cls]向量取出来并不好用!!!)BERT微调的参照我的文章:《使用BERT预训练模型+微调进行文本分类

那么每一个句子得到了一个shape为(768,)的向量,这就是这个句子的embedding,然后一个样本设定有80个句子,如果超过80个句子,则取前80个,如果不到80个句子,则填充768维全0向量。最终生成的结果是:(N,80,768)。N代表样本数,80代表句子最大长度,768代表向量维度,然后可以用这个结果去做mean_pooling,或者做卷积之类的。

下面代码(注释比较清晰,就不解释了):

# 配置文件
# data_root是模型文件,可以用预训练的,也可以用在分类任务上微调过的模型
data_root = '../chinese_wwm_ext_L-12_H-768_A-12/'
bert_config_file = data_root + 'bert_config.json'
bert_config = modeling.BertConfig.from_json_file(bert_config_file)
# init_checkpoint = data_root + 'bert_model.ckpt'
# 这样的话,就是使用在具体任务上微调过的模型来做词向量
init_checkpoint = '../model/cnews_fine_tune/model.ckpt-18674'
# init_checkpoint = '../model/legal_fine_tune/model.ckpt-4153'
bert_vocab_file = data_root + 'vocab.txt' # 经过处理的输入文件路径
file_input_x_c_train = '../data/cnews/train_x.txt'
file_input_x_c_val = '../data/cnews/val_x.txt'
file_input_x_c_test = '../data/cnews/test_x.txt' # embedding存放路径
# emb_file_dir = '../data/legal_domain/emb_fine_tune.h5' # graph
input_ids = tf.placeholder(tf.int32, shape=[None, None], name='input_ids')
input_mask = tf.placeholder(tf.int32, shape=[None, None], name='input_masks')
segment_ids = tf.placeholder(tf.int32, shape=[None, None], name='segment_ids') # 每个sample固定为80个句子
SEQ_LEN = 80
# 每个句子固定为128个token
SENTENCE_LEN = 126 def get_batch_data(x):
"""生成批次数据,一个batch一个batch地产生句子向量"""
data_len = len(x) word_mask = [[1] * (SENTENCE_LEN + 2) for i in range(data_len)]
word_segment_ids = [[0] * (SENTENCE_LEN + 2) for i in range(data_len)]
return x, word_mask, word_segment_ids def read_input(file_dir):
# 从文件中读到所有需要转化的句子
# 这里需要做统一长度为510
# input_list = []
with open(file_dir, 'r', encoding='utf-8') as f:
input_list = f.readlines() # input_list是输入list,每一个元素是一个str,代表输入文本
# 现在需要转化成id_list
word_id_list = []
for query in input_list:
tmp_word_id_list = []
quert_str = ''.join(query.strip().split())
sentences = re.split('。', quert_str)
# 在这里截取掉大于seq_len个句子的样本,保留其前seq_len个句子
if len(sentences) > SEQ_LEN:
sentences = sentences[:SEQ_LEN]
for sentence in sentences:
split_tokens = token.tokenize(sentence)
if len(split_tokens) > SENTENCE_LEN:
split_tokens = split_tokens[:SENTENCE_LEN]
else:
while len(split_tokens) < SENTENCE_LEN:
split_tokens.append('[PAD]')
# ****************************************************
# 如果是需要用到句向量,需要用这个方法
# 加个CLS头,加个SEP尾
tokens = []
tokens.append("[CLS]")
for i_token in split_tokens:
tokens.append(i_token)
tokens.append("[SEP]")
# ****************************************************
word_ids = token.convert_tokens_to_ids(tokens)
tmp_word_id_list.append(word_ids)
word_id_list.append(tmp_word_id_list)
return word_id_list # 初始化BERT
model = modeling.BertModel(
config=bert_config,
is_training=False,
input_ids=input_ids,
input_mask=input_mask,
token_type_ids=segment_ids,
use_one_hot_embeddings=False
) # 加载BERT模型
tvars = tf.trainable_variables()
(assignment, initialized_variable_names) = modeling.get_assignment_map_from_checkpoint(tvars, init_checkpoint)
tf.train.init_from_checkpoint(init_checkpoint, assignment)
# 获取最后一层和倒数第二层
encoder_last_layer = model.get_sequence_output()
encoder_last2_layer = model.all_encoder_layers[-2] # 读取数据
token = tokenization.FullTokenizer(vocab_file=bert_vocab_file) input_train_data = read_input(file_dir=file_input_x_c_train)
input_val_data = read_input(file_dir=file_input_x_c_val)
input_test_data = read_input(file_dir=file_input_x_c_test) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
save_file = h5py.File('../downstream/cnews_emb_sentences.h5', 'w') # 训练集
emb_train = []
ssum = 0
pad_vector = [0 for i in range(768)]
for sample in input_train_data:
ssum += 1
# 一个样本(假设有n个句子)就为一个batch
word_id, mask, segment = get_batch_data(sample)
feed_data = {input_ids: np.asarray(word_id), input_mask: np.asarray(mask), segment_ids: np.asarray(segment)}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
print('******************************************************************')
print(last2.shape)
print(ssum)
# last2 shape:(seq_len, 50, 768)
tmp_list = []
for i in last2:
tmp_list.append(i[0])
if len(tmp_list) > SEQ_LEN:
tmp_list = tmp_list[:SEQ_LEN]
else:
while len(tmp_list) < SEQ_LEN:
tmp_list.append(pad_vector) emb_train.append(tmp_list)
# 保存
emb_train_array = np.asarray(emb_train)
save_file.create_dataset('train', data=emb_train_array) # 验证集
print("开始验证集")
emb_val = []
for sample in input_val_data:
# 一个样本(假设有n个句子)就为一个batch
word_id, mask, segment = get_batch_data(sample)
feed_data = {input_ids: np.asarray(word_id), input_mask: np.asarray(mask), segment_ids: np.asarray(segment)}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# last2 shape:(seq_len, 50, 768)
tmp_list = []
for i in last2:
tmp_list.append(i[0])
if len(tmp_list) > SEQ_LEN:
tmp_list = tmp_list[:SEQ_LEN]
else:
while len(tmp_list) < SEQ_LEN:
tmp_list.append(pad_vector) emb_val.append(tmp_list)
# 保存
emb_val_array = np.asarray(emb_val)
save_file.create_dataset('val', data=emb_val_array) # 测试集
emb_test = []
for sample in input_test_data:
# 一个样本(假设有n个句子)就为一个batch
word_id, mask, segment = get_batch_data(sample)
feed_data = {input_ids: np.asarray(word_id), input_mask: np.asarray(mask), segment_ids: np.asarray(segment)}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# last2 shape:(seq_len, 50, 768)
tmp_list = []
for i in last2:
tmp_list.append(i[0])
if len(tmp_list) > SEQ_LEN:
tmp_list = tmp_list[:SEQ_LEN]
else:
while len(tmp_list) < SEQ_LEN:
tmp_list.append(pad_vector) emb_test.append(tmp_list)
# 保存
emb_test_array = np.asarray(emb_test)
save_file.create_dataset('test', data=emb_test_array) save_file.close() print(emb_train_array.shape)
print(emb_val_array.shape)
print(emb_test_array.shape) # 这边目标是接下游CNN任务,因此先写入所有token的embedding,768维
# 写入shape直接是(N, max_seq_len + 2, 768)
# 下游需要选用的时候,如果卷积,则去掉头尾使用,如果全连接,则直接使用头部
# 这里直接设定max_seq_len=510,加上[cls]和[sep],得到512
# 写入(n, 512, 768) ndarray到文件,需要用的时候再读出来,就直接舍弃embedding层

  

  

项目地址

代码在/down_stream/sentence_features.py 文件中


2019-9-18更新:

之前犯了一个错误,就是获取batch数据,逻辑是按照一个样本产生一个batch,具体就是一个样本里有N个句子,拆分开来,batch_size就是N,把这个喂入模型。然后我们设定了一个最大句子序列长度SEQ_LEN,所有大于SEQ_LEN个句子的样本都只保留前SEQ_LEN个句子。我之前的做法是先喂入模型拿到输出,然后视情况截取,然而最近在一个数据集上碰到了问题,爆内存了,检查后发现是有一个样本按照句子划分达到了600多个句子(可能是因为有很多中文“。”符号导致),也就是batch_size来到了600多,那对于我小小一块11g现存的1080Ti肯定会爆,所以修改了一下逻辑,在获取batch数据中就完成截取。


2019-9-19更新:

在稍大一点的数据集上还是会跑着跑着内存溢出,没办法,可以尝试降低SEQ_LEN和SENTENCE_LEN。例如我在THUCNEWS数据集上,SEQ_LEN设置为20,SENTENCE_LEN设置为40.也就是说允许一句话最多有20个句子,每个句子包含40个字。

另外,程序稍微做了一些改动, 把所有耗时操作都移出了session,可以用下面这个版本的代码,也可以用上面的,完成的功能是一模一样的,其实我觉得内存溢出的瓶颈似乎不在这里,这部分的改动可能有帮助,帮助不大,瓶颈还是在SEQ_LEN(bert中的batch_size)和SENTENCE_LEN(bert终得seq_len)。下面代码

# 配置文件
# data_root是模型文件,可以用预训练的,也可以用在分类任务上微调过的模型
data_root = '../chinese_wwm_ext_L-12_H-768_A-12/'
bert_config_file = data_root + 'bert_config.json'
bert_config = modeling.BertConfig.from_json_file(bert_config_file)
# init_checkpoint = data_root + 'bert_model.ckpt'
# 这样的话,就是使用在具体任务上微调过的模型来做词向量
init_checkpoint = '../model/cnews_fine_tune/model.ckpt-18674'
# init_checkpoint = '../model/legal_fine_tune/model.ckpt-4153'
bert_vocab_file = data_root + 'vocab.txt' # 经过处理的输入文件路径
file_input_x_c_train = '../data/cnews/train_x.txt'
file_input_x_c_val = '../data/cnews/val_x.txt'
file_input_x_c_test = '../data/cnews/test_x.txt' # embedding存放路径
# emb_file_dir = '../data/legal_domain/emb_fine_tune.h5' # graph
input_ids = tf.placeholder(tf.int32, shape=[None, None], name='input_ids')
input_mask = tf.placeholder(tf.int32, shape=[None, None], name='input_masks')
segment_ids = tf.placeholder(tf.int32, shape=[None, None], name='segment_ids') # 每个sample固定为80个句子
SEQ_LEN = 80
# 每个句子固定为128个token
SENTENCE_LEN = 126
# 在cnews上尝试 SEQ_LEN=20,sentence_len=50 def get_batch_data(x):
"""生成批次数据,一个batch一个batch地产生句子向量"""
data_len = len(x) word_mask = [[1] * (SENTENCE_LEN + 2) for i in range(data_len)]
word_segment_ids = [[0] * (SENTENCE_LEN + 2) for i in range(data_len)]
return x, word_mask, word_segment_ids def read_input(file_dir):
# 从文件中读到所有需要转化的句子
# 这里需要做统一长度为510
# input_list = []
with open(file_dir, 'r', encoding='utf-8') as f:
input_list = f.readlines() # input_list是输入list,每一个元素是一个str,代表输入文本
# 现在需要转化成id_list
word_id_list = []
for query in input_list:
tmp_word_id_list = []
quert_str = ''.join(query.strip().split())
sentences = re.split('。', quert_str)
# 在这里截取掉大于seq_len个句子的样本,保留其前seq_len个句子
if len(sentences) > SEQ_LEN:
sentences = sentences[:SEQ_LEN]
# else:
# # 小于seq_len个句子的也用[PAD]补齐,为了批量
# while len(sentences) < SEQ_LEN:
# sentences.append('')
for sentence in sentences:
split_tokens = token.tokenize(sentence)
if len(split_tokens) > SENTENCE_LEN:
split_tokens = split_tokens[:SENTENCE_LEN]
else:
while len(split_tokens) < SENTENCE_LEN:
split_tokens.append('[PAD]')
# ****************************************************
# 如果是需要用到句向量,需要用这个方法
# 加个CLS头,加个SEP尾
tokens = []
tokens.append("[CLS]")
for i_token in split_tokens:
tokens.append(i_token)
tokens.append("[SEP]")
# ****************************************************
word_ids = token.convert_tokens_to_ids(tokens)
tmp_word_id_list.append(word_ids)
word_id_list.append(tmp_word_id_list)
return word_id_list # 初始化BERT
model = modeling.BertModel(
config=bert_config,
is_training=False,
input_ids=input_ids,
input_mask=input_mask,
token_type_ids=segment_ids,
use_one_hot_embeddings=False
) # 加载BERT模型
tvars = tf.trainable_variables()
(assignment, initialized_variable_names) = modeling.get_assignment_map_from_checkpoint(tvars, init_checkpoint)
tf.train.init_from_checkpoint(init_checkpoint, assignment)
# 获取最后一层和倒数第二层
encoder_last_layer = model.get_sequence_output()
encoder_last2_layer = model.all_encoder_layers[-2] # 读取数据
token = tokenization.FullTokenizer(vocab_file=bert_vocab_file) input_train_data = read_input(file_dir=file_input_x_c_train)
input_val_data = read_input(file_dir=file_input_x_c_val)
input_test_data = read_input(file_dir=file_input_x_c_test) # 把获取batch数据放到session外
# train
train_word_ids = []
train_masks = []
train_segment_ids = []
for sample in input_train_data:
word_id, mask, segment = get_batch_data(sample)
train_word_ids.append(np.asarray(word_id))
train_masks.append(np.asarray(mask))
train_segment_ids.append(np.asarray(segment)) # val
val_word_ids = []
val_masks = []
val_segment_ids = []
for sample in input_val_data:
word_id, mask, segment = get_batch_data(sample)
val_word_ids.append(np.asarray(word_id))
val_masks.append(np.asarray(mask))
val_segment_ids.append(np.asarray(segment)) # test
test_word_ids = []
test_masks = []
test_segment_ids = []
for sample in input_test_data:
word_id, mask, segment = get_batch_data(sample)
test_word_ids.append(np.asarray(word_id))
test_masks.append(np.asarray(mask))
test_segment_ids.append(np.asarray(segment)) rst_list_train = []
rst_list_val = []
rst_list_test = []
save_file = h5py.File('../downstream/cnews_emb_sentences.h5', 'w') with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 训练集
ssum = 0
sess.graph.finalize()
for sample_index in range(len(input_train_data)):
ssum += 1
# 一个样本(假设有n个句子)就为一个batch
# word_id, mask, segment = get_batch_data(sample)
# print(np.asarray(word_id).shape)
print(train_word_ids[sample_index].shape)
feed_data = {input_ids: train_word_ids[sample_index], input_mask: train_masks[sample_index], segment_ids: train_segment_ids[sample_index]}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
print('******************************************************************')
print(last2.shape)
print(ssum)
tmp = last2[:,0,:]
print(tmp.shape)
rst_list_train.append(tmp) # 验证集
print("开始验证集")
# emb_val = []
for sample_index in range(len(input_val_data)):
# 一个样本(假设有n个句子)就为一个batch
# word_id, mask, segment = get_batch_data(sample)
# feed_data = {input_ids: np.asarray(word_id), input_mask: np.asarray(mask), segment_ids: np.asarray(segment)}
feed_data = {input_ids: val_word_ids[sample_index], input_mask: val_masks[sample_index],
segment_ids: val_segment_ids[sample_index]}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# last2 shape:(seq_len, 50, 768)
tmp = last2[:, 0, :]
print(tmp.shape)
rst_list_val.append(tmp) # 测试集
# emb_test = []
for sample_index in range(len(input_test_data)):
# 一个样本(假设有n个句子)就为一个batch
# word_id, mask, segment = get_batch_data(sample)
# feed_data = {input_ids: np.asarray(word_id), input_mask: np.asarray(mask), segment_ids: np.asarray(segment)}
feed_data = {input_ids: test_word_ids[sample_index], input_mask: test_masks[sample_index],
segment_ids: test_segment_ids[sample_index]}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# last2 shape:(seq_len, 50, 768)
tmp = last2[:, 0, :]
print(tmp.shape)
rst_list_test.append(tmp) pad_vector = np.array([0 for i in range(768)]) emb_train = []
for train_sample in rst_list_train:
# (?,768)
if len(train_sample) > SEQ_LEN:
emb_train.append(train_sample[:SEQ_LEN])
else:
tmp = list(train_sample)
while len(tmp) < SEQ_LEN:
tmp.append(pad_vector)
emb_train.append(tmp)
emb_train_array = np.asarray(emb_train)
save_file.create_dataset('train', data=emb_train_array) emb_val = []
for val_sample in rst_list_val:
# (?,768)
if len(val_sample) > SEQ_LEN:
emb_val.append(val_sample[:SEQ_LEN])
else:
tmp = list(val_sample)
while len(tmp) < SEQ_LEN:
tmp.append(pad_vector)
emb_val.append(tmp)
emb_val_array = np.asarray(emb_val)
save_file.create_dataset('val', data=emb_val_array) emb_test = []
for test_sample in rst_list_test:
# (?,768)
if len(test_sample) > SEQ_LEN:
emb_test.append(test_sample[:SEQ_LEN])
else:
tmp = list(test_sample)
while len(tmp) < SEQ_LEN:
tmp.append(pad_vector)
emb_test.append(tmp)
emb_test_array = np.asarray(emb_test)
save_file.create_dataset('test', data=emb_test_array) save_file.close() print(emb_train_array.shape)
print(emb_val_array.shape)
print(emb_test_array.shape) # 这边目标是接下游CNN任务,因此先写入所有token的embedding,768维
# 写入shape直接是(N, max_seq_len + 2, 768)
# 下游需要选用的时候,如果卷积,则去掉头尾使用,如果全连接,则直接使用头部
# 这里直接设定max_seq_len=510,加上[cls]和[sep],得到512
# 写入(n, 512, 768) ndarray到文件,需要用的时候再读出来,就直接舍弃embedding层

  

使用BERT模型生成句子序列向量的更多相关文章

  1. 使用BERT模型生成token级向量

    本文默认读者有一定的Transformer基础,如果没有,请先稍作学习Transormer以及BERT. 相信网上有很多方法可以生成BERT向量,最有代表性的一个就是bert as service,用 ...

  2. Bert模型实现垃圾邮件分类

    近日,对近些年在NLP领域很火的BERT模型进行了学习,并进行实践.今天在这里做一下笔记. 本篇博客包含下列内容: BERT模型简介 概览 BERT模型结构 BERT项目学习及代码走读 项目基本特性介 ...

  3. BERT模型图解

    转载于 腾讯Bugly 发表于 腾讯Bugly的专栏 原文链接:https://cloud.tencent.com/developer/article/1389555 本文首先介绍BERT模型要做什么 ...

  4. 图示详解BERT模型的输入与输出

    一.BERT整体结构 BERT主要用了Transformer的Encoder,而没有用其Decoder,我想是因为BERT是一个预训练模型,只要学到其中语义关系即可,不需要去解码完成具体的任务.整体架 ...

  5. Pytorch基础——使用 RNN 生成简单序列

    一.介绍 内容 使用 RNN 进行序列预测 今天我们就从一个基本的使用 RNN 生成简单序列的例子中,来窥探神经网络生成符号序列的秘密. 我们首先让神经网络模型学习形如 0^n 1^n 形式的上下文无 ...

  6. 使用 TF-IDF 加权的空间向量模型实现句子相似度计算

    使用 TF-IDF 加权的空间向量模型实现句子相似度计算 字符匹配层次计算句子相似度 计算两个句子相似度的算法有很多种,但是对于从未了解过这方面算法的人来说,可能最容易想到的就是使用字符串匹配相关的算 ...

  7. 从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史(转载)

    转载 https://zhuanlan.zhihu.com/p/49271699 首发于深度学习前沿笔记 写文章   从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史 张 ...

  8. BERT模型介绍

    前不久,谷歌AI团队新发布的BERT模型,在NLP业内引起巨大反响,认为是NLP领域里程碑式的进步.BERT模型在机器阅读理解顶级水平测试SQuAD1.1中表现出惊人的成绩:全部两个衡量指标上全面超越 ...

  9. 想研究BERT模型?先看看这篇文章吧!

    最近,笔者想研究BERT模型,然而发现想弄懂BERT模型,还得先了解Transformer. 本文尽量贴合Transformer的原论文,但考虑到要易于理解,所以并非逐句翻译,而是根据笔者的个人理解进 ...

随机推荐

  1. stylelint那些事儿

    一.参考文档 -   http://stylelint.cn/ -   https://stylelint.io/ -   https://stylelint.io/user-guide/exampl ...

  2. [LeetCode] 259. 3Sum Smaller 三数之和较小值

    Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 < ...

  3. [LeetCode] 801. Minimum Swaps To Make Sequences Increasing 最少交换使得序列递增

    We have two integer sequences A and B of the same non-zero length. We are allowed to swap elements A ...

  4. GitLab数据备份与恢复

    创建备份 $ sudo gitlab-rake gitlab:backup:create 执行完备份命令后会在/var/opt/gitlab/backups目录下生成备份后的文件,如150080913 ...

  5. System.gc()介绍

    System.gc()用于垃圾收集器,调用垃圾收集器将回收未使用的 System.gc()进行回收的准则: 回收没有被任何可达变量指向的对象 JDK实现 public static void gc() ...

  6. harbor的安装和简单使用【h】

    安装docker的私有仓库, 利用vmware提供的harbor工具, 参考Docker 私有仓库方案比较与搭建, Harbor安装 -- 企业级Registry仓库 2.2harborProject ...

  7. 问题二:appium 搞定权限弹框的一个小办法

    public void permission() { for (int i=0; i <= 10; i++) { if (getPageSource().contains("允许&qu ...

  8. Appium移动端自动化测试--录制测试用例并运行

    目录 文章目录如下 录制用例并执行 1.使用Appium desktop录制用例 2.安装Pythony依赖Appium-Python-Client 3.增加隐式等待增强稳定性 4.重新运行 文章目录 ...

  9. 中国大学MOOC-翁恺-C语言程序设计习题集(一)

    练习 02-0. 整数四则运算(10) 本题要求编写程序,计算2个正整数的和.差.积.商并输出.题目保证输入和输出全部在整型范围内. 输入格式: 输入在一行中给出2个正整数A和B. 输出格式: 在4行 ...

  10. centos jira wiki 开机自启

    启动jira 和 wiki 需要java环境变量 操作步骤: 在 /etc/rc.d/rc.local 文件中[ 注意要有可执行权限 ] export JAVA_HOME=xxxxxxxx expor ...