diff --git a/.metas/ernie_tiny.png b/.metas/ernie_tiny.png
new file mode 100644
index 0000000000000..580d9381c7523
Binary files /dev/null and b/.metas/ernie_tiny.png differ
diff --git a/README.md b/README.md
index 58b0bd325878f..7a3854bb5f8a3 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,6 @@ English | [简体中文](./README.zh.md)
* [Results](#results)
* [Results on English Datasets](#results-on-english-datasets)
* [Results on Chinese Datasets](#results-on-chinese-datasets)
- * [Release Notes](#release-notes)
* [Communication](#communication)
* [Usage](#usage)
@@ -615,14 +614,6 @@ LCQMC is a Chinese question semantic matching corpus published in COLING2018. [u
BQ Corpus (Bank Question corpus) is a Chinese corpus for sentence semantic equivalence identification. This dataset was published in EMNLP 2018. [url: https://www.aclweb.org/anthology/D18-1536]
```
-## Release Notes
-
-- Aug 21, 2019: featuers update: fp16 finetuning, multiprocess finetining.
-- July 30, 2019: release ERNIE 2.0
-- Apr 10, 2019: update ERNIE_stable-1.0.1.tar.gz, update config and vocab
-- Mar 18, 2019: update ERNIE_stable.tgz
-- Mar 15, 2019: release ERNIE 1.0
-
## Communication
@@ -657,6 +648,7 @@ BQ Corpus (Bank Question corpus) is a Chinese corpus for sentence semantic equiv
* [FAQ3: Is the argument batch_size for one GPU card or for all GPU cards?](#faq3-is-the--argument-batch_size-for-one-gpu-card-or-for-all-gpu-cards)
* [FAQ4: Can not find library: libcudnn.so. Please try to add the lib path to LD_LIBRARY_PATH.](#faq4-can-not-find-library-libcudnnso-please-try-to-add-the-lib-path-to-ld_library_path)
* [FAQ5: Can not find library: libnccl.so. Please try to add the lib path to LD_LIBRARY_PATH.](#faq5-can-not-find-library-libncclso-please-try-to-add-the-lib-path-to-ld_library_path)
+ * [FQA6: Runtime error: `ModuleNotFoundError No module named propeller`](#faq6)
### Install PaddlePaddle
@@ -1009,3 +1001,9 @@ Export the path of cuda to LD_LIBRARY_PATH, e.g.: `export LD_LIBRARY_PATH=/home/
#### FAQ5: Can not find library: libnccl.so. Please try to add the lib path to LD_LIBRARY_PATH.
Download [NCCL2](https://developer.nvidia.com/nccl/nccl-download), and export the library path to LD_LIBRARY_PATH, e.g.:`export LD_LIBRARY_PATH=/home/work/nccl/lib`
+
+### FAQ6: Runtime error: `ModuleNotFoundError No module named propeller`
+
+you can import propeller to your PYTHONPATH by `export PYTHONPATH:./:$PYTHONPATH`
+`
+
diff --git a/README.zh.md b/README.zh.md
index 461042bb69a49..9101f43777621 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -19,7 +19,7 @@
* [效果验证](#效果验证)
* [中文效果验证](#中文效果验证)
* [英文效果验证](#英文效果验证)
- * [开源记录](#开源记录)
+ * [ERNIE tiny](#ernie-tiny)
* [技术交流](#技术交流)
* [使用](#使用)
@@ -589,7 +589,6 @@ ERNIE 2.0 的英文效果验证在 GLUE 上进行。GLUE 评测的官方地址
-
#### GLUE - 验证集结果
| 数据集 | CoLA | SST-2 | MRPC | STS-B | QQP | MNLI-m | QNLI | RTE |
@@ -617,11 +616,34 @@ ERNIE 2.0 的英文效果验证在 GLUE 上进行。GLUE 评测的官方地址
由于 XLNet 暂未公布 GLUE 测试集上的单模型结果,所以我们只与 BERT 进行单模型比较。上表为ERNIE 2.0 单模型在 GLUE 测试集的表现结果。
-## 开源记录
-- 2019-07-30 发布 ERNIE 2.0
-- 2019-04-10 更新: update ERNIE_stable-1.0.1.tar.gz, 将模型参数、配置 ernie_config.json、vocab.txt 打包发布
-- 2019-03-18 更新: update ERNIE_stable.tgz
-- 2019-03-15 发布 ERNIE 1.0
+### ERNIE tiny
+
+为了提升ERNIE模型在实际工业应用中的落地能力,我们推出ERNIE-tiny模型。
+
+![ernie_tiny](.metas/ernie_tiny.png)
+
+ERNIE-tiny作为小型化ERNIE,采用了以下4点技术,保证了在实际真实数据中将近4.3倍的预测提速。
+
+1. 浅:12层的ERNIE Base模型直接压缩为3层,线性提速4倍,但效果也会有较大幅度的下降;
+
+1. 胖:模型变浅带来的损失可通过hidden size的增大来弥补。由于fluid inference框架对于通用矩阵运算(gemm)的最后一维(hidden size)参数的不同取值会有深度的优化,因为将hidden size从768提升至1024并不会带来速度线性的增加;
+
+1. 短:ERNIE Tiny是首个开源的中文subword粒度的预训练模型。这里的短是指通过subword粒度替换字(char)粒度,能够明显地缩短输入文本的长度,而输入文本长度是和预测速度有线性相关。统计表明,在XNLI dev集上采用subword字典切分出来的序列长度比字表平均缩短40%;
+
+1. 萃:为了进一步提升模型的效果,ERNIE Tiny扮演学生角色,利用模型蒸馏的方式在Transformer层和Prediction层去学习教师模型ERNIE模型对应层的分布或输出,这种方式能够缩近ERNIE Tiny和ERNIE的效果差异。
+
+
+#### Benchmark
+
+ERNIE Tiny轻量级模型在公开数据集的效果如下所示,任务均值相对于ERNIE Base只下降了2.37%,但相对于“SOTA Before BERT”提升了8%。在延迟测试中,ERNIE Tiny能够带来4.3倍的速度提升
+(测试环境为:GPU P4,Paddle Inference C++ API,XNLI Dev集,最大maxlen=128,测试结果10次均值)
+
+|model|XNLI(acc)|LCQCM(acc)|CHNSENTICORP(acc)|NLPCC-DBQA(mrr/f1)|Average|Latency
+|--|--|--|--|--|--|--|
+|SOTA-before-ERNIE|68.3|83.4|92.2|72.01/-|78.98|-|
+|ERNIE2.0-base|79.7|87.9|95.5|95.7/85.3|89.70|146ms(4.3x)|
+|ERNIE-tiny-subword|75.1|86.1|95.2|92.9/78.6|87.33|633ms(1x)|
+
## 技术交流
@@ -646,6 +668,7 @@ ERNIE 2.0 的英文效果验证在 GLUE 上进行。GLUE 评测的官方地址
* [序列标注任务](#序列标注任务)
* [实体识别](#实体识别)
* [阅读理解任务](#阅读理解任务-1)
+ * [ERNIE tiny](#tune-ernie-tiny)
* [利用Propeller进行二次开发](#利用propeller进行二次开发)
* [预训练 (ERNIE 1.0)](#预训练-ernie-10)
* [数据预处理](#数据预处理)
@@ -695,6 +718,7 @@ pip install -r requirements.txt
| [ERNIE 1.0 中文 Base 模型(max_len=512)](https://ernie.bj.bcebos.com/ERNIE_1.0_max-len-512.tar.gz) | 包含预训练模型参数、词典 vocab.txt、模型配置 ernie_config.json|
| [ERNIE 2.0 英文 Base 模型](https://ernie.bj.bcebos.com/ERNIE_Base_en_stable-2.0.0.tar.gz) | 包含预训练模型参数、词典 vocab.txt、模型配置 ernie_config.json|
| [ERNIE 2.0 英文 Large 模型](https://ernie.bj.bcebos.com/ERNIE_Large_en_stable-2.0.0.tar.gz) | 包含预训练模型参数、词典 vocab.txt、模型配置 ernie_config.json|
+| [ERNIE tiny 中文模型](https://ernie.bj.bcebos.com/ernie_tiny.tar.gz)|包含预训练模型参数、词典 vocab.txt、模型配置 ernie_config.json 以及切词词表|
@@ -894,6 +918,16 @@ text_a label
[test evaluation] em: 88.061838, f1: 93.520152, avg: 90.790995, question_num: 3493
```
+
+### ERNIE tiny
+
+ERNIE tiny 模型采用了subword粒度输入,需要在数据前处理中加入切词(segmentation)并使用[sentence piece](https://github.com/google/sentencepiece)进行tokenization.
+segmentation 以及 tokenization 需要使用的模型包含在了 ERNIE tiny 的[预训练模型文件](#预训练模型下载)中,分别是 `./subword/dict.wordseg.pickle` 和 `./subword/spm_cased_simp_sampled.model`.
+
+目前`./example/`下的代码针对 ERNIE tiny 的前处理进行了适配只需在脚本中通过 `--sentence_piece_model` 引入tokenization 模型,再通过 `--word_dict` 引入 segmentation 模型之后即可进行 ERNIE tiny 的 Fine-tune。
+对于命名实体识别类型的任务,为了跟输入标注对齐,ERNIE tiny 仍然采用中文单字粒度进行作为输入。因此使用 `./example/finetune_ner.py` 时只需要打开 `--use_sentence_piece_vocab` 即可。
+具体的使用方法可以参考[下节](#利用propeller进行二次开发).
+
## 利用Propeller进行二次开发
[Propeller](./propeller/README.md) 是基于PaddlePaddle构建的一键式训练API,对于具备一定机器学习应用经验的开发者可以使用Propeller获得定制化开发体验。
@@ -1099,6 +1133,6 @@ python -u infer_classifyer.py \
需要先下载 [NCCL](https://developer.nvidia.com/nccl/nccl-download),然后在 LD_LIBRARY_PATH 中添加 NCCL 库的路径,如`export LD_LIBRARY_PATH=/home/work/nccl/lib`
-### FQA6: 运行报错`ModuleNotFoundError: No module named 'propeller'`
+### FAQ6: 运行报错`ModuleNotFoundError: No module named 'propeller'`
您可以通过`export PYTHONPATH=./:$PYTHONPATH`的方式引入Propeller.
diff --git a/ernie/utils/data.py b/ernie/utils/data.py
index 42ff3d816d8e4..8f54826a12ace 100644
--- a/ernie/utils/data.py
+++ b/ernie/utils/data.py
@@ -4,6 +4,7 @@
from propeller import log
import itertools
from propeller.paddle.data import Dataset
+import pickle
import six
@@ -101,7 +102,7 @@ def __call__(self, sen):
class CharTokenizer(object):
- def __init__(self, vocab, lower=True):
+ def __init__(self, vocab, lower=True, sentencepiece_style_vocab=False):
"""
char tokenizer (wordpiece english)
normed txt(space seperated or not) => list of word-piece
@@ -110,6 +111,7 @@ def __init__(self, vocab, lower=True):
#self.pat = re.compile(r'([,.!?\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]|[\u4e00-\u9fa5]|[a-zA-Z0-9]+)')
self.pat = re.compile(r'([a-zA-Z0-9]+|\S)')
self.lower = lower
+ self.sentencepiece_style_vocab = sentencepiece_style_vocab
def __call__(self, sen):
if len(sen) == 0:
@@ -119,11 +121,51 @@ def __call__(self, sen):
sen = sen.lower()
res = []
for match in self.pat.finditer(sen):
- words, _ = wordpiece(match.group(0), vocab=self.vocab, unk_token='[UNK]')
+ words, _ = wordpiece(match.group(0), vocab=self.vocab, unk_token='[UNK]', sentencepiece_style_vocab=self.sentencepiece_style_vocab)
res.extend(words)
return res
+class WSSPTokenizer(object):
+ def __init__(self, sp_model_dir, word_dict, ws=True, lower=True):
+ self.ws = ws
+ self.lower = lower
+ self.dict = pickle.load(open(word_dict, 'rb'), encoding='utf8')
+ import sentencepiece as spm
+ self.sp_model = spm.SentencePieceProcessor()
+ self.window_size = 5
+ self.sp_model.Load(sp_model_dir)
+
+ def cut(self, chars):
+ words = []
+ idx = 0
+ while idx < len(chars):
+ matched = False
+ for i in range(self.window_size, 0, -1):
+ cand = chars[idx: idx+i]
+ if cand in self.dict:
+ words.append(cand)
+ matched = True
+ break
+ if not matched:
+ i = 1
+ words.append(chars[idx])
+ idx += i
+ return words
+
+ def __call__(self, sen):
+ sen = sen.decode('utf8')
+ if self.ws:
+ sen = [s for s in self.cut(sen) if s != ' ']
+ else:
+ sen = sen.split(' ')
+ if self.lower:
+ sen = [s.lower() for s in sen]
+ sen = ' '.join(sen)
+ ret = self.sp_model.EncodeAsPieces(sen)
+ return ret
+
+
def build_2_pair(seg_a, seg_b, max_seqlen, cls_id, sep_id):
token_type_a = np.ones_like(seg_a, dtype=np.int64) * 0
token_type_b = np.ones_like(seg_b, dtype=np.int64) * 1
diff --git a/example/finetune_classifier.py b/example/finetune_classifier.py
index 77a68ad989def..fb65ec6abf4ae 100644
--- a/example/finetune_classifier.py
+++ b/example/finetune_classifier.py
@@ -55,7 +55,7 @@ def forward(self, features):
pos_ids = L.cast(pos_ids, 'int64')
pos_ids.stop_gradient = True
input_mask.stop_gradient = True
- task_ids = L.zeros_like(src_ids) + self.hparam.task_id #this shit wont use at the moment
+ task_ids = L.zeros_like(src_ids) + self.hparam.task_id
task_ids.stop_gradient = True
ernie = ErnieModel(
@@ -128,6 +128,8 @@ def metrics(self, predictions, label):
parser.add_argument('--vocab_file', type=str, required=True)
parser.add_argument('--do_predict', action='store_true')
parser.add_argument('--warm_start_from', type=str)
+ parser.add_argument('--sentence_piece_model', type=str, default=None)
+ parser.add_argument('--word_dict', type=str, default=None)
args = parser.parse_args()
run_config = propeller.parse_runconfig(args)
hparams = propeller.parse_hparam(args)
@@ -138,7 +140,12 @@ def metrics(self, predictions, label):
cls_id = vocab['[CLS]']
unk_id = vocab['[UNK]']
- tokenizer = utils.data.CharTokenizer(vocab.keys())
+ if args.sentence_piece_model is not None:
+ if args.word_dict is None:
+ raise ValueError('--word_dict no specified in subword Model')
+ tokenizer = utils.data.WSSPTokenizer(args.sentence_piece_model, args.word_dict, ws=True, lower=True)
+ else:
+ tokenizer = utils.data.CharTokenizer(vocab.keys())
def tokenizer_func(inputs):
'''avoid pickle error'''
@@ -179,7 +186,7 @@ def after(sentence, segments, label):
dev_ds.data_shapes = shapes
dev_ds.data_types = types
- varname_to_warmstart = re.compile('encoder.*|pooled.*|.*embedding|pre_encoder_.*')
+ varname_to_warmstart = re.compile(r'^encoder.*[wb]_0$|^.*embedding$|^.*bias$|^.*scale$|^pooled_fc.[wb]_0$')
warm_start_dir = args.warm_start_from
ws = propeller.WarmStartSetting(
predicate_fn=lambda v: varname_to_warmstart.match(v.name) and os.path.exists(os.path.join(warm_start_dir, v.name)),
diff --git a/example/finetune_ner.py b/example/finetune_ner.py
index 89a9e22ffc16d..954f2a7b44de7 100644
--- a/example/finetune_ner.py
+++ b/example/finetune_ner.py
@@ -32,7 +32,6 @@
from model.ernie import ErnieModel
from optimization import optimization
-import tokenization
import utils.data
from propeller import log
@@ -121,7 +120,7 @@ def metrics(self, predictions, label):
def make_sequence_label_dataset(name, input_files, label_list, tokenizer, batch_size, max_seqlen, is_train):
label_map = {v: i for i, v in enumerate(label_list)}
no_entity_id = label_map['O']
- delimiter = ''
+ delimiter = b''
def read_bio_data(filename):
ds = propeller.data.Dataset.from_file(filename)
@@ -132,10 +131,10 @@ def gen():
while 1:
line = next(iterator)
cols = line.rstrip(b'\n').split(b'\t')
+ tokens = cols[0].split(delimiter)
+ labels = cols[1].split(delimiter)
if len(cols) != 2:
continue
- tokens = tokenization.convert_to_unicode(cols[0]).split(delimiter)
- labels = tokenization.convert_to_unicode(cols[1]).split(delimiter)
if len(tokens) != len(labels) or len(tokens) == 0:
continue
yield [tokens, labels]
@@ -151,7 +150,8 @@ def gen():
ret_tokens = []
ret_labels = []
for token, label in zip(tokens, labels):
- sub_token = tokenizer.tokenize(token)
+ sub_token = tokenizer(token)
+ label = label.decode('utf8')
if len(sub_token) == 0:
continue
ret_tokens.extend(sub_token)
@@ -179,7 +179,7 @@ def gen():
labels = labels[: max_seqlen - 2]
tokens = ['[CLS]'] + tokens + ['[SEP]']
- token_ids = tokenizer.convert_tokens_to_ids(tokens)
+ token_ids = [vocab[t] for t in tokens]
label_ids = [no_entity_id] + [label_map[x] for x in labels] + [no_entity_id]
token_type_ids = [0] * len(token_ids)
input_seqlen = len(token_ids)
@@ -211,7 +211,7 @@ def after(*features):
def make_sequence_label_dataset_from_stdin(name, tokenizer, batch_size, max_seqlen):
- delimiter = ''
+ delimiter = b''
def stdin_gen():
if six.PY3:
@@ -232,9 +232,9 @@ def gen():
while 1:
line, = next(iterator)
cols = line.rstrip(b'\n').split(b'\t')
+ tokens = cols[0].split(delimiter)
if len(cols) != 1:
continue
- tokens = tokenization.convert_to_unicode(cols[0]).split(delimiter)
if len(tokens) == 0:
continue
yield tokens,
@@ -247,7 +247,7 @@ def gen():
tokens, = next(iterator)
ret_tokens = []
for token in tokens:
- sub_token = tokenizer.tokenize(token)
+ sub_token = tokenizer(token)
if len(sub_token) == 0:
continue
ret_tokens.extend(sub_token)
@@ -266,7 +266,7 @@ def gen():
tokens = tokens[: max_seqlen - 2]
tokens = ['[CLS]'] + tokens + ['[SEP]']
- token_ids = tokenizer.convert_tokens_to_ids(tokens)
+ token_ids = [vocab[t] for t in tokens]
token_type_ids = [0] * len(token_ids)
input_seqlen = len(token_ids)
@@ -296,13 +296,15 @@ def after(*features):
parser.add_argument('--data_dir', type=str, required=True)
parser.add_argument('--vocab_file', type=str, required=True)
parser.add_argument('--do_predict', action='store_true')
+ parser.add_argument('--use_sentence_piece_vocab', action='store_true')
parser.add_argument('--warm_start_from', type=str)
args = parser.parse_args()
run_config = propeller.parse_runconfig(args)
hparams = propeller.parse_hparam(args)
- tokenizer = tokenization.FullTokenizer(args.vocab_file)
- vocab = tokenizer.vocab
+
+ vocab = {j.strip().split('\t')[0]: i for i, j in enumerate(open(args.vocab_file, 'r', encoding='utf8'))}
+ tokenizer = utils.data.CharTokenizer(vocab, sentencepiece_style_vocab=args.use_sentence_piece_vocab)
sep_id = vocab['[SEP]']
cls_id = vocab['[CLS]']
unk_id = vocab['[UNK]']
@@ -358,7 +360,7 @@ def after(*features):
from_dir=warm_start_dir
)
- best_exporter = propeller.train.exporter.BestExporter(os.path.join(run_config.model_dir, 'best'), cmp_fn=lambda old, new: new['dev']['f1'] > old['dev']['f1'])
+ best_exporter = propeller.train.exporter.BestInferenceModelExporter(os.path.join(run_config.model_dir, 'best'), cmp_fn=lambda old, new: new['dev']['f1'] > old['dev']['f1'])
propeller.train.train_and_eval(
model_class_or_model_fn=SequenceLabelErnieModel,
params=hparams,
@@ -387,7 +389,6 @@ def after(*features):
predict_ds.data_types = types
rev_label_map = {i: v for i, v in enumerate(label_list)}
- best_exporter = propeller.train.exporter.BestExporter(os.path.join(run_config.model_dir, 'best'), cmp_fn=lambda old, new: new['dev']['f1'] > old['dev']['f1'])
learner = propeller.Learner(SequenceLabelErnieModel, run_config, hparams)
for pred, _ in learner.predict(predict_ds, ckpt=-1):
pred_str = ' '.join([rev_label_map[idx] for idx in np.argmax(pred, 1).tolist()])
diff --git a/example/finetune_ranker.py b/example/finetune_ranker.py
index db40b26a56e15..bb0661ece976c 100644
--- a/example/finetune_ranker.py
+++ b/example/finetune_ranker.py
@@ -146,6 +146,7 @@ def backward(self, loss):
parser.add_argument('--data_dir', type=str, required=True)
parser.add_argument('--warm_start_from', type=str)
parser.add_argument('--sentence_piece_model', type=str, default=None)
+ parser.add_argument('--word_dict', type=str, default=None)
args = parser.parse_args()
run_config = propeller.parse_runconfig(args)
hparams = propeller.parse_hparam(args)
@@ -157,7 +158,9 @@ def backward(self, loss):
unk_id = vocab['[UNK]']
if args.sentence_piece_model is not None:
- tokenizer = utils.data.JBSPTokenizer(args.sentence_piece_model, jb=True, lower=True)
+ if args.word_dict is None:
+ raise ValueError('--word_dict no specified in subword Model')
+ tokenizer = utils.data.WSSPTokenizer(args.sentence_piece_model, args.word_dict, ws=True, lower=True)
else:
tokenizer = utils.data.CharTokenizer(vocab.keys())
@@ -218,7 +221,7 @@ def after(sentence, segments, qid, label):
from_dir=warm_start_dir
)
- best_exporter = propeller.train.exporter.BestExporter(os.path.join(run_config.model_dir, 'best'), cmp_fn=lambda old, new: new['dev']['f1'] > old['dev']['f1'])
+ best_exporter = propeller.train.exporter.BestInferenceModelExporter(os.path.join(run_config.model_dir, 'best'), cmp_fn=lambda old, new: new['dev']['f1'] > old['dev']['f1'])
propeller.train_and_eval(
model_class_or_model_fn=RankingErnieModel,
params=hparams,
@@ -258,6 +261,7 @@ def after(sentence, segments, qid):
est = propeller.Learner(RankingErnieModel, run_config, hparams)
for qid, res in est.predict(predict_ds, ckpt=-1):
print('%d\t%d\t%.5f\t%.5f' % (qid[0], np.argmax(res), res[0], res[1]))
+
#for i in predict_ds:
# sen = i[0]
# for ss in np.squeeze(sen):
diff --git a/requirements.txt b/requirements.txt
index 84aaf34f16047..2e08a150940fd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,5 @@ scikit-learn==0.20.3
scipy==1.2.1
six==1.11.0
sklearn==0.0
+sentencepiece==0.1.8
+paddlepaddle-gpu==1.5.2.post107