<jupyter_start><jupyter_text>本案例创建环境须创建10G以上的EVS配置![创建环境](./img/创建环境.png)  命名实体识别命名实体识别（Named EntitiesRecognition，NER）是NLP里的一项很基础的任务，就是指从文本中识别出命名性指称项，为信息抽取、信息检索、机器翻译、问答系统等任务做铺垫。其目的是识别语料中人名、地名、组织机构名等命名实体。在特定的领域中，会相应地定义领域内的各种实体类型。命名实体是命名实体识别的研究主体，一般包括三大类（实体类、时间类和数字类）和七小类（人名、地名、机构名、时间、日期、货币和百分比）命名实体。其中人名、地名、机构名相对于其他实体识别较复杂，无法用模式匹配的方式获得较好的识别效果，是近年来研究的主要部分。汉语作为象形文字，相比于英文等拼音文字来说，针对中文的NER任务来说往往要更有挑战性，下面列举几点：1.  中文文本里不像英文那样有空格作为词语的界限标志，而且“词”在中文里本来就是一个很模糊的概念，中文也不具备英文中的字母大小写等形态指示。2. 中文的用字灵活多变，有些词语在脱离上下文语境的情况下无法判断是否是命名实体，而且就算是命名实体，当其处在不同的上下文语境下也可能是不同的实体类型。3. 命名实体存在嵌套现象，如“北京大学第三医院”这一组织机构名中还嵌套着同样可以作为组织机构名的“北京大学”，而且这种现象在组织机构名中尤其严重。4. 中文里广泛存在简化表达现象，如“北医三院”、“国科大”，乃至简化表达构成的命名实体，如“国科大桥”。常用的命名实体标注方法有BIO标注、BIOES标注、BMES等。在本案例中选择使用BIO标注：将每个元素标注为“B-X”、“I-X”或者“O”。1. “B-X”（Begin）表示此元素所在的片段属于X类型并且此元素在此片段的开头；2. “I-X”（Inside）表示此元素所在的片段属于X类型并且此元素在此片段的中间位置；3. “O”（Outside）表示不属于任何类型。即B-PER、I-PER代表人名首字、人名非首字，B-LOC、I-LOC代表地名首字、地名非首字，B-ORG、I-ORG代表组织机构名首字、组织机构名非首字，示例如下：![BIO](./img/BIO.png)  BERT简介BERT（Bidirectional Encoder Representations from Transformers）是一种预训练NLP模型，由Google在2018年10月发布的论文[《BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding》](https://arxiv.org/pdf/1810.04805.pdf)中提出。该模型刷新了自然语言处理任务的11项记录。BERT的通过联合调节所有层中的双向Transformer来训练预训练深度双向表示，只需要一个额外的输出层来对预训练BERT进行微调就可以满足各种任务，没有必要针对特定任务对模型进行修改，其先进性基于两点：其一，是使用Masked Langauge Model（MLM）和Next Sentense Prediction（NSP）的新预训练任务，两种方法分别捕捉词语和句子级别的representation；其二，是大量数据和计算能力满足BERT的训练强度，BERT训练数据采用了英文的开源语料BooksCropus 以及英文维基百科数据，一共有33亿个词,同时BERT模型的标准版本有1亿的参数量，而BERT的large版本有3亿多参数量,其团队训练一个预训练模型需要在64块TPU芯片上训练4天完成，而一块TPU的速度约是目前主流GPU的7-8倍。Google团队开源了多个预训练模型，以供多种下游任务需求使用。开源的预训练模型如下：- [BERT-Base, Uncased](https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip): 12-layer, 768-hidden, 12-heads, 110M parameters- [BERT-Large, Uncased](https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-24_H-1024_A-16.zip): 24-layer, 1024-hidden, 16-heads, 340M parameters- [BERT-Base, Cased](https://storage.googleapis.com/bert_models/2018_10_18/cased_L-12_H-768_A-12.zip): 12-layer, 768-hidden, 12-heads , 110M parameters- [BERT-Large, Cased](https://storage.googleapis.com/bert_models/2018_10_18/cased_L-24_H-1024_A-16.zip): 24-layer, 1024-hidden, 16-heads, 340M parameters- [BERT-Base, Multilingual Cased (New, recommended)](https://storage.googleapis.com/bert_models/2018_11_23/multi_cased_L-12_H-768_A-12.zip): 104 languages, 12-layer, 768-hidden, 12-heads, 110M parameters- [BERT-Base, Multilingual Uncased (Orig, not recommended)](https://storage.googleapis.com/bert_models/2018_11_03/multilingual_L-12_H-768_A-12.zip)(Not recommended, use Multilingual Cased instead): 102 languages, 12-layer, 768-hidden, 12-heads, 110M parameters- [BERT-Base, Chinese](https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip): Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters前4个是英文模型，Multilingual 是多语言模型，最后一个是中文模型（只有字级别的）。其中 Uncased 是字母全部转换成小写，而Cased是保留了大小写。这里layer是layers层数（即Transformer blocks个数），hidden是hidden vector size，heads是self-attention的heads。  BERT预训练的两个重要步骤 Masked语言模型为了训练深度双向语言表示向量，作者用了一个非常直接的方式，遮住句子里某些单词，让编码器预测这个单词是什么。训练方法为：1）80%的单词用***[MASK]*** token来代替my dog is ***hairy*** → my dog is ***[MASK]***2）10%单词用任意的词来进行代替my dog is ***hairy*** → my dog is ***apple***3）10%单词不变my dog is ***hairy*** → my dog is ***hairy***作者在论文中提到这样做的好处是，编码器不知道哪些词需要预测的，哪些词是错误的，因此被迫需要学习每一个token的表示向量。另外作者表示，每个batchsize只有15%的词被遮盖的原因，是性能开销。双向编码器比单项编码器训练要慢。 预测下一个句子：Next Sentence Prediction（NSP）预训练一个二分类的模型，来学习句子之间的关系。预测下一个句子的方法对学习句子之间关系很有帮助。训练方法：正样本和负样本比例是1：1，50%的句子是正样本，随机选择50%的句子作为负样本。  数据集本案例使用《人民日报1998年中文标注语料库》以1998年人民日报语料为对象，由北京大学计算语言学研究所和富士通研究开发中心有限公司共同制作的标注语料库。该语料库对600多万字节的中文文章进行了分词及词性标注，其被作为原始数据应用于大量的研究和论文中。数据集格式如下图，每行的第一个是字，第二个是它的标签，字与标签之间使用空格分隔，两句话之间空一行。![data](./img/数据集示例.png)  预训练模型本案例使用中文**BERT-Base,Chinese**预训练模型。从链接[BERT-Base, Chinese](https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip)下载解压后得到如下预训练模型文件- bert_config.json- bert_model.ckpt.data-00000-of-00001- bert_model.ckpt.index- bert_model.ckpt.meta- vocab.txt其中* 一个config file(bert_config.json) ：存储预训练模型超参数* 三个tensorflow checkpoint (bert_model.ckpt) ：包含预训练模型checkpoint* 一个vocab文件(vocab.txt)：将WordPiece映射成word id  从obs上下载所需数据集、文件及模型如下：<jupyter_code>from modelarts.session import Session
session = Session()
print("start download NER .tar.gz. Waiting......30 sec")
session.download_data(bucket_path="modelarts-labs/end2end/NLP/NER .tar.gz", path="./NER.tar.gz")
# 以上步骤在ModelArts Notebook中运行，只有在下载完毕后才有输出

#查看目录
!ls -la<jupyter_output>start download NER .tar.gz. Waiting......30 sec
Successfully download file modelarts-labs/end2end/NLP/NER .tar.gz from OBS to local ./NER.tar.gz
total 750272
drwsrwsrwx  7 root   users      4096 May 26 05:07 .
drwsrwsr-x 20 jovyan users      4096 May 26 04:34 ..
drwsrws---  3 root   users      4096 May 26 04:44 img
drwsrws---  2 root   users      4096 May 26 05:07 .ipynb_checkpoints
drwsrws---  2 root   users     16384 May 24 01:42 lost+found
drwsrws---  8 root   users      4096 May 25 14:49 NER
-rw-rw----  1 root   users 384063639 May 25 15:26 NER1.tar.gz
-rw-r-----  1 jovyan users    101022 May 26 04:54 NER-BERT.ipynb
-rw-r-----  1 jovyan users 384063613 May 26 05:11 NER.tar.gz
drwsrws---  4 root   users      4096 May 26 04:44 .Trash-1000
-rw-rw----  1 root   users       809 May 26 03:01 Untitled.ipynb<jupyter_text>解压从obs下载的压缩包，解压后删除压缩包。<jupyter_code># 使用tar命令解压资源包
!tar xf ./NER.tar.gz

# 使用rm命令删除压缩包
!rm ./NER.tar.gz

#查看目录，与上对比检查是否增加NER文件夹
!ls -la<jupyter_output>tar: ./NER/chinese_L-12_H-768_A-12: Cannot utime: Operation not permitted
tar: ./NER/chinese_L-12_H-768_A-12: Cannot change mode to rwsr-s---: Operation not permitted
tar: ./NER/bert/__pycache__: Cannot utime: Operation not permitted
tar: ./NER/bert/__pycache__: Cannot change mode to rwsr-s---: Operation not permitted
tar: ./NER/bert: Cannot utime: Operation not permitted
tar: ./NER/bert: Cannot change mode to rwsr-s---: Operation not permitted
tar: ./NER/data: Cannot utime: Operation not permitted
tar: ./NER/data: Cannot change mode to rwsr-s---: Operation not permitted
tar: ./NER/output/.ipynb_checkpoints: Cannot utime: Operation not permitted
tar: ./NER/output/.ipynb_checkpoints: Cannot change mode to rwsr-s---: Operation not permitted
tar: ./NER/output: Cannot utime: Operation not permitted
tar: ./NER/output: Cannot change mode to rwsr-s---: Operation not permitted
tar: ./NER/.ipynb_checkpoints: Cannot utime: Operation not permitted
tar: ./NER/.ipynb_checkpoints: Cannot change mode[...]<jupyter_text>解压后文件夹结构如下：```bashNER├── bert│       ├── modeling.py│       ├── optimization.py│       └── tokenization.py│├── chinese_L-12_H-768_A-12│       ├──bert_config.json│       ├──bert_model.ckpt.data-00000-of-00001│       ├──bert_model.ckpt.index│       ├──bert_model.ckpt.meta│       └──vocab.txt├── data│       ├── train.txt  │       ├── dev.txt│       └── test.txt├── src│       ├── conlleval.pl  │       ├── conlleval.py│       ├── models.py│       └── terminal_predict.py├── img└── output```  引入运行需要的包本案例所需条件：tensorflow >= 1.11.0   tensorflow-gpu  >= 1.11.0 因此需要首先升级tensorflow和tensorflow-gpu<jupyter_code>print("start update tensorflow Waiting......15 sec")
!pip install tensorflow==1.11.0
print("下载完毕后tensorflow需要自动配置，等待15秒再执行后面的内容")
print("start update tensorflow-gpu Waiting.....15 sec")
!pip install tensorflow-gpu==1.11.0
print("下载完毕后tensorflow-gpu需要自动配置，等待15秒再执行后面的内容")
print("打印tensorflow版本，确认正确更新 等待10秒")
import tensorflow as tf
print("tensorflow-version：", tf.__version__)
#引入本案例需要的主要lib
import os
import json
import numpy as np
import tensorflow as tf
import codecs
import pickle
import collections
from NER.bert import modeling, optimization, tokenization
print("成功引入本案例需要的主要lib")<jupyter_output>成功引入本案例需要的主要lib<jupyter_text>定义数据、模型等路径和执行的任务。<jupyter_code>print("设置所需要的路径参数&全局参数")
data_dir = "./NER/data"    #数据集路径
output_dir = "./NER/output"    #输出路径
vocab_file = "./NER/chinese_L-12_H-768_A-12/vocab.txt"    #中文字典路径
data_config_path = "./NER/chinese_L-12_H-768_A-12/bert_config.json"    #预训练模型参数路径
init_checkpoint = "./NER/chinese_L-12_H-768_A-12/bert_model.ckpt"    #预训练模型checkpoint路径
max_seq_length = 128    #预先设定的最大序列长度
batch_size = 64    #批尺寸
num_train_epochs = 3.0    #训练轮数
print("数据集路径为：",data_dir)
print("输出路径为：",output_dir)
print("中文字典路径为：",vocab_file)
print("预训练模型参数路径为：",data_config_path)
print("预训练模型checkpoint路径为：",init_checkpoint)
print("预先设定的最大序列长度为：",max_seq_length)
print("批尺寸为：",batch_size)
print("训练轮数：",num_train_epochs)<jupyter_output>设置所需要的路径参数&全局参数
数据集路径为： ./NER/data
输出路径为： ./NER/output
中文字典路径为： ./NER/chinese_L-12_H-768_A-12/vocab.txt
预训练模型参数路径为： ./NER/chinese_L-12_H-768_A-12/bert_config.json
预训练模型checkpoint路径为： ./NER/chinese_L-12_H-768_A-12/bert_model.ckpt
预先设定的最大序列长度为： 128
批尺寸为： 64
训练轮数： 3.0<jupyter_text>***最佳实践：******batch_size = 64    批尺寸，本案例中为了加快训练速度只设置为64，如果需要提高训练精度可适当增大。******num_train_epochs = 3.0 训练轮数，本案例中为了加快训练速度只设置为3，如果需要提高训练精度可适当增大。*** 首先，定义一个NER处理部分的字典为processor，打印labels<jupyter_code>#将 TensorFlow 日志信息输出到屏幕
tf.logging.set_verbosity(tf.logging.INFO)
from NER.src.models import InputFeatures, InputExample, DataProcessor, NerProcessor

#构造NER processor类，用来获取训练集、验证集、测试集，读取BIO标注数据
processors = {"ner": NerProcessor }
processor = processors["ner"](output_dir)

#输出processor的labels 确认构造processor成功
label_list = processor.get_labels()
print("labels:", label_list)<jupyter_output>labels: ['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'X', '[CLS]', '[SEP]']<jupyter_text>以上labels分别表示：- O：非标注实体- B-PER：人名首字- I-PER：人名非首字- B-ORG：组织首字- I-ORG：组织名非首字- B-LOC：地名首字- I-LOC：地名非首字- X：未知- [CLS]：句首- [SEP]：句尾 从预训练模型的json文件中加载参数，打印预训练参数，读取中文字典。<jupyter_code>data_config = json.load(codecs.open(data_config_path))
train_examples = processor.get_train_examples(data_dir)    #获取训练数据
num_train_steps = int(len(train_examples) / batch_size * num_train_epochs)    #训练步骤
num_warmup_steps = int(num_train_steps * 0.1)    #预热训练步骤
data_config['num_train_steps'] = num_train_steps
data_config['num_warmup_steps'] = num_warmup_steps
data_config['num_train_size'] = len(train_examples)

print("显示配置信息:")
for key,value in data_config.items():
    print('{key}:{value}'.format(key = key, value = value))

#读取bert 官方提供的预训练模型参数配置(对应文件： ~/work/NER/chinese_L-12_H-768_A-12/bert_config.json)
bert_config = modeling.BertConfig.from_json_file(data_config_path)
#读取bert 预训练模型中文字典(对应文件： ~/work/NER/chinese_L-12_H-768_A-12/vocab.txt)
tokenizer = tokenization.FullTokenizer(vocab_file=vocab_file, do_lower_case=True)
#tf.estimator运行参数
run_config = tf.estimator.RunConfig(
    model_dir=output_dir,
    save_summary_steps=500,
    save_checkpoints_steps=500,
    session_config=tf.ConfigProto(
        log_device_placement=False,
        inter_op_parallelism_threads=0,
        intra_op_parallelism_threads=0,
        allow_soft_placement=True
    )
)

print("\n加载参数完毕！")<jupyter_output>显示配置信息:
attention_probs_dropout_prob:0.1
directionality:bidi
hidden_act:gelu
hidden_dropout_prob:0.1
hidden_size:768
initializer_range:0.02
intermediate_size:3072
max_position_embeddings:512
num_attention_heads:12
num_hidden_layers:12
pooler_fc_size:768
pooler_num_attention_heads:12
pooler_num_fc_layers:3
pooler_size_per_head:128
pooler_type:first_token_transform
type_vocab_size:2
vocab_size:21128
num_train_steps:978
num_warmup_steps:97
num_train_size:20864

加载参数完毕！<jupyter_text>BERT的输入部分是个线性序列，句子通过分隔符分割，句前和句后分别增加两个标识符号[CLS]和[SEP]。![BERT输入](./img/bert输入.png)- **token embeddings**：词向量，将各个词转换成固定维度的向量。在BERT中，每个词会被转换成768维的向量表示。输入文本在送入token embeddings 层之前要先进行tokenization处理。在目前的BERT模型中，文章作者还将英文词汇作进一步切割，划分为更细粒度的语义单位（WordPiece），例如：将playing分割为play和ing。对于中文，目前作者尚未对输入文本进行分词，而是直接将单字作为构成文本的基本单位。- **position embeddings**：位置向量，表示位置信息，由于词顺序是很重要的特征，出现在文本不同位置的字/词所携带的语义信息存在差异（比如：“我爱你”和“你爱我”），因此，BERT模型对不同位置的字/词分别附加一个不同的向量以作区分。- **segment embeddings**：文本向量，用来区别两种句子，只有两种向量表示。前一个向量是把0赋给第一个句子中的各个token, 后一个向量是把1赋给第二个句子中的各个token。如果输入仅仅只有一个句子，那么它的segment embedding就是全0。最后，BERT模型将字向量、文本向量和位置向量的加和作为模型输入。BERT能够处理最长512个token的输入序列。论文作者通过让BERT在各个位置上学习一个向量表示来讲序列顺序的信息编码进来。这意味着Position Embeddings layer 实际上就是一个大小为 (512, 768) 的lookup表，表的第一行是代表第一个序列的第一个位置，第二行代表序列的第二个位置，以此类推。因此，如果有这样两个句子“Hello world” 和“Hi there”, “Hello” 和“Hi”会由完全相同的position embeddings，因为他们都是句子的第一个词。同理，“world” 和“there”也会有相同的position embedding。在此，我们将训练集字符转换成的向量特征储存在tf_record文件中。tfrecord格式是tensorflow官方推荐的数据格式，把数据、标签进行统一的存储。tfrecord内部使用了“Protocol Buffer”二进制数据编码方案，它只占用一个内存块，只需要一次性加载一个二进制文件的方式即可，简单，快速，尤其对大型训练数据很友好。<jupyter_code>#convert_single_example函数用来把一句话的每个字转换成BERT的输入，即词向量、文本向量、位置向量，
#以及每个字对应的实体标签。同时在句首和句尾分别加上[CLS]和[SEP]标志。
def convert_single_example(ex_index, example, label_list, max_seq_length, 
                           tokenizer, output_dir, mode):
    #将一个样本进行分析，然后将字转化为id, 标签转化为id,然后结构化到InputFeatures对象中
    label_map = {}
    # 1表示从1开始对标签进行index化
    for (i, label) in enumerate(label_list, 1):
        label_map[label] = i
    # 保存label->index 的map
    if not os.path.exists(os.path.join(output_dir, 'label2id.pkl')):
        with codecs.open(os.path.join(output_dir, 'label2id.pkl'), 'wb') as w:
            pickle.dump(label_map, w)

    textlist = example.text.split(' ')
    labellist = example.label.split(' ')
    tokens = []
    labels = []
    for i, word in enumerate(textlist):
        # 中文是分字,但是对于一些不在BERT的vocab.txt中得字符会被进行WordPiece处理
        #（例如中文的引号），可以将所有的分字操作替换为list(input)
        token = tokenizer.tokenize(word)
        tokens.extend(token)
        label_1 = labellist[i]
        for m in range(len(token)):
            if m == 0:
                labels.append(label_1)
            else:  # 一般不会出现else
                labels.append("X")
    # 序列截断
    if len(tokens) >= max_seq_length - 1:
        tokens = tokens[0:(max_seq_length - 2)]
        # -2 是因为序列需要加一个句首和句尾标志
        labels = labels[0:(max_seq_length - 2)]
    ntokens = []
    segment_ids = []
    label_ids = []
    ntokens.append("[CLS]")  # 句子开始设置CLS 标志
    segment_ids.append(0)
    label_ids.append(label_map["[CLS]"])  
    for i, token in enumerate(tokens):
        ntokens.append(token)
        segment_ids.append(0)
        label_ids.append(label_map[labels[i]])
    ntokens.append("[SEP]")  # 句尾添加[SEP] 标志
    segment_ids.append(0)
    label_ids.append(label_map["[SEP]"])
    input_ids = tokenizer.convert_tokens_to_ids(ntokens)  # 将序列中的字(ntokens)转化为ID形式
    input_mask = [1] * len(input_ids)

    while len(input_ids) < max_seq_length:
        input_ids.append(0)
        input_mask.append(0)
        segment_ids.append(0)
        label_ids.append(0)
        ntokens.append("**NULL**")

    assert len(input_ids) == max_seq_length
    assert len(input_mask) == max_seq_length
    assert len(segment_ids) == max_seq_length
    assert len(label_ids) == max_seq_length

    # 打印前5个数据
    if ex_index < 5:
        tf.logging.info("*** Example ***")
        tf.logging.info("guid: %s" % (example.guid))    #每个句子的独立id
        tf.logging.info("tokens: %s" % " ".join([tokenization.printable_text(x) for x in tokens]))    #每个字作为一个token
        tf.logging.info("input_ids: %s" % " ".join([str(x) for x in input_ids]))    #字向量token embeddings
        tf.logging.info("input_mask: %s" % " ".join([str(x) for x in input_mask]))    #位置向量position embeddings
        tf.logging.info("segment_ids: %s" % " ".join([str(x) for x in segment_ids]))    #文本向量segment embeddings
        tf.logging.info("label_ids: %s" % " ".join([str(x) for x in label_ids]))    #标签labels

    # 结构化为一个类
    feature = InputFeatures(
        input_ids=input_ids,
        input_mask=input_mask,
        segment_ids=segment_ids,
        label_ids=label_ids,
    )
   
    return feature

#filed_based_convert_examples_to_features函数使用convert_single_example函数把所有句子example转化成作为BERT输入的features，
#并保存为tf_record格式
def filed_based_convert_examples_to_features(
        examples, label_list, max_seq_length, tokenizer, output_file, mode=None):
    #将数据转化为TF_Record 结构，作为模型数据输入
    writer = tf.python_io.TFRecordWriter(output_file)
    # 遍历数据集
    for (ex_index, example) in enumerate(examples):
        if ex_index % 5000 == 0:
            tf.logging.info("Writing example %d of %d" % (ex_index, len(examples)))
        # 对于每一个训练样本,
        feature = convert_single_example(ex_index, example, label_list, max_seq_length, tokenizer, output_dir, mode)

        def create_int_feature(values):
            f = tf.train.Feature(int64_list=tf.train.Int64List(value=list(values)))
            return f

        features = collections.OrderedDict()
        features["input_ids"] = create_int_feature(feature.input_ids)
        features["input_mask"] = create_int_feature(feature.input_mask)
        features["segment_ids"] = create_int_feature(feature.segment_ids)
        features["label_ids"] = create_int_feature(feature.label_ids)
        tf_example = tf.train.Example(features=tf.train.Features(feature=features))
        writer.write(tf_example.SerializeToString())
    print("数据集读取完成！")

#在output_dir中保存训练集的tf_record
train_file = os.path.join(output_dir, "train.tf_record")

#将训练集中字符转化为features作为训练的输入
filed_based_convert_examples_to_features(
            train_examples, label_list, max_seq_length, tokenizer, output_file=train_file)<jupyter_output>INFO:tensorflow:Writing example 0 of 20864
INFO:tensorflow:*** Example ***
INFO:tensorflow:guid: train-0
INFO:tensorflow:tokens: 全 国 人 民 代 表 大 会 澳 门 特 别 行 政 区 筹 备 委 员 会 第 一 次 全 体 会 议 今 天 上 午 在 北 京 人 民 大 会 堂 开 幕 ， 国 务 院 副 总 理 、 筹 委 会 主 任 委 员 钱 其 琛 在 致 开 幕 词 中 指 出 ， 筹 建 澳 门 特 别 行 政 区 的 工 作 已 经 启 动 ， 筹 委 会 面 临 的 工 作 是 大 量 的 、 紧 迫 的 ， 筹 委 们 任 重 道 远 ， 希 望 大 家 齐 心 协 力 为 澳 门 的 平 稳 过 渡
INFO:tensorflow:input_ids: 101 1059 1744 782 3696 807 6134 1920 833 4078 7305 4294 1166 6121 3124 1277 5040 1906 1999 1447 833 5018 671 3613 1059 860 833 6379 791 1921 677 1286 1762 1266 776 782 3696 1920 833 1828 2458 2391 8024 1744 1218 7368 1199 2600 4415 510 5040 1999 833 712 818 1999 1447 7178 1071 4422 1762 5636 2458 2391 6404 704 2900 1139 8024 5040 2456 4078 7305 4294 1166 6121 3124 1277 4638 2339 868 2347 5307 1423 1220 8024 5040 1999 833 7481 707 4638 2339 868 3221 1920 7030 4638 510 5165 6833 4638 8024 5040 1999 812 818 7028 6887 6823 8024 2361 3307 1920 2157 7970 2552 1291 1213 711 4078 7305 4638 239[...]<jupyter_text>以上输出了五个样本句子，分别标出了token embeddings，segment embeddings，position embeddings和label，预先设定的最大序列长度max_seq_length为128 ，少于128字符的用“0”补齐。其中labels的1-10分别代表：'O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'X', '[CLS]', '[SEP]'。以第五个示例（guid = train-4）为例，分别标识为：![tokens](./img/tokens.png)  BiLSTM+CRF层LSTM的全称是Long Short-Term Memory，它是RNN（Recurrent Neural Network，循环神经网络）的一种。LSTM区别于RNN的地方，主要就在于它在算法中加入了一个判断信息有用与否的结构，该结构中被放置了三扇门，分别叫做输入门、遗忘门和输出门。一个信息进入LSTM的网络当中，可以根据规则来判断是否有用。只有符合算法认证的信息才会留下，不符的信息则通过遗忘门被遗忘。LSTM由于其设计的特点，非常适合用于对时序数据的建模，如文本数据。BiLSTM是Bi-directional Long Short-Term Memory的缩写，是由前向LSTM与后向LSTM组合而成。这两个都连接着一个输出层。这个结构提供给输出层输入序列中每一个点的完整的过去和未来的上下文信息。我们可以单独使用BiLSTM命名实体识别模型，如图。![BiLSTM](./img/LSTM.png)因为每个单词的BiLSTM输出是标签分数。我们可以选择每个单词得分最高的标签。例如，对于w0，“B-Person”得分最高（1.5），因此我们可以选择“B-Person”作为其最佳预测标签。同样，我们可以为w1选择“I-Person”，w2选择”O“，w3选择“B-Organization”，w4选择"O"。 虽然由此可以得到句子中每个单元的正确标签，但是我们不能保证标签每次都是预测正确的。例如，下图中的例子，标签序列是“I-Organization I-Person” and “B-Organization I-Person”，很显然这是错误的。![BiLSTM2](./img/LSTM2.png) 而CRF层能从训练数据中获得约束性的规则。CRF（Conditional Random Fields，条件随机场）是给定一组输入随机变量条件下，另外一组输出随机变量的条件概率分布模型，其特点是假设输出变量构成马尔可夫随机场。CRF层可以为最后预测的标签添加一些约束来保证预测的标签是合法的。这些约束可以是：- 句子中第一个词总是以标签“B-“ 或 “O”开始，而不是“I-”- 标签“B-label1 I-label2 I-label3 I-…”,label1, label2, label3应该属于同一类实体。例如，“B-Person I-Person” 是合法的序列, 但是“B-Person I-Organization” 是非法标签序列- 标签序列“O I-label” is 非法的.实体标签的首个标签应该是 “B-“ ，而非 “I-“, 换句话说,有效的标签序列应该是“O B-label”。有了这些约束，标签序列预测中非法序列出现的概率将会大大降低。![CRF](./img/CRF.png)上图说明了BiLSTM层的输出是每个标签的分数。例如，对于w0，BiLSTM节点的输出为1.5（B-Person），0.9（I-Person），0.1（B-Organization），0.08（I-Organization）和0.05（O）。这些分数将是CRF层的输入。然后，BiLSTM块预测的所有分数被馈送到CRF层。在CRF层中，将选择具有最高预测分数的标签序列作为最佳答案。下面引入BiLSTM+CRF层，具体结构可从src文件夹下的models.py中的BLSTM_CRF类中找到。把BiLSTM+CRF作为下游模型，结合BERT建立NER模型。<jupyter_code>#定义模型参数
learning_rate = 5e-5    #学习率（关系模型收敛速率）
dropout_rate = 1.0    #随机失活率（减少过拟合）
lstm_size=1    
cell='lstm'
num_layers=1

#从models里引用BiLSTM-CRF层
from NER.src.models import BLSTM_CRF
from tensorflow.contrib.layers.python.layers import initializers

def create_model(bert_config, is_training, input_ids, input_mask,
                 segment_ids, labels, num_labels, use_one_hot_embeddings,
                 dropout_rate=dropout_rate, lstm_size=1, cell='lstm', num_layers=1):
    # 使用数据加载BertModel,获取对应的字embedding
    model = modeling.BertModel(
        config=bert_config,
        is_training=is_training,
        input_ids=input_ids,
        input_mask=input_mask,
        token_type_ids=segment_ids,
        use_one_hot_embeddings=use_one_hot_embeddings
    )
    # 获取对应的embedding 输入数据[batch_size, seq_length, embedding_size]
    embedding = model.get_sequence_output()
    max_seq_length = embedding.shape[1].value
    # 算序列真实长度
    used = tf.sign(tf.abs(input_ids))
    lengths = tf.reduce_sum(used, reduction_indices=1)  # [batch_size] 大小的向量，包含了当前batch中的序列长度
    # 加载BLSTM-CRF模型对象
    blstm_crf = BLSTM_CRF(embedded_chars=embedding, hidden_unit=1, cell_type='lstm', num_layers=1,
                          dropout_rate=dropout_rate, initializers=initializers, num_labels=num_labels,
                          seq_length=max_seq_length, labels=labels, lengths=lengths, is_training=is_training)
    rst = blstm_crf.add_blstm_crf_layer(crf_only=True)
    return rst

def model_fn_builder(bert_config, num_labels, init_checkpoint, learning_rate,
                     num_train_steps, num_warmup_steps,use_one_hot_embeddings=False):
    #构建模型
    def model_fn(features, labels, mode, params):
        tf.logging.info("*** Features ***")
        for name in sorted(features.keys()):
            tf.logging.info("  name = %s, shape = %s" % (name, features[name].shape))
        input_ids = features["input_ids"]
        input_mask = features["input_mask"]
        segment_ids = features["segment_ids"]
        label_ids = features["label_ids"]

        print('shape of input_ids', input_ids.shape)
        is_training = (mode == tf.estimator.ModeKeys.TRAIN)

        # 使用参数构建模型,input_idx 就是输入的样本idx表示，label_ids 就是标签的idx表示
        total_loss, logits, trans, pred_ids = create_model(
            bert_config, is_training, input_ids, input_mask, segment_ids, label_ids,
            num_labels, False, dropout_rate, lstm_size, cell, num_layers)

        tvars = tf.trainable_variables()
        # 加载BERT模型
        if init_checkpoint:
            (assignment_map, initialized_variable_names) = \
                 modeling.get_assignment_map_from_checkpoint(tvars, init_checkpoint)
            tf.train.init_from_checkpoint(init_checkpoint, assignment_map)
        
        output_spec = None
        if mode == tf.estimator.ModeKeys.TRAIN:
            train_op = optimization.create_optimizer(
                 total_loss, learning_rate, num_train_steps, num_warmup_steps, False)
            hook_dict = {}
            hook_dict['loss'] = total_loss
            hook_dict['global_steps'] = tf.train.get_or_create_global_step()
            logging_hook = tf.train.LoggingTensorHook(
                hook_dict, every_n_iter=100)

            output_spec = tf.estimator.EstimatorSpec(
                mode=mode,
                loss=total_loss,
                train_op=train_op,
                training_hooks=[logging_hook])

        elif mode == tf.estimator.ModeKeys.EVAL:
            def metric_fn(label_ids, pred_ids):
                # 首先对结果进行维特比解码
                # crf 解码
                return {
                    "eval_loss": tf.metrics.mean_squared_error(labels=label_ids, predictions=pred_ids),   }
            
            eval_metrics = metric_fn(label_ids, pred_ids)
            output_spec = tf.estimator.EstimatorSpec(
                mode=mode,
                loss=total_loss,
                eval_metric_ops=eval_metrics
            )
        else:
            output_spec = tf.estimator.EstimatorSpec(
                mode=mode,
                predictions=pred_ids
            )
        return output_spec

    return model_fn

print("创建模型model_fn")

model_fn = model_fn_builder(
        bert_config=bert_config,
        num_labels=len(label_list) + 1,
        init_checkpoint=init_checkpoint,
        learning_rate=learning_rate,
        num_train_steps=num_train_steps,
        num_warmup_steps=num_warmup_steps,
        use_one_hot_embeddings=False)<jupyter_output>创建模型model_fn<jupyter_text>在训练集上进行训练<jupyter_code>def file_based_input_fn_builder(input_file, seq_length, is_training, drop_remainder):
    name_to_features = {
        "input_ids": tf.FixedLenFeature([seq_length], tf.int64),
        "input_mask": tf.FixedLenFeature([seq_length], tf.int64),
        "segment_ids": tf.FixedLenFeature([seq_length], tf.int64),
        "label_ids": tf.FixedLenFeature([seq_length], tf.int64),
    }

    def _decode_record(record, name_to_features):
        example = tf.parse_single_example(record, name_to_features)
        for name in list(example.keys()):
            t = example[name]
            if t.dtype == tf.int64:
                t = tf.to_int32(t)
            example[name] = t
        return example

    def input_fn(params):
        params["batch_size"] = 32
        batch_size = params["batch_size"]
        d = tf.data.TFRecordDataset(input_file)
        if is_training:
            d = d.repeat()
            d = d.shuffle(buffer_size=300)
        d = d.apply(tf.contrib.data.map_and_batch(
            lambda record: _decode_record(record, name_to_features),
            batch_size=batch_size,
            drop_remainder=drop_remainder
        ))
        return d

    return input_fn

#训练输入
train_input_fn = file_based_input_fn_builder(
            input_file=train_file,
            seq_length=max_seq_length,
            is_training=True,
            drop_remainder=True)

num_train_size = len(train_examples)

print("进行训练，约20min")
tf.logging.info("***** Running training *****")
tf.logging.info("  Num examples = %d", num_train_size)
tf.logging.info("  Batch size = %d", batch_size)
tf.logging.info("  Num steps = %d", num_train_steps)

#模型预测estimator
estimator = tf.estimator.Estimator(
        model_fn=model_fn,
        config=run_config,
        params={
        'batch_size':batch_size
    })

estimator.train(input_fn=train_input_fn, max_steps=num_train_steps)
print("训练结束")<jupyter_output>INFO:tensorflow:***** Running training *****
INFO:tensorflow:  Num examples = 20864
INFO:tensorflow:  Batch size = 64
INFO:tensorflow:  Num steps = 978
INFO:tensorflow:Using config: {'_model_dir': './NER/output', '_tf_random_seed': None, '_save_summary_steps': 500, '_save_checkpoints_steps': 500, '_save_checkpoints_secs': None, '_session_config': allow_soft_placement: true
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_service': None, '_cluster_spec': <tensorflow.python.training.server_lib.ClusterSpec object at 0x7f3663340470>, '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:*** Features ***
INFO:tensorflow:  name = input_ids, shape[...]<jupyter_text>在验证集上验证模型<jupyter_code>eval_examples = processor.get_dev_examples(data_dir)
eval_file = os.path.join(output_dir, "eval.tf_record")
filed_based_convert_examples_to_features(
                eval_examples, label_list, max_seq_length, tokenizer, eval_file)
data_config['eval.tf_record_path'] = eval_file
data_config['num_eval_size'] = len(eval_examples)
num_eval_size = data_config.get('num_eval_size', 0)

# 打印验证集数据信息
tf.logging.info("***** Running evaluation *****")
tf.logging.info("  Num examples = %d", num_eval_size)
tf.logging.info("  Batch size = %d", batch_size)

eval_steps = None
eval_drop_remainder = False
eval_input_fn = file_based_input_fn_builder(
            input_file=eval_file,
            seq_length=max_seq_length,
            is_training=False,
            drop_remainder=eval_drop_remainder)

result = estimator.evaluate(input_fn=eval_input_fn, steps=eval_steps)
output_eval_file = os.path.join(output_dir, "eval_results.txt")
with codecs.open(output_eval_file, "w", encoding='utf-8') as writer:
    tf.logging.info("***** Eval results *****")
    for key in sorted(result.keys()):
        tf.logging.info("  %s = %s", key, str(result[key]))
        writer.write("%s = %s\n" % (key, str(result[key])))

# 保存数据的配置文件，避免在以后的训练过程中多次读取训练以及测试数据集，消耗时间
if not os.path.exists(data_config_path):
    with codecs.open(data_config_path, 'a', encoding='utf-8') as fd:
        json.dump(data_config, fd)<jupyter_output>INFO:tensorflow:Writing example 0 of 4631
INFO:tensorflow:*** Example ***
INFO:tensorflow:guid: dev-0
INFO:tensorflow:tokens: 1 9 9 0 年 4 月 通 过 的 《 中 华 人 民 共 和 国 香 港 特 别 行 政 区 基 本 法 》 明 确 规 定 ， 香 港 特 区 的 终 审 权 属 于 特 区 终 审 法 院 。
INFO:tensorflow:input_ids: 101 122 130 130 121 2399 125 3299 6858 6814 4638 517 704 1290 782 3696 1066 1469 1744 7676 3949 4294 1166 6121 3124 1277 1825 3315 3791 518 3209 4802 6226 2137 8024 7676 3949 4294 1277 4638 5303 2144 3326 2247 754 4294 1277 5303 2144 3791 7368 511 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
INFO:tensorflow:input_mask: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
INFO:tensorflow:segment_ids: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 [...]<jupyter_text>在测试集上进行测试<jupyter_code>token_path = os.path.join(output_dir, "token_test.txt")
if os.path.exists(token_path):
    os.remove(token_path)

with codecs.open(os.path.join(output_dir, 'label2id.pkl'), 'rb') as rf:
    label2id = pickle.load(rf)
    id2label = {value: key for key, value in label2id.items()}

predict_examples = processor.get_test_examples(data_dir)
predict_file = os.path.join(output_dir, "predict.tf_record")
filed_based_convert_examples_to_features(predict_examples, label_list,
                                                 max_seq_length, tokenizer,
                                                 predict_file, mode="test")

tf.logging.info("***** Running prediction*****")
tf.logging.info("  Num examples = %d", len(predict_examples))
tf.logging.info("  Batch size = %d", batch_size)
    
predict_drop_remainder = False
predict_input_fn = file_based_input_fn_builder(
            input_file=predict_file,
            seq_length=max_seq_length,
            is_training=False,
            drop_remainder=predict_drop_remainder)

predicted_result = estimator.evaluate(input_fn=predict_input_fn)
output_eval_file = os.path.join(output_dir, "predicted_results.txt")
with codecs.open(output_eval_file, "w", encoding='utf-8') as writer:
    tf.logging.info("***** Predict results *****")
    for key in sorted(predicted_result.keys()):
        tf.logging.info("  %s = %s", key, str(predicted_result[key]))
        writer.write("%s = %s\n" % (key, str(predicted_result[key])))

result = estimator.predict(input_fn=predict_input_fn)
output_predict_file = os.path.join(output_dir, "label_test.txt")

def result_to_pair(writer):
    for predict_line, prediction in zip(predict_examples, result):
        idx = 0
        line = ''
        line_token = str(predict_line.text).split(' ')
        label_token = str(predict_line.label).split(' ')
        if len(line_token) != len(label_token):
            tf.logging.info(predict_line.text)
            tf.logging.info(predict_line.label)
        for id in prediction:
            if id == 0:
                continue
            curr_labels = id2label[id]
            if curr_labels in ['[CLS]', '[SEP]']:
                continue
            try:
                line += line_token[idx] + ' ' + label_token[idx] + ' ' + curr_labels + '\n'
            except Exception as e:
                tf.logging.info(e)
                tf.logging.info(predict_line.text)
                tf.logging.info(predict_line.label)
                line = ''
                break
            idx += 1
        writer.write(line + '\n')
            
from NER.src.conlleval import return_report

with codecs.open(output_predict_file, 'w', encoding='utf-8') as writer:
    result_to_pair(writer)
eval_result = return_report(output_predict_file)
for line in eval_result:
    print(line)<jupyter_output>INFO:tensorflow:Writing example 0 of 68
INFO:tensorflow:*** Example ***
INFO:tensorflow:guid: test-0
INFO:tensorflow:tokens: 美 国 的 华 莱 士 ， 我 和 他 谈 笑 风 生 。
INFO:tensorflow:input_ids: 101 5401 1744 4638 1290 5812 1894 8024 2769 1469 800 6448 5010 7599 4495 511 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
INFO:tensorflow:input_mask: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
INFO:tensorflow:segment_ids: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 [...]<jupyter_text>在线命名实体识别由以上训练得到模型进行在线测试，可以任意输入句子，进行命名实体识别。 若下述程序未执行成功，则表示训练完成后，GPU显存还在占用，需要restart kernel，然后执行下面的%run命令。释放资源具体流程为：菜单 > Kernel > Restart  ![释放资源](./img/释放资源.png)以保证后续步骤资源充足<jupyter_code>%run NER/src/terminal_predict.py<jupyter_output>checkpoint path:./NER/output/checkpoint
going to restore checkpoint
INFO:tensorflow:Restoring parameters from ./NER/output/model.ckpt-978
{1: 'O', 2: 'B-PER', 3: 'I-PER', 4: 'B-ORG', 5: 'I-ORG', 6: 'B-LOC', 7: 'I-LOC', 8: 'X', 9: '[CLS]', 10: '[SEP]'}
输入句子:
“双打最佳搭档”许昕将与陈梦联手出战混双比赛。即将于5月28日至6月2日在深圳进行的国际乒联世界巡回赛中国公开赛上，他们将作为6号种子与头号种子中国香港组合黄镇廷/杜凯琹、2号种子斯洛伐克组合皮斯特耶/巴拉佐娃、8号种子日本组合张本智和/石川佳纯等展开对决。
[['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'B-PER', 'I-PER', 'O', 'O', 'B-PER', 'I-PER', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'B-LOC', 'I-LOC', 'O', 'O', 'O', 'B-ORG', 'I-ORG', 'I-ORG', 'I-ORG', 'O', 'O', 'O', 'O', 'O', 'B-LOC', 'I-LOC', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'B-LOC', 'I-ORG', 'B-LOC', 'I-LOC', 'O', 'O', 'B-PER', 'I-PER', 'I-PER', 'O', 'B-PER', 'I-PER', 'I-PER', 'O', 'O', 'O', 'O', 'O', 'B-LOC', 'I-LOC', 'I-ORG', 'I-LOC', 'O', 'O', 'B-PER', 'I-PER', 'I-PER', 'I-P[...]
{'fraction_non_alphanumeric': 0.12305398663952521, 'fraction_numerical': 0.06092491183806875, 'mean_word_length': 4.120375807398708, 'pattern_counts': {'":': 6, '<': 40, '<?xml version=': 0, '>': 44, 'https://': 9, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 138, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '369428', 'license': 'Apache-2.0', 'n_tokens_mistral': 17736, 'n_tokens_neox': 17186, 'n_words': 2750, 'path': 'notebook/DL_nlp_bert_ner/nlp_bert_ner.ipynb', 'repo_name': 'shankewei/ModelArts-Lab', 'size': '34879'}