资讯专栏INFORMATION COLUMN

从Word2Vec到Bert

DandJ / 2517人阅读

摘要:采用作为特征提取器,并采用双向语言模型。此外,预训练的数据规模非常庞大。输入部分处理输入是一个线性序列,两个句子通过分隔符分割,前后两端分别增加标识符号。输出处理评价从模型或者方法的角度来看,借鉴了以及,主要提出了语言模型和。

Word2Vec模型

Word2Vec有两种训练方法:CBOWSkip-gram。CBOW的核心思想是上下文预测某个单词,Skip-gram正好相反,输入单词,要求网络预测它的上下文。

如上图所示,一个单词表达成word embedding后,很容易找到词义相近的其它词汇。

word embedding使用:句子中的单词以one-hot的形式作为输入,然后乘以学好的word embedding矩阵Q,就直接取出单词对应的word embedding了。word embedding矩阵Q其实就是网络one-hot层到embedding层映射的网络参数矩阵。所以,word embedding等价于把one-hot层到embedding层的网络用预训练好的参数矩阵Q初始化了。不过,word embedding只能初始化第一层网络参数,再高层就无能为力了。下游NLP任务使用word embedding的时候与图像类似,有两种方法,一种是frozen:word embedding层的网络参数固定不动;另一种是fine-tuning,即:word embedding这层参数使用新的训练集合训练。

word embedding存在的问题

如图所示,多义词bank有两种含义,但是word embedding在对bank这个词进行编码的时候无法区分这两个含义。尽管上下文环境中出现的单词相同,但是在语言模型训练的时候,不论什么上下文的句子经过word2vec,都是预测相同的单词bank,而同一个单词占用的同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间去。所以,word embedding无法区分多义词的不同语义,这是它一个比较严重的问题。

Bert

Bert采用transformer作为特征提取器,并采用双向语言模型。此外,Bert预训练的数据规模非常庞大。

NLP的四大类任务:

序列标注:中文分词,词性标注,命名实体识别,语义角色标注等。特点是,句子中的每个单词要求模型根据上下文给出一个分类类别。

分类任务:文本分类,情感计算。特点是,不管文章有多长,总体给出一个分类类别即可。

句子关系判断:问答,语义改写,自然语言推理等任务。特点是,给定两个句子,模型判断两个句子是否具有某种语义关系。

生成式任务:机器翻译,文本摘要,写诗造句,看图说话等。特点是,输入文本后,需要自主生成另外一种文字。

对于句子关系类任务,加上一个其实符号和终结符号,句子之间加上分隔符号即可。对输出来说,把第一个起始符号对应的transformer最后一层位置上串联一个softmax分类层。

对于分类问题,增加起始和终结符号,输出部分和句子关系类任务类似。

序列标注问题:输入部分和单句分类问题一样,只需要输出部分transformer最后一层每个单词对应位置都进行分类即可。

从这里可以看出,NLP四大类任务都可以比较方便的改造bert能够接受的方式,这意味着bert具有很强的普适性。

bert构造双向语言模型

Masked双向语言模型,随机选择语料中15%的单词,其中80%替换成mask标记,10%随机替换另一个单词,10%不做改动。
Next Sentence Prediction,分两种情况选择句子,一种是在语料中选择真正顺序相连的两个句子;另一种方式是,第二个句子随机选择拼接到第一个句子的后面。要求模型做句子关系预测,判断第二个句子是不是真的第一个句子的后续句子。这么做的目的是,在很多NLP任务中是句子关系判断任务,单词预测颗粒度的训练到不了句子关系这个层级,增加这个任务有助于下游句子关系判断任务。由此可以看到,bert的预训练是个多任务过程。

bert输入部分处理

bert输入是一个线性序列,两个句子通过分隔符分割,前后两端分别增加标识符号。每个单词有三个embedding。

位置embedding:NLP中单词顺序是重要特征,需要对位置进行编码。

单词embedding

句子embedding:前面提到的训练数据都是由两个句子构成,那么每个句子有个句子整体的embedding对应每个单词。

三者叠加,就形成了bert的输入。

bert输出处理

bert评价

