乐趣区

关于tensorflow:深度学习之Bert中文分类学习

深度学习之 Bert 中文分类学习

BERT 试验

预训练后果剖析

tfhub_handle_preprocess = "https://hub.tensorflow.google.cn/tensorflow/bert_zh_preprocess/3"
bert_preprocess_model = hub.KerasLayer(tfhub_handle_preprocess)

text_test = ['我真是个蠢才啊!!']
text_preprocessed = bert_preprocess_model(text_test)

print(f'Keys       : {list(text_preprocessed.keys())}')
print(f'Shape      : {text_preprocessed["input_word_ids"].shape}')
print(f'Word Ids   : {text_preprocessed["input_word_ids"][0, :12]}')
print(f'Input Mask : {text_preprocessed["input_mask"][0, :12]}')
print(f'Type Ids   : {text_preprocessed["input_type_ids"][0, :12]}')

打印后果

Keys       : ['input_mask', 'input_type_ids', 'input_word_ids']
Shape      : (1, 128)
Word Ids   : [101 2769 4696 3221  702 1921 2798 1557 8013 8013  102    0]
Input Mask : [1 1 1 1 1 1 1 1 1 1 1 0]
Type Ids   : [0 0 0 0 0 0 0 0 0 0 0 0]

Shape 中的 128 猜测应该是 最大长度。

Keys 对应了三个属性,但其实 bert 应该是有 7 个特色属性。为什么另外四个属性在这里没有,目前不是很分明,但我感觉 Hub 外面的 Advance topics 预计是在讲这个事件。但因为当初要做的工作是文本分类,以下的四个特色是不须要的。

  • input_ids: 输出的 token 对应的 id
  • input_mask: 输出的 mask,1 代表是失常输出,0 代表的是 padding 的输出
  • segment_ids: 输出的 0:代表句子 A 或者 padding 句子,1 代表句子 B
  • masked_lm_positions:咱们 mask 的 token 的地位
  • masked_lm_ids:咱们 mask 的 token 的对应 id
  • masked_lm_weights:咱们 mask 的 token 的权重,1 代表是实在 mask 的,0 代表的是 padding 的 mask
  • next_sentence_labels:句子 A 和 B 是否是高低句

接下来看下输入怎么了解。为了保障本人的猜测正确,我又丢了一句 真是个蠢才啊!!进去看后果。把两个后果合并起来是这样的。

Word Ids   : [101 2769 4696 3221  702 1921 2798 1557 8013 8013  102    0]
Word Ids   : [101 4696 3221  702 1921 2798 1557 8013 8013  102    0    0]
Input Mask : [1 1 1 1 1 1 1 1 1 1 1 0]
Input Mask : [1 1 1 1 1 1 1 1 1 1 0 0]
Type Ids   : [0 0 0 0 0 0 0 0 0 0 0 0]
Type Ids   : [0 0 0 0 0 0 0 0 0 0 0 0]

有意思吧。bert 目前没有分词组的概念,它是一个一个词离开的。依据材料查看 bert 的预训练分成两步,这个显然是第一步。

我真是个蠢才啊啊!被宰割成了 [CLS] 我 真 是 个 天 才 啊 啊![SEP],所以这个跟 Word Ids 是能对得上的。
TypeIds 应该跟 segment_ids 是对等的,也是对得上的,都是 0。
Input Mask 也是对得上的。它的算法如下
mask 是 1 示意是 ” 真正 ” 的 Token,0 则是 Padding 进去的。
在前面的 Attention 时会通过 tricky 的技巧让模型不能 attend to 这些 padding 进去的 Token 上。
input_mask = [1] * len(input_ids)

后果输入剖析

tfhub_handle_encoder = "https://hub.tensorflow.google.cn/tensorflow/bert_zh_L-12_H-768_A-12/4"
bert_model = hub.KerasLayer(tfhub_handle_encoder)

bert_results = bert_model(text_preprocessed)
print(f'Keys       : {list(bert_results.keys())}')
print(f'Pooled Outputs Shape:{bert_results["pooled_output"].shape}')
print(f'Pooled Outputs Values:{bert_results["pooled_output"][0, :12]}')
print(f'Sequence Outputs Shape:{bert_results["sequence_output"].shape}')
print(f'Sequence Outputs Values:{bert_results["sequence_output"][0, :12]}')
  • sequence_output:维度【batch_size, seq_length, hidden_size】,这是训练后每个 token 的词向量。
  • pooled_output:维度是【batch_size, hidden_size】,每个 sequence 第一个地位 CLS 的向量输入,用于分类工作。

BERT 实战中文多分类

参考官网的 BERT 分类,官网用的是情感剖析的场景,其实跟中文多分类场景相似。
要做的工作就是换 BERT 的 pre-process 和 encoder,再把最初一层输入换成多分类即可。

