Kashgari

极简且强大的NLP框架,用于文本分类与标注

Posted by SHF on December 26, 2019

Kashgari概述

Kashgari 是一个极简且强大的 NLP 框架,可用于文本分类和标注的学习,研究及部署上线

  • 方便易用 Kashgari 提供了简洁统一的 API 和完善的文档,使其非常方便易用。
  • 内置迁移学习模块 Kashgari 通过提供 BertEmbedding, GPT2Embedding, WordEmbedding 等特征提取类, 方便利用预训练语言模型实现迁移学习。
  • 易扩展 Kashgari 提供简便的接口和继承关系,自行扩展新的模型结构非常方便。
  • 可用于生产 通过把 Kashgari 模型导出为 SavedModel 格式,可以使用 TensorFlow Serving 模块提供服务,直接在线上环境使用。
  • Kashgari 的Github 网址
  • Kashgari 本质上是基于keras的高层封装,所以使用格式,性能方面是相差无多的。

Kashgari 主要用来文本分类与标注,目前本文主要介绍了文本标注(NER)的使用 以及填坑,以后有时间会把分类的坑填上

NER 命名实体识别

安装

Kashgari 项目基于 Tensorflow 1.14.0Python 3.6+

目前也有 tf2.0 的版本支持,但目前没有 tf1.4 稳定

pip install kashgari
# CPU
pip install tensorflow==1.14.0
# GPU
pip install tensorflow-gpu==1.14.0

载入数据

这里model的数据格式是固定的,如果要使用自己的数据,需要认真效验自己的数据格式。

我遇到的几个问题:

  1. 空格 \t 与 ‘ ‘区别
  2. entity实体值缺省的,在对应的标注位置上
  3. 标注规范支持BIO(推荐)、BIEO、SIEO、SIO
# 加载内置数据集,此处可以替换成自己的数据集,保证格式一致即可

# load internal data
from kashgari.corpus import ChineseDailyNerCorpus

train_x, train_y = ChineseDailyNerCorpus.load_data('train')
valid_x, valid_y = ChineseDailyNerCorpus.load_data('validate')
test_x, test_y = ChineseDailyNerCorpus.load_data('test')

# load own data
from kashgari.corpus import DataReader

train_x, train_y = DataReader().read_conll_format_file('./data/surround/train.txt')
valid_x, valid_y = DataReader().read_conll_format_file('data/surround/dev.txt')
test_x, test_y = DataReader().read_conll_format_file('./data/surround/test.txt')
print(f"train data count: {len(train_x)}")
print(f"validate data count: {len(valid_x)}")
print(f"test data count: {len(test_x)}")

创建 embedding

create Bare embedding

在 BareEmbedding 中参数sequence_length有三种模式:

  • ‘auto’: 这里默认的是 ‘auto’,即将语料库长度的 95% 作为序列长度
  • ‘variable’: 模型输入形状将设置为 None ,可以处理各种长度的输入,它将使用每批中最大序列的长度作为序列长度
  • ‘int值’: 如果使用整数,假设为 50,则输入输出序列长度将设置为 50

这里我选择的是 ‘variable’ 模式,因为在下面的model推理(tensorflow serving)的时候,能够自适应语句的长度值, 不会有多余的 ‘PAD’值。

from kashgari.embeddings import BareEmbedding

bare_embed = BareEmbedding(task=kashgari.LABELING,
                           sequence_length='variable',
                           embedding_size=300)

create BERT embedding

如果使用 BERTEmbedding 编码的时候,sequence_length模式默认设置的只接受’int值’ 🙉

BERT_PATH 中的 '<bert-model-folder>'是我们下载的 bert 预训练模型(pre−training)的文件路径, 比如 BERT_PATH = './models/bert_base_models/chinese_L-12_H-768_A-12/'

google 官方提供的中文bert模型的下载地址google-research-bert

from kashgari.embeddings import BERTEmbedding

BERT_PATH = '<bert-model-folder>'
bert_embed = BERTEmbedding(BERT_PATH,
                           task=kashgari.LABELING,
                           sequence_length=50)

对于使用 GPT-2 embedding 的方法与 BERT embedding 是类似的。

openAI GPT-2 官方提供的两种预训练模型:small(117M),medium(345M),large 版本并没有发布,据说是【他们认为如此强力的模型有遭到恶意滥用的风险】🙉 模型的下载地址为openai-gpt-2

但对于中文的支持并不良好,有兴趣研究中文版的 GPT-2 模型的小伙伴请移步GPT2-Chinese

选择 model

Kashgari 提供的 model有:

CNN_LSTM_Model, BiLSTM_Model, BiGRU_Model or BiGRU_CRF_Model, BiLSTM_CRF_Model

from kashgari.tasks.labeling import BiLSTM_CRF_Model

model = BiLSTM_CRF_Model(bare_embed)
# or model = BiLSTM_CRF_Model(bert_embed)
# or model = BiLSTM_CRF_Model(gpt2_embed)

可视化选择

这是 Kashgari 内置回调函数,会在训练过程计算精确度,召回率和 F1

from kashgari.callbacks import EvalCallBack