从模型或者方法的角度来看,bert借鉴了ELMO,GPT以及CBOW,主要提出了masked语言模型和next sentence prediction。训练采用两阶段模型,第一阶段双向语言模型预训练,第二阶段采用具体任务fine-tuning或者做特征集成;第二是特征提取采用transformer作为特征提取器而不是rnn或cnn。bert最大的两点是效果好普适性强,几乎所有的NLP任务都可以套用bert这种两阶段解决思路。

案例实现:Predicting Movie Review Sentiment with BERT on TF Hub

bert已经添加到TF-Hub模块,可以快速集成到现有项目中。bert层可以替代之前的elmo,glove层,并且通过fine-tuning,bert可以同时提供精度,训练速度的提升。
此案例中,我们将在tensorflow中使用bert训练一个模型用于判断电影评论的情绪是消极还是积极。

导入模块
from sklearn.model_selection import train_test_split
import pandas as pd
import tensorflow as tf
import tensorflow_hub as hub
from datetime import datetime
!pip install bert-tensorflow

import bert
from bert import run_classifier
from bert import optimization
from bert import tokenization
数据下载



# 读取文件,创建dataframe
def load_directory_data(directory):
  data={}
  data["sentence"]=[]
  data["sentiment"]=[]

  for file_path in os.listdir(directory):
    with tf.gfile.GFile(os.path.join(directory,file_path),"r") as f:
      data["sentence"].append(f.read())
      data["sentiment"].append(re.match("d+_(d+).txt",file_path).group(1))

  return pd.DataFrame.from_dict(data)

# 添加新列,并打乱数据
def load_dataset(directory):

  # 积极情绪文件
  pos_df=load_directory_data(os.path.join(directory,"pos"))

  # 消极情绪
  neg_df=load_directory_data(os.path.join(directory,"neg"))

  pos_df["polarity"]=1
  neg_df["polarity"]=0

  # sample 参数frac,返回的比例,如:df中有10行数据,想返回30%,设置值为:0.3
  return pd.concat([pos_df,neg_df]).sample(frac=1).reset_index(drop=True)

# 下载并加载数据
def download_and_load_datasets(force_download=False):
  dataset=tf.keras.utils.get_file(
    fname="aclImdb.tar.gz",
    origin="http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz",
    extract=True
  )

  train_df=load_dataset(os.path.join(os.path.dirname(dataset),"aclImdb","train"))
  test_df=load_dataset(os.path.join(os.path.dirname(dataset),"aclImdb","test"))

  return train_df,test_df

train,test=download_and_load_datasets()
# 去前5000个样本
train=train.sample(5000)
test=test.sample(5000)

DATA_COLUMN="sentence"
LABEL_COLUMN="polarity"

label_list=[0,1]
数据处理

我们需要将数据转换成bert能够处理的格式。我们首先创建InputExample构造函数:

test_a:我们要分类的数据,如:DATA_COLUMN

test_b:用于句子关系判断,如:问答,翻译等

label:数据标签

train_InputExample=train.apply(lambda x:bert.run_classifier.InputExample(
  guid=None,
  text_a=x[DATA_COLUMN],
  text_b=None,
  label=x[LABEL_COLUMN]
),axis=1)
test_InputExample=test.apply(lambda x:bert.run_classifier.InputExample(
  guid=None,
  text_a=x[DATA_COLUMN],
  text_b=None,
  label=x[LABEL_COLUMN]
),axis=1)

接下来,我们需要处理数据以适合bert进行训练。步骤依次如下:

单词全部小写

将文本转换成序列(如:‘sally says hi’ -> ["sally","says","hi"])

将单词分解为wordpieces(如:‘calling’->["call","##ing"])

用bert提供的词汇文件进行单词索引映射

添加‘CLS’,"SEP"标记符

每次输入添加‘index’和‘segment’标记

# lowercase bert版本
BERT_MODEL_HUB="https://tfhub.dev/google/bert_uncased_L-12_H-768_A-12/1"