根本能够照搬。

环境筹备

pip install -q tensorflow-text
pip install -q tf-models-official

import tensorflow as tf
import os
import shutil

import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_text as text
from official.nlp import optimization  # to create AdamW optimizer

import matplotlib.pyplot as plt

tfhub_handle_preprocess = "https://hub.tensorflow.google.cn/tensorflow/bert_zh_preprocess/3"
tfhub_handle_encoder = "https://hub.tensorflow.google.cn/tensorflow/bert_zh_L-12_H-768_A-12/4"
bert_preprocess_model = hub.KerasLayer(tfhub_handle_preprocess)
bert_model = hub.KerasLayer(tfhub_handle_encoder)

筹备数据集

因为是在 Colab 做的试验,上传貌似只能上传文件的模式。所以在本地压缩成一个 zip 包, 上传到 Colab 后做解压。

import zipfile
from pathlib import Path
zFile = zipfile.ZipFile("path.zip","r")
for fileM in zFile.namelist(): 
        zFile.extract(fileM, "path")
zFile.close();

文件格式的要求跟官网示例统一即可。

文件目录
    分类 1
        分类 1 的题目 1
        分类 1 的题目 2
        ...
    分类 2
    分类 3
    ...

拆分数据集

AUTOTUNE = tf.data.AUTOTUNE
batch_size = 32
seed = 42

raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'train',
    batch_size=batch_size,
    validation_split=0.2,
    subset='training',
    seed=seed)

class_names = raw_train_ds.class_names
print(class_names)
train_ds = raw_train_ds.cache().prefetch(buffer_size=AUTOTUNE)
val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'train',
    batch_size=batch_size,
    validation_split=0.2,
    subset='validation',
    seed=seed)

val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

看一下数据集

for text_batch, label_batch in train_ds.take(1):
  print(text_batch)
  for i in range(3):
    print(f'Review: {text_batch.numpy()[i]}')
    label = label_batch.numpy()[i]
    print(f'Label : {label} ({class_names[label]})')

定义模型

def build_classifier_model():
  text_input = tf.keras.layers.Input(shape=(), dtype=tf.string, name='text')
  preprocessing_layer = hub.KerasLayer(tfhub_handle_preprocess, name='preprocessing')
  encoder_inputs = preprocessing_layer(text_input)
  encoder = hub.KerasLayer(tfhub_handle_encoder, trainable=True, name='BERT_encoder')
  outputs = encoder(encoder_inputs)
  net = outputs['pooled_output']
  net = tf.keras.layers.Dropout(0.5)(net)

  net = tf.keras.layers.Dense(6, activation='softmax', name='classifier')(net)
  return tf.keras.Model(text_input, net)

classifier_model = build_classifier_model()
epochs = 10
steps_per_epoch = tf.data.experimental.cardinality(train_ds).numpy()
num_train_steps = steps_per_epoch * epochs
print(num_train_steps)
num_warmup_steps = int(0.1*num_train_steps)

init_lr = 3e-5
optimizer = optimization.create_optimizer(init_lr=init_lr,                                          num_train_steps=num_train_steps,                                          num_warmup_steps=num_warmup_steps,
optimizer_type='adamw')
loss = tf.keras.losses.SparseCategoricalCrossentropy()
metrics = tf.metrics.SparseCategoricalAccuracy()

classifier_model.compile(optimizer=optimizer,
                         loss=loss,
                         metrics=metrics)

训练模型

history = classifier_model.fit(x=train_ds,
                               validation_data=val_ds,
                               epochs=epochs)

预测数据

发现尽管我只用了 300 条样本,train 样本 240 条,val 样本 60 条,然而准确率也能到 90%。BERT 果然叼。

import numpy as np

def print_my_examples(inputs, results):
  result_for_printing = \
    [f'input: {inputs[i]:<30} : class: {class_names[np.argmax(results[i])] }'
                         for i in range(len(inputs))]
  print(*result_for_printing, sep='\n')
  print()

examples = ['德国米技(MIJI)蒸汽喷淋式煮茶壶 全自动保温泡茶壶 HK-K018',
            '公牛插座收纳盒装办公接线板家用创意电源',
            '生存元素烧水壶办公室家用多功能小型烧水壶保温一体煮茶器煮茶壶',
            '信安智囊老人防摔防跌倒术后爱护重复使用智能气囊马甲服神器送礼',
            '七度空间卫生巾优雅系列日夜超值组合',
            '【贝拉家】贝拉爸爸本人家工厂生产的科技布狗窝']
example_result = classifier_model(tf.constant(examples))

print_my_examples(examples,example_result)

附录

  • 官网示例 BERT 分类代码
  • Tensorflow hub Bert 中文模型解决
  • 知乎 Bert 详解
  • 腾讯云 Bert 详解
  • Bert 源码解读
退出移动版