深度学习之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-textpip install -q tf-models-officialimport tensorflow as tfimport osimport shutilimport tensorflow as tfimport tensorflow_hub as hubimport tensorflow_text as textfrom official.nlp import optimization # to create AdamW optimizerimport matplotlib.pyplot as plttfhub_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 zipfilefrom pathlib import PathzFile = 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.AUTOTUNEbatch_size = 32seed = 42raw_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_namesprint(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 = 10steps_per_epoch = tf.data.experimental.cardinality(train_ds).numpy()num_train_steps = steps_per_epoch * epochsprint(num_train_steps)num_warmup_steps = int(0.1*num_train_steps)init_lr = 3e-5optimizer = 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 npdef 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源码解读