# 获取词表文件,小写数据处理
def create_tokenizer_from_hub_module():
  with tf.Graph().as_default():
    bert_module = hub.Module(BERT_MODEL_HUB)
    tokenization_info = bert_module(signature="tokenization_info", as_dict=True)
    with tf.Session() as sess:
      vocab_file,do_lower_case=sess.run([tokenization_info["vocab_file"],tokenization_info["do_lower_case"]])
  return bert.tokenization.FullTokenizer(
    vocab_file=vocab_file,
    do_lower_case=do_lower_case
  )

tokenizer=create_tokenizer_from_hub_module()

# 序列最长
MAX_SEQ_LENGTH=128
# 将训练,测试数据特征转换成bert需要的格式
train_features=bert.run_classifier.convert_examples_to_features(train_InputExample,label_list,MAX_SEQ_LENGTH,tokenizer)
test_features=bert.run_classifier.convert_examples_to_features(test_InputExample,label_list,MAX_SEQ_LENGTH,tokenizer)
创建模型
def create_model(is_predicting,input_ids,input_mask,segment_ids,labels,num_labels):

  # 创建分类模型
  bert_module=hub.Module(
    BERT_MODEL_HUB,
    trainable=True
  )
  bert_inputs=dict(
    input_ids=input_ids,
    input_mask=input_mask,
    segment_ids=segment_ids
  )
  bert_outputs=bert_module(
    inputs=bert_inputs,
    signature="tonkens",
    as_dict=True
  )
  output_layer=bert_outputs["pooled_output"]
  hidden_size=output_layer.shape[-1].value

  output_weights=tf.get_variable(
    "output_weights",[num_labels,hidden_size],
    initializer=tf.truncated_normal_initializer(stddev=0.02)
  )
  output_bias=tf.get_variable(
    "output_bias",[num_labels],initializer=tf.zeros_initializer()
  )

  with tf.variable_scope("loss"):
    # dropout用于防止过拟合,仅训练时使用
    output_layer=tf.nn.dropout(output_layer,keep_prob=0.9)

    logits=tf.matmul(output_layer,output_weights,transpose_b=True)
    logits=tf.nn.bias_add(logits,output_bias)
    log_prob=tf.nn.log_softmax(logits,axis=-1)

    # 将标签转为one-hot格式
    one_hot_labels=tf.one_hot(labels,depth=num_labels)
    predcited_labels=tf.squeeze(tf.argmax(log_prob,axis=-1))

    if is_predicting:
      return (predcited_labels,log_prob)
    per_example_loss=-tf.reduce_sum(one_hot_labels*log_prob,axis=-1)
    loss=tf.reduce_mean(per_example_loss)

    return (loss,predcited_labels,log_prob)
创建InputFn
def model_fn_builder(num_labels,learning_rate,num_train_steps,num_warmup_steps):
  def model_fn(features,labels,mode,params):

    input_idx=features["input_idx"]
    input_mask=features["input_mask"]
    segment_ids=features["segment_ids"]
    lable_ids=features["labels_ids"]

    is_predicting=(mode == tf.estimator.ModeKeys.PREDICT)

    if not is_predicting:
      (loss,predicted_labels,log_probs)=create_model(
        is_predicting,input_idx,input_mask,segment_ids,lable_ids,num_labels
      )
      train_op = bert.optimization.create_optimizer(
        loss, learning_rate, num_train_steps, num_warmup_steps, use_tpu=False)
      def metric_fn(label_ids,predicted_labels):
        accuracy=tf.metrics.accuracy(labels=lable_ids,predictions=predicted_labels)
        f1_score = tf.contrib.metrics.f1_score(
          label_ids,
          predicted_labels)
        auc = tf.metrics.auc(
          label_ids,
          predicted_labels)
        recall = tf.metrics.recall(
          label_ids,
          predicted_labels)
        precision = tf.metrics.precision(
          label_ids,
          predicted_labels)
        true_pos = tf.metrics.true_positives(
          label_ids,
          predicted_labels)
        true_neg = tf.metrics.true_negatives(
          label_ids,
          predicted_labels)
        false_pos = tf.metrics.false_positives(
          label_ids,
          predicted_labels)
        false_neg = tf.metrics.false_negatives(
          label_ids,
          predicted_labels)
        return {
          "eval_accuracy": accuracy,
          "f1_score": f1_score,
          "auc": auc,
          "precision": precision,
          "recall": recall,
          "true_positives": true_pos,
          "true_negatives": true_neg,
          "false_positives": false_pos,
          "false_negatives": false_neg
        }
      eval_metrics=metric_fn(lable_ids,predicted_labels)

      if mode == tf.estimator.ModeKeys.TRAIN:
        return tf.estimator.EstimatorSpec(
          mode=mode,
          loss=loss,
          train_op=train_op
        )
      else:
        return tf.estimator.EstimatorSpec(
          mode=mode,
          loss=loss,
          eval_metric_ops=eval_metrics
        )
    else:
      (predicted_labels, log_probs) = create_model(
        is_predicting, input_idx, input_mask, segment_ids, lable_ids, num_labels)

      predictions = {
        "probabilities": log_probs,
        "labels": predicted_labels
      }
      return tf.estimator.EstimatorSpec(mode, predictions=predictions)
  return model_fn
