从Word2Vec到Bert

栏目: 编程工具 · 发布时间: 6年前

内容简介:Word2Vec有两种训练方法:

Word2Vec模型

从Word2Vec到Bert

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

从Word2Vec到Bert

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

从Word2Vec到Bert

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存在的问题

从Word2Vec到Bert

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

Bert

从Word2Vec到Bert

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

从Word2Vec到Bert

NLP的四大类任务:

  • 序列标注:中文分词,词性标注,命名实体识别,语义角色标注等。特点是,句子中的每个单词要求模型根据上下文给出一个分类类别。
  • 分类任务:文本分类,情感计算。特点是,不管文章有多长,总体给出一个分类类别即可。
  • 句子关系判断:问答,语义改写,自然语言推理等任务。特点是,给定两个句子,模型判断两个句子是否具有某种语义关系。
  • 生成式任务:机器翻译,文本摘要,写诗造句,看图说话等。特点是,输入文本后,需要自主生成另外一种文字。

从Word2Vec到Bert

  • 对于句子关系类任务,加上一个其实符号和终结符号,句子之间加上分隔符号即可。对输出来说,把第一个起始符号对应的 transformer 最后一层位置上串联一个 softmax 分类层。
  • 对于分类问题,增加起始和终结符号,输出部分和句子关系类任务类似。
  • 序列标注问题:输入部分和单句分类问题一样,只需要输出部分 transformer 最后一层每个单词对应位置都进行分类即可。

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

bert构造双向语言模型

从Word2Vec到Bert

Masked双向语言模型,随机选择语料中15%的单词,其中80%替换成mask标记,10%随机替换另一个单词,10%不做改动。

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

从Word2Vec到Bert

bert输入部分处理

从Word2Vec到Bert

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

  • 位置embedding:NLP中单词顺序是重要特征,需要对位置进行编码。
  • 单词embedding
  • 句子embedding:前面提到的训练数据都是由两个句子构成,那么每个句子有个句子整体的embedding对应每个单词。

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

bert输出处理

从Word2Vec到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

从Word2Vec到Bert

import bert
from bert import run_classifier
from bert import optimization
from bert import tokenization

数据下载

从Word2Vec到Bert

从Word2Vec到Bert

从Word2Vec到Bert

# 读取文件,创建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)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Coding the Matrix

Coding the Matrix

Philip N. Klein / Newtonian Press / 2013-7-26 / $35.00

An engaging introduction to vectors and matrices and the algorithms that operate on them, intended for the student who knows how to program. Mathematical concepts and computational problems are motiva......一起来看看 《Coding the Matrix》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具