使用BERT模型生成token级向量
本文默认读者有一定的Transformer基础,如果没有,请先稍作学习Transormer以及BERT。
相信网上有很多方法可以生成BERT向量,最有代表性的一个就是bert as service,用几行代码就可以生成向量,但是这样生成的是句向量,也就是说,正确的做法是输入一句句子:
我是一个中国人,我热爱着中国的每一个城市。
输出的是这句句子的向量,一个768维的向量(google预训练是这么做的),这个向量是具有上下文信息的,详细参考Transformer结构。但是网上有一些做法是用bert as service来生成词级向量,例如输入[‘我’,‘是’,‘一个’, ‘中国’, ‘人’],得到5个768维的向量,用来作为词向量,但这样做是错误的!具体原因参照我前面的描述,既然思想是错误的,也就不奇怪效果不好了,所以在这种情况下,请先别着急说BERT预训练模型不work。
BERT生成token级别的向量,这两篇文章理解的比较准确(我的代码有一部分参考第二篇博客):
https://blog.csdn.net/u012526436/article/details/87697242
https://blog.csdn.net/shine19930820/article/details/85730536
为什么说是token级别的向量呢?因为Transformer结构所决定其输入和输出的长度相等的,而且对于中文预训练模型,做法是将中文拆成一个个的字来做学习的,因此每一个token就是一个字。对于一句话,我们会在其头上加[cls]在尾部加[SEP],并且BERT是一个多任务的预训练过程,现在假设text_a是我们需要获取向量的句子,text_b为空,那么,输入:
我是一个中国人,我热爱着中国的每一个城市。
处理后:
[CLS]我是一个中国人,我热爱着中国的每一个城市。[SEP]
通常我们会用第一个字符[CLS]的输出向量(768维)作为整个句子的向量表示,用来接全连接、softmax层做分类,现在我打算获取这样一个句子中每一个字符的向量表示,并存储下来,以备下游任务,如果我只用[CLS]的向量来做分类,那么就只取第一个向量,如果用所有token的向量来做卷积,那么就舍弃一头一尾的向量,取中间的向量来做卷积,这样下游任务不用改太多代码,把这样的信息存储在文件里,下游任务用起来比较灵活。
存储ndarray
要能够把词向量存储下来供下次使用,就肯定要知道怎么存储ndarray,因为拿到的词向量是shape为(N, seq_len, 768)的ndarray,N代表有多少个句子,seq_len代表句子的长度(加上头尾),768即向量的维度。这里我使用h5py存储ndarray,当然也有一些别的方法。
import h5py
# shape a: (3, 4, 5)
a = np.array([[[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1]],
[[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1]],
[[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1],[1,0.5,1,0.3,-1]]])
print(a.shape) save_file = h5py.File('../downstream/input_c_emb.h5', 'w')
save_file.create_dataset('train', data=a)
save_file.close() open_file = h5py.File('../downstream/input_c_emb.h5', 'r')
data = open_file['train'][:]
open_file.close()
print(data)
print(type(data))
print(data.shape)
字符级Token
因为我希望对中文字符进行一个字符一个字符的提取,而不是会把非中文字符拼在一起(这是google的逻辑),比如说”我出生于1996年“这句话,我希望提取为‘我’,‘出’,‘生’,‘于’,‘1’,‘9’,‘9’,‘6’,‘年’,因此需要自己写一个token类,在bert项目中tokenization.py文件中。
class CharTokenizer(object):
"""Runs end-to-end tokenziation.""" def __init__(self, vocab_file, do_lower_case=True):
self.vocab = load_vocab(vocab_file)
self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case)
self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) def tokenize(self, text):
split_tokens = []
for token in self.basic_tokenizer.tokenize(text):
for sub_token in token:
# 有的字符在预训练词典里没有
# 这部分字符替换成[UNK]符号
if not sub_token in self.vocab:
split_tokens.append('[UNK]')
else:
split_tokens.append(sub_token)
return split_tokens def convert_tokens_to_ids(self, tokens):
return convert_tokens_to_ids(self.vocab, tokens)
提取向量作为特征
这里对于输入输出稍作解释,我的输入有三个文件,train.txt,val.txt,test.txt,顾名思义了。每个文件中的一系列的句子,比如train.txt中有5000多行,代表5000多个句子,每一个句子是已经以空格分开的序列,比如”我 爱 中 国“。输出就是一个input_c_emb.h5,里面保存了所有的嵌入向量,以train,val,test标识为分隔。
代码注释还行,就不详细说了。
这段代码在项目中是token_features.py,项目地址后面会放。
# 获取token features,即每一个字符的向量,可以用cls作为句子向量,也可以用每一个字符的向量
import os
import sys
curPath = os.path.abspath(os.path.dirname(__file__))
rootPath = os.path.split(curPath)[0]
sys.path.append(rootPath)
print(sys.path)
import tensorflow as tf
import tokenization
import modeling
import numpy as np
import h5py # 配置文件
# 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'
bert_vocab_file = data_root + 'vocab.txt' # 经过处理的输入文件路径
file_input_x_c_train = '../data/legal_domain/train_x_c.txt'
file_input_x_c_val = '../data/legal_domain/val_x_c.txt'
file_input_x_c_test = '../data/legal_domain/test_x_c.txt' # embedding存放路径
emb_file_dir = '../data/legal_domain/emb.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') BATCH_SIZE = 16
SEQ_LEN = 510 def batch_iter(x, batch_size=64, shuffle=False):
"""生成批次数据,一个batch一个batch地产生句子向量"""
data_len = len(x)
num_batch = int((data_len - 1) / batch_size) + 1 if shuffle:
indices = np.random.permutation(np.arange(data_len))
x_shuffle = np.array(x)[indices]
else:
x_shuffle = x[:] word_mask = [[1] * (SEQ_LEN + 2) for i in range(data_len)]
word_segment_ids = [[0] * (SEQ_LEN + 2) for i in range(data_len)] for i in range(num_batch):
start_id = i * batch_size
end_id = min((i + 1) * batch_size, data_len)
yield x_shuffle[start_id:end_id], word_mask[start_id:end_id], word_segment_ids[start_id:end_id] 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:
split_tokens = token.tokenize(query)
if len(split_tokens) > SEQ_LEN:
split_tokens = split_tokens[:SEQ_LEN]
else:
while len(split_tokens) < SEQ_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)
word_id_list.append(word_ids)
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.CharTokenizer(vocab_file=bert_vocab_file) input_train_data = read_input(file_dir='../data/legal_domain/train_x_c.txt')
input_val_data = read_input(file_dir='../data/legal_domain/val_x_c.txt')
input_test_data = read_input(file_dir='../data/legal_domain/test_x_c.txt') with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
save_file = h5py.File('../downstream/input_c_emb.h5', 'w')
emb_train = []
train_batches = batch_iter(input_train_data, batch_size=BATCH_SIZE, shuffle=False)
for word_id, mask, segment in train_batches:
feed_data = {input_ids: word_id, input_mask: mask, segment_ids: segment}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# print(last2.shape)
for sub_array in last2:
emb_train.append(sub_array)
# 可以保存了
emb_train_array = np.asarray(emb_train)
save_file.create_dataset('train', data=emb_train_array) # val
emb_val = []
val_batches = batch_iter(input_val_data, batch_size=BATCH_SIZE, shuffle=False)
for word_id, mask, segment in val_batches:
feed_data = {input_ids: word_id, input_mask: mask, segment_ids: segment}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# print(last2.shape)
for sub_array in last2:
emb_val.append(sub_array)
# 可以保存了
emb_val_array = np.asarray(emb_val)
save_file.create_dataset('val', data=emb_val_array) # test
emb_test = []
test_batches = batch_iter(input_test_data, batch_size=BATCH_SIZE, shuffle=False)
for word_id, mask, segment in test_batches:
feed_data = {input_ids: word_id, input_mask: mask, segment_ids: segment}
last2 = sess.run(encoder_last2_layer, feed_dict=feed_data)
# print(last2.shape)
for sub_array in last2:
emb_test.append(sub_array)
# 可以保存了
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模型生成token级向量的更多相关文章
- 使用BERT模型生成句子序列向量
之前我写过一篇文章,利用bert来生成token级向量(对于中文语料来说就是字级别向量),参考我的文章:<使用BERT模型生成token级向量>.但是这样做有一个致命的缺点就是字符序列长度 ...
- BERT模型图解
转载于 腾讯Bugly 发表于 腾讯Bugly的专栏 原文链接:https://cloud.tencent.com/developer/article/1389555 本文首先介绍BERT模型要做什么 ...
- 图示详解BERT模型的输入与输出
一.BERT整体结构 BERT主要用了Transformer的Encoder,而没有用其Decoder,我想是因为BERT是一个预训练模型,只要学到其中语义关系即可,不需要去解码完成具体的任务.整体架 ...
- BERT模型介绍
前不久,谷歌AI团队新发布的BERT模型,在NLP业内引起巨大反响,认为是NLP领域里程碑式的进步.BERT模型在机器阅读理解顶级水平测试SQuAD1.1中表现出惊人的成绩:全部两个衡量指标上全面超越 ...
- [NLP自然语言处理]谷歌BERT模型深度解析
我的机器学习教程「美团」算法工程师带你入门机器学习 已经开始更新了,欢迎大家订阅~ 任何关于算法.编程.AI行业知识或博客内容的问题,可以随时扫码关注公众号「图灵的猫」,加入”学习小组“,沙雕博主 ...
- BERT模型
BERT模型是什么 BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,因为de ...
- Pytorch | BERT模型实现,提供转换脚本【横扫NLP】
<谷歌终于开源BERT代码:3 亿参数量,机器之心全面解读>,上周推送的这篇文章,全面解读基于TensorFlow实现的BERT代码.现在,PyTorch用户的福利来了:一个名为Huggi ...
- BERT模型详解
1 简介 BERT全称Bidirectional Enoceder Representations from Transformers,即双向的Transformers的Encoder.是谷歌于201 ...
- Bert模型实现垃圾邮件分类
近日,对近些年在NLP领域很火的BERT模型进行了学习,并进行实践.今天在这里做一下笔记. 本篇博客包含下列内容: BERT模型简介 概览 BERT模型结构 BERT项目学习及代码走读 项目基本特性介 ...
随机推荐
- SRC漏洞挖掘
SRC目标搜集 文章类的平台 https://www.anquanke.com/src 百度搜索 首先得知道SRC厂商的关键字,利用脚本搜集一波. 比如[应急响应中心]就可以作为一个关键字.通过搜索引 ...
- mysql查询之部门工资最高的员工
最近发现一个网站 力扣 查看 上面有很多算法和数据库的题目,做了一下,发现自己平时都疏忽了,因此边做边记录下来 Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 de ...
- (C#)Application.Exit()、Environment.Exit(0)区别
Application.Exit:通知winform消息循环退出.会在所有前台线程退出后,退出应用 强行退出方式,就像 Win32 的 PostQuitMessage().它意味着放弃所有消息泵,展开 ...
- vue-cli3 配置跨域并通axios进行数据请求
在项目根目录下创建vue.config.js文件,配置信息: module.exports = { devServer: { proxy: { '/api': { target: 'http://19 ...
- docker使用1
1. 安装 可以参考https://www.runoob.com/docker/centos-docker-install.html 注意linux版本是centos7.6 2. docker启动,停 ...
- Spring 框架的概述以及Spring中基于XML的IOC配置
Spring 框架的概述以及Spring中基于XML的IOC配置 一.简介 Spring的两大核心:IOC(DI)与AOP,IOC是反转控制,DI依赖注入 特点:轻量级.依赖注入.面向切面编程.容器. ...
- 基于卷积神经网络的人脸识别项目_使用Tensorflow-gpu+dilib+sklearn
https://www.cnblogs.com/31415926535x/p/11001669.html 基于卷积神经网络的人脸识别项目_使用Tensorflow-gpu+dilib+sklearn ...
- LeetCode第152场周赛(Java)
这算是我第一次正式参加 LeetCode 的周赛吧.通过两道题.意料之中(通过上次模拟可以看出来).总的来说,脑袋还是不太灵光.想的有点慢.全球第一名 0:10:19 就全部通过...感觉我的智商被狠 ...
- Android--卸载应用
获取应用列表: List<PackageInfo> packages = getPackageManager().getInstalledPackages(0); for (Package ...
- Dubbo学习摘录(零)
节点角色说明: Provider:暴露服务的服务提供方: Consumer:调用远程服务的服务消费方: Registry:服务注册与发现的注册中心: Monitor: 统计服务的调用次数和调用时间: ...