参数配置
BATCH_SIZE=32
LEARNING_RATE=2e-5
NUM_TRAIN_EPOCHS=3
WARMUP_PROPORTION=0.1
SAVE_CHECKEPOINTS_STEPS=500
SAVE_SUMMARY_STEPS=100

# 计算train,warmup总训练步数
num_train_steps=int(len(train_features)/BATCH_SIZE*NUM_TRAIN_EPOCHS)
num_warmup_steps=int(num_train_steps*WARMUP_PROPORTION)

# 设置模型保存路径/次数,图信息保存次数
run_config=tf.estimator.RunConfig(
  model_dir=OUTPUT_DIR,
  save_checkpoints_steps=SAVE_CHECKEPOINTS_STEPS,
  save_summary_steps=SAVE_SUMMARY_STEPS
)
模型训练,验证
modle_fn=model_fn_builder(
  num_labels=len(label_list),
  learning_rate=LEARNING_RATE,
  num_train_steps=num_train_steps,
  num_warmup_steps=num_train_steps
)
estimator=tf.estimator.Estimator(
  model_fn=modle_fn,
  config=run_config,
  params={"batch_size":BATCH_SIZE}
)
train_input_fn=bert.run_classifier.input_fn_builder(
  features=train_features,
  seq_length=MAX_SEQ_LENGTH,
  is_training=True,
  drop_remainder=False
)
estimator.train(input_fn=train_input_fn,max_steps=num_train_steps)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/20512.html

相关文章

  • Word2VecBert

    摘要:采用作为特征提取器,并采用双向语言模型。此外,预训练的数据规模非常庞大。输入部分处理输入是一个线性序列,两个句子通过分隔符分割,前后两端分别增加标识符号。输出处理评价从模型或者方法的角度来看,借鉴了以及,主要提出了语言模型和。 Word2Vec模型 showImg(https://segmentfault.com/img/bVbphHw?w=1282&h=726); Word2Vec有...

    leeon 评论0 收藏0
  • Word2VecBert

    摘要:采用作为特征提取器,并采用双向语言模型。此外,预训练的数据规模非常庞大。输入部分处理输入是一个线性序列,两个句子通过分隔符分割,前后两端分别增加标识符号。输出处理评价从模型或者方法的角度来看,借鉴了以及,主要提出了语言模型和。 Word2Vec模型 showImg(https://segmentfault.com/img/bVbphHw?w=1282&h=726); Word2Vec有...

    LittleLiByte 评论0 收藏0
  • 一文读懂深度学习:神经元BERT

    摘要:今天,蚂蚁金服财富对话算法团队整理对比了深度学习模型在自然语言处理领域的发展历程。深度学习网络相对于浅层网络结构,有两层三层及以上隐藏层的我们就可以称为深度网络。 阿里妹导读:自然语言处理领域的殿堂标志 BERT 并非横空出世,背后有它的发展原理。今天,蚂蚁金服财富对话算法团队整理对比了深度学习模型在自然语言处理领域的发展历程。从简易的神经元到当前最复杂的BERT模型,深入浅出地介绍了...

    xialong 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<