本文默认读者有一定的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级向量的更多相关文章

  1. 使用BERT模型生成句子序列向量

    之前我写过一篇文章,利用bert来生成token级向量(对于中文语料来说就是字级别向量),参考我的文章:<使用BERT模型生成token级向量>.但是这样做有一个致命的缺点就是字符序列长度 ...

  2. BERT模型图解

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

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

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

  4. BERT模型介绍

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

  5. [NLP自然语言处理]谷歌BERT模型深度解析

    我的机器学习教程「美团」算法工程师带你入门机器学习   已经开始更新了,欢迎大家订阅~ 任何关于算法.编程.AI行业知识或博客内容的问题,可以随时扫码关注公众号「图灵的猫」,加入”学习小组“,沙雕博主 ...

  6. BERT模型

    BERT模型是什么 BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,因为de ...

  7. Pytorch | BERT模型实现,提供转换脚本【横扫NLP】

    <谷歌终于开源BERT代码:3 亿参数量,机器之心全面解读>,上周推送的这篇文章,全面解读基于TensorFlow实现的BERT代码.现在,PyTorch用户的福利来了:一个名为Huggi ...

  8. BERT模型详解

    1 简介 BERT全称Bidirectional Enoceder Representations from Transformers,即双向的Transformers的Encoder.是谷歌于201 ...

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

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

随机推荐

  1. [简短问答]C-Lodop中一些测试用的地址

    测试访问:访问http://localhost:8000欢迎页面试试进入欢迎页面http://localhost:8000,点欢迎页面的预览试试 查看下c-lodop启动界面,在设置里查看下当前启动的 ...

  2. Kubernetes 使用 ingress 配置 https 集群(十五)

    目录 一.背景 1.1 需求 1.2 Ingress 1.3 环境介绍 二.安装部署 2.1.创建后端 Pod 应用 2.2 创建后端 Pod Service 2.3.创建 ingress 资源 2. ...

  3. 【Java语言特性学习之一】设计模式

    设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于 ...

  4. ue4读取灰度图生成三维地形mesh

    转自:https://www.cnblogs.com/gucheng/p/10116857.html 新建ue c++工程. 在Build.cs中添加"ProceduralMeshCompo ...

  5. webpack打包完成,复制,打包,移动,删除已生成的文件插件

    const FileManagerPlugin = require('filemanager-webpack-plugin'); 详情请到 https://www.npmjs.com/ 一看究竟 贴个 ...

  6. SpringBoot系列教程之Redis集群环境配置

    之前介绍的几篇redis的博文都是基于单机的redis基础上进行演示说明的,然而在实际的生产环境中,使用redis集群的可能性应该是大于单机版的redis的,那么集群的redis如何操作呢?它的配置和 ...

  7. Module 'mysql' already loaded in Unknown on line 0解决方法

    Module 'mysql' already loaded in Unknown on line 0解决方法 直接进入php.ini 把;extension=mysql.so注释掉就好了

  8. [转帖]Latch

    Latch (转) http://blog.csdn.net/tianlesoftware/article/details/5263238 2013-05-24 15:33:09 huashanlun ...

  9. 在Jenkins的pipeline项目中运行jmeter测试-教程

    Jenkins 2.0的发布引入了一种新的项目类型 - Pipeline,以前只能通过插件获得.从Jenkins 2.0开始,Pipeline项目开箱即用. 与通常的“自由式”项目相比,管道构建具有几 ...

  10. STS,修改Ctrl+Shift+R匹配类的配置

    在使用STS(Spring Tools)时,每次通过Ctrl+Shift+R查询类时,会出来一堆不想看到的类.如下所示: 上面的.class文件和父项目中的.java文件,在匹配类时,是不想看到的. ...