# check  $ tensorboard --logdir logs
tf_board_callback = keras.callbacks.TensorBoard(log_dir='./logs', update_freq=1000)
eval_callback = EvalCallBack(kash_model=model,
                             valid_x=valid_x,
                             valid_y=valid_y,
                             step=5)

训练模型

选择可视化

model.fit(train_x,
          train_y,
          x_validate=valid_x,
          y_validate=valid_y,
          epochs=5,
          batch_size=32,
          callbacks=[eval_callback, tf_board_callback])

不选择可视化

model.fit(train_x,
          train_y,
          x_validate=valid_x,
          y_validate=valid_y,
          epochs=1,
          batch_size=32)

评估模型

评估模型的具体 metrics ,请查看相关的 api 文档

model.evaluate(test_x, test_y)

保存模型

可以选择其他的库函数来保存模型,这里选择的函数是方便下面 tensorflow sering 的推理;

注意 model_path 的路径格式。

from kashgari import utils

utils.convert_to_saved_model(model,
                             model_path='./save_models/ner',
                             version='1')

results test

为了快速验证 model 的结果:

数据使用的是内置的人名日报的数据集(ChineseDailyNerCorpus)

使用的编码是 BareEmbeddingepoch只设了1轮的结果。

train data count: 20864
validate data count: 2318
test data count: 4636
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input (InputLayer)           [(None, None)]            0         
_________________________________________________________________
layer_embedding (Embedding)  (None, None, 300)         1050000   
_________________________________________________________________
layer_blstm (Bidirectional)  (None, None, 256)         439296    
_________________________________________________________________
layer_dense (Dense)          (None, None, 64)          16448     
_________________________________________________________________
layer_crf_dense (Dense)      (None, None, 8)           520       
_________________________________________________________________
layer_crf (CRF)              (None, None, 8)           64        
=================================================================
Total params: 1,506,328
Trainable params: 1,506,328
Non-trainable params: 0
_________________________________________________________________
653/653 [==============================] - 350s 536ms/step - loss: 15.2126 - accuracy: 0.9662 - val_loss: 157.9817 - val_accuracy: 0.8512
           precision    recall  f1-score   support

      ORG     0.4330    0.4050    0.4185      2185
      LOC     0.6217    0.5831    0.6018      3658
      PER     0.6569    0.6604    0.6586      1864

micro avg     0.5778    0.5513    0.5642      7707
macro avg     0.5767    0.5513    0.5636      7707

同样的人名日报的数据集,同样的超参数设置,也跑 1 个epoch,使用的编码换成 BERTEmbedding的结果。

653/653 [==============================] - 124s 190ms/step - loss: 2.5958 - accuracy: 0.9820 - val_loss: 51.5484 - val_accuracy: 0.9898
           precision    recall  f1-score   support

      LOC     0.8402    0.9115    0.8744      3016
      ORG     0.7987    0.8339    0.8159      1932
      PER     0.9248    0.9486    0.9365      1594

micro avg     0.8476    0.8976    0.8719      6542
macro avg     0.8485    0.8976    0.8722      6542

模型推理

docker 镜像挂载模型

通过 docker 安装tensorflow/serving 镜像,然后将服务挂载到本地的 8501 端口,

其中"/Users/shf/PycharmProjects/Kashgari/save_models:/models"是本地生成模型的路径格式

如果是远程服务器上进行模型推理,过程是相似的

docker run -t --rm -p 8501:8501 -v "/Users/shf/PycharmProjects/Kashgari/save_models:/models" -e MODEL_NAME=ner tensorflow/serving

载入模型

from kashgari import utils

processor = utils.load_processor(model_path='./save_models/ner/1')

预处理数据

test_x = ['中', '国', '队', '将', '于', '北', '京', '对', '阵', '来', '着', '中', '东', '的', '叙', '利', '亚', '队', '。']
tensor = processor.process_x_dataset(data=[test_x])

如果使用 BERTEmbedding,需要多一步经过下面的预处理

# if you using BERT, you need to reformat tensor first
# ------ Only for BERT Embedding Start --------
import numpy as np

tensor = [{
   "Input-Token:0": i.tolist(),
   "Input-Segment:0": np.zeros(i.shape).tolist()
} for i in tensor]
# ------ Only for BERT Embedding End ----------

进行预测

注意 Bare embedding 与 BERT embedding使用区别

import requests

r = requests.post("http://localhost:8501/v1/models/ner:predict", json={"instances": tensor.tolist()}) # Bare embedding
# or r = requests.post("http://localhost:8501/v1/models/ner:predict", json={"instances": tensor})  # BERT embedding
preds = r.json()['predictions']

# Convert result back to labels
pred = np.array(preds).argmax(-1)
labels = processor.reverse_numerize_label_sequences(pred)
print(test_x)
print(labels)

results predict

['中', '国', '队', '将', '于', '北', '京', '对', '阵', '来', '着', '中', '东', '的', '叙', '利', '亚', '队', '。']
[['B-ORG', 'I-ORG', 'I-ORG', 'O', 'O', 'B-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'B-LOC', 'I-LOC', 'O', 'B-ORG', 'I-ORG', 'I-ORG', 'I-ORG', 'O']]