diff --git a/.gitignore b/.gitignore index aaa0630fc9b187a1a4760bce1e15b0302764eabf..336502e288e2e76551751ed44a2677716efea7d6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ *.json output* *checkpoint* +build +dist +hapi.egg-info diff --git a/examples/bert/bert.yaml b/examples/bert/bert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5b21a6c635903cb8506d70e23c9294cbd7771d9e --- /dev/null +++ b/examples/bert/bert.yaml @@ -0,0 +1,27 @@ +bert_config_path: "./config/bert_config.json" +init_checkpoint: None +init_pretraining_params: None +checkpoints: "./saved_model" +epoch: 3 +learning_rate: 0.0001 +lr_scheduler: "linear_warmup_decay" +weight_decay: 0.01 +warmup_proportion: 0.1 +save_steps: 100000 +validation_steps: 100000 +loss_scaling: 1.0 +skip_steps: 100 +data_dir: None +vocab_path: None +max_seq_len: 512 +batch_size: 32 +in_tokens: False +do_lower_case: True +random_seed: 5512 +use_cuda: True +shuffle: True +do_train: True +do_test: True +use_data_parallel: False +verbose: False + diff --git a/examples/bert/bert_classifier.py b/examples/bert/bert_classifier.py new file mode 100644 index 0000000000000000000000000000000000000000..ef43ae2076e665a88fd896dd7e6f830c9e38640c --- /dev/null +++ b/examples/bert/bert_classifier.py @@ -0,0 +1,173 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""BERT fine-tuning in Paddle Dygraph Mode.""" + +import paddle.fluid as fluid +from hapi.metrics import Accuracy +from hapi.configure import Config +from hapi.text.bert import BertEncoder +from paddle.fluid.dygraph import Linear, Layer +from hapi.model import set_device, Model, SoftmaxWithCrossEntropy, Input +import hapi.text.tokenizer.tokenization as tokenization +from hapi.text.bert import Optimizer, BertConfig, BertDataLoader, BertInputExample + + +class ClsModelLayer(Model): + """ + classify model + """ + + def __init__(self, + args, + config, + num_labels, + return_pooled_out=True, + use_fp16=False): + super(ClsModelLayer, self).__init__() + self.config = config + self.use_fp16 = use_fp16 + self.loss_scaling = args.loss_scaling + + self.bert_layer = BertEncoder( + config=self.config, return_pooled_out=True, use_fp16=self.use_fp16) + + self.cls_fc = Linear( + input_dim=self.config["hidden_size"], + output_dim=num_labels, + param_attr=fluid.ParamAttr( + name="cls_out_w", + initializer=fluid.initializer.TruncatedNormal(scale=0.02)), + bias_attr=fluid.ParamAttr( + name="cls_out_b", initializer=fluid.initializer.Constant(0.))) + + def forward(self, src_ids, position_ids, sentence_ids, input_mask): + """ + forward + """ + + enc_output, next_sent_feat = self.bert_layer(src_ids, position_ids, + sentence_ids, input_mask) + + cls_feats = fluid.layers.dropout( + x=next_sent_feat, + dropout_prob=0.1, + dropout_implementation="upscale_in_train") + + pred = self.cls_fc(cls_feats) + + return pred + + +def main(): + + config = Config(yaml_file="./bert.yaml") + config.build() + config.Print() + + device = set_device("gpu" if config.use_cuda else "cpu") + fluid.enable_dygraph(device) + + bert_config = BertConfig(config.bert_config_path) + bert_config.print_config() + + tokenizer = tokenization.FullTokenizer( + vocab_file=config.vocab_path, do_lower_case=config.do_lower_case) + + def mnli_line_processor(line_id, line): + if line_id == "0": + return None + uid = tokenization.convert_to_unicode(line[0]) + text_a = tokenization.convert_to_unicode(line[8]) + text_b = tokenization.convert_to_unicode(line[9]) + label = tokenization.convert_to_unicode(line[-1]) + if label not in ["contradiction", "entailment", "neutral"]: + label = "contradiction" + return BertInputExample( + uid=uid, text_a=text_a, text_b=text_b, label=label) + + train_dataloader = BertDataLoader( + "./data/glue_data/MNLI/train.tsv", + tokenizer, ["contradiction", "entailment", "neutral"], + max_seq_length=config.max_seq_len, + batch_size=config.batch_size, + line_processor=mnli_line_processor) + + test_dataloader = BertDataLoader( + "./data/glue_data/MNLI/dev_matched.tsv", + tokenizer, ["contradiction", "entailment", "neutral"], + max_seq_length=config.max_seq_len, + batch_size=config.batch_size, + line_processor=mnli_line_processor, + shuffle=False, + phase="predict") + + trainer_count = fluid.dygraph.parallel.Env().nranks + num_train_examples = len(train_dataloader.dataset) + max_train_steps = config.epoch * num_train_examples // config.batch_size // trainer_count + warmup_steps = int(max_train_steps * config.warmup_proportion) + + print("Trainer count: %d" % trainer_count) + print("Num train examples: %d" % num_train_examples) + print("Max train steps: %d" % max_train_steps) + print("Num warmup steps: %d" % warmup_steps) + + inputs = [ + Input( + [None, None], 'int64', name='src_ids'), Input( + [None, None], 'int64', name='pos_ids'), Input( + [None, None], 'int64', name='sent_ids'), Input( + [None, None], 'float32', name='input_mask') + ] + + labels = [Input([None, 1], 'int64', name='label')] + + cls_model = ClsModelLayer( + config, + bert_config, + len(["contradiction", "entailment", "neutral"]), + return_pooled_out=True) + + optimizer = Optimizer( + warmup_steps=warmup_steps, + num_train_steps=max_train_steps, + learning_rate=config.learning_rate, + model_cls=cls_model, + weight_decay=config.weight_decay, + scheduler=config.lr_scheduler, + loss_scaling=config.loss_scaling, + parameter_list=cls_model.parameters()) + + cls_model.prepare( + optimizer, + SoftmaxWithCrossEntropy(), + Accuracy(topk=(1, 2)), + inputs, + labels, + device=device) + + cls_model.bert_layer.init_parameters( + config.init_pretraining_params, verbose=config.verbose) + + # do train + cls_model.fit(train_data=train_dataloader.dataloader, + epochs=config.epoch, + save_dir=config.checkpoints) + + # do eval + cls_model.evaluate( + eval_data=test_dataloader.dataloader, batch_size=config.batch_size) + + +if __name__ == '__main__': + main() diff --git a/examples/bert/run_classifier_single_gpu.sh b/examples/bert/run_classifier_single_gpu.sh new file mode 100755 index 0000000000000000000000000000000000000000..5b52aafd0a63dfb250c7ab7dcefc09b60f406ac2 --- /dev/null +++ b/examples/bert/run_classifier_single_gpu.sh @@ -0,0 +1,29 @@ +#!/bin/bash +BERT_BASE_PATH="./data/pretrained_models/uncased_L-12_H-768_A-12/" +TASK_NAME='MNLI' +DATA_PATH="./data/glue_data/MNLI/" +CKPT_PATH="./data/saved_model/mnli_models" + +export CUDA_VISIBLE_DEVICES=0 + +# start fine-tuning +python3.7 bert_classifier.py\ + --use_cuda true \ + --do_train true \ + --do_test true \ + --batch_size 64 \ + --init_pretraining_params ${BERT_BASE_PATH}/dygraph_params/ \ + --data_dir ${DATA_PATH} \ + --vocab_path ${BERT_BASE_PATH}/vocab.txt \ + --checkpoints ${CKPT_PATH} \ + --save_steps 1000 \ + --weight_decay 0.01 \ + --warmup_proportion 0.1 \ + --validation_steps 100 \ + --epoch 3 \ + --max_seq_len 128 \ + --bert_config_path ${BERT_BASE_PATH}/bert_config.json \ + --learning_rate 5e-5 \ + --skip_steps 10 \ + --shuffle true + diff --git a/examples/bert_leveldb/bert.yaml b/examples/bert_leveldb/bert.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5b21a6c635903cb8506d70e23c9294cbd7771d9e --- /dev/null +++ b/examples/bert_leveldb/bert.yaml @@ -0,0 +1,27 @@ +bert_config_path: "./config/bert_config.json" +init_checkpoint: None +init_pretraining_params: None +checkpoints: "./saved_model" +epoch: 3 +learning_rate: 0.0001 +lr_scheduler: "linear_warmup_decay" +weight_decay: 0.01 +warmup_proportion: 0.1 +save_steps: 100000 +validation_steps: 100000 +loss_scaling: 1.0 +skip_steps: 100 +data_dir: None +vocab_path: None +max_seq_len: 512 +batch_size: 32 +in_tokens: False +do_lower_case: True +random_seed: 5512 +use_cuda: True +shuffle: True +do_train: True +do_test: True +use_data_parallel: False +verbose: False + diff --git a/examples/bert_leveldb/bert_classifier.py b/examples/bert_leveldb/bert_classifier.py new file mode 100644 index 0000000000000000000000000000000000000000..11bc85758ebbe81ae68b3c141d4582ee8d41508c --- /dev/null +++ b/examples/bert_leveldb/bert_classifier.py @@ -0,0 +1,175 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""BERT fine-tuning in Paddle Dygraph Mode.""" + +import paddle.fluid as fluid +from hapi.metrics import Accuracy +from hapi.configure import Config +from hapi.text.bert import BertEncoder +from paddle.fluid.dygraph import Linear, Layer +from hapi.model import set_device, Model, SoftmaxWithCrossEntropy, Input +import hapi.text.tokenizer.tokenization as tokenization +from hapi.text.bert import Optimizer, BertConfig, BertDataLoader, BertInputExample + + +class ClsModelLayer(Model): + """ + classify model + """ + + def __init__(self, + args, + config, + num_labels, + return_pooled_out=True, + use_fp16=False): + super(ClsModelLayer, self).__init__() + self.config = config + self.use_fp16 = use_fp16 + self.loss_scaling = args.loss_scaling + + self.bert_layer = BertEncoder( + config=self.config, return_pooled_out=True, use_fp16=self.use_fp16) + + self.cls_fc = Linear( + input_dim=self.config["hidden_size"], + output_dim=num_labels, + param_attr=fluid.ParamAttr( + name="cls_out_w", + initializer=fluid.initializer.TruncatedNormal(scale=0.02)), + bias_attr=fluid.ParamAttr( + name="cls_out_b", initializer=fluid.initializer.Constant(0.))) + + def forward(self, src_ids, position_ids, sentence_ids, input_mask): + """ + forward + """ + + enc_output, next_sent_feat = self.bert_layer(src_ids, position_ids, + sentence_ids, input_mask) + + cls_feats = fluid.layers.dropout( + x=next_sent_feat, + dropout_prob=0.1, + dropout_implementation="upscale_in_train") + + pred = self.cls_fc(cls_feats) + + return pred + + +def main(): + + config = Config(yaml_file="./bert.yaml") + config.build() + config.Print() + + device = set_device("gpu" if config.use_cuda else "cpu") + fluid.enable_dygraph(device) + + bert_config = BertConfig(config.bert_config_path) + bert_config.print_config() + + tokenizer = tokenization.FullTokenizer( + vocab_file=config.vocab_path, do_lower_case=config.do_lower_case) + + def mnli_line_processor(line_id, line): + if line_id == "0": + return None + uid = tokenization.convert_to_unicode(line[0]) + text_a = tokenization.convert_to_unicode(line[8]) + text_b = tokenization.convert_to_unicode(line[9]) + label = tokenization.convert_to_unicode(line[-1]) + if label not in ["contradiction", "entailment", "neutral"]: + label = "contradiction" + return BertInputExample( + uid=uid, text_a=text_a, text_b=text_b, label=label) + + train_dataloader = BertDataLoader( + "./data/glue_data/MNLI/train.tsv", + tokenizer, ["contradiction", "entailment", "neutral"], + max_seq_length=config.max_seq_len, + batch_size=config.batch_size, + line_processor=mnli_line_processor, + mode="leveldb", + phase="train") + + test_dataloader = BertDataLoader( + "./data/glue_data/MNLI/dev_matched.tsv", + tokenizer, ["contradiction", "entailment", "neutral"], + max_seq_length=config.max_seq_len, + batch_size=config.batch_size, + line_processor=mnli_line_processor, + shuffle=False, + phase="predict") + + trainer_count = fluid.dygraph.parallel.Env().nranks + num_train_examples = len(train_dataloader.dataset) + max_train_steps = config.epoch * num_train_examples // config.batch_size // trainer_count + warmup_steps = int(max_train_steps * config.warmup_proportion) + + print("Trainer count: %d" % trainer_count) + print("Num train examples: %d" % num_train_examples) + print("Max train steps: %d" % max_train_steps) + print("Num warmup steps: %d" % warmup_steps) + + inputs = [ + Input( + [None, None], 'int64', name='src_ids'), Input( + [None, None], 'int64', name='pos_ids'), Input( + [None, None], 'int64', name='sent_ids'), Input( + [None, None], 'float32', name='input_mask') + ] + + labels = [Input([None, 1], 'int64', name='label')] + + cls_model = ClsModelLayer( + config, + bert_config, + len(["contradiction", "entailment", "neutral"]), + return_pooled_out=True) + + optimizer = Optimizer( + warmup_steps=warmup_steps, + num_train_steps=max_train_steps, + learning_rate=config.learning_rate, + model_cls=cls_model, + weight_decay=config.weight_decay, + scheduler=config.lr_scheduler, + loss_scaling=config.loss_scaling, + parameter_list=cls_model.parameters()) + + cls_model.prepare( + optimizer, + SoftmaxWithCrossEntropy(), + Accuracy(topk=(1, 2)), + inputs, + labels, + device=device) + + cls_model.bert_layer.init_parameters( + config.init_pretraining_params, verbose=config.verbose) + + # do train + cls_model.fit(train_data=train_dataloader.dataloader, + epochs=config.epoch, + save_dir=config.checkpoints) + + # do eval + cls_model.evaluate( + eval_data=test_dataloader.dataloader, batch_size=config.batch_size) + + +if __name__ == '__main__': + main() diff --git a/examples/bert_leveldb/run_classifier_multi_gpu.sh b/examples/bert_leveldb/run_classifier_multi_gpu.sh new file mode 100755 index 0000000000000000000000000000000000000000..1b7d6aea60c385e32bafbcfd35ae420f1e5824a6 --- /dev/null +++ b/examples/bert_leveldb/run_classifier_multi_gpu.sh @@ -0,0 +1,27 @@ +#!/bin/bash +BERT_BASE_PATH="./data/pretrained_models/uncased_L-12_H-768_A-12/" +TASK_NAME='MNLI' +DATA_PATH="./data/glue_data/MNLI/" +CKPT_PATH="./data/saved_model/mnli_models" + +# start fine-tuning +python3.7 -m paddle.distributed.launch --started_port 8899 --selected_gpus=0,1,2,3 bert_classifier.py\ + --use_cuda true \ + --do_train true \ + --do_test true \ + --batch_size 64 \ + --init_pretraining_params ${BERT_BASE_PATH}/dygraph_params/ \ + --data_dir ${DATA_PATH} \ + --vocab_path ${BERT_BASE_PATH}/vocab.txt \ + --checkpoints ${CKPT_PATH} \ + --save_steps 1000 \ + --weight_decay 0.01 \ + --warmup_proportion 0.1 \ + --validation_steps 100 \ + --epoch 3 \ + --max_seq_len 128 \ + --bert_config_path ${BERT_BASE_PATH}/bert_config.json \ + --learning_rate 5e-5 \ + --skip_steps 10 \ + --shuffle true + diff --git a/examples/bert_leveldb/run_classifier_single_gpu.sh b/examples/bert_leveldb/run_classifier_single_gpu.sh new file mode 100755 index 0000000000000000000000000000000000000000..5b52aafd0a63dfb250c7ab7dcefc09b60f406ac2 --- /dev/null +++ b/examples/bert_leveldb/run_classifier_single_gpu.sh @@ -0,0 +1,29 @@ +#!/bin/bash +BERT_BASE_PATH="./data/pretrained_models/uncased_L-12_H-768_A-12/" +TASK_NAME='MNLI' +DATA_PATH="./data/glue_data/MNLI/" +CKPT_PATH="./data/saved_model/mnli_models" + +export CUDA_VISIBLE_DEVICES=0 + +# start fine-tuning +python3.7 bert_classifier.py\ + --use_cuda true \ + --do_train true \ + --do_test true \ + --batch_size 64 \ + --init_pretraining_params ${BERT_BASE_PATH}/dygraph_params/ \ + --data_dir ${DATA_PATH} \ + --vocab_path ${BERT_BASE_PATH}/vocab.txt \ + --checkpoints ${CKPT_PATH} \ + --save_steps 1000 \ + --weight_decay 0.01 \ + --warmup_proportion 0.1 \ + --validation_steps 100 \ + --epoch 3 \ + --max_seq_len 128 \ + --bert_config_path ${BERT_BASE_PATH}/bert_config.json \ + --learning_rate 5e-5 \ + --skip_steps 10 \ + --shuffle true + diff --git a/bmn/BMN.png b/examples/bmn/BMN.png similarity index 100% rename from bmn/BMN.png rename to examples/bmn/BMN.png diff --git a/bmn/README.md b/examples/bmn/README.md similarity index 66% rename from bmn/README.md rename to examples/bmn/README.md index dbf593512c07da9645418b92b2a6819c9be13fa5..4ff05519010cf002ba6fc1bf145113bde7aa239a 100644 --- a/bmn/README.md +++ b/examples/bmn/README.md @@ -29,7 +29,6 @@ BMN Overview ├── train.py # 训练代码,训练网络 ├── eval.py # 评估代码,评估网络性能 ├── predict.py # 预测代码,针对任意输入预测结果 -├── bmn_model.py # 网络结构与损失函数定义 ├── bmn_metric.py # 精度评估方法定义 ├── reader.py # 数据reader,构造Dataset和Dataloader ├── bmn_utils.py # 模型细节相关代码 @@ -41,7 +40,7 @@ BMN Overview ## 数据准备 -BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理好的视频特征,请下载[bmn\_feat](https://paddlemodels.bj.bcebos.com/video_detection/bmn_feat.tar.gz)数据后解压,同时相应的修改bmn.yaml中的特征路径feat\_path。对应的标签文件请下载[label](https://paddlemodels.bj.bcebos.com/video_detection/activitynet_1.3_annotations.json)并修改bmn.yaml中的标签文件路径anno\_file。 +BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理好的视频特征和对应的标签文件,请下载特征数据[bmn\_feat](https://paddlemodels.bj.bcebos.com/video_detection/bmn_feat.tar.gz)和标签数据[label](https://paddlemodels.bj.bcebos.com/video_detection/activitynet_1.3_annotations.json),并相应地修改配置文件bmn.yaml中的特征文件路径feat\_path和标签文件路径anno\_file。 ## 模型训练 @@ -52,22 +51,17 @@ BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理 bash run.sh -若使用单卡训练,启动方式如下: +若使用单卡训练,请将配置文件bmn.yaml中的batch\_size调整为16,启动方式如下: - export CUDA_VISIBLE_DEVICES=0 python train.py -- 代码运行需要先安装pandas - -- 从头开始训练,使用上述启动命令行或者脚本程序即可启动训练,不需要用到预训练模型 +默认使用静态图训练,若使用动态图训练只需要在运行脚本添加`-d`参数即可,如: -- 单卡训练时,请将配置文件中的batch_size调整为16 + python train.py -d -**训练策略:** +- 代码运行需要先安装pandas -* 采用Adam优化器,初始learning\_rate=0.001 -* 权重衰减系数为1e-4 -* 学习率在迭代次数达到4200的时候做一次衰减,衰减系数为0.1 +- 从头开始训练,使用上述启动命令行或者脚本程序即可启动训练,不需要用到预训练模型 ## 模型评估 @@ -76,9 +70,9 @@ BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理 python eval.py --weights=$PATH_TO_WEIGHTS -- 进行评估时,可修改命令行中的`weights`参数指定需要评估的权重,如果不设置,将使用默认参数文件checkpoint/final.pdparams。 +- 进行评估时,可修改命令行中的`weights`参数指定需要评估的权重,若未指定,脚本会下载已发布的模型[model](https://paddlemodels.bj.bcebos.com/hapi/bmn.pdparams)进行评估。 -- 上述程序会将运行结果保存在output/EVAL/BMN\_results文件夹下,测试结果保存在evaluate\_results/bmn\_results\_validation.json文件中。 +- 上述程序会将运行结果保存在`--output_path`参数指定的文件夹下,默认为output/EVAL/BMN\_results;测试结果保存在`--result_path`参数指定的文件夹下,默认为evaluate\_results。 - 注:评估时可能会出现loss为nan的情况。这是由于评估时用的是单个样本,可能存在没有iou>0.6的样本,所以为nan,对最终的评估结果没有影响。 @@ -87,9 +81,9 @@ BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理 - ActivityNet数据集的具体使用说明可以参考其[官方网站](http://activity-net.org) -- 下载指标评估代码,请从[ActivityNet Gitub repository](https://github.com/activitynet/ActivityNet.git)下载,将Evaluation文件夹拷贝至models/dygraph/bmn目录下。(注:由于第三方评估代码不支持python3,此处建议使用python2进行评估;若使用python3,print函数需要添加括号,请对Evaluation目录下的.py文件做相应修改。) +- 下载指标评估代码,请从[ActivityNet Gitub repository](https://github.com/activitynet/ActivityNet.git)下载,将Evaluation文件夹拷贝至hapi/examples/bmn目录下。(注:由于第三方评估代码不支持python3,此处建议使用python2进行评估;若使用python3,print函数需要添加括号,请对Evaluation目录下的.py文件做相应修改。) -- 请下载[activity\_net\_1\_3\_new.json](https://paddlemodels.bj.bcebos.com/video_detection/activity_net_1_3_new.json)文件,并将其放置在models/dygraph/bmn/Evaluation/data目录下,相较于原始的activity\_net.v1-3.min.json文件,我们过滤了其中一些失效的视频条目。 +- 请下载[activity\_net\_1\_3\_new.json](https://paddlemodels.bj.bcebos.com/video_detection/activity_net_1_3_new.json)文件,并将其放置在hapi/examples/bmn/Evaluation/data目录下,相较于原始的activity\_net.v1-3.min.json文件,我们过滤了其中一些失效的视频条目。 - 计算精度指标 @@ -100,7 +94,7 @@ BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理 | AR@1 | AR@5 | AR@10 | AR@100 | AUC | | :---: | :---: | :---: | :---: | :---: | -| 33.46 | 49.25 | 56.25 | 75.40 | 67.16% | +| 33.10 | 49.18 | 56.54 | 75.12 | 67.16% | ## 模型推断 @@ -110,9 +104,9 @@ BMN的训练数据采用ActivityNet1.3提供的数据集,我们提供了处理 python predict.py --weights=$PATH_TO_WEIGHTS \ --filelist=$FILELIST -- 使用python命令行启动程序时,`--filelist`参数指定待推断的文件列表,如果不设置,默认为./infer.list。`--weights`参数为训练好的权重参数,如果不设置,将使用默认参数文件checkpoint/final.pdparams。 +- 使用python命令行启动程序时,`--filelist`参数指定待推断的文件列表,如果不设置,默认为./infer.list。`--weights`参数为训练好的权重参数,若未指定,脚本会下载已发布的模型[model](https://paddlemodels.bj.bcebos.com/hapi/bmn.pdparams)进行预测。 -- 上述程序会将运行结果保存在output/INFER/BMN\_results文件夹下,测试结果保存在predict\_results/bmn\_results\_test.json文件中。 +- 上述程序会将运行结果保存在`--output_path`参数指定的文件夹下,默认为output/INFER/BMN\_results;测试结果保存在`--result_path`参数指定的文件夹下,默认为predict\_results。 ## 参考论文 diff --git a/bmn/bmn.yaml b/examples/bmn/bmn.yaml similarity index 81% rename from bmn/bmn.yaml rename to examples/bmn/bmn.yaml index da50ea4f7c654d40fbf2498863cb7e87664fe55a..7964f92161f8a5f55289e21a465494c32e99fcbf 100644 --- a/bmn/bmn.yaml +++ b/examples/bmn/bmn.yaml @@ -12,11 +12,10 @@ MODEL: TRAIN: subset: "train" epoch: 9 - batch_size: 4 + batch_size: 4 num_workers: 4 use_shuffle: True device: "gpu" - num_gpus: 4 learning_rate: 0.001 learning_rate_decay: 0.1 lr_decay_iter: 4200 @@ -29,10 +28,6 @@ TEST: subset: "validation" batch_size: 1 num_workers: 1 - use_buffer: False - snms_alpha: 0.001 - snms_t1: 0.5 - snms_t2: 0.9 output_path: "output/EVAL/BMN_results" result_path: "evaluate_results" @@ -40,10 +35,6 @@ INFER: subset: "test" batch_size: 1 num_workers: 1 - use_buffer: False - snms_alpha: 0.4 - snms_t1: 0.5 - snms_t2: 0.9 filelist: './infer.list' output_path: "output/INFER/BMN_results" result_path: "predict_results" diff --git a/bmn/bmn_metric.py b/examples/bmn/bmn_metric.py similarity index 82% rename from bmn/bmn_metric.py rename to examples/bmn/bmn_metric.py index a19f87c6b42b8737ddeb52c3a330f59dcc932004..cbcad9a1e15d5356127c748194ac907bcfda5967 100644 --- a/bmn/bmn_metric.py +++ b/examples/bmn/bmn_metric.py @@ -20,7 +20,7 @@ import json sys.path.append('../') -from metrics import Metric +from hapi.metrics import Metric from bmn_utils import boundary_choose, bmn_post_processing @@ -36,13 +36,26 @@ class BmnMetric(Metric): #get video_dict and video_list if self.mode == 'test': self.get_test_dataset_dict() + if not os.path.isdir(self.cfg.TEST.output_path): + os.makedirs(self.cfg.TEST.output_path) + if not os.path.isdir(self.cfg.TEST.result_path): + os.makedirs(self.cfg.TEST.result_path) elif self.mode == 'infer': self.get_infer_dataset_dict() + if not os.path.isdir(self.cfg.INFER.output_path): + os.makedirs(self.cfg.INFER.output_path) + if not os.path.isdir(self.cfg.INFER.result_path): + os.makedirs(self.cfg.INFER.result_path) - def add_metric_op(self, preds, label): - pred_bm, pred_start, pred_en = preds - video_index = label[-1] - return [pred_bm, pred_start, pred_en, video_index] #return list + def add_metric_op(self, *args): + if self.mode == 'test': + # only extract pred_bm, pred_start, pred_en from outputs + # and video_index from label here + pred_bm, pred_start, pred_en, _, _, _, video_index = args + else: + # in infer mode, labels only contains video_index + pred_bm, pred_start, pred_en, video_index = args + return pred_bm, pred_start, pred_en, video_index def update(self, pred_bm, pred_start, pred_end, fid): # generate proposals diff --git a/bmn/bmn_utils.py b/examples/bmn/bmn_utils.py similarity index 69% rename from bmn/bmn_utils.py rename to examples/bmn/bmn_utils.py index 06812e636fdaf6ccc419ca58151402ab50082112..cccf50647a55fabdfe94dd0f1f7e1370e15d0fe2 100644 --- a/bmn/bmn_utils.py +++ b/examples/bmn/bmn_utils.py @@ -162,56 +162,3 @@ def bmn_post_processing(video_dict, subset, output_path, result_path): outfile.close() -def _get_interp1d_bin_mask(seg_xmin, seg_xmax, tscale, num_sample, - num_sample_perbin): - """ generate sample mask for a boundary-matching pair """ - plen = float(seg_xmax - seg_xmin) - plen_sample = plen / (num_sample * num_sample_perbin - 1.0) - total_samples = [ - seg_xmin + plen_sample * ii - for ii in range(num_sample * num_sample_perbin) - ] - p_mask = [] - for idx in range(num_sample): - bin_samples = total_samples[idx * num_sample_perbin:(idx + 1) * - num_sample_perbin] - bin_vector = np.zeros([tscale]) - for sample in bin_samples: - sample_upper = math.ceil(sample) - sample_decimal, sample_down = math.modf(sample) - if int(sample_down) <= (tscale - 1) and int(sample_down) >= 0: - bin_vector[int(sample_down)] += 1 - sample_decimal - if int(sample_upper) <= (tscale - 1) and int(sample_upper) >= 0: - bin_vector[int(sample_upper)] += sample_decimal - bin_vector = 1.0 / num_sample_perbin * bin_vector - p_mask.append(bin_vector) - p_mask = np.stack(p_mask, axis=1) - return p_mask - - -def get_interp1d_mask(tscale, dscale, prop_boundary_ratio, num_sample, - num_sample_perbin): - """ generate sample mask for each point in Boundary-Matching Map """ - mask_mat = [] - for start_index in range(tscale): - mask_mat_vector = [] - for duration_index in range(dscale): - if start_index + duration_index < tscale: - p_xmin = start_index - p_xmax = start_index + duration_index - center_len = float(p_xmax - p_xmin) + 1 - sample_xmin = p_xmin - center_len * prop_boundary_ratio - sample_xmax = p_xmax + center_len * prop_boundary_ratio - p_mask = _get_interp1d_bin_mask(sample_xmin, sample_xmax, - tscale, num_sample, - num_sample_perbin) - else: - p_mask = np.zeros([tscale, num_sample]) - mask_mat_vector.append(p_mask) - mask_mat_vector = np.stack(mask_mat_vector, axis=2) - mask_mat.append(mask_mat_vector) - mask_mat = np.stack(mask_mat, axis=3) - mask_mat = mask_mat.astype(np.float32) - - sample_mask = np.reshape(mask_mat, [tscale, -1]) - return sample_mask diff --git a/bmn/config_utils.py b/examples/bmn/config_utils.py similarity index 100% rename from bmn/config_utils.py rename to examples/bmn/config_utils.py diff --git a/bmn/eval.py b/examples/bmn/eval.py similarity index 74% rename from bmn/eval.py rename to examples/bmn/eval.py index d25fc5c79d21fd55743def09445db5821e3e93af..2b129d146b4b8d46dc293f23c8a1bcb458da8fe4 100644 --- a/bmn/eval.py +++ b/examples/bmn/eval.py @@ -18,11 +18,10 @@ import sys import logging import paddle.fluid as fluid -sys.path.append('../') +from hapi.model import set_device, Input -from model import set_device, Input +from modeling import bmn, BmnLoss from bmn_metric import BmnMetric -from bmn_model import BMN, BmnLoss from reader import BmnDataset from config_utils import * @@ -39,7 +38,6 @@ def parse_args(): parser.add_argument( "-d", "--dynamic", - default=True, action='store_true', help="enable dygraph mode, only support dynamic mode at present time") parser.add_argument( @@ -55,9 +53,20 @@ def parse_args(): parser.add_argument( '--weights', type=str, - default="checkpoint/final", + default=None, help='weight path, None to automatically download weights provided by Paddle.' ) + parser.add_argument( + '--output_path', + type=str, + default="output/EVAL/BMN_results", + help='output dir path, default to use output/EVAL/BMN_results') + parser.add_argument( + '--result_path', + type=str, + default="evaluate_results/", + help='output dir path after post processing, default to use ./evaluate_results/' + ) parser.add_argument( '--log_interval', type=int, @@ -69,17 +78,21 @@ def parse_args(): # Performance Evaluation def test_bmn(args): - # only support dynamic mode at present time device = set_device(args.device) fluid.enable_dygraph(device) if args.dynamic else None + #config setting config = parse_config(args.config_file) eval_cfg = merge_configs(config, 'test', vars(args)) - if not os.path.isdir(config.TEST.output_path): - os.makedirs(config.TEST.output_path) - if not os.path.isdir(config.TEST.result_path): - os.makedirs(config.TEST.result_path) + feat_dim = config.MODEL.feat_dim + tscale = config.MODEL.tscale + dscale = config.MODEL.dscale + prop_boundary_ratio = config.MODEL.prop_boundary_ratio + num_sample = config.MODEL.num_sample + num_sample_perbin = config.MODEL.num_sample_perbin + + #input and video index inputs = [ Input( [None, config.MODEL.feat_dim, config.MODEL.tscale], @@ -99,9 +112,14 @@ def test_bmn(args): eval_dataset = BmnDataset(eval_cfg, 'test') #model - model = BMN(config, args.dynamic) + model = bmn(tscale, + dscale, + prop_boundary_ratio, + num_sample, + num_sample_perbin, + pretrained=args.weights is None) model.prepare( - loss_function=BmnLoss(config), + loss_function=BmnLoss(tscale, dscale), metrics=BmnMetric( config, mode='test'), inputs=inputs, @@ -109,11 +127,11 @@ def test_bmn(args): device=device) #load checkpoint - if args.weights: + if args.weights is not None: assert os.path.exists(args.weights + '.pdparams'), \ "Given weight dir {} not exist.".format(args.weights) - logger.info('load test weights from {}'.format(args.weights)) - model.load(args.weights) + logger.info('load test weights from {}'.format(args.weights)) + model.load(args.weights) model.evaluate( eval_data=eval_dataset, diff --git a/bmn/eval_anet_prop.py b/examples/bmn/eval_anet_prop.py similarity index 100% rename from bmn/eval_anet_prop.py rename to examples/bmn/eval_anet_prop.py diff --git a/bmn/infer.list b/examples/bmn/infer.list similarity index 100% rename from bmn/infer.list rename to examples/bmn/infer.list diff --git a/bmn/bmn_model.py b/examples/bmn/modeling.py similarity index 68% rename from bmn/bmn_model.py rename to examples/bmn/modeling.py index dfde7bcc5cdaac8aa5ea3c069f580308b49ec01f..f0fa26e1a687fc1d524870a03be470edf280fc9c 100644 --- a/bmn/bmn_model.py +++ b/examples/bmn/modeling.py @@ -17,11 +17,73 @@ from paddle.fluid import ParamAttr import numpy as np import math -from bmn_utils import get_interp1d_mask -from model import Model, Loss +from hapi.model import Model, Loss +from hapi.download import get_weights_path + +__all__ = ["BMN", "BmnLoss", "bmn"] DATATYPE = 'float32' +pretrain_infos = { + 'bmn': ('https://paddlemodels.bj.bcebos.com/hapi/bmn.pdparams', + 'aa84e3386e1fbd117fb96fa572feeb94') +} + + +def _get_interp1d_bin_mask(seg_xmin, seg_xmax, tscale, num_sample, + num_sample_perbin): + """ generate sample mask for a boundary-matching pair """ + plen = float(seg_xmax - seg_xmin) + plen_sample = plen / (num_sample * num_sample_perbin - 1.0) + total_samples = [ + seg_xmin + plen_sample * ii + for ii in range(num_sample * num_sample_perbin) + ] + p_mask = [] + for idx in range(num_sample): + bin_samples = total_samples[idx * num_sample_perbin:(idx + 1) * + num_sample_perbin] + bin_vector = np.zeros([tscale]) + for sample in bin_samples: + sample_upper = math.ceil(sample) + sample_decimal, sample_down = math.modf(sample) + if int(sample_down) <= (tscale - 1) and int(sample_down) >= 0: + bin_vector[int(sample_down)] += 1 - sample_decimal + if int(sample_upper) <= (tscale - 1) and int(sample_upper) >= 0: + bin_vector[int(sample_upper)] += sample_decimal + bin_vector = 1.0 / num_sample_perbin * bin_vector + p_mask.append(bin_vector) + p_mask = np.stack(p_mask, axis=1) + return p_mask + + +def get_interp1d_mask(tscale, dscale, prop_boundary_ratio, num_sample, + num_sample_perbin): + """ generate sample mask for each point in Boundary-Matching Map """ + mask_mat = [] + for start_index in range(tscale): + mask_mat_vector = [] + for duration_index in range(dscale): + if start_index + duration_index < tscale: + p_xmin = start_index + p_xmax = start_index + duration_index + center_len = float(p_xmax - p_xmin) + 1 + sample_xmin = p_xmin - center_len * prop_boundary_ratio + sample_xmax = p_xmax + center_len * prop_boundary_ratio + p_mask = _get_interp1d_bin_mask(sample_xmin, sample_xmax, + tscale, num_sample, + num_sample_perbin) + else: + p_mask = np.zeros([tscale, num_sample]) + mask_mat_vector.append(p_mask) + mask_mat_vector = np.stack(mask_mat_vector, axis=2) + mask_mat.append(mask_mat_vector) + mask_mat = np.stack(mask_mat, axis=3) + mask_mat = mask_mat.astype(np.float32) + + sample_mask = np.reshape(mask_mat, [tscale, -1]) + return sample_mask + # Net class Conv1D(fluid.dygraph.Layer): @@ -64,16 +126,27 @@ class Conv1D(fluid.dygraph.Layer): class BMN(Model): - def __init__(self, cfg, is_dygraph=True): + """BMN model from + `"BMN: Boundary-Matching Network for Temporal Action Proposal Generation" `_ + + Args: + tscale (int): sequence length, default 100. + dscale (int): max duration length, default 100. + prop_boundary_ratio (float): ratio of expanded temporal region in proposal boundary, default 0.5. + num_sample (int): number of samples betweent starting boundary and ending boundary of each propoasl, default 32. + num_sample_perbin (int): number of selected points in each sample, default 3. + """ + + def __init__(self, tscale, dscale, prop_boundary_ratio, num_sample, + num_sample_perbin): super(BMN, self).__init__() #init config - self.tscale = cfg.MODEL.tscale - self.dscale = cfg.MODEL.dscale - self.prop_boundary_ratio = cfg.MODEL.prop_boundary_ratio - self.num_sample = cfg.MODEL.num_sample - self.num_sample_perbin = cfg.MODEL.num_sample_perbin - self.is_dygraph = is_dygraph + self.tscale = tscale + self.dscale = dscale + self.prop_boundary_ratio = prop_boundary_ratio + self.num_sample = num_sample + self.num_sample_perbin = num_sample_perbin self.hidden_dim_1d = 256 self.hidden_dim_2d = 128 @@ -124,23 +197,17 @@ class BMN(Model): padding=1, act="relu") - # init to speed up + # get sample mask sample_mask_array = get_interp1d_mask( self.tscale, self.dscale, self.prop_boundary_ratio, self.num_sample, self.num_sample_perbin) - if self.is_dygraph: - self.sample_mask = fluid.dygraph.base.to_variable( - sample_mask_array) - else: # static - self.sample_mask = fluid.layers.create_parameter( - shape=[ - self.tscale, self.num_sample * self.dscale * self.tscale - ], - dtype=DATATYPE, - attr=fluid.ParamAttr( - name="sample_mask", trainable=False), - default_initializer=fluid.initializer.NumpyArrayInitializer( - sample_mask_array)) + self.sample_mask = fluid.layers.create_parameter( + shape=[self.tscale, self.num_sample * self.dscale * self.tscale], + dtype=DATATYPE, + attr=fluid.ParamAttr( + name="sample_mask", trainable=False), + default_initializer=fluid.initializer.NumpyArrayInitializer( + sample_mask_array)) self.sample_mask.stop_gradient = True @@ -221,21 +288,30 @@ class BMN(Model): class BmnLoss(Loss): - def __init__(self, cfg): + """Loss for BMN model + + Args: + tscale (int): sequence length, default 100. + dscale (int): max duration length, default 100. + """ + + def __init__(self, tscale, dscale): super(BmnLoss, self).__init__() - self.cfg = cfg + self.tscale = tscale + self.dscale = dscale def _get_mask(self): - dscale = self.cfg.MODEL.dscale - tscale = self.cfg.MODEL.tscale bm_mask = [] - for idx in range(dscale): - mask_vector = [1 for i in range(tscale - idx) + for idx in range(self.dscale): + mask_vector = [1 for i in range(self.tscale - idx) ] + [0 for i in range(idx)] bm_mask.append(mask_vector) bm_mask = np.array(bm_mask, dtype=np.float32) self_bm_mask = fluid.layers.create_global_var( - shape=[dscale, tscale], value=0, dtype=DATATYPE, persistable=True) + shape=[self.dscale, self.tscale], + value=0, + dtype=DATATYPE, + persistable=True) fluid.layers.assign(bm_mask, self_bm_mask) self_bm_mask.stop_gradient = True return self_bm_mask @@ -362,3 +438,29 @@ class BmnLoss(Loss): loss = tem_loss + 10 * pem_reg_loss + pem_cls_loss return loss + + +def bmn(tscale, + dscale, + prop_boundary_ratio, + num_sample, + num_sample_perbin, + pretrained=True): + """BMN model + + Args: + tscale (int): sequence length, default 100. + dscale (int): max duration length, default 100. + prop_boundary_ratio (float): ratio of expanded temporal region in proposal boundary, default 0.5. + num_sample (int): number of samples betweent starting boundary and ending boundary of each propoasl, default 32. + num_sample_perbin (int): number of selected points in each sample, default 3. + pretrained (bool): If True, returns a model with pre-trained model, default True. + """ + model = BMN(tscale, dscale, prop_boundary_ratio, num_sample, + num_sample_perbin) + if pretrained: + weight_path = get_weights_path(*(pretrain_infos['bmn'])) + assert weight_path.endswith('.pdparams'), \ + "suffix of weight must be .pdparams" + model.load(weight_path) + return model diff --git a/bmn/predict.py b/examples/bmn/predict.py similarity index 72% rename from bmn/predict.py rename to examples/bmn/predict.py index e52927b60562425a1f03cfea12ab6cb21e76b3ef..6e0ae99d189fa5812765dd31f0461499acbe6fcc 100644 --- a/bmn/predict.py +++ b/examples/bmn/predict.py @@ -18,11 +18,10 @@ import os import logging import paddle.fluid as fluid -sys.path.append('../') +from hapi.model import set_device, Input -from model import set_device, Input +from modeling import bmn, BmnLoss from bmn_metric import BmnMetric -from bmn_model import BMN, BmnLoss from reader import BmnDataset from config_utils import * @@ -39,7 +38,6 @@ def parse_args(): parser.add_argument( "-d", "--dynamic", - default=True, action='store_true', help="enable dygraph mode, only support dynamic mode at present time") parser.add_argument( @@ -52,14 +50,25 @@ def parse_args(): parser.add_argument( '--weights', type=str, - default="checkpoint/final", + default=None, help='weight path, None to automatically download weights provided by Paddle.' ) parser.add_argument( - '--save_dir', + '--filelist', + type=str, + default="infer.list", + help='infer file list, default to use ./infer.list') + parser.add_argument( + '--output_path', + type=str, + default="output/INFER/BMN_results", + help='output dir path, default to use output/INFER/BMN_results') + parser.add_argument( + '--result_path', type=str, default="predict_results/", - help='output dir path, default to use ./predict_results/') + help='output dir path after post processing, default to use ./predict_results/' + ) parser.add_argument( '--log_interval', type=int, @@ -71,18 +80,21 @@ def parse_args(): # Prediction def infer_bmn(args): - # only support dynamic mode at present time device = set_device(args.device) fluid.enable_dygraph(device) if args.dynamic else None + #config setting config = parse_config(args.config_file) infer_cfg = merge_configs(config, 'infer', vars(args)) - if not os.path.isdir(config.INFER.output_path): - os.makedirs(config.INFER.output_path) - if not os.path.isdir(config.INFER.result_path): - os.makedirs(config.INFER.result_path) + feat_dim = config.MODEL.feat_dim + tscale = config.MODEL.tscale + dscale = config.MODEL.dscale + prop_boundary_ratio = config.MODEL.prop_boundary_ratio + num_sample = config.MODEL.num_sample + num_sample_perbin = config.MODEL.num_sample_perbin + #input and video index inputs = [ Input( [None, config.MODEL.feat_dim, config.MODEL.tscale], @@ -94,7 +106,13 @@ def infer_bmn(args): #data infer_dataset = BmnDataset(infer_cfg, 'infer') - model = BMN(config, args.dynamic) + #model + model = bmn(tscale, + dscale, + prop_boundary_ratio, + num_sample, + num_sample_perbin, + pretrained=args.weights is None) model.prepare( metrics=BmnMetric( config, mode='infer'), @@ -103,12 +121,12 @@ def infer_bmn(args): device=device) # load checkpoint - if args.weights: + if args.weights is not None: assert os.path.exists( args.weights + ".pdparams"), "Given weight dir {} not exist.".format(args.weights) - logger.info('load test weights from {}'.format(args.weights)) - model.load(args.weights) + logger.info('load test weights from {}'.format(args.weights)) + model.load(args.weights) # here use model.eval instead of model.test, as post process is required in our case model.evaluate( diff --git a/bmn/reader.py b/examples/bmn/reader.py similarity index 98% rename from bmn/reader.py rename to examples/bmn/reader.py index e1c1da592e932b6ddbd19476e994f4267ae2f927..58fd21fb0583c25a71d2c092f6bcfde7806a7a47 100644 --- a/bmn/reader.py +++ b/examples/bmn/reader.py @@ -21,8 +21,8 @@ import sys sys.path.append('../') -from distributed import DistributedBatchSampler -from paddle.fluid.io import Dataset, DataLoader +from hapi.distributed import DistributedBatchSampler +from paddle.io import Dataset, DataLoader logger = logging.getLogger(__name__) diff --git a/bmn/run.sh b/examples/bmn/run.sh similarity index 98% rename from bmn/run.sh rename to examples/bmn/run.sh index 24fd8e3da991c74628f6d345badf7bfe2e67c35d..979a7301705e6a02c59907221dbaa7a152d9dc47 100644 --- a/bmn/run.sh +++ b/examples/bmn/run.sh @@ -1,3 +1,2 @@ export CUDA_VISIBLE_DEVICES=0,1,2,3 - python -m paddle.distributed.launch train.py diff --git a/bmn/train.py b/examples/bmn/train.py similarity index 78% rename from bmn/train.py rename to examples/bmn/train.py index fe46f6a607c6ab8f93be45ffeee11478ef862eb6..c6bcdef549e943774f11313bd58db0cde6c0e0aa 100644 --- a/bmn/train.py +++ b/examples/bmn/train.py @@ -18,12 +18,11 @@ import logging import sys import os -sys.path.append('../') +from hapi.model import set_device, Input -from model import set_device, Input -from bmn_model import BMN, BmnLoss from reader import BmnDataset from config_utils import * +from modeling import bmn, BmnLoss DATATYPE = 'float32' @@ -36,11 +35,7 @@ logger = logging.getLogger(__name__) def parse_args(): parser = argparse.ArgumentParser("Paddle high level api of BMN.") parser.add_argument( - "-d", - "--dynamic", - default=True, - action='store_true', - help="enable dygraph mode") + "-d", "--dynamic", action='store_true', help="enable dygraph mode") parser.add_argument( '--config_file', type=str, @@ -50,7 +45,7 @@ def parse_args(): '--batch_size', type=int, default=None, - help='training batch size. None to use config file setting.') + help='training batch size. None for read from config file.') parser.add_argument( '--learning_rate', type=float, @@ -70,8 +65,8 @@ def parse_args(): parser.add_argument( '--epoch', type=int, - default=9, - help='epoch number, 0 for read from config file') + default=None, + help='epoch number, None for read from config file') parser.add_argument( '--valid_interval', type=int, @@ -115,22 +110,23 @@ def train_bmn(args): if not os.path.isdir(args.save_dir): os.makedirs(args.save_dir) + #config setting config = parse_config(args.config_file) train_cfg = merge_configs(config, 'train', vars(args)) val_cfg = merge_configs(config, 'valid', vars(args)) - inputs = [ - Input( - [None, config.MODEL.feat_dim, config.MODEL.tscale], - 'float32', - name='feat_input') - ] - gt_iou_map = Input( - [None, config.MODEL.dscale, config.MODEL.tscale], - 'float32', - name='gt_iou_map') - gt_start = Input([None, config.MODEL.tscale], 'float32', name='gt_start') - gt_end = Input([None, config.MODEL.tscale], 'float32', name='gt_end') + feat_dim = config.MODEL.feat_dim + tscale = config.MODEL.tscale + dscale = config.MODEL.dscale + prop_boundary_ratio = config.MODEL.prop_boundary_ratio + num_sample = config.MODEL.num_sample + num_sample_perbin = config.MODEL.num_sample_perbin + + # input and label list + inputs = [Input([None, feat_dim, tscale], 'float32', name='feat_input')] + gt_iou_map = Input([None, dscale, tscale], 'float32', name='gt_iou_map') + gt_start = Input([None, tscale], 'float32', name='gt_start') + gt_end = Input([None, tscale], 'float32', name='gt_end') labels = [gt_iou_map, gt_start, gt_end] # data @@ -138,11 +134,16 @@ def train_bmn(args): val_dataset = BmnDataset(val_cfg, 'valid') # model - model = BMN(config, args.dynamic) + model = bmn(tscale, + dscale, + prop_boundary_ratio, + num_sample, + num_sample_perbin, + pretrained=False) optim = optimizer(config, parameter_list=model.parameters()) model.prepare( optimizer=optim, - loss_function=BmnLoss(config), + loss_function=BmnLoss(tscale, dscale), inputs=inputs, labels=labels, device=device) @@ -150,11 +151,10 @@ def train_bmn(args): # if resume weights is given, load resume weights directly if args.resume is not None: model.load(args.resume) - model.fit(train_data=train_dataset, eval_data=val_dataset, batch_size=train_cfg.TRAIN.batch_size, - epochs=args.epoch, + epochs=train_cfg.TRAIN.epoch, eval_freq=args.valid_interval, log_freq=args.log_interval, save_dir=args.save_dir, diff --git a/cyclegan/README.md b/examples/cyclegan/README.md similarity index 94% rename from cyclegan/README.md rename to examples/cyclegan/README.md index ef35c3ab1b3ec53ca2f9b7d6ba28f210b6d36e91..8481fb66e7c46a987476931b0e2d4858bd91405a 100644 --- a/cyclegan/README.md +++ b/examples/cyclegan/README.md @@ -80,12 +80,19 @@ data/cityscapes/testA/412_A.jpg ### 训练 -在GPU单卡上训练: +在GPU单卡上静态图训练: ``` -env CUDA_VISIBLE_DEVICES=0 python train.py +env CUDA_VISIBLE_DEVICES=0 python train.py --checkpoint_path=checkpoint_static ``` +在GPU单卡上动态图训练: + +``` +env CUDA_VISIBLE_DEVICES=0 python train.py --dynamic --checkpoint_path=checkpoint_dynamic +``` + + 执行`python train.py --help`可查看更多使用方式和参数详细说明。 图1为训练152轮的训练损失示意图,其中横坐标轴为训练轮数,纵轴为在训练集上的损失。其中,'g_loss','da_loss'和'db_loss'分别为生成器、判别器A和判别器B的训练损失。 diff --git a/cyclegan/__init__.py b/examples/cyclegan/__init__.py similarity index 100% rename from cyclegan/__init__.py rename to examples/cyclegan/__init__.py diff --git a/cyclegan/check.py b/examples/cyclegan/check.py similarity index 100% rename from cyclegan/check.py rename to examples/cyclegan/check.py diff --git a/cyclegan/cyclegan.py b/examples/cyclegan/cyclegan.py similarity index 99% rename from cyclegan/cyclegan.py rename to examples/cyclegan/cyclegan.py index 6fdd21c1bdf41a8ed3b6743297b99ef239bd5543..2c5cbd364c35c71dd5bd1d6831bb5b6d4ada07fb 100644 --- a/cyclegan/cyclegan.py +++ b/examples/cyclegan/cyclegan.py @@ -18,9 +18,10 @@ from __future__ import print_function import numpy as np -from layers import ConvBN, DeConvBN import paddle.fluid as fluid -from model import Model, Loss +from hapi.model import Model, Loss + +from layers import ConvBN, DeConvBN class ResnetBlock(fluid.dygraph.Layer): diff --git a/cyclegan/data.py b/examples/cyclegan/data.py similarity index 98% rename from cyclegan/data.py rename to examples/cyclegan/data.py index effa4eeee12a7a4905f3cc40687d8349601bc6c6..b78c4602abd3f2d3997726b78a34ec5bac73b5ab 100644 --- a/cyclegan/data.py +++ b/examples/cyclegan/data.py @@ -20,6 +20,8 @@ import random import numpy as np from PIL import Image, ImageOps +import paddle + DATASET = "cityscapes" A_LIST_FILE = "./data/" + DATASET + "/trainA.txt" B_LIST_FILE = "./data/" + DATASET + "/trainB.txt" @@ -27,10 +29,8 @@ A_TEST_LIST_FILE = "./data/" + DATASET + "/testA.txt" B_TEST_LIST_FILE = "./data/" + DATASET + "/testB.txt" IMAGES_ROOT = "./data/" + DATASET + "/" -import paddle.fluid as fluid - -class Cityscapes(fluid.io.Dataset): +class Cityscapes(paddle.io.Dataset): def __init__(self, root_path, file_path, mode='train', return_name=False): self.root_path = root_path self.file_path = file_path diff --git a/cyclegan/image/A2B.png b/examples/cyclegan/image/A2B.png similarity index 100% rename from cyclegan/image/A2B.png rename to examples/cyclegan/image/A2B.png diff --git a/cyclegan/image/B2A.png b/examples/cyclegan/image/B2A.png similarity index 100% rename from cyclegan/image/B2A.png rename to examples/cyclegan/image/B2A.png diff --git a/cyclegan/image/net.png b/examples/cyclegan/image/net.png similarity index 100% rename from cyclegan/image/net.png rename to examples/cyclegan/image/net.png diff --git a/cyclegan/image/testA/123_A.jpg b/examples/cyclegan/image/testA/123_A.jpg similarity index 100% rename from cyclegan/image/testA/123_A.jpg rename to examples/cyclegan/image/testA/123_A.jpg diff --git a/cyclegan/image/testB/78_B.jpg b/examples/cyclegan/image/testB/78_B.jpg similarity index 100% rename from cyclegan/image/testB/78_B.jpg rename to examples/cyclegan/image/testB/78_B.jpg diff --git a/cyclegan/infer.py b/examples/cyclegan/infer.py similarity index 91% rename from cyclegan/infer.py rename to examples/cyclegan/infer.py index 0b61a958d59e19b73fa01d3c484e1e3231fae71b..2fb2b35eefc8ffb1b4fc10f58ce519e998415055 100644 --- a/cyclegan/infer.py +++ b/examples/cyclegan/infer.py @@ -25,9 +25,9 @@ from PIL import Image from scipy.misc import imsave import paddle.fluid as fluid -from check import check_gpu, check_version +from hapi.model import Model, Input, set_device -from model import Model, Input, set_device +from check import check_gpu, check_version from cyclegan import Generator, GeneratorCombine @@ -43,7 +43,7 @@ def main(): im_shape = [-1, 3, 256, 256] input_A = Input(im_shape, 'float32', 'input_A') input_B = Input(im_shape, 'float32', 'input_B') - g.prepare(inputs=[input_A, input_B]) + g.prepare(inputs=[input_A, input_B], device=FLAGS.device) g.load(FLAGS.init_model, skip_mismatch=True, reset_optimizer=True) out_path = FLAGS.output + "/single" @@ -59,10 +59,10 @@ def main(): data = image.transpose([2, 0, 1])[np.newaxis, :] if FLAGS.input_style == "A": - _, fake, _, _ = g.test([data, data]) + _, fake, _, _ = g.test_batch([data, data]) if FLAGS.input_style == "B": - fake, _, _, _ = g.test([data, data]) + fake, _, _, _ = g.test_batch([data, data]) fake = np.squeeze(fake[0]).transpose([1, 2, 0]) @@ -74,7 +74,7 @@ def main(): if __name__ == "__main__": parser = argparse.ArgumentParser("CycleGAN inference") parser.add_argument( - "-d", "--dynamic", action='store_false', help="Enable dygraph mode") + "-d", "--dynamic", action='store_true', help="Enable dygraph mode") parser.add_argument( "-p", "--device", diff --git a/cyclegan/layers.py b/examples/cyclegan/layers.py similarity index 100% rename from cyclegan/layers.py rename to examples/cyclegan/layers.py diff --git a/cyclegan/test.py b/examples/cyclegan/test.py similarity index 92% rename from cyclegan/test.py rename to examples/cyclegan/test.py index 995663090f07e345e54be47da26a8c0e7fd32a4a..67f7183e2229ec9509e23e9ac81dc54122290056 100644 --- a/cyclegan/test.py +++ b/examples/cyclegan/test.py @@ -22,9 +22,9 @@ import numpy as np from scipy.misc import imsave import paddle.fluid as fluid -from check import check_gpu, check_version +from hapi.model import Model, Input, set_device -from model import Model, Input, set_device +from check import check_gpu, check_version from cyclegan import Generator, GeneratorCombine import data as data @@ -41,7 +41,7 @@ def main(): im_shape = [-1, 3, 256, 256] input_A = Input(im_shape, 'float32', 'input_A') input_B = Input(im_shape, 'float32', 'input_B') - g.prepare(inputs=[input_A, input_B]) + g.prepare(inputs=[input_A, input_B], device=FLAGS.device) g.load(FLAGS.init_model, skip_mismatch=True, reset_optimizer=True) if not os.path.exists(FLAGS.output): @@ -56,7 +56,7 @@ def main(): data_A = np.array(data_A).astype("float32") data_B = np.array(data_B).astype("float32") - fake_A, fake_B, cyc_A, cyc_B = g.test([data_A, data_B]) + fake_A, fake_B, cyc_A, cyc_B = g.test_batch([data_A, data_B]) datas = [fake_A, fake_B, cyc_A, cyc_B, data_A, data_B] odatas = [] @@ -75,7 +75,7 @@ def main(): if __name__ == "__main__": parser = argparse.ArgumentParser("CycleGAN test") parser.add_argument( - "-d", "--dynamic", action='store_false', help="Enable dygraph mode") + "-d", "--dynamic", action='store_true', help="Enable dygraph mode") parser.add_argument( "-p", "--device", diff --git a/cyclegan/train.py b/examples/cyclegan/train.py similarity index 82% rename from cyclegan/train.py rename to examples/cyclegan/train.py index c2203fc19c8e0381fa27bde26a22a863130532e9..4ca77dd66996afcc00218eafaafefa25a2a7c771 100644 --- a/cyclegan/train.py +++ b/examples/cyclegan/train.py @@ -24,12 +24,11 @@ import time import paddle import paddle.fluid as fluid -from check import check_gpu, check_version - -from model import Model, Input, set_device +from hapi.model import Model, Input, set_device -import data as data +from check import check_gpu, check_version from cyclegan import Generator, Discriminator, GeneratorCombine, GLoss, DLoss +import data as data step_per_epoch = 2974 @@ -76,23 +75,26 @@ def main(): fake_A = Input(im_shape, 'float32', 'fake_A') fake_B = Input(im_shape, 'float32', 'fake_B') - g_AB.prepare(inputs=[input_A]) - g_BA.prepare(inputs=[input_B]) + g_AB.prepare(inputs=[input_A], device=FLAGS.device) + g_BA.prepare(inputs=[input_B], device=FLAGS.device) - g.prepare(g_optimizer, GLoss(), inputs=[input_A, input_B]) - d_A.prepare(da_optimizer, DLoss(), inputs=[input_B, fake_B]) - d_B.prepare(db_optimizer, DLoss(), inputs=[input_A, fake_A]) + g.prepare(g_optimizer, GLoss(), inputs=[input_A, input_B], + device=FLAGS.device) + d_A.prepare(da_optimizer, DLoss(), inputs=[input_B, fake_B], + device=FLAGS.device) + d_B.prepare(db_optimizer, DLoss(), inputs=[input_A, fake_A], + device=FLAGS.device) if FLAGS.resume: g.load(FLAGS.resume) - loader_A = fluid.io.DataLoader( + loader_A = paddle.io.DataLoader( data.DataA(), places=place, shuffle=True, return_list=True, batch_size=FLAGS.batch_size) - loader_B = fluid.io.DataLoader( + loader_B = paddle.io.DataLoader( data.DataB(), places=place, shuffle=True, @@ -108,14 +110,14 @@ def main(): data_B = data_B[0][0] if not FLAGS.dynamic else data_B[0] start = time.time() - fake_B = g_AB.test(data_A)[0] - fake_A = g_BA.test(data_B)[0] - g_loss = g.train([data_A, data_B])[0] + fake_B = g_AB.test_batch(data_A)[0] + fake_A = g_BA.test_batch(data_B)[0] + g_loss = g.train_batch([data_A, data_B])[0] fake_pb = B_pool.get(fake_B) - da_loss = d_A.train([data_B, fake_pb])[0] + da_loss = d_A.train_batch([data_B, fake_pb])[0] fake_pa = A_pool.get(fake_A) - db_loss = d_B.train([data_A, fake_pa])[0] + db_loss = d_B.train_batch([data_A, fake_pa])[0] t = time.time() - start if i % 20 == 0: @@ -128,7 +130,7 @@ def main(): if __name__ == "__main__": parser = argparse.ArgumentParser("CycleGAN Training on Cityscapes") parser.add_argument( - "-d", "--dynamic", action='store_false', help="Enable dygraph mode") + "-d", "--dynamic", action='store_true', help="Enable dygraph mode") parser.add_argument( "-p", "--device", diff --git a/image_classification/README.MD b/examples/image_classification/README.MD similarity index 79% rename from image_classification/README.MD rename to examples/image_classification/README.MD index 9be3362090e97e64ee5c09ead6247f5ecb217781..5b50370dd4b2ad76e62f0e99877849f5fe2fed8f 100644 --- a/image_classification/README.MD +++ b/examples/image_classification/README.MD @@ -43,13 +43,13 @@ CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch main.py --arch ### 单卡预测 执行如下命令进行预测 ```bash -python -u main.py --arch resnet50 -d --evaly-only /path/to/imagenet +python -u main.py --arch resnet50 -d --eval-only /path/to/imagenet ``` ### 多卡预测 执行如下命令进行多卡预测 ```bash -CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch main.py --arch resnet50 --evaly-only /path/to/imagenet +CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch main.py --arch resnet50 --eval-only /path/to/imagenet ``` @@ -71,15 +71,20 @@ CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch main.py --arch * **weight-decay**: 模型权重正则化系数,默认值:1e-4 * **momentum**: SGD优化器的动量,默认值:0.9 +注意:使用```--resume```恢复训练时,假如你的模型路径为```./output/118.pdparams```,你输入的路径不需要带后缀,即```--resume ./output/118```即可。 ## 模型 | 模型 | top1 acc | top5 acc | | --- | --- | --- | -| [ResNet50](https://paddle-hapi.bj.bcebos.com/models/resnet50.pdparams) | 76.28 | 93.04 | -| [vgg16](https://paddle-hapi.bj.bcebos.com/models/vgg16.pdparams) | 71.84 | 90.71 | -| [mobilenet_v1](https://paddle-hapi.bj.bcebos.com/models/mobilenet_v1_x1.0.pdparams) | 71.25 | 89.92 | -| [mobilenet_v2](https://paddle-hapi.bj.bcebos.com/models/mobilenet_v2_x1.0.pdparams) | 72.27 | 90.66 | +| [ResNet18](https://paddle-hapi.bj.bcebos.com/models/resnet18.pdparams) | 71.72 | 90.60 | +| [ResNet34](https://paddle-hapi.bj.bcebos.com/models/resnet34.pdparams) | 75.02 | 92.31 | +| [ResNet50](https://paddle-hapi.bj.bcebos.com/models/resnet50.pdparams) | 76.27 | 93.03 | +| [ResNet101](https://paddle-hapi.bj.bcebos.com/models/resnet101.pdparams) | 78.33 | 94.04 | +| [ResNet152](https://paddle-hapi.bj.bcebos.com/models/resnet152.pdparams) | 78.78 | 94.40 | +| [vgg16](https://paddle-hapi.bj.bcebos.com/models/vgg16.pdparams) | 71.92 | 90.65 | +| [mobilenet_v1](https://paddle-hapi.bj.bcebos.com/models/mobilenet_v1_x1.0.pdparams) | 71.16 | 89.89 | +| [mobilenet_v2](https://paddle-hapi.bj.bcebos.com/models/mobilenet_v2_x1.0.pdparams) | 72.30 | 90.74 | 上述模型的复现参数请参考scripts下的脚本。 diff --git a/examples/image_classification/imagenet_dataset.py b/examples/image_classification/imagenet_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..6572df01440a36c21330cc905da045e03ff79700 --- /dev/null +++ b/examples/image_classification/imagenet_dataset.py @@ -0,0 +1,52 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import cv2 +import math +import random +import numpy as np + +from hapi.datasets import DatasetFolder +from hapi.vision.transforms import transforms +from paddle import fluid + + +class ImageNetDataset(DatasetFolder): + def __init__(self, path, mode='train'): + super(ImageNetDataset, self).__init__(path) + self.mode = mode + + normalize = transforms.Normalize( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.120, 57.375]) + if self.mode == 'train': + self.transform = transforms.Compose([ + transforms.RandomResizedCrop(224), + transforms.RandomHorizontalFlip(), + transforms.Permute(mode='CHW'), normalize + ]) + else: + self.transform = transforms.Compose([ + transforms.Resize(256), transforms.CenterCrop(224), + transforms.Permute(mode='CHW'), normalize + ]) + + def __getitem__(self, idx): + img_path, label = self.samples[idx] + img = cv2.imread(img_path).astype(np.float32) + label = np.array([label]) + return self.transform(img, label) + + def __len__(self): + return len(self.samples) diff --git a/image_classification/main.py b/examples/image_classification/main.py similarity index 89% rename from image_classification/main.py rename to examples/image_classification/main.py index 781824fa60f9d703187697825595d81889b9c53c..76360df91cd64a66e2e288c90a37ac667cdc3eea 100644 --- a/image_classification/main.py +++ b/examples/image_classification/main.py @@ -24,15 +24,17 @@ sys.path.append('../') import time import math import numpy as np -import models + import paddle.fluid as fluid +from paddle.fluid.dygraph.parallel import ParallelEnv +from paddle.io import BatchSampler, DataLoader + +from hapi.model import CrossEntropy, Input, set_device +from hapi.distributed import DistributedBatchSampler +from hapi.metrics import Accuracy +import hapi.vision.models as models -from model import CrossEntropy, Input, set_device from imagenet_dataset import ImageNetDataset -from distributed import DistributedBatchSampler -from paddle.fluid.dygraph.parallel import ParallelEnv -from metrics import Accuracy -from paddle.fluid.io import BatchSampler, DataLoader def make_optimizer(step_per_epoch, parameter_list=None): @@ -74,6 +76,9 @@ def main(): device = set_device(FLAGS.device) fluid.enable_dygraph(device) if FLAGS.dynamic else None + model_list = [x for x in models.__dict__["__all__"]] + assert FLAGS.arch in model_list, "Expected FLAGS.arch in {}, but received {}".format( + model_list, FLAGS.arch) model = models.__dict__[FLAGS.arch](pretrained=FLAGS.eval_only and not FLAGS.resume) @@ -92,7 +97,13 @@ def main(): len(train_dataset) * 1. / FLAGS.batch_size / ParallelEnv().nranks), parameter_list=model.parameters()) - model.prepare(optim, CrossEntropy(), Accuracy(topk=(1, 5)), inputs, labels) + model.prepare( + optim, + CrossEntropy(), + Accuracy(topk=(1, 5)), + inputs, + labels, + FLAGS.device) if FLAGS.eval_only: model.evaluate( @@ -150,7 +161,7 @@ if __name__ == '__main__': type=str, help="checkpoint path to resume") parser.add_argument( - "--eval-only", action='store_true', help="enable dygraph mode") + "--eval-only", action='store_true', help="only evaluate the model") parser.add_argument( "--lr-scheduler", default='piecewise', diff --git a/examples/image_classification/scripts/mobilenet_v1_x1.0.sh b/examples/image_classification/scripts/mobilenet_v1_x1.0.sh new file mode 100644 index 0000000000000000000000000000000000000000..16734e64c0fe3e6e93eacadd89ce366b48969dbd --- /dev/null +++ b/examples/image_classification/scripts/mobilenet_v1_x1.0.sh @@ -0,0 +1,13 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch mobilenet_v1 \ + --epoch 120 \ + --batch-size 64 \ + --learning-rate 0.1 \ + --lr-scheduler piecewise \ + --milestones 30 60 90 \ + --weight-decay 3e-5 \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/mobilenet_v2_x1.0.sh b/examples/image_classification/scripts/mobilenet_v2_x1.0.sh new file mode 100644 index 0000000000000000000000000000000000000000..2616d7ef8668b0d85fa56cf28c0bf95e86212fd1 --- /dev/null +++ b/examples/image_classification/scripts/mobilenet_v2_x1.0.sh @@ -0,0 +1,12 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch mobilenet_v2 \ + --epoch 240 \ + --batch-size 64 \ + --learning-rate 0.1 \ + --lr-scheduler cosine \ + --weight-decay 4e-5 \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/resnet101.sh b/examples/image_classification/scripts/resnet101.sh new file mode 100644 index 0000000000000000000000000000000000000000..34844cafb61f7373b9e9f9c997dc44bb5a3308ca --- /dev/null +++ b/examples/image_classification/scripts/resnet101.sh @@ -0,0 +1,10 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch resnet101 \ + --epoch 90 \ + --batch-size 64 \ + --learning-rate 0.1 \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/resnet152.sh b/examples/image_classification/scripts/resnet152.sh new file mode 100644 index 0000000000000000000000000000000000000000..26541637b1a2d45a6e65db513f3a806b9aa92594 --- /dev/null +++ b/examples/image_classification/scripts/resnet152.sh @@ -0,0 +1,10 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch resnet152 \ + --epoch 90 \ + --batch-size 64 \ + --learning-rate 0.1 \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/resnet18.sh b/examples/image_classification/scripts/resnet18.sh new file mode 100644 index 0000000000000000000000000000000000000000..f1f20e55bed5106b58b3b90b5909d5c93c09e4cd --- /dev/null +++ b/examples/image_classification/scripts/resnet18.sh @@ -0,0 +1,11 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch resnet18 \ + --epoch 120 \ + --batch-size 64 \ + --learning-rate 0.1 \ + --lr-scheduler cosine \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/resnet34.sh b/examples/image_classification/scripts/resnet34.sh new file mode 100644 index 0000000000000000000000000000000000000000..a4a36614dfad023d98a3e5ae3e26ddc96449e2f2 --- /dev/null +++ b/examples/image_classification/scripts/resnet34.sh @@ -0,0 +1,11 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch resnet34 \ + --epoch 120 \ + --batch-size 64 \ + --learning-rate 0.1 \ + --lr-scheduler cosine \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/resnet50.sh b/examples/image_classification/scripts/resnet50.sh new file mode 100644 index 0000000000000000000000000000000000000000..50a0e7398bd68aa046345095774b5403022331d2 --- /dev/null +++ b/examples/image_classification/scripts/resnet50.sh @@ -0,0 +1,10 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch resnet50 \ + --epoch 90 \ + --batch-size 64 \ + --learning-rate 0.1 \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/image_classification/scripts/vgg16.sh b/examples/image_classification/scripts/vgg16.sh new file mode 100644 index 0000000000000000000000000000000000000000..7372ce315efef42524550fec1dd549146f4e1a54 --- /dev/null +++ b/examples/image_classification/scripts/vgg16.sh @@ -0,0 +1,11 @@ +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +# 默认imagenet数据存储在data/ILSVRC2012/下,去除-d便使用静态图模式运行 +python -m paddle.distributed.launch main.py \ + --arch vgg16 \ + --epoch 90 \ + --batch-size 64 \ + --learning-rate 0.01 \ + --lr-scheduler cosine \ + -d \ + data/ILSVRC2012/ \ No newline at end of file diff --git a/examples/ocr/README.md b/examples/ocr/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d3d592d195702e196c1e525da38424b47274a18b --- /dev/null +++ b/examples/ocr/README.md @@ -0,0 +1,76 @@ +简介 +-------- +本OCR任务是识别图片单行的字母信息,基于attention的seq2seq结构。 运行本目录下的程序示例需要使用PaddlePaddle develop最新版本。 + +## 代码结构 +``` +. +|-- data.py # 数据读取 +|-- eval.py # 评估脚本 +|-- images # 测试图片 +|-- predict.py # 预测脚本 +|-- seq2seq_attn.py # 模型 +|-- train.py # 训练脚本 +`-- utility.py # 公共模块 +``` + +## 训练/评估/预测流程 + +- 设置GPU环境: + +``` +export CUDA_VISIBLE_DEVICES=0 +``` + +- 训练 + +``` +python train.py +``` + +更多参数可以通过`--help`查看。 + + +- 动静切换 + + +``` +python train.py --dynamic=True +``` + + +- 评估 + +``` +python eval.py --init_model=checkpoint/final +``` + + +- 预测 + +目前不支持动态图预测 + +``` +python predict.py --init_model=checkpoint/final --image_path=images/ --dynamic=False --beam_size=3 +``` + +预测结果如下: + +``` +Image 1: images/112_chubbiness_13557.jpg +0: chubbines +1: chubbiness +2: chubbinesS +Image 2: images/177_Interfiled_40185.jpg +0: Interflied +1: Interfiled +2: InterfIled +Image 3: images/325_dame_19109.jpg +0: da +1: damo +2: dame +Image 4: images/368_fixtures_29232.jpg +0: firtures +1: Firtures +2: fixtures +``` diff --git a/examples/ocr/data.py b/examples/ocr/data.py new file mode 100644 index 0000000000000000000000000000000000000000..23e676e2625d3c75be9bec9b00777a11c38e0e6e --- /dev/null +++ b/examples/ocr/data.py @@ -0,0 +1,234 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from os import path +import random +import traceback +import copy +import math +import tarfile +from PIL import Image + +import logging +logger = logging.getLogger(__name__) + +import paddle +from paddle import fluid +from paddle.fluid.dygraph.parallel import ParallelEnv + +DATA_MD5 = "7256b1d5420d8c3e74815196e58cdad5" +DATA_URL = "http://paddle-ocr-data.bj.bcebos.com/data.tar.gz" +CACHE_DIR_NAME = "attention_data" +SAVED_FILE_NAME = "data.tar.gz" +DATA_DIR_NAME = "data" +TRAIN_DATA_DIR_NAME = "train_images" +TEST_DATA_DIR_NAME = "test_images" +TRAIN_LIST_FILE_NAME = "train.list" +TEST_LIST_FILE_NAME = "test.list" + + +class Resize(object): + def __init__(self, height=48): + self.interp = Image.NEAREST # Image.ANTIALIAS + self.height = height + + def __call__(self, samples): + shape = samples[0][0].size + for i in range(len(samples)): + im = samples[i][0] + im = im.resize((shape[0], self.height), self.interp) + samples[i][0] = im + return samples + + +class Normalize(object): + def __init__(self, + mean=[127.5], + std=[1.0], + scale=False, + channel_first=True): + self.mean = mean + self.std = std + self.scale = scale + self.channel_first = channel_first + if not (isinstance(self.mean, list) and isinstance(self.std, list) and + isinstance(self.scale, bool)): + raise TypeError("{}: input type is invalid.".format(self)) + + def __call__(self, samples): + for i in range(len(samples)): + im = samples[i][0] + im = np.array(im).astype(np.float32, copy=False) + im = im[np.newaxis, ...] + mean = np.array(self.mean)[np.newaxis, np.newaxis, :] + std = np.array(self.std)[np.newaxis, np.newaxis, :] + if self.scale: + im = im / 255.0 + #im -= mean + im -= 127.5 + #im /= std + samples[i][0] = im + return samples + + +class PadTarget(object): + def __init__(self, SOS=0, EOS=1): + self.SOS = SOS + self.EOS = EOS + + def __call__(self, samples): + lens = np.array([len(s[1]) for s in samples], dtype="int64") + max_len = np.max(lens) + for i in range(len(samples)): + label = samples[i][1] + if max_len > len(label): + pad_label = label + [self.EOS] * (max_len - len(label)) + else: + pad_label = label + samples[i][1] = np.array([self.SOS] + pad_label, dtype='int64') + # label_out + samples[i].append(np.array(pad_label + [self.EOS], dtype='int64')) + mask = np.zeros((max_len + 1)).astype('float32') + mask[:len(label) + 1] = 1.0 + # mask + samples[i].append(np.array(mask, dtype='float32')) + return samples + + +class BatchSampler(fluid.io.BatchSampler): + def __init__(self, + dataset, + batch_size, + shuffle=False, + drop_last=True, + seed=None): + self._dataset = dataset + self._batch_size = batch_size + self._shuffle = shuffle + self._drop_last = drop_last + self._random = np.random + self._random.seed(seed) + self._nranks = ParallelEnv().nranks + self._local_rank = ParallelEnv().local_rank + self._device_id = ParallelEnv().dev_id + self._num_samples = int( + math.ceil(len(self._dataset) * 1.0 / self._nranks)) + self._total_size = self._num_samples * self._nranks + self._epoch = 0 + + def __iter__(self): + infos = copy.copy(self._dataset._sample_infos) + skip_num = 0 + if self._shuffle: + if self._batch_size == 1: + self._random.RandomState(self._epoch).shuffle(infos) + else: # partial shuffle + infos = sorted(infos, key=lambda x: x.w) + skip_num = random.randint(1, 100) + + infos = infos[skip_num:] + infos[:skip_num] + infos += infos[:(self._total_size - len(infos))] + last_size = self._total_size % (self._batch_size * self._nranks) + batches = [] + for i in range(self._local_rank * self._batch_size, + len(infos) - last_size, + self._batch_size * self._nranks): + batches.append(infos[i:i + self._batch_size]) + + if (not self._drop_last) and last_size != 0: + last_local_size = last_size // self._nranks + last_infos = infos[len(infos) - last_size:] + start = self._local_rank * last_local_size + batches.append(last_infos[start:start + last_local_size]) + + if self._shuffle: + self._random.RandomState(self._epoch).shuffle(batches) + self._epoch += 1 + + for batch in batches: + batch_indices = [info.idx for info in batch] + yield batch_indices + + def __len__(self): + if self._drop_last: + return self._total_size // self._batch_size + else: + return math.ceil(self._total_size / float(self._batch_size)) + + +class SampleInfo(object): + def __init__(self, idx, h, w, im_name, labels): + self.idx = idx + self.h = h + self.w = w + self.im_name = im_name + self.labels = labels + + +class OCRDataset(paddle.io.Dataset): + def __init__(self, image_dir, anno_file): + self.image_dir = image_dir + self.anno_file = anno_file + self._sample_infos = [] + with open(anno_file, 'r') as f: + for i, line in enumerate(f): + w, h, im_name, labels = line.strip().split(' ') + h, w = int(h), int(w) + labels = [int(c) for c in labels.split(',')] + self._sample_infos.append(SampleInfo(i, h, w, im_name, labels)) + + def __getitem__(self, idx): + info = self._sample_infos[idx] + im_name, labels = info.im_name, info.labels + image = Image.open(path.join(self.image_dir, im_name)).convert('L') + return [image, labels] + + def __len__(self): + return len(self._sample_infos) + + +def train( + root_dir=None, + images_dir=None, + anno_file=None, + shuffle=True, ): + if root_dir is None: + root_dir = download_data() + if images_dir is None: + images_dir = TRAIN_DATA_DIR_NAME + images_dir = path.join(root_dir, TRAIN_DATA_DIR_NAME) + if anno_file is None: + anno_file = TRAIN_LIST_FILE_NAME + anno_file = path.join(root_dir, TRAIN_LIST_FILE_NAME) + return OCRDataset(images_dir, anno_file) + + +def test( + root_dir=None, + images_dir=None, + anno_file=None, + shuffle=True, ): + if root_dir is None: + root_dir = download_data() + if images_dir is None: + images_dir = TEST_DATA_DIR_NAME + images_dir = path.join(root_dir, TEST_DATA_DIR_NAME) + if anno_file is None: + anno_file = TEST_LIST_FILE_NAME + anno_file = path.join(root_dir, TEST_LIST_FILE_NAME) + return OCRDataset(images_dir, anno_file) + + +def download_data(): + '''Download train and test data. + ''' + tar_file = paddle.dataset.common.download( + DATA_URL, CACHE_DIR_NAME, DATA_MD5, save_name=SAVED_FILE_NAME) + data_dir = path.join(path.dirname(tar_file), DATA_DIR_NAME) + if not path.isdir(data_dir): + t = tarfile.open(tar_file, "r:gz") + t.extractall(path=path.dirname(tar_file)) + t.close() + return data_dir diff --git a/examples/ocr/eval.py b/examples/ocr/eval.py new file mode 100644 index 0000000000000000000000000000000000000000..1adffa5401679ab0d49cc586c0238ce1c01fa1b8 --- /dev/null +++ b/examples/ocr/eval.py @@ -0,0 +1,152 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + +import argparse +import functools + +import paddle.fluid.profiler as profiler +import paddle.fluid as fluid + +from hapi.model import Input, set_device +from hapi.vision.transforms import BatchCompose + +from utility import add_arguments, print_arguments +from utility import SeqAccuracy, LoggerCallBack, SeqBeamAccuracy +from utility import postprocess +from seq2seq_attn import Seq2SeqAttModel, Seq2SeqAttInferModel, WeightCrossEntropy +import data + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +# yapf: disable +add_arg('batch_size', int, 32, "Minibatch size.") +add_arg('test_images', str, None, "The directory of images to be used for test.") +add_arg('test_list', str, None, "The list file of images to be used for training.") +add_arg('init_model', str, 'checkpoint/final', "The init model file of directory.") +add_arg('use_gpu', bool, True, "Whether use GPU to train.") +add_arg('encoder_size', int, 200, "Encoder size.") +add_arg('decoder_size', int, 128, "Decoder size.") +add_arg('embedding_dim', int, 128, "Word vector dim.") +add_arg('num_classes', int, 95, "Number classes.") +add_arg('beam_size', int, 0, "If set beam size, will use beam search.") +add_arg('dynamic', bool, False, "Whether to use dygraph.") +# yapf: enable + + +def main(FLAGS): + device = set_device("gpu" if FLAGS.use_gpu else "cpu") + fluid.enable_dygraph(device) if FLAGS.dynamic else None + model = Seq2SeqAttModel( + encoder_size=FLAGS.encoder_size, + decoder_size=FLAGS.decoder_size, + emb_dim=FLAGS.embedding_dim, + num_classes=FLAGS.num_classes) + + # yapf: disable + inputs = [ + Input([None, 1, 48, 384], "float32", name="pixel"), + Input([None, None], "int64", name="label_in") + ] + labels = [ + Input([None, None], "int64", name="label_out"), + Input([None, None], "float32", name="mask") + ] + # yapf: enable + + model.prepare( + loss_function=WeightCrossEntropy(), + metrics=SeqAccuracy(), + inputs=inputs, + labels=labels, + device=device) + model.load(FLAGS.init_model) + + test_dataset = data.test() + test_collate_fn = BatchCompose( + [data.Resize(), data.Normalize(), data.PadTarget()]) + test_sampler = data.BatchSampler( + test_dataset, + batch_size=FLAGS.batch_size, + drop_last=False, + shuffle=False) + test_loader = fluid.io.DataLoader( + test_dataset, + batch_sampler=test_sampler, + places=device, + num_workers=0, + return_list=True, + collate_fn=test_collate_fn) + + model.evaluate( + eval_data=test_loader, + callbacks=[LoggerCallBack(10, 2, FLAGS.batch_size)]) + + +def beam_search(FLAGS): + device = set_device("gpu" if FLAGS.use_gpu else "cpu") + fluid.enable_dygraph(device) if FLAGS.dynamic else None + model = Seq2SeqAttInferModel( + encoder_size=FLAGS.encoder_size, + decoder_size=FLAGS.decoder_size, + emb_dim=FLAGS.embedding_dim, + num_classes=FLAGS.num_classes, + beam_size=FLAGS.beam_size) + + inputs = [ + Input( + [None, 1, 48, 384], "float32", name="pixel"), Input( + [None, None], "int64", name="label_in") + ] + labels = [ + Input( + [None, None], "int64", name="label_out"), Input( + [None, None], "float32", name="mask") + ] + model.prepare( + loss_function=None, + metrics=SeqBeamAccuracy(), + inputs=inputs, + labels=labels, + device=device) + model.load(FLAGS.init_model) + + test_dataset = data.test() + test_collate_fn = BatchCompose( + [data.Resize(), data.Normalize(), data.PadTarget()]) + test_sampler = data.BatchSampler( + test_dataset, + batch_size=FLAGS.batch_size, + drop_last=False, + shuffle=False) + test_loader = fluid.io.DataLoader( + test_dataset, + batch_sampler=test_sampler, + places=device, + num_workers=0, + return_list=True, + collate_fn=test_collate_fn) + + model.evaluate( + eval_data=test_loader, + callbacks=[LoggerCallBack(10, 2, FLAGS.batch_size)]) + + +if __name__ == '__main__': + FLAGS = parser.parse_args() + print_arguments(FLAGS) + if FLAGS.beam_size: + beam_search(FLAGS) + else: + main(FLAGS) diff --git a/examples/ocr/images/112_chubbiness_13557.jpg b/examples/ocr/images/112_chubbiness_13557.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4474a0db2b40a618ecb5401022958651e9aa0543 Binary files /dev/null and b/examples/ocr/images/112_chubbiness_13557.jpg differ diff --git a/examples/ocr/images/177_Interfiled_40185.jpg b/examples/ocr/images/177_Interfiled_40185.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c110e3d8ef85bc917c5574feaa8e9bb8a65d80c9 Binary files /dev/null and b/examples/ocr/images/177_Interfiled_40185.jpg differ diff --git a/examples/ocr/images/325_dame_19109.jpg b/examples/ocr/images/325_dame_19109.jpg new file mode 100644 index 0000000000000000000000000000000000000000..12554431319a03fedd33a51da806414b56e2119e Binary files /dev/null and b/examples/ocr/images/325_dame_19109.jpg differ diff --git a/examples/ocr/images/368_fixtures_29232.jpg b/examples/ocr/images/368_fixtures_29232.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7566131c8f1e222be21a4f9dd6f9321705dea617 Binary files /dev/null and b/examples/ocr/images/368_fixtures_29232.jpg differ diff --git a/examples/ocr/predict.py b/examples/ocr/predict.py new file mode 100644 index 0000000000000000000000000000000000000000..242d4f80b9bbdbade61b0cc086196482ffa588e9 --- /dev/null +++ b/examples/ocr/predict.py @@ -0,0 +1,101 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + +import os +import sys +import random +import numpy as np + +import argparse +import functools +from PIL import Image + +import paddle.fluid.profiler as profiler +import paddle.fluid as fluid + +from hapi.model import Input, set_device +from hapi.datasets.folder import ImageFolder +from hapi.vision.transforms import BatchCompose + +from utility import add_arguments, print_arguments +from utility import postprocess, index2word +from seq2seq_attn import Seq2SeqAttInferModel, WeightCrossEntropy +import data + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +# yapf: disable +add_arg('batch_size', int, 1, "Minibatch size.") +add_arg('image_path', str, None, "The directory of images to be used for test.") +add_arg('init_model', str, None, "The init model file of directory.") +add_arg('use_gpu', bool, True, "Whether use GPU to train.") +# model hyper paramters +add_arg('encoder_size', int, 200, "Encoder size.") +add_arg('decoder_size', int, 128, "Decoder size.") +add_arg('embedding_dim', int, 128, "Word vector dim.") +add_arg('num_classes', int, 95, "Number classes.") +add_arg('beam_size', int, 3, "Beam size for beam search.") +add_arg('dynamic', bool, False, "Whether to use dygraph.") +# yapf: enable + + +def main(FLAGS): + device = set_device("gpu" if FLAGS.use_gpu else "cpu") + fluid.enable_dygraph(device) if FLAGS.dynamic else None + model = Seq2SeqAttInferModel( + encoder_size=FLAGS.encoder_size, + decoder_size=FLAGS.decoder_size, + emb_dim=FLAGS.embedding_dim, + num_classes=FLAGS.num_classes, + beam_size=FLAGS.beam_size) + + inputs = [Input([None, 1, 48, 384], "float32", name="pixel"), ] + + model.prepare(inputs=inputs, device=device) + model.load(FLAGS.init_model) + + fn = lambda p: Image.open(p).convert('L') + test_dataset = ImageFolder(FLAGS.image_path, loader=fn) + test_collate_fn = BatchCompose([data.Resize(), data.Normalize()]) + test_loader = fluid.io.DataLoader( + test_dataset, + places=device, + num_workers=0, + return_list=True, + collate_fn=test_collate_fn) + + samples = test_dataset.samples + #outputs = model.predict(test_loader) + ins_id = 0 + for image, in test_loader: + image = image if FLAGS.dynamic else image[0] + pred = model.test_batch([image])[0] + pred = pred[:, :, np.newaxis] if len(pred.shape) == 2 else pred + pred = np.transpose(pred, [0, 2, 1]) + for ins in pred: + impath = samples[ins_id] + ins_id += 1 + print('Image {}: {}'.format(ins_id, impath)) + for beam_idx, beam in enumerate(ins): + id_list = postprocess(beam) + word_list = index2word(id_list) + sequence = "".join(word_list) + print('{}: {}'.format(beam_idx, sequence)) + + +if __name__ == '__main__': + FLAGS = parser.parse_args() + print_arguments(FLAGS) + main(FLAGS) diff --git a/examples/ocr/seq2seq_attn.py b/examples/ocr/seq2seq_attn.py new file mode 100644 index 0000000000000000000000000000000000000000..675e4e4ab0b30874dffd1b0bbc84b7c54c42354b --- /dev/null +++ b/examples/ocr/seq2seq_attn.py @@ -0,0 +1,333 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + +import numpy as np + +import paddle.fluid as fluid +import paddle.fluid.layers as layers +from paddle.fluid.layers import BeamSearchDecoder + +from hapi.text import RNNCell, RNN, DynamicDecode +from hapi.model import Model, Loss + + +class ConvBNPool(fluid.dygraph.Layer): + def __init__(self, + in_ch, + out_ch, + act="relu", + is_test=False, + pool=True, + use_cudnn=True): + super(ConvBNPool, self).__init__() + self.pool = pool + + filter_size = 3 + std = (2.0 / (filter_size**2 * in_ch))**0.5 + param_0 = fluid.ParamAttr( + initializer=fluid.initializer.Normal(0.0, std)) + + std = (2.0 / (filter_size**2 * out_ch))**0.5 + param_1 = fluid.ParamAttr( + initializer=fluid.initializer.Normal(0.0, std)) + + self.conv0 = fluid.dygraph.Conv2D( + in_ch, + out_ch, + 3, + padding=1, + param_attr=param_0, + bias_attr=False, + act=None, + use_cudnn=use_cudnn) + self.bn0 = fluid.dygraph.BatchNorm(out_ch, act=act) + self.conv1 = fluid.dygraph.Conv2D( + out_ch, + out_ch, + filter_size=3, + padding=1, + param_attr=param_1, + bias_attr=False, + act=None, + use_cudnn=use_cudnn) + self.bn1 = fluid.dygraph.BatchNorm(out_ch, act=act) + + if self.pool: + self.pool = fluid.dygraph.Pool2D( + pool_size=2, + pool_type='max', + pool_stride=2, + use_cudnn=use_cudnn, + ceil_mode=True) + + def forward(self, inputs): + out = self.conv0(inputs) + out = self.bn0(out) + out = self.conv1(out) + out = self.bn1(out) + if self.pool: + out = self.pool(out) + return out + + +class CNN(fluid.dygraph.Layer): + def __init__(self, in_ch=1, is_test=False): + super(CNN, self).__init__() + self.conv_bn1 = ConvBNPool(in_ch, 16) + self.conv_bn2 = ConvBNPool(16, 32) + self.conv_bn3 = ConvBNPool(32, 64) + self.conv_bn4 = ConvBNPool(64, 128, pool=False) + + def forward(self, inputs): + conv = self.conv_bn1(inputs) + conv = self.conv_bn2(conv) + conv = self.conv_bn3(conv) + conv = self.conv_bn4(conv) + return conv + + +class GRUCell(RNNCell): + def __init__(self, + input_size, + hidden_size, + param_attr=None, + bias_attr=None, + gate_activation='sigmoid', + candidate_activation='tanh', + origin_mode=False): + super(GRUCell, self).__init__() + self.hidden_size = hidden_size + self.fc_layer = fluid.dygraph.Linear( + input_size, + hidden_size * 3, + param_attr=param_attr, + bias_attr=False) + + self.gru_unit = fluid.dygraph.GRUUnit( + hidden_size * 3, + param_attr=param_attr, + bias_attr=bias_attr, + activation=candidate_activation, + gate_activation=gate_activation, + origin_mode=origin_mode) + + def forward(self, inputs, states): + # step_outputs, new_states = cell(step_inputs, states) + # for GRUCell, `step_outputs` and `new_states` both are hidden + x = self.fc_layer(inputs) + hidden, _, _ = self.gru_unit(x, states) + return hidden, hidden + + @property + def state_shape(self): + return [self.hidden_size] + + +class Encoder(fluid.dygraph.Layer): + def __init__( + self, + in_channel=1, + rnn_hidden_size=200, + decoder_size=128, + is_test=False, ): + super(Encoder, self).__init__() + self.rnn_hidden_size = rnn_hidden_size + + self.backbone = CNN(in_ch=in_channel, is_test=is_test) + + para_attr = fluid.ParamAttr( + initializer=fluid.initializer.Normal(0.0, 0.02)) + bias_attr = fluid.ParamAttr( + initializer=fluid.initializer.Normal(0.0, 0.02), learning_rate=2.0) + self.gru_fwd = RNN(cell=GRUCell( + input_size=128 * 6, + hidden_size=rnn_hidden_size, + param_attr=para_attr, + bias_attr=bias_attr, + candidate_activation='relu'), + is_reverse=False, + time_major=False) + self.gru_bwd = RNN(cell=GRUCell( + input_size=128 * 6, + hidden_size=rnn_hidden_size, + param_attr=para_attr, + bias_attr=bias_attr, + candidate_activation='relu'), + is_reverse=True, + time_major=False) + self.encoded_proj_fc = fluid.dygraph.Linear( + rnn_hidden_size * 2, decoder_size, bias_attr=False) + + def forward(self, inputs): + conv_features = self.backbone(inputs) + conv_features = fluid.layers.transpose( + conv_features, perm=[0, 3, 1, 2]) + + n, w, c, h = conv_features.shape + seq_feature = fluid.layers.reshape(conv_features, [0, -1, c * h]) + + gru_fwd, _ = self.gru_fwd(seq_feature) + gru_bwd, _ = self.gru_bwd(seq_feature) + + encoded_vector = fluid.layers.concat(input=[gru_fwd, gru_bwd], axis=2) + encoded_proj = self.encoded_proj_fc(encoded_vector) + return gru_bwd, encoded_vector, encoded_proj + + +class Attention(fluid.dygraph.Layer): + """ + Neural Machine Translation by Jointly Learning to Align and Translate. + https://arxiv.org/abs/1409.0473 + """ + + def __init__(self, decoder_size): + super(Attention, self).__init__() + self.fc1 = fluid.dygraph.Linear( + decoder_size, decoder_size, bias_attr=False) + self.fc2 = fluid.dygraph.Linear(decoder_size, 1, bias_attr=False) + + def forward(self, encoder_vec, encoder_proj, decoder_state): + # alignment model, single-layer multilayer perceptron + decoder_state = self.fc1(decoder_state) + decoder_state = fluid.layers.unsqueeze(decoder_state, [1]) + + e = fluid.layers.elementwise_add(encoder_proj, decoder_state) + e = fluid.layers.tanh(e) + + att_scores = self.fc2(e) + att_scores = fluid.layers.squeeze(att_scores, [2]) + att_scores = fluid.layers.softmax(att_scores) + + context = fluid.layers.elementwise_mul( + x=encoder_vec, y=att_scores, axis=0) + context = fluid.layers.reduce_sum(context, dim=1) + return context + + +class DecoderCell(RNNCell): + def __init__(self, encoder_size=200, decoder_size=128): + super(DecoderCell, self).__init__() + self.attention = Attention(decoder_size) + self.gru_cell = GRUCell( + input_size=encoder_size * 2 + decoder_size, + hidden_size=decoder_size) + + def forward(self, current_word, states, encoder_vec, encoder_proj): + context = self.attention(encoder_vec, encoder_proj, states) + decoder_inputs = fluid.layers.concat([current_word, context], axis=1) + hidden, _ = self.gru_cell(decoder_inputs, states) + return hidden, hidden + + +class Decoder(fluid.dygraph.Layer): + def __init__(self, num_classes, emb_dim, encoder_size, decoder_size): + super(Decoder, self).__init__() + self.decoder_attention = RNN(DecoderCell(encoder_size, decoder_size)) + self.fc = fluid.dygraph.Linear( + decoder_size, num_classes + 2, act='softmax') + + def forward(self, target, initial_states, encoder_vec, encoder_proj): + out, _ = self.decoder_attention( + target, + initial_states=initial_states, + encoder_vec=encoder_vec, + encoder_proj=encoder_proj) + pred = self.fc(out) + return pred + + +class Seq2SeqAttModel(Model): + def __init__( + self, + in_channle=1, + encoder_size=200, + decoder_size=128, + emb_dim=128, + num_classes=None, ): + super(Seq2SeqAttModel, self).__init__() + self.encoder = Encoder(in_channle, encoder_size, decoder_size) + self.fc = fluid.dygraph.Linear( + input_dim=encoder_size, + output_dim=decoder_size, + bias_attr=False, + act='relu') + self.embedding = fluid.dygraph.Embedding( + [num_classes + 2, emb_dim], dtype='float32') + self.decoder = Decoder(num_classes, emb_dim, encoder_size, + decoder_size) + + def forward(self, inputs, target): + gru_backward, encoded_vector, encoded_proj = self.encoder(inputs) + decoder_boot = self.fc(gru_backward[:, 0]) + trg_embedding = self.embedding(target) + prediction = self.decoder(trg_embedding, decoder_boot, encoded_vector, + encoded_proj) + return prediction + + +class Seq2SeqAttInferModel(Seq2SeqAttModel): + def __init__( + self, + in_channle=1, + encoder_size=200, + decoder_size=128, + emb_dim=128, + num_classes=None, + beam_size=0, + bos_id=0, + eos_id=1, + max_out_len=20, ): + super(Seq2SeqAttInferModel, self).__init__( + in_channle, encoder_size, decoder_size, emb_dim, num_classes) + self.beam_size = beam_size + # dynamic decoder for inference + decoder = BeamSearchDecoder( + self.decoder.decoder_attention.cell, + start_token=bos_id, + end_token=eos_id, + beam_size=beam_size, + embedding_fn=self.embedding, + output_fn=self.decoder.fc) + self.infer_decoder = DynamicDecode( + decoder, max_step_num=max_out_len, is_test=True) + + def forward(self, inputs, *args): + gru_backward, encoded_vector, encoded_proj = self.encoder(inputs) + decoder_boot = self.fc(gru_backward[:, 0]) + + if self.beam_size: + # Tile the batch dimension with beam_size + encoded_vector = BeamSearchDecoder.tile_beam_merge_with_batch( + encoded_vector, self.beam_size) + encoded_proj = BeamSearchDecoder.tile_beam_merge_with_batch( + encoded_proj, self.beam_size) + # dynamic decoding with beam search + rs, _ = self.infer_decoder( + inits=decoder_boot, + encoder_vec=encoded_vector, + encoder_proj=encoded_proj) + return rs + + +class WeightCrossEntropy(Loss): + def __init__(self): + super(WeightCrossEntropy, self).__init__(average=False) + + def forward(self, outputs, labels): + predict, (label, mask) = outputs[0], labels + loss = layers.cross_entropy(predict, label=label) + loss = layers.elementwise_mul(loss, mask, axis=0) + loss = layers.reduce_sum(loss) + return loss diff --git a/examples/ocr/train.py b/examples/ocr/train.py new file mode 100644 index 0000000000000000000000000000000000000000..d72173dfde7791b53af80f04697f8e3defd01445 --- /dev/null +++ b/examples/ocr/train.py @@ -0,0 +1,138 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + +import os +import sys +import random +import numpy as np + +import argparse +import functools + +import paddle.fluid.profiler as profiler +import paddle.fluid as fluid + +from hapi.model import Input, set_device +from hapi.vision.transforms import BatchCompose + +from utility import add_arguments, print_arguments +from utility import SeqAccuracy, LoggerCallBack +from seq2seq_attn import Seq2SeqAttModel, WeightCrossEntropy +import data + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +# yapf: disable +add_arg('batch_size', int, 32, "Minibatch size.") +add_arg('epoch', int, 30, "Epoch number.") +add_arg('num_workers', int, 0, "workers number.") +add_arg('lr', float, 0.001, "Learning rate.") +add_arg('lr_decay_strategy', str, "", "Learning rate decay strategy.") +add_arg('checkpoint_path', str, "checkpoint", "The directory the model to be saved to.") +add_arg('train_images', str, None, "The directory of images to be used for training.") +add_arg('train_list', str, None, "The list file of images to be used for training.") +add_arg('test_images', str, None, "The directory of images to be used for test.") +add_arg('test_list', str, None, "The list file of images to be used for training.") +add_arg('resume_path', str, None, "The init model file of directory.") +add_arg('use_gpu', bool, True, "Whether use GPU to train.") +# model hyper paramters +add_arg('encoder_size', int, 200, "Encoder size.") +add_arg('decoder_size', int, 128, "Decoder size.") +add_arg('embedding_dim', int, 128, "Word vector dim.") +add_arg('num_classes', int, 95, "Number classes.") +add_arg('gradient_clip', float, 5.0, "Gradient clip value.") +add_arg('dynamic', bool, False, "Whether to use dygraph.") +# yapf: enable + + +def main(FLAGS): + device = set_device("gpu" if FLAGS.use_gpu else "cpu") + fluid.enable_dygraph(device) if FLAGS.dynamic else None + + model = Seq2SeqAttModel( + encoder_size=FLAGS.encoder_size, + decoder_size=FLAGS.decoder_size, + emb_dim=FLAGS.embedding_dim, + num_classes=FLAGS.num_classes) + + lr = FLAGS.lr + if FLAGS.lr_decay_strategy == "piecewise_decay": + learning_rate = fluid.layers.piecewise_decay( + [200000, 250000], [lr, lr * 0.1, lr * 0.01]) + else: + learning_rate = lr + grad_clip = fluid.clip.GradientClipByGlobalNorm(FLAGS.gradient_clip) + optimizer = fluid.optimizer.Adam( + learning_rate=learning_rate, + parameter_list=model.parameters(), + grad_clip=grad_clip) + + # yapf: disable + inputs = [ + Input([None,1,48,384], "float32", name="pixel"), + Input([None, None], "int64", name="label_in"), + ] + labels = [ + Input([None, None], "int64", name="label_out"), + Input([None, None], "float32", name="mask"), + ] + # yapf: enable + + model.prepare( + optimizer, + WeightCrossEntropy(), + SeqAccuracy(), + inputs=inputs, + labels=labels) + + train_dataset = data.train() + train_collate_fn = BatchCompose( + [data.Resize(), data.Normalize(), data.PadTarget()]) + train_sampler = data.BatchSampler( + train_dataset, batch_size=FLAGS.batch_size, shuffle=True) + train_loader = fluid.io.DataLoader( + train_dataset, + batch_sampler=train_sampler, + places=device, + num_workers=FLAGS.num_workers, + return_list=True, + collate_fn=train_collate_fn) + test_dataset = data.test() + test_collate_fn = BatchCompose( + [data.Resize(), data.Normalize(), data.PadTarget()]) + test_sampler = data.BatchSampler( + test_dataset, + batch_size=FLAGS.batch_size, + drop_last=False, + shuffle=False) + test_loader = fluid.io.DataLoader( + test_dataset, + batch_sampler=test_sampler, + places=device, + num_workers=0, + return_list=True, + collate_fn=test_collate_fn) + + model.fit(train_data=train_loader, + eval_data=test_loader, + epochs=FLAGS.epoch, + save_dir=FLAGS.checkpoint_path, + callbacks=[LoggerCallBack(10, 2, FLAGS.batch_size)]) + + +if __name__ == '__main__': + FLAGS = parser.parse_args() + print_arguments(FLAGS) + main(FLAGS) diff --git a/examples/ocr/utility.py b/examples/ocr/utility.py new file mode 100644 index 0000000000000000000000000000000000000000..d47b3f17d16452c1292402abc15b534eec4b3459 --- /dev/null +++ b/examples/ocr/utility.py @@ -0,0 +1,186 @@ +"""Contains common utility functions.""" +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import distutils.util +import numpy as np +import paddle.fluid as fluid +import six + +from hapi.metrics import Metric +from hapi.callbacks import ProgBarLogger + + +def print_arguments(args): + """Print argparse's arguments. + + Usage: + + .. code-block:: python + + parser = argparse.ArgumentParser() + parser.add_argument("name", default="Jonh", type=str, help="User name.") + args = parser.parse_args() + print_arguments(args) + + :param args: Input argparse.Namespace for printing. + :type args: argparse.Namespace + """ + print("----------- Configuration Arguments -----------") + for arg, value in sorted(six.iteritems(vars(args))): + print("%s: %s" % (arg, value)) + print("------------------------------------------------") + + +def add_arguments(argname, type, default, help, argparser, **kwargs): + """Add argparse's argument. + + Usage: + + .. code-block:: python + + parser = argparse.ArgumentParser() + add_argument("name", str, "Jonh", "User name.", parser) + args = parser.parse_args() + """ + type = distutils.util.strtobool if type == bool else type + argparser.add_argument( + "--" + argname, + default=default, + type=type, + help=help + ' Default: %(default)s.', + **kwargs) + + +class SeqAccuracy(Metric): + def __init__(self, name=None, *args, **kwargs): + super(SeqAccuracy, self).__init__(*args, **kwargs) + self._name = 'seq_acc' + self.reset() + + def add_metric_op(self, output, label, mask, *args, **kwargs): + pred = fluid.layers.flatten(output, axis=2) + score, topk = fluid.layers.topk(pred, 1) + return topk, label, mask + + def update(self, topk, label, mask, *args, **kwargs): + topk = topk.reshape(label.shape[0], -1) + seq_len = np.sum(mask, -1) + acc = 0 + for i in range(label.shape[0]): + l = int(seq_len[i] - 1) + pred = topk[i][:l - 1] + ref = label[i][:l - 1] + if np.array_equal(pred, ref): + self.total += 1 + acc += 1 + self.count += 1 + return float(acc) / label.shape[0] + + def reset(self): + self.total = 0. + self.count = 0. + + def accumulate(self): + return float(self.total) / self.count + + def name(self): + return self._name + + +class LoggerCallBack(ProgBarLogger): + def __init__(self, log_freq=1, verbose=2, train_bs=None, eval_bs=None): + super(LoggerCallBack, self).__init__(log_freq, verbose) + self.train_bs = train_bs + self.eval_bs = eval_bs if eval_bs else train_bs + + def on_train_batch_end(self, step, logs=None): + logs = logs or {} + logs['loss'] = [l / self.train_bs for l in logs['loss']] + super(LoggerCallBack, self).on_train_batch_end(step, logs) + + def on_epoch_end(self, epoch, logs=None): + logs = logs or {} + logs['loss'] = [l / self.train_bs for l in logs['loss']] + super(LoggerCallBack, self).on_epoch_end(epoch, logs) + + def on_eval_batch_end(self, step, logs=None): + logs = logs or {} + logs['loss'] = [l / self.eval_bs for l in logs['loss']] + super(LoggerCallBack, self).on_eval_batch_end(step, logs) + + def on_eval_end(self, logs=None): + logs = logs or {} + logs['loss'] = [l / self.eval_bs for l in logs['loss']] + super(LoggerCallBack, self).on_eval_end(logs) + + +def index2word(ids): + return [chr(int(k + 33)) for k in ids] + + +def postprocess(seq, bos_idx=0, eos_idx=1): + if type(seq) is np.ndarray: + seq = seq.tolist() + eos_pos = len(seq) - 1 + for i, idx in enumerate(seq): + if idx == eos_idx: + eos_pos = i + break + seq = [ + idx for idx in seq[:eos_pos + 1] if idx != bos_idx and idx != eos_idx + ] + return seq + + +class SeqBeamAccuracy(Metric): + def __init__(self, name=None, *args, **kwargs): + super(SeqBeamAccuracy, self).__init__(*args, **kwargs) + self._name = 'seq_acc' + self.reset() + + def add_metric_op(self, output, label, mask, *args, **kwargs): + return output, label, mask + + def update(self, preds, labels, masks, *args, **kwargs): + preds = preds[:, :, np.newaxis] if len(preds.shape) == 2 else preds + preds = np.transpose(preds, [0, 2, 1]) + seq_len = np.sum(masks, -1) + acc = 0 + for i in range(labels.shape[0]): + l = int(seq_len[i] - 1) + #ref = labels[i][: l - 1] + ref = np.array(postprocess(labels[i])) + pred = preds[i] + for idx, beam in enumerate(pred): + beam_pred = np.array(postprocess(beam)) + if np.array_equal(beam_pred, ref): + self.total += 1 + acc += 1 + break + self.count += 1 + return float(acc) / labels.shape[0] + + def reset(self): + self.total = 0. + self.count = 0. + + def accumulate(self): + return float(self.total) / self.count + + def name(self): + return self._name diff --git a/examples/sequence_tagging/README.md b/examples/sequence_tagging/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0bcb9ff859a0ab593abcc6769ec671b15581f6c9 --- /dev/null +++ b/examples/sequence_tagging/README.md @@ -0,0 +1,218 @@ +# 序列标注任务 + +## 1. 简介 + +Sequence Tagging,是一个序列标注模型,模型可用于实现,分词、词性标注、专名识别等序列标注任务。我们在自建的数据集上对分词、词性标注、专名识别进行整体的评估效果(即联合标签模型),具体数值见下表; + +|模型|Precision|Recall|F1-score| +|:-:|:-:|:-:|:-:| +|Lexical Analysis|88.26%|89.20%|88.73%| + +## 2. 快速开始 + +### 安装说明 + +#### 1.PaddlePaddle 安装 + +本项目依赖 PaddlePaddle 1.7 及以上版本和PaddleHub 1.0.0及以上版本 ,PaddlePaddle安装请参考官网 [快速安装](http://www.paddlepaddle.org/paddle#quick-start),PaddleHub安装参考 [PaddleHub](https://github.com/PaddlePaddle/PaddleHub)。 + +> Warning: GPU 和 CPU 版本的 PaddlePaddle 分别是 paddlepaddle-gpu 和 paddlepaddle,请安装时注意区别。 + +#### 2. 克隆代码 +克隆工具集代码库到本地 +```bash + git clone https://github.com/PaddlePaddle/hapi.git + cd hapi/sequence_tagging +``` + +#### 3. 环境依赖 +PaddlePaddle的版本要求是:Python 2 版本是 2.7.15+、Python 3 版本是 3.5.1+/3.6/3.7。sequence tagging的代码可支持Python2/3,无具体版本限制 + +### 数据准备 + +#### 1. 快速下载 + +本项目涉及的**数据集**和**训练模型**的数据可通过执行以下脚本进行快速下载,若仅需使用部分数据或者模型,可根据需要参照2和3进行下载 + +```bash +python downloads.py all +``` +或在支持运行shell脚本的环境下执行: +```bash +sh downloads.sh +``` + +#### 2. 训练数据集 + +下载数据集文件,解压后会生成 `./data/` 文件夹 +```bash +python downloads.py dataset +``` + +#### 3. 已训练模型 + +我们开源了在自建数据集上训练的词法分析模型,可供用户直接使用,可通过下述链接进行下载: +```bash +# download baseline model +python downloads.py model +``` + +### 模型训练 +基于示例的数据集,可通过下面的命令,在训练集 `./data/train.tsv` 上进行训练; + +GPU上单卡训练 +``` +# setting visible devices for training +export CUDA_VISIBLE_DEVICES=0 + +python -u train.py \ + --device gpu \ + --dynamic False + +# --device: 使用gpu设备还是cpu设备 +# --dynamic: 是否使用动态图模式进行训练,如果使用静态图训练,设置为True, 动态图设置为False +``` + +GPU上多卡训练 + +``` +# setting visible devices for training +export CUDA_VISIBLE_DEVICES=0,1,2,3 + +python -m paddle.distributed.launch --selected_gpus=0,1,2,3 train.py \ + --device gpu \ + --dynamic False + +# --device: 使用gpu设备还是cpu设备 +# --dynamic: 是否使用动态图模式进行训练,如果使用静态图训练,设置为True, 动态图设置为False +``` + +CPU上训练 + +``` +python -u train.py \ + --device cpu \ + --dynamic False + +# --device: 使用gpu设备还是cpu设备 +# --dynamic: 是否使用动态图模式进行训练,如果使用静态图训练,设置为True, 动态图设置为False +``` + +### 模型预测 + +加载已有的模型,对未知的数据进行预测 +```bash +python predict.py \ + --init_from_checkpoint model_baseline/params \ + --output_file predict.result \ + --mode predict \ + --device cpu \ + --dynamic False + +# --init_from_checkpoint: 初始化模型 +# --output_file: 预测结果文件 +# --device: 使用gpu还是cpu设备 +# --mode: 开启模式, 设置为train时,进行训练,设置为predict时进行预测 +# --dynamic: 是否使用动态图模式进行训练,如果使用静态图训练,设置为True, 动态图设置为False +``` + +### 模型评估 + +我们基于自建的数据集训练了一个词法分析的模型,可以直接用这个模型对测试集 `./data/test.tsv` 进行验证, +```bash +# baseline model +python eval.py \ + --init_from_checkpoint ./model_baseline/params \ + --mode predict \ + --device cpu \ + --dynamic False + +# --init_from_checkpoint: 初始化模型 +# --device: 使用gpu还是cpu设备 +# --mode: 开启模式, 设置为train时,进行训练,设置为predict时进行预测 +# --dynamic: 是否使用动态图模式进行训练,如果使用静态图训练,设置为True, 动态图设置为False +``` + + +## 3. 进阶使用 + +### 任务定义与建模 +序列标注任务的输入是一个字符串(我们后面使用『句子』来指代它),而输出是句子中的词边界和类别。序列标注是词法分析的经典建模方式。我们使用基于 GRU 的网络结构学习特征,将学习到的特征接入 CRF 解码层完成序列标注。CRF 解码层本质上是将传统 CRF 中的线性模型换成了非线性神经网络,基于句子级别的似然概率,因而能够更好的解决标记偏置问题。模型要点如下。 + +1. 输入采用 one-hot 方式表示,每个字以一个 id 表示 +2. one-hot 序列通过字表,转换为实向量表示的字向量序列; +3. 字向量序列作为双向 GRU 的输入,学习输入序列的特征表示,得到新的特性表示序列,我们堆叠了两层双向GRU以增加学习能力; +4. CRF 以 GRU 学习到的特征为输入,以标记序列为监督信号,实现序列标注。 + +可供用户下载的自有数据是对分词、词性标注、专名识别同时标注的联合数据集,进行词性和专名类别标签集合如下表,其中词性标签 24 个(小写字母),专名类别标签 4 个(大写字母)。这里需要说明的是,人名、地名、机构名和时间四个类别,在上表中存在两套标签(PER / LOC / ORG / TIME 和 nr / ns / nt / t),被标注为第二套标签的词,是模型判断为低置信度的人名、地名、机构名和时间词。开发者可以基于这两套标签,在四个类别的准确、召回之间做出自己的权衡。 + +| 标签 | 含义 | 标签 | 含义 | 标签 | 含义 | 标签 | 含义 | +| ---- | -------- | ---- | -------- | ---- | -------- | ---- | -------- | +| n | 普通名词 | f | 方位名词 | s | 处所名词 | t | 时间 | +| nr | 人名 | ns | 地名 | nt | 机构名 | nw | 作品名 | +| nz | 其他专名 | v | 普通动词 | vd | 动副词 | vn | 名动词 | +| a | 形容词 | ad | 副形词 | an | 名形词 | d | 副词 | +| m | 数量词 | q | 量词 | r | 代词 | p | 介词 | +| c | 连词 | u | 助词 | xc | 其他虚词 | w | 标点符号 | +| PER | 人名 | LOC | 地名 | ORG | 机构名 | TIME | 时间 | + +### 模型原理介绍 +上面介绍的模型原理如下图所示:
+ +

+
+Overall Architecture of GRU-CRF-MODEL +

+ +### 数据格式 +训练使用的数据可以由用户根据实际的应用场景,自己组织数据。除了第一行是 `text_a\tlabel` 固定的开头,后面的每行数据都是由两列组成,以制表符分隔,第一列是 utf-8 编码的中文文本,以 `\002` 分割,第二列是对应每个字的标注,以 `\002` 分隔。我们采用 IOB2 标注体系,即以 X-B 作为类型为 X 的词的开始,以 X-I 作为类型为 X 的词的持续,以 O 表示不关注的字(实际上,在词性、专名联合标注中,不存在 O )。示例如下: + +```text +除\002了\002他\002续\002任\002十\002二\002届\002政\002协\002委\002员\002,\002马\002化\002腾\002,\002雷\002军\002,\002李\002彦\002宏\002也\002被\002推\002选\002为\002新\002一\002届\002全\002国\002人\002大\002代\002表\002或\002全\002国\002政\002协\002委\002员 p-B\002p-I\002r-B\002v-B\002v-I\002m-B\002m-I\002m-I\002ORG-B\002ORG-I\002n-B\002n-I\002w-B\002PER-B\002PER-I\002PER-I\002w-B\002PER-B\002PER-I\002w-B\002PER-B\002PER-I\002PER-I\002d-B\002p-B\002v-B\002v-I\002v-B\002a-B\002m-B\002m-I\002ORG-B\002ORG-I\002ORG-I\002ORG-I\002n-B\002n-I\002c-B\002n-B\002n-I\002ORG-B\002ORG-I\002n-B\002n-I +``` + ++ 我们随同代码一并发布了完全版的模型和相关的依赖数据。但是,由于模型的训练数据过于庞大,我们没有发布训练数据,仅在`data`目录下放置少数样本用以示例输入数据格式。 + ++ 模型依赖数据包括: + 1. 输入文本的词典,在`conf`目录下,对应`word.dic` + 2. 对输入文本中特殊字符进行转换的字典,在`conf`目录下,对应`q2b.dic` + 3. 标记标签的词典,在`conf`目录下,对应`tag.dic` + ++ 在训练和预测阶段,我们都需要进行原始数据的预处理,具体处理工作包括: + + 1. 从原始数据文件中抽取出句子和标签,构造句子序列和标签序列 + 2. 将句子序列中的特殊字符进行转换 + 3. 依据词典获取词对应的整数索引 + +### 代码结构说明 +```text +├── README.md # 本文档 +├── data/ # 存放数据集的目录 +├── conf/ # 词典及程序默认配置的目录 +├── images/ # 文档图片存放位置 +├── utils/ # 常用工具函数 +├── train.py # 训练脚本 +├── predict.py # 预测脚本 +├── eval.py # 词法分析评估的脚本 +├── downloads.py # 用于下载数据和模型的脚本 +├── downloads.sh # 用于下载数据和模型的脚本 +└──reader.py # 文件读取相关函数 +``` + + +## 4. 其他 +### 在论文中引用 sequence tagging + +如果您的学术工作成果中使用了 sequence tagging,请您增加下述引用。我们非常欣慰sequence tagging模型能够对您的学术工作带来帮助。 + +```text +@article{jiao2018LAC, + title={Chinese Lexical Analysis with Deep Bi-GRU-CRF Network}, + author={Jiao, Zhenyu and Sun, Shuqi and Sun, Ke}, + journal={arXiv preprint arXiv:1807.01882}, + year={2018}, + url={https://arxiv.org/abs/1807.01882} +} +``` +### 如何贡献代码 +如果你可以修复某个 issue 或者增加一个新功能,欢迎给我们提交PR。如果对应的PR被接受了,我们将根据贡献的质量和难度 进行打分(0-5分,越高越好)。如果你累计获得了 10 分,可以联系我们获得面试机会或为你写推荐信。 diff --git a/examples/sequence_tagging/conf/q2b.dic b/examples/sequence_tagging/conf/q2b.dic new file mode 100755 index 0000000000000000000000000000000000000000..d1f14691e228be8a5d5d1385ad968bcafab0c07a --- /dev/null +++ b/examples/sequence_tagging/conf/q2b.dic @@ -0,0 +1,172 @@ +  +、 , +。 . +— - +~ ~ +‖ | +… . +‘ ' +’ ' +“ " +” " +〔 ( +〕 ) +〈 < +〉 > +「 ' +」 ' +『 " +』 " +〖 [ +〗 ] +【 [ +】 ] +∶ : +$ $ +! ! +" " +# # +% % +& & +' ' +( ( +) ) +* * ++ + +, , +- - +. . +/ / +0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 +: : +; ; +< < += = +> > +? ? +@ @ +A a +B b +C c +D d +E e +F f +G g +H h +I i +J j +K k +L l +M m +N n +O o +P p +Q q +R r +S s +T t +U u +V v +W w +X x +Y y +Z z +[ [ +\ \ +] ] +^ ^ +_ _ +` ` +a a +b b +c c +d d +e e +f f +g g +h h +i i +j j +k k +l l +m m +n n +o o +p p +q q +r r +s s +t t +u u +v v +w w +x x +y y +z z +{ { +| | +} } + ̄ ~ +〝 " +〞 " +﹐ , +﹑ , +﹒ . +﹔ ; +﹕ : +﹖ ? +﹗ ! +﹙ ( +﹚ ) +﹛ { +﹜ { +﹝ [ +﹞ ] +﹟ # +﹠ & +﹡ * +﹢ + +﹣ - +﹤ < +﹥ > +﹦ = +﹨ \ +﹩ $ +﹪ % +﹫ @ + , +A a +B b +C c +D d +E e +F f +G g +H h +I i +J j +K k +L l +M m +N n +O o +P p +Q q +R r +S s +T t +U u +V v +W w +X x +Y y +Z z diff --git a/examples/sequence_tagging/conf/tag.dic b/examples/sequence_tagging/conf/tag.dic new file mode 100755 index 0000000000000000000000000000000000000000..753fa9670e92b45a9daa54ddcb1e2f06491a792e --- /dev/null +++ b/examples/sequence_tagging/conf/tag.dic @@ -0,0 +1,57 @@ +0 a-B +1 a-I +2 ad-B +3 ad-I +4 an-B +5 an-I +6 c-B +7 c-I +8 d-B +9 d-I +10 f-B +11 f-I +12 m-B +13 m-I +14 n-B +15 n-I +16 nr-B +17 nr-I +18 ns-B +19 ns-I +20 nt-B +21 nt-I +22 nw-B +23 nw-I +24 nz-B +25 nz-I +26 p-B +27 p-I +28 q-B +29 q-I +30 r-B +31 r-I +32 s-B +33 s-I +34 t-B +35 t-I +36 u-B +37 u-I +38 v-B +39 v-I +40 vd-B +41 vd-I +42 vn-B +43 vn-I +44 w-B +45 w-I +46 xc-B +47 xc-I +48 PER-B +49 PER-I +50 LOC-B +51 LOC-I +52 ORG-B +53 ORG-I +54 TIME-B +55 TIME-I +56 O diff --git a/examples/sequence_tagging/conf/word.dic b/examples/sequence_tagging/conf/word.dic new file mode 100755 index 0000000000000000000000000000000000000000..d0ec32491250c8da85800069e4ca7260c6c3700f --- /dev/null +++ b/examples/sequence_tagging/conf/word.dic @@ -0,0 +1,20940 @@ +0 a +1 e +2 i +3 n +4 o +5 s +6 r +7 t +8 l +9 0 +10 u +11 c +12 1 +13 d +14 m +15 h +16 g +17 2 +18 p +19 b +20 y +21 5 +22 3 +23 8 +24 6 +25 k +26 A +27 4 +28 9 +29 f +30 7 +31 S +32 v +33 E +34 w +35 z +36 C +37 x +38 T +39 I +40 j +41 M +42 R +43 O +44 D +45 L +46 N +47 B +48 P +49 H +50 G +51 李 +52 F +53 K +54 王 +55 张 +56 q +57 U +58 刘 +59 陈 +60 W +61 Y +62 V +63 斯 +64 文 +65 X +66 J +67 Z +68 华 +69 明 +70 尔 +71 林 +72 德 +73 晓 +74 杨 +75 金 +76 Q +77 克 +78 小 +79 志 +80 国 +81 海 +82 丽 +83 平 +84 玉 +85 黄 +86 吴 +87 建 +88 特 +89 拉 +90 子 +91 赵 +92 利 +93 马 +94 军 +95 周 +96 亚 +97 伟 +98 东 +99 红 +100 龙 +101 春 +102 云 +103 生 +104 朱 +105 孙 +106 徐 +107 永 +108 达 +109 美 +110 安 +111 杰 +112 卡 +113 天 +114 新 +115 罗 +116 里 +117 大 +118 光 +119 波 +120 家 +121 成 +122 福 +123 高 +124 胡 +125 荣 +126 英 +127 阿 +128 思 +129 立 +130 瑞 +131 峰 +132 宝 +133 郭 +134 清 +135 兰 +136 西 +137 山 +138 维 +139 爱 +140 宇 +141 佳 +142 辉 +143 俊 +144 雅 +145 庆 +146 尼 +147 梅 +148 格 +149 之 +150 一 +151 君 +152 忠 +153 强 +154 学 +155 世 +156 雪 +157 良 +158 民 +159 芳 +160 郑 +161 敏 +162 秀 +163 迪 +164 元 +165 洪 +166 祥 +167 泽 +168 中 +169 康 +170 科 +171 嘉 +172 正 +173 飞 +174 巴 +175 兴 +176 松 +177 恩 +178 江 +179 乐 +180 宏 +181 振 +182 斌 +183 路 +184 雨 +185 娜 +186 雷 +187 玲 +188 长 +189 多 +190 凯 +191 米 +192 加 +193 奇 +194 吉 +195 青 +196 武 +197 水 +198 布 +199 力 +200 燕 +201 纳 +202 白 +203 慧 +204 宋 +205 万 +206 莱 +207 勇 +208 丹 +209 威 +210 宁 +211 南 +212 士 +213 堂 +214 何 +215 普 +216 洛 +217 秋 +218 胜 +219 仁 +220 韩 +221 奥 +222 富 +223 丁 +224 月 +225 石 +226 方 +227 博 +228 森 +229 艳 +230 鹏 +231 刚 +232 凤 +233 诺 +234 阳 +235 涛 +236 叶 +237 香 +238 比 +239 曹 +240 少 +241 昌 +242 泰 +243 伊 +244 亮 +245 沈 +246 霞 +247 梁 +248 菲 +249 谢 +250 唐 +251 智 +252 梦 +253 希 +254 曼 +255 贝 +256 杜 +257 木 +258 花 +259 苏 +260 星 +261 萍 +262 心 +263 景 +264 超 +265 欣 +266 树 +267 广 +268 许 +269 伯 +270 来 +271 夫 +272 塔 +273 卫 +274 义 +275 冯 +276 可 +277 田 +278 道 +279 圣 +280 汉 +281 三 +282 娟 +283 友 +284 夏 +285 基 +286 宗 +287 人 +288 贵 +289 婷 +290 鲁 +291 根 +292 艾 +293 静 +294 诗 +295 惠 +296 法 +297 蔡 +298 玛 +299 喜 +300 浩 +301 欧 +302 保 +303 潘 +304 风 +305 莉 +306 珍 +307 源 +308 桂 +309 远 +310 孟 +311 沙 +312 继 +313 顺 +314 锦 +315 邓 +316 贤 +317 书 +318 全 +319 得 +320 轩 +321 通 +322 吕 +323 才 +324 妮 +325 董 +326 曾 +327 彭 +328 雄 +329 琴 +330 旭 +331 袁 +332 城 +333 琳 +334 芬 +335 豪 +336 村 +337 卢 +338 剑 +339 蒋 +340 伦 +341 培 +342 魏 +343 瓦 +344 哈 +345 莫 +346 丝 +347 兵 +348 古 +349 银 +350 泉 +351 发 +352 传 +353 群 +354 若 +355 虎 +356 连 +357 如 +358 肖 +359 鑫 +360 盛 +361 先 +362 凡 +363 鸿 +364 图 +365 章 +366 姜 +367 琪 +368 启 +369 柏 +370 耀 +371 开 +372 依 +373 坤 +374 有 +375 萨 +376 怡 +377 崔 +378 川 +379 祖 +380 尚 +381 贾 +382 园 +383 素 +384 托 +385 淑 +386 健 +387 彦 +388 余 +389 双 +390 信 +391 麦 +392 范 +393 汪 +394 蒂 +395 程 +396 朝 +397 和 +398 然 +399 本 +400 塞 +401 灵 +402 秦 +403 铭 +404 河 +405 进 +406 姆 +407 百 +408 陆 +409 彬 +410 锋 +411 洁 +412 莲 +413 冰 +414 晨 +415 邦 +416 兆 +417 钟 +418 日 +419 绍 +420 铁 +421 怀 +422 赛 +423 善 +424 舒 +425 恒 +426 其 +427 行 +428 旺 +429 修 +430 易 +431 任 +432 莎 +433 . +434 顾 +435 艺 +436 丰 +437 皮 +438 帕 +439 延 +440 隆 +441 门 +442 太 +443 哲 +444 定 +445 蒙 +446 洋 +447 紫 +448 庄 +449 姚 +450 戴 +451 向 +452 顿 +453 礼 +454 权 +455 桥 +456 颖 +457 镇 +458 茂 +459 益 +460 露 +461 齐 +462 仙 +463 儿 +464 勒 +465 地 +466 真 +467 凌 +468 毛 +469 佩 +470 冬 +471 弗 +472 九 +473 润 +474 涵 +475 千 +476 史 +477 碧 +478 自 +479 承 +480 彩 +481 翔 +482 乔 +483 施 +484 治 +485 索 +486 会 +487 运 +488 卓 +489 毅 +490 年 +491 莹 +492 沃 +493 于 +494 孔 +495 薛 +496 业 +497 柳 +498 内 +499 钱 +500 廷 +501 登 +502 仕 +503 熙 +504 守 +505 敬 +506 孝 +507 雯 +508 增 +509 相 +510 时 +511 楠 +512 二 +513 竹 +514 谷 +515 不 +516 牛 +517 好 +518 京 +519 仲 +520 赫 +521 黑 +522 朗 +523 汤 +524 悦 +525 蓝 +526 公 +527 梓 +528 珠 +529 芝 +530 苑 +531 炳 +532 奎 +533 黎 +534 老 +535 佛 +536 谭 +537 鱼 +538 尹 +539 神 +540 温 +541 帝 +542 锡 +543 陶 +544 墨 +545 媛 +546 上 +547 乌 +548 常 +549 言 +550 熊 +551 化 +552 火 +553 升 +554 庭 +555 臣 +556 同 +557 头 +558 晶 +559 磊 +560 楚 +561 提 +562 优 +563 勤 +564 歌 +565 岩 +566 琦 +567 草 +568 韦 +569 库 +570 溪 +571 逸 +572 五 +573 政 +574 冠 +575 果 +576 跃 +577 辰 +578 柯 +579 戈 +580 廖 +581 薇 +582 琼 +583 申 +584 占 +585 湖 +586 辛 +587 代 +588 四 +589 严 +590 扎 +591 倩 +592 邹 +593 乃 +594 宜 +595 捷 +596 理 +597 洲 +598 鸣 +599 邱 +600 栋 +601 翠 +602 睿 +603 满 +604 容 +605 霖 +606 纪 +607 岳 +608 卿 +609 羽 +610 扬 +611 阁 +612 亦 +613 邵 +614 居 +615 久 +616 桑 +617 寿 +618 记 +619 北 +620 哥 +621 瑶 +622 埃 +623 彤 +624 贺 +625 菊 +626 湘 +627 诚 +628 宾 +629 郝 +630 非 +631 珊 +632 存 +633 无 +634 颜 +635 意 +636 盖 +637 é +638 霍 +639 初 +640 派 +641 野 +642 摩 +643 妍 +644 应 +645 口 +646 馨 +647 名 +648 坚 +649 品 +650 能 +651 寒 +652 纯 +653 蓉 +654 声 +655 葛 +656 航 +657 以 +658 坦 +659 童 +660 尤 +661 色 +662 晴 +663 令 +664 重 +665 聪 +666 芙 +667 亭 +668 柱 +669 合 +670 兹 +671 育 +672 音 +673 厚 +674 迈 +675 付 +676 奈 +677 语 +678 情 +679 宫 +680 列 +681 都 +682 钦 +683 炎 +684 必 +685 客 +686 蕾 +687 龚 +688 笑 +689 左 +690 作 +691 楼 +692 切 +693 娇 +694 宪 +695 韵 +696 农 +697 流 +698 密 +699 关 +700 岭 +701 干 +702 为 +703 夜 +704 氏 +705 微 +706 男 +707 显 +708 腾 +709 甘 +710 娅 +711 晋 +712 昊 +713 仪 +714 查 +715 焕 +716 姬 +717 印 +718 台 +719 苗 +720 钰 +721 甲 +722 勋 +723 车 +724 班 +725 锐 +726 原 +727 虹 +728 六 +729 段 +730 曲 +731 崇 +732 七 +733 茹 +734 萌 +735 & +736 巧 +737 州 +738 那 +739 标 +740 俞 +741 堡 +742 劳 +743 联 +744 土 +745 血 +746 起 +747 乡 +748 瑜 +749 岛 +750 池 +751 战 +752 师 +753 茶 +754 鹤 +755 彪 +756 鼎 +757 婉 +758 裕 +759 季 +760 耶 +761 闫 +762 冷 +763 昆 +764 知 +765 绿 +766 麟 +767 朵 +768 默 +769 贞 +770 什 +771 赖 +772 倪 +773 尧 +774 灿 +775 因 +776 官 +777 昭 +778 奕 +779 穆 +780 佐 +781 影 +782 荷 +783 功 +784 撒 +785 照 +786 井 +787 宽 +788 桐 +789 萱 +790 坊 +791 聚 +792 萧 +793 球 +794 璐 +795 晖 +796 鬼 +797 面 +798 字 +799 慕 +800 费 +801 越 +802 约 +803 曦 +804 后 +805 欢 +806 枫 +807 玮 +808 殷 +809 包 +810 念 +811 八 +812 汝 +813 翰 +814 黃 +815 奴 +816 手 +817 望 +818 茜 +819 儒 +820 傅 +821 气 +822 玄 +823 黛 +824 汇 +825 肯 +826 龍 +827 耐 +828 佑 +829 湾 +830 单 +831 岚 +832 舍 +833 热 +834 昂 +835 步 +836 钢 +837 环 +838 御 +839 缘 +840 伍 +841 下 +842 机 +843 乾 +844 魔 +845 前 +846 震 +847 巨 +848 线 +849 皓 +850 盈 +851 庞 +852 谦 +853 宣 +854 女 +855 体 +856 靖 +857 均 +858 劲 +859 济 +860 硕 +861 营 +862 帆 +863 妙 +864 瑟 +865 财 +866 出 +867 在 +868 炜 +869 味 +870 斗 +871 留 +872 深 +873 芸 +874 耿 +875 沛 +876 经 +877 管 +878 菜 +879 献 +880 外 +881 殿 +882 房 +883 焦 +884 骨 +885 点 +886 禹 +887 禄 +888 毕 +889 桃 +890 空 +891 侯 +892 鹰 +893 岗 +894 津 +895 雁 +896 帅 +897 妃 +898 复 +899 衣 +900 骏 +901 聂 +902 绪 +903 娃 +904 眼 +905 舟 +906 打 +907 分 +908 油 +909 者 +910 度 +911 角 +912 朴 +913 藤 +914 枝 +915 落 +916 亨 +917 游 +918 潮 +919 皇 +920 華 +921 梵 +922 滨 +923 禾 +924 郎 +925 洞 +926 精 +927 烈 +928 翁 +929 允 +930 塘 +931 璇 +932 事 +933 祝 +934 翼 +935 粉 +936 板 +937 赤 +938 盘 +939 昕 +940 蕊 +941 姿 +942 侠 +943 回 +944 á +945 秉 +946 征 +947 圆 +948 考 +949 茨 +950 娘 +951 邢 +952 电 +953 瑾 +954 酒 +955 寺 +956 尊 +957 冉 +958 边 +959 别 +960 刀 +961 工 +962 筱 +963 馬 +964 坡 +965 弘 +966 樊 +967 裴 +968 柔 +969 甫 +970 妹 +971 浦 +972 锁 +973 渊 +974 映 +975 当 +976 鲍 +977 见 +978 麻 +979 婧 +980 选 +981 牙 +982 烟 +983 翟 +984 钧 +985 屋 +986 冲 +987 放 +988 芹 +989 煜 +990 再 +991 尘 +992 司 +993 创 +994 恋 +995 幼 +996 展 +997 镜 +998 实 +999 浪 +1000 珂 +1001 爽 +1002 驰 +1003 鹿 +1004 吾 +1005 简 +1006 虫 +1007 网 +1008 从 +1009 è +1010 紅 +1011 食 +1012 赞 +1013 à +1014 柴 +1015 沟 +1016 魂 +1017 張 +1018 叔 +1019 端 +1020 入 +1021 闻 +1022 耳 +1023 慈 +1024 汀 +1025 集 +1026 郁 +1027 娥 +1028 死 +1029 伏 +1030 观 +1031 鸟 +1032 港 +1033 仓 +1034 芭 +1035 羊 +1036 纽 +1037 詹 +1038 唯 +1039 主 +1040 亿 +1041 旗 +1042 朋 +1043 蔚 +1044 商 +1045 斐 +1046 拜 +1047 凝 +1048 十 +1049 酷 +1050 片 +1051 性 +1052 烨 +1053 長 +1054 寨 +1055 蓓 +1056 动 +1057 魁 +1058 猫 +1059 迎 +1060 魚 +1061 敦 +1062 浮 +1063 東 +1064 用 +1065 霜 +1066 咏 +1067 采 +1068 狼 +1069 解 +1070 衡 +1071 录 +1072 府 +1073 琛 +1074 舞 +1075 街 +1076 澜 +1077 致 +1078 则 +1079 努 +1080 愛 +1081 举 +1082 淼 +1083 ì +1084 晟 +1085 肉 +1086 身 +1087 巷 +1088 伽 +1089 畅 +1090 典 +1091 首 +1092 ê +1093 斋 +1094 拿 +1095 沐 +1096 骆 +1097 丙 +1098 狗 +1099 瓜 +1100 內 +1101 细 +1102 í +1103 视 +1104 屯 +1105 臻 +1106 酸 +1107 速 +1108 頭 +1109 养 +1110 傲 +1111 牧 +1112 添 +1113 直 +1114 鸡 +1115 泊 +1116 勃 +1117 昱 +1118 巍 +1119 宸 +1120 式 +1121 茵 +1122 豆 +1123 休 +1124 半 +1125 场 +1126 蛇 +1127 灯 +1128 临 +1129 玺 +1130 煌 +1131 顶 +1132 次 +1133 忆 +1134 壮 +1135 社 +1136 席 +1137 物 +1138 陵 +1139 醉 +1140 毒 +1141 媚 +1142 風 +1143 积 +1144 佰 +1145 車 +1146 庚 +1147 过 +1148 猛 +1149 菁 +1150 母 +1151 两 +1152 龄 +1153 破 +1154 买 +1155 效 +1156 祺 +1157 發 +1158 玥 +1159 我 +1160 藏 +1161 县 +1162 号 +1163 坎 +1164 训 +1165 嘎 +1166 众 +1167 懿 +1168 ò +1169 底 +1170 党 +1171 門 +1172 尾 +1173 予 +1174 達 +1175 转 +1176 变 +1177 盟 +1178 是 +1179 阮 +1180 药 +1181 船 +1182 足 +1183 快 +1184 蘭 +1185 毓 +1186 乙 +1187 讯 +1188 杏 +1189 渡 +1190 陽 +1191 而 +1192 拓 +1193 象 +1194 喻 +1195 汗 +1196 眉 +1197 散 +1198 也 +1199 横 +1200 召 +1201 节 +1202 归 +1203 离 +1204 坪 +1205 位 +1206 制 +1207 暗 +1208 榕 +1209 今 +1210 量 +1211 器 +1212 仔 +1213 脱 +1214 所 +1215 交 +1216 结 +1217 轻 +1218 颂 +1219 现 +1220 又 +1221 界 +1222 病 +1223 封 +1224 祁 +1225 寅 +1226 岸 +1227 樱 +1228 阴 +1229 妖 +1230 澳 +1231 期 +1232 历 +1233 命 +1234 绮 +1235 彼 +1236 夕 +1237 丸 +1238 异 +1239 淳 +1240 苦 +1241 ó +1242 澄 +1243 求 +1244 開 +1245 杀 +1246 途 +1247 ü +1248 珀 +1249 调 +1250 沁 +1251 國 +1252 反 +1253 零 +1254 茗 +1255 0 +1256 族 +1257 蒲 +1258 泓 +1259 棠 +1260 引 +1261 弟 +1262 爾 +1263 牌 +1264 团 +1265 至 +1266 独 +1267 娴 +1268 迷 +1269 倒 +1270 瀚 +1271 铃 +1272 苍 +1273 淇 +1274 轮 +1275 狄 +1276 繁 +1277 樂 +1278 卜 +1279 氣 +1280 校 +1281 婚 +1282 断 +1283 霸 +1284 寶 +1285 固 +1286 豹 +1287 韬 +1288 隐 +1289 教 +1290 姓 +1291 1 +1292 极 +1293 带 +1294 走 +1295 羅 +1296 帮 +1297 亞 +1298 净 +1299 婕 +1300 难 +1301 挺 +1302 糖 +1303 招 +1304 凉 +1305 蜜 +1306 收 +1307 数 +1308 奧 +1309 雲 +1310 述 +1311 逊 +1312 杭 +1313 幽 +1314 脚 +1315 2 +1316 廉 +1317 桦 +1318 灰 +1319 医 +1320 与 +1321 陳 +1322 坝 +1323 芮 +1324 目 +1325 丘 +1326 舜 +1327 覃 +1328 潇 +1329 含 +1330 亲 +1331 铜 +1332 晚 +1333 支 +1334 猪 +1335 画 +1336 玖 +1337 ú +1338 店 +1339 项 +1340 渝 +1341 排 +1342 旋 +1343 笔 +1344 压 +1345 芷 +1346 报 +1347 強 +1348 乳 +1349 融 +1350 笛 +1351 冈 +1352 的 +1353 棋 +1354 领 +1355 瑛 +1356 屈 +1357 狂 +1358 院 +1359 峻 +1360 孤 +1361 谋 +1362 未 +1363 兔 +1364 鲜 +1365 衍 +1366 术 +1367 吟 +1368 间 +1369 计 +1370 觉 +1371 泥 +1372 乱 +1373 蝶 +1374 倍 +1375 卷 +1376 残 +1377 蓬 +1378 对 +1379 植 +1380 耕 +1381 盾 +1382 迦 +1383 缪 +1384 条 +1385 域 +1386 欲 +1387 杯 +1388 虚 +1389 习 +1390 爷 +1391 早 +1392 麗 +1393 郡 +1394 浅 +1395 退 +1396 纸 +1397 策 +1398 a +1399 活 +1400 窦 +1401 攀 +1402 屏 +1403 刺 +1404 泳 +1405 旦 +1406 补 +1407 防 +1408 姝 +1409 恺 +1410 晔 +1411 肤 +1412 軍 +1413 漫 +1414 失 +1415 滕 +1416 背 +1417 词 +1418 晗 +1419 表 +1420 來 +1421 涂 +1422 坑 +1423 誉 +1424 装 +1425 受 +1426 甜 +1427 機 +1428 邪 +1429 嘴 +1430 雍 +1431 棉 +1432 霄 +1433 针 +1434 荆 +1435 料 +1436 鼠 +1437 革 +1438 炫 +1439 将 +1440 绝 +1441 锅 +1442 取 +1443 電 +1444 宿 +1445 货 +1446 粤 +1447 葵 +1448 姐 +1449 介 +1450 爵 +1451 阔 +1452 涅 +1453 闪 +1454 听 +1455 央 +1456 掌 +1457 近 +1458 贡 +1459 沉 +1460 迟 +1461 改 +1462 配 +1463 庙 +1464 染 +1465 铮 +1466 阎 +1467 芯 +1468 汐 +1469 颐 +1470 蛋 +1471 护 +1472 部 +1473 孚 +1474 伤 +1475 狐 +1476 饭 +1477 鼓 +1478 娄 +1479 戚 +1480 略 +1481 啸 +1482 幸 +1483 滋 +1484 指 +1485 悠 +1486 妻 +1487 脉 +1488 丛 +1489 警 +1490 模 +1491 洗 +1492 奶 +1493 枪 +1494 恶 +1495 宴 +1496 靳 +1497 3 +1498 契 +1499 想 +1500 床 +1501 泪 +1502 随 +1503 市 +1504 探 +1505 焰 +1506 豫 +1507 點 +1508 住 +1509 淮 +1510 炮 +1511 圈 +1512 吐 +1513 楷 +1514 话 +1515 線 +1516 接 +1517 假 +1518 仇 +1519 射 +1520 蜀 +1521 婴 +1522 佟 +1523 追 +1524 奔 +1525 胶 +1526 晏 +1527 兽 +1528 跳 +1529 弦 +1530 质 +1531 體 +1532 扣 +1533 更 +1534 卉 +1535 梨 +1536 形 +1537 息 +1538 壁 +1539 共 +1540 菱 +1541 闵 +1542 渠 +1543 感 +1544 寻 +1545 盐 +1546 惊 +1547 珺 +1548 慎 +1549 去 +1550 狮 +1551 韶 +1552 雙 +1553 瞳 +1554 宅 +1555 座 +1556 总 +1557 趣 +1558 萬 +1559 短 +1560 奉 +1561 滩 +1562 飛 +1563 扶 +1564 折 +1565 筠 +1566 寇 +1567 ā +1568 尖 +1569 暖 +1570 弥 +1571 惜 +1572 涌 +1573 符 +1574 8 +1575 匠 +1576 嫣 +1577 璞 +1578 杉 +1579 让 +1580 雾 +1581 動 +1582 蕴 +1583 处 +1584 宠 +1585 楊 +1586 务 +1587 猴 +1588 翻 +1589 到 +1590 竞 +1591 参 +1592 某 +1593 闽 +1594 送 +1595 匡 +1596 钊 +1597 薄 +1598 磨 +1599 芒 +1600 婵 +1601 厄 +1602 渔 +1603 户 +1604 推 +1605 研 +1606 纲 +1607 恭 +1608 聖 +1609 茅 +1610 资 +1611 宛 +1612 魅 +1613 软 +1614 胎 +1615 鸭 +1616 愚 +1617 喆 +1618 鉴 +1619 荒 +1620 协 +1621 罪 +1622 铎 +1623 迅 +1624 笙 +1625 語 +1626 葆 +1627 匹 +1628 区 +1629 绣 +1630 型 +1631 轶 +1632 额 +1633 消 +1634 靓 +1635 硬 +1636 着 +1637 姣 +1638 偏 +1639 票 +1640 碎 +1641 套 +1642 遥 +1643 冀 +1644 际 +1645 架 +1646 拳 +1647 巫 +1648 6 +1649 妇 +1650 赋 +1651 私 +1652 曙 +1653 站 +1654 载 +1655 抗 +1656 芦 +1657 膜 +1658 尸 +1659 适 +1660 错 +1661 潭 +1662 击 +1663 俭 +1664 巢 +1665 幻 +1666 婆 +1667 麒 +1668 值 +1669 止 +1670 种 +1671 維 +1672 c +1673 岐 +1674 後 +1675 伶 +1676 墙 +1677 刃 +1678 缇 +1679 琰 +1680 殇 +1681 烧 +1682 窝 +1683 砚 +1684 無 +1685 矿 +1686 遗 +1687 争 +1688 怪 +1689 b +1690 末 +1691 逆 +1692 码 +1693 释 +1694 屠 +1695 问 +1696 恬 +1697 腰 +1698 掉 +1699 時 +1700 具 +1701 脸 +1702 璋 +1703 隋 +1704 芽 +1705 控 +1706 壹 +1707 甄 +1708 會 +1709 价 +1710 劫 +1711 菌 +1712 熱 +1713 岁 +1714 痛 +1715 刻 +1716 單 +1717 咸 +1718 書 +1719 兮 +1720 服 +1721 敖 +1722 禁 +1723 差 +1724 沫 +1725 栗 +1726 暮 +1727 倾 +1728 戰 +1729 投 +1730 戏 +1731 币 +1732 要 +1733 造 +1734 冥 +1735 肌 +1736 降 +1737 龟 +1738 低 +1739 o +1740 痕 +1741 學 +1742 弹 +1743 淡 +1744 迹 +1745 箭 +1746 岑 +1747 读 +1748 灭 +1749 萝 +1750 潜 +1751 穗 +1752 俄 +1753 吊 +1754 虞 +1755 斑 +1756 炉 +1757 肥 +1758 说 +1759 稳 +1760 焱 +1761 隽 +1762 急 +1763 橙 +1764 卞 +1765 雀 +1766 停 +1767 槐 +1768 级 +1769 剧 +1770 姑 +1771 岱 +1772 e +1773 弄 +1774 脑 +1775 蔓 +1776 论 +1777 壳 +1778 鼻 +1779 圖 +1780 醒 +1781 犬 +1782 堤 +1783 闲 +1784 坐 +1785 专 +1786 蜂 +1787 饶 +1788 证 +1789 液 +1790 莺 +1791 导 +1792 跑 +1793 砂 +1794 谈 +1795 虾 +1796 湛 +1797 杂 +1798 看 +1799 父 +1800 埠 +1801 盲 +1802 敌 +1803 泛 +1804 摇 +1805 翎 +1806 霆 +1807 核 +1808 屿 +1809 换 +1810 股 +1811 产 +1812 呈 +1813 漏 +1814 興 +1815 铺 +1816 刑 +1817 省 +1818 裝 +1819 刁 +1820 曰 +1821 劉 +1822 察 +1823 除 +1824 齿 +1825 峥 +1826 牟 +1827 飘 +1828 律 +1829 鞋 +1830 禅 +1831 瞿 +1832 右 +1833 璟 +1834 滑 +1835 煤 +1836 滢 +1837 琨 +1838 逢 +1839 税 +1840 宮 +1841 状 +1842 納 +1843 谨 +1844 寄 +1845 弓 +1846 练 +1847 序 +1848 纱 +1849 恨 +1850 凱 +1851 寧 +1852 帶 +1853 境 +1854 局 +1855 操 +1856 妤 +1857 裂 +1858 猎 +1859 眠 +1860 泡 +1861 辞 +1862 i +1863 势 +1864 戎 +1865 室 +1866 順 +1867 透 +1868 享 +1869 演 +1870 裘 +1871 由 +1872 助 +1873 第 +1874 奋 +1875 储 +1876 伐 +1877 沪 +1878 9 +1879 磁 +1880 拍 +1881 盼 +1882 珈 +1883 贻 +1884 偷 +1885 混 +1886 仰 +1887 队 +1888 場 +1889 胤 +1890 呼 +1891 案 +1892 驹 +1893 还 +1894 铂 +1895 栾 +1896 腿 +1897 响 +1898 禧 +1899 溢 +1900 饼 +1901 4 +1902 馆 +1903 材 +1904 粮 +1905 姗 +1906 缺 +1907 桢 +1908 業 +1909 歆 +1910 惟 +1911 纹 +1912 祯 +1913 崖 +1914 预 +1915 肇 +1916 連 +1917 悲 +1918 唱 +1919 鹭 +1920 胸 +1921 杆 +1922 暴 +1923 園 +1924 准 +1925 汶 +1926 吳 +1927 钻 +1928 纤 +1929 氧 +1930 冶 +1931 脂 +1932 怨 +1933 島 +1934 爆 +1935 尽 +1936 夹 +1937 挂 +1938 肠 +1939 绵 +1940 崎 +1941 銀 +1942 措 +1943 算 +1944 陀 +1945 橋 +1946 执 +1947 职 +1948 徽 +1949 邑 +1950 瑪 +1951 荡 +1952 戒 +1953 旧 +1954 丑 +1955 浓 +1956 便 +1957 仑 +1958 歇 +1959 縣 +1960 围 +1961 纬 +1962 褚 +1963 丞 +1964 胆 +1965 辅 +1966 减 +1967 贯 +1968 圭 +1969 乘 +1970 率 +1971 別 +1972 藍 +1973 扇 +1974 萊 +1975 瘦 +1976 漢 +1977 n +1978 滿 +1979 榆 +1980 屹 +1981 廣 +1982 句 +1983 借 +1984 鞠 +1985 垂 +1986 骥 +1987 鐵 +1988 雞 +1989 號 +1990 胃 +1991 玩 +1992 雕 +1993 罕 +1994 墩 +1995 谊 +1996 贼 +1997 對 +1998 件 +1999 编 +2000 d +2001 嫂 +2002 葉 +2003 栓 +2004 湿 +2005 统 +2006 箱 +2007 庸 +2008 终 +2009 轉 +2010 吹 +2011 噶 +2012 炼 +2013 聯 +2014 谱 +2015 悬 +2016 甸 +2017 兩 +2018 委 +2019 徒 +2020 午 +2021 忘 +2022 藻 +2023 遇 +2024 師 +2025 數 +2026 激 +2027 經 +2028 炯 +2029 怒 +2030 珏 +2031 靈 +2032 熹 +2033 靜 +2034 兒 +2035 報 +2036 調 +2037 圩 +2038 袋 +2039 妆 +2040 各 +2041 祭 +2042 层 +2043 聲 +2044 陌 +2045 幕 +2046 帽 +2047 了 +2048 舌 +2049 碗 +2050 記 +2051 窑 +2052 丕 +2053 貝 +2054 盤 +2055 過 +2056 醇 +2057 紧 +2058 类 +2059 娣 +2060 嵘 +2061 弃 +2062 嵩 +2063 卖 +2064 侨 +2065 p +2066 块 +2067 束 +2068 绳 +2069 橫 +2070 鄂 +2071 窗 +2072 粒 +2073 膏 +2074 灏 +2075 義 +2076 馥 +2077 藥 +2078 卧 +2079 夷 +2080 诸 +2081 侃 +2082 抱 +2083 絲 +2084 故 +2085 厨 +2086 喷 +2087 荔 +2088 俏 +2089 凶 +2090 斜 +2091 忍 +2092 關 +2093 完 +2094 皖 +2095 逃 +2096 榜 +2097 样 +2098 淫 +2099 運 +2100 喀 +2101 互 +2102 浆 +2103 結 +2104 侧 +2105 闯 +2106 抽 +2107 腊 +2108 秘 +2109 请 +2110 写 +2111 续 +2112 组 +2113 此 +2114 烁 +2115 吸 +2116 销 +2117 翊 +2118 漾 +2119 荫 +2120 進 +2121 ù +2122 键 +2123 囚 +2124 等 +2125 疏 +2126 弱 +2127 棒 +2128 渣 +2129 嫁 +2130 夺 +2131 链 +2132 懒 +2133 你 +2134 骁 +2135 励 +2136 胖 +2137 螺 +2138 恰 +2139 珉 +2140 须 +2141 墅 +2142 款 +2143 堆 +2144 轴 +2145 整 +2146 咪 +2147 注 +2148 救 +2149 網 +2150 勾 +2151 播 +2152 称 +2153 裸 +2154 频 +2155 棚 +2156 尿 +2157 珑 +2158 旻 +2159 害 +2160 枣 +2161 阵 +2162 备 +2163 稻 +2164 叫 +2165 就 +2166 攻 +2167 辣 +2168 邻 +2169 俐 +2170 昀 +2171 踏 +2172 肝 +2173 坛 +2174 像 +2175 夢 +2176 愿 +2177 斩 +2178 腹 +2179 苟 +2180 愁 +2181 樹 +2182 錢 +2183 蟹 +2184 傻 +2185 鹅 +2186 态 +2187 苇 +2188 筒 +2189 溫 +2190 諾 +2191 蕙 +2192 穿 +2193 紙 +2194 涧 +2195 奸 +2196 厂 +2197 鸥 +2198 琅 +2199 漆 +2200 昶 +2201 檀 +2202 险 +2203 昇 +2204 補 +2205 译 +2206 枕 +2207 悅 +2208 持 +2209 评 +2210 庵 +2211 黔 +2212 煞 +2213 拾 +2214 熟 +2215 试 +2216 题 +2217 浴 +2218 遠 +2219 摆 +2220 邬 +2221 枯 +2222 鞭 +2223 蔻 +2224 7 +2225 劍 +2226 吃 +2227 勉 +2228 纶 +2229 迁 +2230 伴 +2231 疯 +2232 使 +2233 肃 +2234 审 +2235 梭 +2236 他 +2237 拔 +2238 悟 +2239 穴 +2240 豐 +2241 勝 +2242 實 +2243 綠 +2244 玻 +2245 彻 +2246 告 +2247 蛮 +2248 抢 +2249 瓷 +2250 枢 +2251 系 +2252 峡 +2253 蘇 +2254 淘 +2255 负 +2256 s +2257 员 +2258 乎 +2259 邊 +2260 賽 +2261 歐 +2262 纵 +2263 哀 +2264 被 +2265 籍 +2266 肩 +2267 尺 +2268 圓 +2269 旅 +2270 漪 +2271 泗 +2272 莊 +2273 臧 +2274 標 +2275 朔 +2276 搜 +2277 塑 +2278 視 +2279 狱 +2280 铸 +2281 筑 +2282 附 +2283 剂 +2284 筋 +2285 柜 +2286 购 +2287 滚 +2288 驴 +2289 腳 +2290 墓 +2291 盆 +2292 骑 +2293 溜 +2294 垒 +2295 陰 +2296 始 +2297 废 +2298 赢 +2299 隔 +2300 粗 +2301 议 +2302 峪 +2303 蒸 +2304 傷 +2305 芊 +2306 砖 +2307 變 +2308 检 +2309 巾 +2310 充 +2311 免 +2312 版 +2313 拼 +2314 笼 +2315 袖 +2316 滔 +2317 鴻 +2318 貨 +2319 置 +2320 疮 +2321 灌 +2322 槽 +2323 厉 +2324 錦 +2325 瓶 +2326 企 +2327 栖 +2328 吧 +2329 睡 +2330 渭 +2331 梯 +2332 胥 +2333 织 +2334 價 +2335 荟 +2336 坏 +2337 唇 +2338 澈 +2339 臭 +2340 怜 +2341 赌 +2342 玫 +2343 柒 +2344 囊 +2345 慢 +2346 樓 +2347 穷 +2348 養 +2349 扫 +2350 僧 +2351 鸽 +2352 凰 +2353 燃 +2354 溶 +2355 绒 +2356 勿 +2357 亡 +2358 贴 +2359 燈 +2360 詞 +2361 宰 +2362 湯 +2363 鲸 +2364 帛 +2365 漠 +2366 饰 +2367 吻 +2368 條 +2369 惑 +2370 詩 +2371 做 +2372 u +2373 財 +2374 阅 +2375 移 +2376 忧 +2377 诱 +2378 麥 +2379 奚 +2380 串 +2381 級 +2382 奖 +2383 寂 +2384 剪 +2385 盗 +2386 偶 +2387 妈 +2388 驿 +2389 突 +2390 滴 +2391 煊 +2392 昔 +2393 往 +2394 限 +2395 帐 +2396 蛟 +2397 败 +2398 輝 +2399 椿 +2400 殺 +2401 酱 +2402 約 +2403 撞 +2404 痴 +2405 庐 +2406 寰 +2407 陪 +2408 苹 +2409 辽 +2410 霓 +2411 擎 +2412 澤 +2413 俗 +2414 嗣 +2415 拥 +2416 t +2417 碟 +2418 待 +2419 菡 +2420 缸 +2421 傳 +2422 阶 +2423 络 +2424 欠 +2425 兄 +2426 殊 +2427 枭 +2428 遂 +2429 難 +2430 環 +2431 课 +2432 危 +2433 巡 +2434 話 +2435 耘 +2436 樟 +2437 逐 +2438 候 +2439 遊 +2440 爪 +2441 钉 +2442 畫 +2443 當 +2444 疆 +2445 插 +2446 糕 +2447 薪 +2448 阻 +2449 缩 +2450 頂 +2451 割 +2452 袭 +2453 弯 +2454 挑 +2455 铨 +2456 見 +2457 葬 +2458 咒 +2459 倚 +2460 祎 +2461 贷 +2462 輪 +2463 筆 +2464 测 +2465 產 +2466 蜡 +2467 每 +2468 脫 +2469 腔 +2470 仟 +2471 叙 +2472 h +2473 肾 +2474 領 +2475 误 +2476 熠 +2477 邮 +2478 荃 +2479 ē +2480 稅 +2481 径 +2482 扁 +2483 臨 +2484 g +2485 绯 +2486 蓮 +2487 缝 +2488 伪 +2489 悉 +2490 碳 +2491 丫 +2492 魯 +2493 援 +2494 宙 +2495 蚁 +2496 換 +2497 費 +2498 莘 +2499 刊 +2500 區 +2501 疾 +2502 炬 +2503 己 +2504 巩 +2505 祈 +2506 伞 +2507 妥 +2508 孜 +2509 襄 +2510 拖 +2511 呆 +2512 汁 +2513 猿 +2514 疑 +2515 赟 +2516 及 +2517 叉 +2518 缠 +2519 裤 +2520 硫 +2521 翘 +2522 丧 +2523 识 +2524 赐 +2525 頓 +2526 椰 +2527 戶 +2528 x +2529 浙 +2530 笃 +2531 壶 +2532 哉 +2533 饮 +2534 俪 +2535 碑 +2536 倫 +2537 潤 +2538 截 +2539 棍 +2540 规 +2541 餐 +2542 岙 +2543 稿 +2544 绘 +2545 骐 +2546 牢 +2547 累 +2548 葱 +2549 裙 +2550 衫 +2551 侍 +2552 哨 +2553 離 +2554 叹 +2555 祸 +2556 避 +2557 萃 +2558 蒿 +2559 哭 +2560 將 +2561 几 +2562 渐 +2563 决 +2564 供 +2565 斷 +2566 困 +2567 租 +2568 闷 +2569 灼 +2570 氯 +2571 扑 +2572 例 +2573 膠 +2574 間 +2575 橘 +2576 虛 +2577 飯 +2578 尉 +2579 蟲 +2580 赣 +2581 涼 +2582 灾 +2583 質 +2584 犯 +2585 % +2586 導 +2587 節 +2588 轨 +2589 拐 +2590 瀛 +2591 骞 +2592 沅 +2593 妾 +2594 骅 +2595 旁 +2596 觅 +2597 且 +2598 示 +2599 似 +2600 赏 +2601 粟 +2602 復 +2603 哑 +2604 觀 +2605 敢 +2606 只 +2607 烏 +2608 親 +2609 姨 +2610 豬 +2611 著 +2612 選 +2613 浚 +2614 兜 +2615 监 +2616 驾 +2617 并 +2618 蚕 +2619 針 +2620 磷 +2621 扩 +2622 烂 +2623 履 +2624 泼 +2625 闹 +2626 泾 +2627 办 +2628 吞 +2629 蛙 +2630 焊 +2631 坟 +2632 盒 +2633 愈 +2634 y +2635 焚 +2636 抓 +2637 偉 +2638 垚 +2639 烤 +2640 羚 +2641 淋 +2642 披 +2643 阙 +2644 m +2645 罡 +2646 慰 +2647 洼 +2648 髮 +2649 柄 +2650 燒 +2651 荻 +2652 弈 +2653 番 +2654 參 +2655 技 +2656 碱 +2657 捕 +2658 夸 +2659 逼 +2660 漂 +2661 鳞 +2662 慶 +2663 鸾 +2664 裳 +2665 樵 +2666 隊 +2667 懋 +2668 稀 +2669 預 +2670 验 +2671 缓 +2672 旱 +2673 函 +2674 稚 +2675 鲨 +2676 幅 +2677 佘 +2678 資 +2679 返 +2680 划 +2681 專 +2682 沖 +2683 忌 +2684 藩 +2685 璃 +2686 奏 +2687 陇 +2688 腸 +2689 鎮 +2690 廊 +2691 批 +2692 绫 +2693 签 +2694 幺 +2695 忻 +2696 璧 +2697 肽 +2698 涉 +2699 桶 +2700 苔 +2701 搭 +2702 替 +2703 種 +2704 把 +2705 鳳 +2706 減 +2707 苓 +2708 锤 +2709 優 +2710 煙 +2711 即 +2712 舰 +2713 颈 +2714 贱 +2715 钩 +2716 冻 +2717 獨 +2718 銅 +2719 卯 +2720 妞 +2721 碰 +2722 袍 +2723 赶 +2724 填 +2725 霁 +2726 债 +2727 闸 +2728 择 +2729 趙 +2730 胺 +2731 阜 +2732 絕 +2733 刮 +2734 罐 +2735 虐 +2736 扭 +2737 铝 +2738 钙 +2739 聘 +2740 汽 +2741 铅 +2742 牵 +2743 烽 +2744 棣 +2745 葯 +2746 恕 +2747 藝 +2748 售 +2749 極 +2750 壓 +2751 喉 +2752 皂 +2753 触 +2754 異 +2755 彈 +2756 菇 +2757 翅 +2758 垫 +2759 腦 +2760 寸 +2761 珩 +2762 锌 +2763 昏 +2764 膳 +2765 逝 +2766 绅 +2767 损 +2768 現 +2769 l +2770 肺 +2771 畏 +2772 伙 +2773 煦 +2774 挽 +2775 韓 +2776 涤 +2777 v +2778 霏 +2779 恐 +2780 炸 +2781 貓 +2782 鳥 +2783 芋 +2784 笠 +2785 冢 +2786 坂 +2787 叠 +2788 皋 +2789 腐 +2790 桓 +2791 噴 +2792 皆 +2793 蝉 +2794 崩 +2795 鋼 +2796 忙 +2797 疗 +2798 篇 +2799 鄉 +2800 跨 +2801 答 +2802 衛 +2803 涩 +2804 庫 +2805 處 +2806 驼 +2807 硝 +2808 堃 +2809 試 +2810 務 +2811 棕 +2812 孕 +2813 杖 +2814 爹 +2815 劇 +2816 椒 +2817 拙 +2818 兼 +2819 诡 +2820 册 +2821 應 +2822 栏 +2823 仿 +2824 抛 +2825 卒 +2826 访 +2827 枚 +2828 鲤 +2829 f +2830 卵 +2831 孽 +2832 蚀 +2833 认 +2834 歪 +2835 厦 +2836 钛 +2837 挖 +2838 哇 +2839 熏 +2840 涯 +2841 悍 +2842 咬 +2843 曉 +2844 竺 +2845 厝 +2846 說 +2847 鲲 +2848 遮 +2849 榮 +2850 弋 +2851 跟 +2852 臂 +2853 貴 +2854 禮 +2855 創 +2856 骄 +2857 讲 +2858 距 +2859 硅 +2860 灣 +2861 恆 +2862 權 +2863 臺 +2864 览 +2865 贫 +2866 圃 +2867 孑 +2868 磐 +2869 澎 +2870 醫 +2871 陸 +2872 刷 +2873 笋 +2874 属 +2875 贪 +2876 町 +2877 堰 +2878 闭 +2879 彰 +2880 账 +2881 已 +2882 評 +2883 侬 +2884 農 +2885 覆 +2886 拨 +2887 炒 +2888 洙 +2889 臉 +2890 媒 +2891 爬 +2892 捞 +2893 嫩 +2894 肚 +2895 鏡 +2896 驱 +2897 伸 +2898 甚 +2899 掛 +2900 垣 +2901 况 +2902 滞 +2903 匯 +2904 催 +2905 傑 +2906 ū +2907 總 +2908 桔 +2909 猜 +2910 炽 +2911 職 +2912 冒 +2913 莽 +2914 聽 +2915 骚 +2916 洒 +2917 曜 +2918 衰 +2919 绕 +2920 暄 +2921 诉 +2922 授 +2923 奢 +2924 題 +2925 晃 +2926 眸 +2927 踢 +2928 妄 +2929 護 +2930 簡 +2931 丈 +2932 灶 +2933 诊 +2934 罩 +2935 醋 +2936 桩 +2937 崗 +2938 绞 +2939 沧 +2940 裁 +2941 拆 +2942 镁 +2943 犁 +2944 判 +2945 尕 +2946 氢 +2947 鸠 +2948 劝 +2949 竖 +2950 飚 +2951 最 +2952 蹄 +2953 羡 +2954 陷 +2955 缨 +2956 旷 +2957 页 +2958 翌 +2959 烛 +2960 筝 +2961 毁 +2962 戀 +2963 荀 +2964 陂 +2965 貼 +2966 鶴 +2967 讀 +2968 輕 +2969 档 +2970 抚 +2971 副 +2972 订 +2973 槍 +2974 凹 +2975 編 +2976 稼 +2977 拱 +2978 雏 +2979 碼 +2980 桌 +2981 霉 +2982 睦 +2983 骊 +2984 摸 +2985 證 +2986 茄 +2987 絮 +2988 匪 +2989 豚 +2990 酥 +2991 團 +2992 厅 +2993 获 +2994 鸦 +2995 押 +2996 沿 +2997 逗 +2998 愉 +2999 椅 +3000 卦 +3001 鞍 +3002 笨 +3003 寫 +3004 純 +3005 緣 +3006 竟 +3007 組 +3008 抄 +3009 滇 +3010 粪 +3011 鍋 +3012 淦 +3013 佬 +3014 泣 +3015 弼 +3016 俠 +3017 旸 +3018 浑 +3019 绥 +3020 设 +3021 薯 +3022 梧 +3023 亢 +3024 幹 +3025 症 +3026 舫 +3027 煮 +3028 咔 +3029 軟 +3030 賢 +3031 賣 +3032 狀 +3033 癌 +3034 氨 +3035 靠 +3036 細 +3037 揭 +3038 构 +3039 彧 +3040 帘 +3041 卤 +3042 秒 +3043 镭 +3044 潼 +3045 k +3046 韧 +3047 栩 +3048 熔 +3049 坞 +3050 污 +3051 遵 +3052 製 +3053 孫 +3054 羲 +3055 忽 +3056 勐 +3057 營 +3058 纷 +3059 殘 +3060 脊 +3061 寡 +3062 洵 +3063 仆 +3064 劈 +3065 辩 +3066 鐘 +3067 缤 +3068 禽 +3069 甬 +3070 勺 +3071 佃 +3072 茸 +3073 蛾 +3074 谁 +3075 虽 +3076 痰 +3077 凸 +3078 酮 +3079 腕 +3080 宵 +3081 穹 +3082 惡 +3083 計 +3084 r +3085 钓 +3086 抵 +3087 给 +3088 晕 +3089 課 +3090 許 +3091 員 +3092 综 +3093 茉 +3094 亂 +3095 啟 +3096 問 +3097 捐 +3098 烦 +3099 脆 +3100 備 +3101 棱 +3102 埋 +3103 泷 +3104 洽 +3105 珞 +3106 婦 +3107 羞 +3108 确 +3109 隨 +3110 犀 +3111 蚊 +3112 毫 +3113 謝 +3114 糊 +3115 颠 +3116 喵 +3117 胞 +3118 邸 +3119 軒 +3120 測 +3121 份 +3122 斧 +3123 弧 +3124 矛 +3125 冕 +3126 琉 +3127 狸 +3128 扒 +3129 甩 +3130 肆 +3131 柚 +3132 屎 +3133 庶 +3134 蓋 +3135 額 +3136 否 +3137 擊 +3138 鴨 +3139 旨 +3140 峙 +3141 騰 +3142 購 +3143 歸 +3144 遁 +3145 檢 +3146 缔 +3147 矮 +3148 煎 +3149 紋 +3150 浸 +3151 梗 +3152 瑰 +3153 闺 +3154 挡 +3155 砍 +3156 筹 +3157 涟 +3158 宥 +3159 纺 +3160 贸 +3161 聊 +3162 缅 +3163 沣 +3164 芃 +3165 銷 +3166 潞 +3167 溥 +3168 虱 +3169 矢 +3170 梳 +3171 输 +3172 晁 +3173 穎 +3174 獸 +3175 呂 +3176 飒 +3177 頻 +3178 析 +3179 帖 +3180 懷 +3181 旬 +3182 裡 +3183 焉 +3184 漁 +3185 層 +3186 个 +3187 跌 +3188 粘 +3189 役 +3190 揚 +3191 鵬 +3192 鳌 +3193 驻 +3194 罚 +3195 晞 +3196 乖 +3197 搏 +3198 岔 +3199 氮 +3200 琢 +3201 粹 +3202 碘 +3203 抹 +3204 骗 +3205 湄 +3206 玟 +3207 鸢 +3208 沸 +3209 誓 +3210 歡 +3211 削 +3212 臀 +3213 铠 +3214 滾 +3215 憨 +3216 框 +3217 耗 +3218 摘 +3219 责 +3220 障 +3221 赠 +3222 遺 +3223 瑄 +3224 搖 +3225 鷹 +3226 踪 +3227 歷 +3228 嶺 +3229 葳 +3230 瑤 +3231 倉 +3232 潔 +3233 拒 +3234 統 +3235 据 +3236 衬 +3237 麓 +3238 啦 +3239 怕 +3240 魄 +3241 窃 +3242 侵 +3243 為 +3244 薩 +3245 璨 +3246 署 +3247 蒼 +3248 叁 +3249 炭 +3250 類 +3251 炀 +3252 讨 +3253 聆 +3254 蝇 +3255 冤 +3256 轰 +3257 裔 +3258 粥 +3259 涨 +3260 沂 +3261 沼 +3262 決 +3263 悔 +3264 壽 +3265 夙 +3266 荼 +3267 ī +3268 按 +3269 担 +3270 堪 +3271 卑 +3272 尋 +3273 苯 +3274 垢 +3275 忱 +3276 濠 +3277 貌 +3278 骂 +3279 澍 +3280 靡 +3281 谜 +3282 館 +3283 璜 +3284 隱 +3285 拴 +3286 瞬 +3287 扰 +3288 违 +3289 铿 +3290 聿 +3291 瞻 +3292 犹 +3293 箫 +3294 酉 +3295 很 +3296 勞 +3297 岡 +3298 燮 +3299 蔺 +3300 薰 +3301 缚 +3302 锭 +3303 楓 +3304 绩 +3305 督 +3306 芥 +3307 茧 +3308 緊 +3309 坠 +3310 辜 +3311 辈 +3312 惨 +3313 搬 +3314 翀 +3315 幣 +3316 镐 +3317 涓 +3318 敛 +3319 锚 +3320 錯 +3321 凭 +3322 埔 +3323 劣 +3324 吏 +3325 糜 +3326 浊 +3327 術 +3328 積 +3329 却 +3330 刹 +3331 蒜 +3332 溯 +3333 餅 +3334 瞎 +3335 锴 +3336 钜 +3337 籽 +3338 掩 +3339 孩 +3340 簽 +3341 驚 +3342 肿 +3343 邝 +3344 谟 +3345 ě +3346 億 +3347 患 +3348 終 +3349 襟 +3350 跪 +3351 獅 +3352 没 +3353 浣 +3354 渚 +3355 痞 +3356 脾 +3357 滤 +3358 凄 +3359 歧 +3360 鎖 +3361 柠 +3362 態 +3363 擒 +3364 泄 +3365 皙 +3366 晒 +3367 陕 +3368 柿 +3369 锟 +3370 膝 +3371 握 +3372 濕 +3373 循 +3374 淹 +3375 敷 +3376 樣 +3377 規 +3378 挚 +3379 址 +3380 論 +3381 株 +3382 仗 +3383 稱 +3384 還 +3385 氟 +3386 辟 +3387 谛 +3388 谌 +3389 譜 +3390 锥 +3391 亏 +3392 阀 +3393 锯 +3394 蛊 +3395 撤 +3396 扯 +3397 钞 +3398 獎 +3399 錄 +3400 銘 +3401 茫 +3402 崧 +3403 侣 +3404 乞 +3405 欺 +3406 瘤 +3407 篮 +3408 泠 +3409 阚 +3410 濑 +3411 钳 +3412 荊 +3413 咲 +3414 蝎 +3415 卸 +3416 耍 +3417 摄 +3418 惹 +3419 壬 +3420 辱 +3421 柑 +3422 顽 +3423 铉 +3424 祚 +3425 複 +3426 挥 +3427 蛤 +3428 沾 +3429 脏 +3430 找 +3431 圍 +3432 促 +3433 賓 +3434 朮 +3435 挤 +3436 郊 +3437 既 +3438 舅 +3439 給 +3440 咕 +3441 骋 +3442 夾 +3443 鄭 +3444 鈴 +3445 浒 +3446 酶 +3447 屁 +3448 茲 +3449 迫 +3450 焯 +3451 晰 +3452 戲 +3453 驗 +3454 舸 +3455 驭 +3456 肢 +3457 罢 +3458 嫡 +3459 栈 +3460 箐 +3461 这 +3462 銮 +3463 認 +3464 鬥 +3465 縮 +3466 愤 +3467 郜 +3468 仝 +3469 递 +3470 勢 +3471 ō +3472 贰 +3473 粵 +3474 痘 +3475 姦 +3476 缴 +3477 揽 +3478 恪 +3479 舵 +3480 艷 +3481 葡 +3482 鋒 +3483 叛 +3484 産 +3485 窩 +3486 嵌 +3487 敲 +3488 蓄 +3489 泻 +3490 畜 +3491 抒 +3492 韻 +3493 項 +3494 摊 +3495 疃 +3496 の +3497 烯 +3498 吓 +3499 戊 +3500 腺 +3501 褲 +3502 監 +3503 谣 +3504 廠 +3505 迭 +3506 鄢 +3507 谏 +3508 載 +3509 拂 +3510 茎 +3511 俱 +3512 斤 +3513 紀 +3514 颤 +3515 尝 +3516 沥 +3517 習 +3518 淞 +3519 昧 +3520 逍 +3521 嗨 +3522 榴 +3523 臥 +3524 嬌 +3525 側 +3526 券 +3527 渗 +3528 雜 +3529 閃 +3530 盜 +3531 艇 +3532 喬 +3533 详 +3534 秃 +3535 採 +3536 汛 +3537 呀 +3538 厌 +3539 喊 +3540 訂 +3541 訊 +3542 燊 +3543 栅 +3544 誠 +3545 夭 +3546 皱 +3547 蛛 +3548 矣 +3549 鳴 +3550 攸 +3551 麵 +3552 冼 +3553 儀 +3554 晉 +3555 濤 +3556 莓 +3557 齊 +3558 晦 +3559 竣 +3560 抖 +3561 w +3562 キ +3563 墻 +3564 媽 +3565 敗 +3566 淺 +3567 礁 +3568 荐 +3569 估 +3570 驳 +3571 舱 +3572 绰 +3573 宦 +3574 泵 +3575 寮 +3576 雌 +3577 脐 +3578 舊 +3579 續 +3580 弩 +3581 羌 +3582 拌 +3583 瓣 +3584 戟 +3585 髓 +3586 暑 +3587 婶 +3588 撕 +3589 豁 +3590 竿 +3591 隙 +3592 谓 +3593 铖 +3594 旌 +3595 蝦 +3596 秧 +3597 或 +3598 颢 +3599 兑 +3600 厥 +3601 鳄 +3602 暂 +3603 汾 +3604 钝 +3605 杠 +3606 買 +3607 苒 +3608 牆 +3609 炊 +3610 糠 +3611 矾 +3612 懂 +3613 侗 +3614 剛 +3615 壇 +3616 帳 +3617 櫃 +3618 毀 +3619 湧 +3620 捉 +3621 練 +3622 窖 +3623 緑 +3624 沽 +3625 馋 +3626 斥 +3627 郵 +3628 喇 +3629 垛 +3630 概 +3631 们 +3632 岂 +3633 腎 +3634 銳 +3635 岷 +3636 烙 +3637 掠 +3638 浜 +3639 泸 +3640 醬 +3641 沱 +3642 蔷 +3643 皎 +3644 榛 +3645 檐 +3646 閣 +3647 抬 +3648 顏 +3649 橡 +3650 镛 +3651 塊 +3652 盡 +3653 壯 +3654 靴 +3655 亥 +3656 酚 +3657 窄 +3658 肛 +3659 亘 +3660 糟 +3661 烘 +3662 貂 +3663 講 +3664 狠 +3665 窥 +3666 賭 +3667 賀 +3668 莞 +3669 箕 +3670 爺 +3671 喘 +3672 但 +3673 咖 +3674 織 +3675 い +3676 彿 +3677 唤 +3678 蕉 +3679 僵 +3680 熬 +3681 妓 +3682 踩 +3683 铲 +3684 匙 +3685 撑 +3686 弛 +3687 耻 +3688 丢 +3689 堵 +3690 膽 +3691 厘 +3692 辨 +3693 瓢 +3694 崴 +3695 篱 +3696 碾 +3697 畔 +3698 涝 +3699 膚 +3700 绛 +3701 黏 +3702 屑 +3703 衝 +3704 簧 +3705 杞 +3706 轲 +3707 贲 +3708 溝 +3709 烷 +3710 霧 +3711 塵 +3712 瘾 +3713 颉 +3714 凿 +3715 彝 +3716 诛 +3717 訪 +3718 鮮 +3719 覺 +3720 歲 +3721 窟 +3722 週 +3723 苞 +3724 濟 +3725 叟 +3726 爭 +3727 椎 +3728 療 +3729 眾 +3730 審 +3731 拋 +3732 棘 +3733 诀 +3734 鹃 +3735 倦 +3736 擦 +3737 暢 +3738 酬 +3739 蠢 +3740 聞 +3741 囧 +3742 從 +3743 脈 +3744 缆 +3745 陋 +3746 哪 +3747 酿 +3748 娆 +3749 屍 +3750 檬 +3751 捧 +3752 凛 +3753 靶 +3754 疣 +3755 餘 +3756 鹊 +3757 陣 +3758 昙 +3759 栎 +3760 鳖 +3761 镶 +3762 飄 +3763 烫 +3764 芜 +3765 垦 +3766 癣 +3767 蟾 +3768 萤 +3769 寓 +3770 診 +3771 蚌 +3772 霈 +3773 诈 +3774 負 +3775 吼 +3776 疹 +3777 縫 +3778 則 +3779 鹽 +3780 啊 +3781 捣 +3782 勘 +3783 俯 +3784 陡 +3785 叮 +3786 $ +3787 饱 +3788 寬 +3789 帥 +3790 漿 +3791 掘 +3792 棺 +3793 汞 +3794 钵 +3795 こ +3796 绸 +3797 括 +3798 濂 +3799 壞 +3800 躲 +3801 拦 +3802 錫 +3803 拟 +3804 钠 +3805 嘛 +3806 趋 +3807 遣 +3808 谐 +3809 墟 +3810 喧 +3811 榭 +3812 閉 +3813 筛 +3814 j +3815 渴 +3816 峨 +3817 嬰 +3818 巳 +3819 梢 +3820 漱 +3821 疤 +3822 祉 +3823 矽 +3824 痒 +3825 咽 +3826 邀 +3827 缀 +3828 庇 +3829 虔 +3830 盏 +3831 羿 +3832 抑 +3833 叨 +3834 弑 +3835 唛 +3836 侑 +3837 賊 +3838 稽 +3839 黨 +3840 妝 +3841 谍 +3842 蓁 +3843 ま +3844 蕃 +3845 藜 +3846 赘 +3847 诞 +3848 眷 +3849 够 +3850 岫 +3851 釣 +3852 喃 +3853 樑 +3854 钮 +3855 鋪 +3856 牡 +3857 溴 +3858 缕 +3859 溺 +3860 溟 +3861 描 +3862 渺 +3863 藕 +3864 胚 +3865 刨 +3866 獵 +3867 琬 +3868 寝 +3869 稷 +3870 缎 +3871 锈 +3872 需 +3873 遍 +3874 醛 +3875 戬 +3876 噬 +3877 闰 +3878 蔣 +3879 協 +3880 響 +3881 顯 +3882 飾 +3883 厢 +3884 钗 +3885 毯 +3886 询 +3887 簪 +3888 堅 +3889 鼬 +3890 貢 +3891 遭 +3892 肘 +3893 燥 +3894 砸 +3895 趾 +3896 豔 +3897 蟒 +3898 淨 +3899 廟 +3900 唑 +3901 z +3902 诠 +3903 垭 +3904 龜 +3905 剥 +3906 辦 +3907 翱 +3908 挨 +3909 峽 +3910 紗 +3911 拘 +3912 绢 +3913 畴 +3914 蔼 +3915 隶 +3916 溃 +3917 濃 +3918 碌 +3919 宓 +3920 趴 +3921 浔 +3922 搞 +3923 挪 +3924 楞 +3925 邈 +3926 虑 +3927 捌 +3928 舉 +3929 嫔 +3930 漓 +3931 捻 +3932 逵 +3933 呢 +3934 砾 +3935 谬 +3936 琥 +3937 撮 +3938 準 +3939 嗜 +3940 它 +3941 議 +3942 於 +3943 執 +3944 顔 +3945 匣 +3946 焘 +3947 狭 +3948 涡 +3949 衔 +3950 靚 +3951 祠 +3952 雉 +3953 疼 +3954 镖 +3955 嚣 +3956 骸 +3957 ん +3958 証 +3959 恢 +3960 凑 +3961 丐 +3962 貞 +3963 蛹 +3964 呵 +3965 昼 +3966 蛉 +3967 翳 +3968 匀 +3969 侦 +3970 設 +3971 轧 +3972 損 +3973 盧 +3974 叩 +3975 這 +3976 跡 +3977 谕 +3978 迴 +3979 鳗 +3980 炕 +3981 珮 +3982 カ +3983 咀 +3984 搅 +3985 矫 +3986 矩 +3987 箍 +3988 渤 +3989 狩 +3990 苛 +3991 劼 +3992 濡 +3993 慌 +3994 勁 +3995 腫 +3996 般 +3997 酌 +3998 徕 +3999 廓 +4000 燎 +4001 颇 +4002 樽 +4003 槎 +4004 鑽 +4005 摔 +4006 诵 +4007 槿 +4008 琐 +4009 塌 +4010 锻 +4011 願 +4012 顧 +4013 萎 +4014 は +4015 膛 +4016 祛 +4017 檔 +4018 蠡 +4019 觸 +4020 虬 +4021 談 +4022 喝 +4023 娱 +4024 噪 +4025 胀 +4026 褐 +4027 疫 +4028 札 +4029 昉 +4030 呱 +4031 禪 +4032 債 +4033 屬 +4034 佶 +4035 垠 +4036 貿 +4037 葭 +4038 齡 +4039 萦 +4040 蕤 +4041 燚 +4042 # +4043 劑 +4044 彥 +4045 棗 +4046 紐 +4047 浇 +4048 汲 +4049 臼 +4050 咎 +4051 絨 +4052 裹 +4053 茬 +4054 厕 +4055 傾 +4056 釋 +4057 秽 +4058 颅 +4059 蹦 +4060 么 +4061 嘟 +4062 锣 +4063 腻 +4064 寐 +4065 妲 +4066 湃 +4067 醜 +4068 另 +4069 泮 +4070 幂 +4071 獄 +4072 滅 +4073 玳 +4074 氰 +4075 鞘 +4076 峭 +4077 鹂 +4078 嗅 +4079 ら +4080 瑙 +4081 咳 +4082 蝗 +4083 瓯 +4084 猷 +4085 樾 +4086 赎 +4087 她 +4088 朕 +4089 淀 +4090 頁 +4091 飙 +4092 羁 +4093 镒 +4094 喂 +4095 袜 +4096 钺 +4097 扉 +4098 曆 +4099 櫻 +4100 曳 +4101 辕 +4102 帧 +4103 誤 +4104 哄 +4105 漳 +4106 亓 +4107 隅 +4108 訴 +4109 螨 +4110 艮 +4111 識 +4112 適 +4113 诏 +4114 饵 +4115 俨 +4116 郦 +4117 坳 +4118 鵝 +4119 礦 +4120 褒 +4121 犇 +4122 隘 +4123 咯 +4124 赴 +4125 競 +4126 個 +4127 劃 +4128 殼 +4129 睛 +4130 究 +4131 兢 +4132 緩 +4133 纠 +4134 惧 +4135 践 +4136 躬 +4137 惯 +4138 稠 +4139 惩 +4140 秤 +4141 嚴 +4142 茁 +4143 濮 +4144 亩 +4145 憬 +4146 撩 +4147 赔 +4148 渎 +4149 镀 +4150 汴 +4151 婢 +4152 菩 +4153 鍾 +4154 锰 +4155 挠 +4156 泱 +4157 毗 +4158 丅 +4159 琮 +4160 痧 +4161 痣 +4162 堕 +4163 鄙 +4164 搓 +4165 な +4166 蕭 +4167 赦 +4168 耆 +4169 稍 +4170 險 +4171 胭 +4172 沢 +4173 婬 +4174 畈 +4175 炖 +4176 毋 +4177 蜗 +4178 煲 +4179 铧 +4180 並 +4181 廚 +4182 佈 +4183 衙 +4184 荧 +4185 钥 +4186 黯 +4187 雳 +4188 吨 +4189 铬 +4190 請 +4191 鎏 +4192 釉 +4193 栽 +4194 騎 +4195 磚 +4196 廢 +4197 郢 +4198 偃 +4199 賞 +4200 奪 +4201 鬓 +4202 鳍 +4203 乏 +4204 蹲 +4205 盯 +4206 ー +4207 く +4208 し +4209 ア +4210 寵 +4211 悶 +4212 構 +4213 煉 +4214 粿 +4215 絶 +4216 诫 +4217 狙 +4218 钾 +4219 敵 +4220 偿 +4221 锄 +4222 姫 +4223 幡 +4224 戳 +4225 澹 +4226 坯 +4227 濯 +4228 骈 +4229 嬉 +4230 砌 +4231 囡 +4232 峦 +4233 漕 +4234 闾 +4235 镍 +4236 罰 +4237 肋 +4238 遐 +4239 荤 +4240 窍 +4241 绾 +4242 怯 +4243 携 +4244 鹄 +4245 戌 +4246 凳 +4247 蕩 +4248 揉 +4249 柘 +4250 冗 +4251 須 +4252 蔽 +4253 焜 +4254 驯 +4255 騙 +4256 騷 +4257 恳 +4258 凈 +4259 籁 +4260 註 +4261 傣 +4262 凍 +4263 霭 +4264 爸 +4265 謀 +4266 酯 +4267 渍 +4268 駿 +4269 绎 +4270 粲 +4271 衷 +4272 葫 +4273 鬆 +4274 況 +4275 掃 +4276 撸 +4277 呗 +4278 碩 +4279 诘 +4280 贊 +4281 坨 +4282 芩 +4283 垌 +4284 茱 +4285 塚 +4286 洱 +4287 齒 +4288 嫚 +4289 篆 +4290 瑯 +4291 贩 +4292 き +4293 啓 +4294 墊 +4295 潛 +4296 瀾 +4297 饥 +4298 笺 +4299 轿 +4300 糞 +4301 範 +4302 嘲 +4303 啶 +4304 繼 +4305 捆 +4306 拢 +4307 脓 +4308 渥 +4309 谅 +4310 迩 +4311 烹 +4312 瀑 +4313 姥 +4314 缦 +4315 蛆 +4316 毙 +4317 腥 +4318 痨 +4319 喪 +4320 に +4321 壤 +4322 饲 +4323 胄 +4324 淚 +4325 濱 +4326 矶 +4327 汰 +4328 ノ +4329 飲 +4330 媳 +4331 磬 +4332 砺 +4333 啼 +4334 瘟 +4335 扈 +4336 祀 +4337 頸 +4338 蘆 +4339 钨 +4340 馳 +4341 佣 +4342 鬧 +4343 舂 +4344 翩 +4345 蝠 +4346 挣 +4347 誘 +4348 蛰 +4349 佚 +4350 辙 +4351 邁 +4352 塗 +4353 賬 +4354 塬 +4355 埭 +4356 诰 +4357 圻 +4358 拗 +4359 耽 +4360 祿 +4361 璠 +4362 瓊 +4363 珣 +4364 た +4365 儲 +4366 棄 +4367 辑 +4368 灸 +4369 狡 +4370 綿 +4371 歼 +4372 糧 +4373 癸 +4374 撫 +4375 帷 +4376 镰 +4377 俩 +4378 垄 +4379 募 +4380 嗔 +4381 滥 +4382 鏈 +4383 僻 +4384 馍 +4385 娼 +4386 撇 +4387 崽 +4388 蚂 +4389 酪 +4390 怿 +4391 愫 +4392 廈 +4393 琏 +4394 械 +4395 些 +4396 恤 +4397 疝 +4398 榄 +4399 琚 +4400 り +4401 リ +4402 妒 +4403 杲 +4404 楣 +4405 槌 +4406 槟 +4407 孺 +4408 桧 +4409 桀 +4410 牲 +4411 戍 +4412 幫 +4413 旎 +4414 铣 +4415 躺 +4416 剃 +4417 锵 +4418 呜 +4419 嫌 +4420 剔 +4421 駕 +4422 谎 +4423 绚 +4424 眩 +4425 阉 +4426 駐 +4427 討 +4428 驅 +4429 腋 +4430 痹 +4431 冊 +4432 饿 +4433 磅 +4434 乍 +4435 毡 +4436 盔 +4437 簇 +4438 殖 +4439 説 +4440 篁 +4441 襲 +4442 攒 +4443 鮑 +4444 哆 +4445 遲 +4446 遷 +4447 禀 +4448 賴 +4449 邰 +4450 軌 +4451 奂 +4452 倌 +4453 荞 +4454 苡 +4455 苷 +4456 圳 +4457 莜 +4458 荪 +4459 菀 +4460 軸 +4461 羹 +4462 爐 +4463 確 +4464 讓 +4465 癬 +4466 獲 +4467 籃 +4468 垟 +4469 奮 +4470 擺 +4471 暈 +4472 瀬 +4473 蓟 +4474 溅 +4475 疥 +4476 届 +4477 綱 +4478 烬 +4479 嵐 +4480 雇 +4481 蹭 +4482 俺 +4483 敞 +4484 砲 +4485 涣 +4486 阑 +4487 聶 +4488 蹇 +4489 糯 +4490 災 +4491 淬 +4492 骡 +4493 吗 +4494 疲 +4495 錶 +4496 狎 +4497 漩 +4498 泫 +4499 泯 +4500 擂 +4501 鹫 +4502 枳 +4503 剩 +4504 韫 +4505 攘 +4506 怂 +4507 镕 +4508 讼 +4509 牝 +4510 譯 +4511 膘 +4512 惶 +4513 铵 +4514 钿 +4515 頔 +4516 硐 +4517 涎 +4518 驮 +4519 裆 +4520 褶 +4521 捍 +4522 绑 +4523 痈 +4524 訓 +4525 膀 +4526 懸 +4527 鴿 +4528 兀 +4529 貪 +4530 壕 +4531 隼 +4532 澡 +4533 躁 +4534 秩 +4535 蚝 +4536 哼 +4537 淤 +4538 盂 +4539 叽 +4540 違 +4541 遙 +4542 欄 +4543 诃 +4544 郗 +4545 劭 +4546 偌 +4547 倬 +4548 阡 +4549 苕 +4550 谒 +4551 莒 +4552 埕 +4553 輸 +4554 葩 +4555 蕨 +4556 爛 +4557 爲 +4558 燦 +4559 拽 +4560 讚 +4561 悼 +4562 籠 +4563 サ +4564 佔 +4565 搶 +4566 曌 +4567 紡 +4568 拷 +4569 緹 +4570 嚼 +4571 藉 +4572 韭 +4573 饺 +4574 綫 +4575 哺 +4576 脖 +4577 吵 +4578 め +4579 ち +4580 痢 +4581 嗟 +4582 馈 +4583 庾 +4584 獾 +4585 獐 +4586 鈺 +4587 蹬 +4588 磕 +4589 愣 +4590 脹 +4591 僚 +4592 噜 +4593 匿 +4594 婊 +4595 啤 +4596 尻 +4597 驷 +4598 骧 +4599 繪 +4600 嗪 +4601 赓 +4602 滟 +4603 鋁 +4604 扮 +4605 纾 +4606 撬 +4607 馃 +4608 朽 +4609 瘘 +4610 嗓 +4611 瑕 +4612 啡 +4613 と +4614 麝 +4615 删 +4616 汕 +4617 胧 +4618 際 +4619 轼 +4620 掰 +4621 讽 +4622 頌 +4623 瘫 +4624 镝 +4625 颓 +4626 涕 +4627 舷 +4628 慾 +4629 憂 +4630 癖 +4631 酣 +4632 鸳 +4633 歹 +4634 翡 +4635 帜 +4636 箴 +4637 箬 +4638 骤 +4639 痔 +4640 姻 +4641 舆 +4642 赃 +4643 嘿 +4644 觞 +4645 遼 +4646 唔 +4647 唧 +4648 桿 +4649 孃 +4650 倭 +4651 偕 +4652 芪 +4653 躍 +4654 縱 +4655 癡 +4656 萘 +4657 堇 +4658 輔 +4659 攝 +4660 據 +4661 忿 +4662 蓼 +4663 辭 +4664 碍 +4665 慷 +4666 か +4667 あ +4668 弊 +4669 啞 +4670 彎 +4671 灘 +4672 煩 +4673 缉 +4674 徑 +4675 綺 +4676 荚 +4677 竭 +4678 簿 +4679 倡 +4680 趁 +4681 釜 +4682 绷 +4683 む +4684 鄧 +4685 モ +4686 垮 +4687 宕 +4688 澧 +4689 撲 +4690 鋆 +4691 洄 +4692 蘑 +4693 樸 +4694 惘 +4695 该 +4696 戮 +4697 榔 +4698 滦 +4699 ゆ +4700 滄 +4701 娑 +4702 闳 +4703 嫖 +4704 篷 +4705 捏 +4706 湟 +4707 恼 +4708 阖 +4709 螟 +4710 膺 +4711 沦 +4712 泌 +4713 帼 +4714 玑 +4715 啃 +4716 鹦 +4717 鹞 +4718 婿 +4719 搁 +4720 惰 +4721 瑗 +4722 筷 +4723 ナ +4724 る +4725 嘶 +4726 枧 +4727 杵 +4728 肴 +4729 芍 +4730 暧 +4731 朦 +4732 绊 +4733 枉 +4734 挫 +4735 奠 +4736 桅 +4737 潍 +4738 辖 +4739 暇 +4740 戾 +4741 龛 +4742 锷 +4743 嘻 +4744 q +4745 矜 +4746 焙 +4747 瑚 +4748 夯 +4749 ン +4750 蟠 +4751 覽 +4752 凋 +4753 酰 +4754 斬 +4755 貫 +4756 胰 +4757 陨 +4758 炙 +4759 謎 +4760 誌 +4761 鯨 +4762 鲈 +4763 匾 +4764 鳅 +4765 拯 +4766 僑 +4767 哒 +4768 恥 +4769 璘 +4770 谧 +4771 讷 +4772 佼 +4773 佗 +4774 畸 +4775 篡 +4776 窜 +4777 涇 +4778 芘 +4779 弁 +4780 壑 +4781 谯 +4782 茭 +4783 冽 +4784 賈 +4785 菽 +4786 燙 +4787 础 +4788 揣 +4789 鬃 +4790 赚 +4791 怠 +4792 筏 +4793 犊 +4794 畢 +4795 タ +4796 弢 +4797 彌 +4798 沒 +4799 瀨 +4800 綏 +4801 窘 +4802 悸 +4803 綾 +4804 枷 +4805 捡 +4806 颊 +4807 疽 +4808 沮 +4809 辊 +4810 箔 +4811 コ +4812 幔 +4813 チ +4814 粱 +4815 鄰 +4816 愧 +4817 扳 +4818 も +4819 鈣 +4820 靛 +4821 鍍 +4822 柵 +4823 艦 +4824 讳 +4825 涞 +4826 浏 +4827 恽 +4828 棵 +4829 峤 +4830 啪 +4831 虏 +4832 嗒 +4833 徵 +4834 硼 +4835 湫 +4836 怅 +4837 嫒 +4838 畦 +4839 鍵 +4840 蔑 +4841 翹 +4842 逯 +4843 渲 +4844 繳 +4845 鈞 +4846 眀 +4847 绶 +4848 钎 +4849 缙 +4850 琊 +4851 呛 +4852 禿 +4853 廳 +4854 懶 +4855 楔 +4856 疳 +4857 蠻 +4858 ラ +4859 咨 +4860 璎 +4861 擅 +4862 鑑 +4863 炅 +4864 腌 +4865 祟 +4866 薑 +4867 轸 +4868 暾 +4869 腮 +4870 玦 +4871 獻 +4872 ろ +4873 ロ +4874 傢 +4875 憩 +4876 吠 +4877 睢 +4878 偽 +4879 憋 +4880 蠟 +4881 钼 +4882 捂 +4883 倘 +4884 韋 +4885 掏 +4886 瓮 +4887 镯 +4888 睇 +4889 烃 +4890 慘 +4891 癞 +4892 癫 +4893 殉 +4894 谚 +4895 骇 +4896 颌 +4897 颍 +4898 饕 +4899 耙 +4900 ひ +4901 酩 +4902 榨 +4903 辐 +4904 刈 +4905 責 +4906 逾 +4907 绽 +4908 蒯 +4909 蚤 +4910 鲫 +4911 麸 +4912 迂 +4913 鲷 +4914 臆 +4915 贮 +4916 佞 +4917 瑀 +4918 痳 +4919 係 +4920 吡 +4921 咩 +4922 呷 +4923 啉 +4924 擴 +4925 擔 +4926 衮 +4927 僖 +4928 嬴 +4929 趕 +4930 踫 +4931 鹵 +4932 邺 +4933 癢 +4934 輩 +4935 莳 +4936 萼 +4937 蘅 +4938 鳝 +4939 鳐 +4940 撰 +4941 瑩 +4942 瘋 +4943 慨 +4944 績 +4945 珅 +4946 哗 +4947 え +4948 シ +4949 墜 +4950 幾 +4951 憶 +4952 擾 +4953 煥 +4954 紛 +4955 桨 +4956 絡 +4957 仅 +4958 ス +4959 褂 +4960 阐 +4961 洺 +4962 橱 +4963 洩 +4964 贬 +4965 釘 +4966 呕 +4967 疟 +4968 や +4969 洮 +4970 っ +4971 氓 +4972 殴 +4973 迤 +4974 ユ +4975 て +4976 偲 +4977 掐 +4978 繩 +4979 臟 +4980 膨 +4981 漉 +4982 暹 +4983 鉻 +4984 妩 +4985 鉛 +4986 珥 +4987 邕 +4988 胁 +4989 楸 +4990 瓒 +4991 叭 +4992 戛 +4993 驶 +4994 炔 +4995 階 +4996 鑒 +4997 缮 +4998 腓 +4999 耸 +5000 腚 +5001 閘 +5002 桉 +5003 恃 +5004 楹 +5005 橹 +5006 蓑 +5007 栀 +5008 侶 +5009 籌 +5010 ね +5011 斓 +5012 畲 +5013 顫 +5014 铳 +5015 砥 +5016 蜕 +5017 锶 +5018 祜 +5019 铛 +5020 唾 +5021 嵇 +5022 袂 +5023 佯 +5024 殃 +5025 婳 +5026 扼 +5027 昨 +5028 赭 +5029 詠 +5030 侄 +5031 踝 +5032 傍 +5033 禺 +5034 貧 +5035 缶 +5036 霾 +5037 邯 +5038 蜚 +5039 翥 +5040 掷 +5041 罔 +5042 蝽 +5043 襪 +5044 怎 +5045 諸 +5046 斛 +5047 誼 +5048 鲛 +5049 媞 +5050 漲 +5051 吖 +5052 叱 +5053 譚 +5054 譽 +5055 漸 +5056 鸮 +5057 郅 +5058 芗 +5059 贏 +5060 貸 +5061 亵 +5062 俎 +5063 剎 +5064 俘 +5065 篙 +5066 気 +5067 荭 +5068 莪 +5069 萸 +5070 蒽 +5071 マ +5072 夼 +5073 藓 +5074 牽 +5075 鱗 +5076 繆 +5077 钒 +5078 珐 +5079 穩 +5080 脯 +5081 珪 +5082 さ +5083 じ +5084 け +5085 エ +5086 ク +5087 彊 +5088 挌 +5089 暉 +5090 棟 +5091 踞 +5092 艰 +5093 缄 +5094 酵 +5095 较 +5096 糾 +5097 糙 +5098 お +5099 メ +5100 釀 +5101 喔 +5102 啾 +5103 篓 +5104 掳 +5105 拧 +5106 哦 +5107 氫 +5108 つ +5109 摹 +5110 悖 +5111 嗝 +5112 沔 +5113 與 +5114 眯 +5115 衢 +5116 娉 +5117 剖 +5118 嫦 +5119 嬷 +5120 湮 +5121 繫 +5122 舖 +5123 鈔 +5124 醚 +5125 庖 +5126 馒 +5127 潋 +5128 逻 +5129 聋 +5130 纖 +5131 潺 +5132 遛 +5133 滲 +5134 绉 +5135 绀 +5136 磺 +5137 菓 +5138 顷 +5139 玠 +5140 淒 +5141 挟 +5142 痫 +5143 鹬 +5144 鹳 +5145 閱 +5146 偵 +5147 胯 +5148 璀 +5149 娶 +5150 甑 +5151 辘 +5152 魇 +5153 ル +5154 嶋 +5155 榻 +5156 杈 +5157 昵 +5158 黍 +5159 塍 +5160 丟 +5161 恣 +5162 れ +5163 袒 +5164 挞 +5165 锂 +5166 旖 +5167 铄 +5168 掀 +5169 砦 +5170 舔 +5171 燧 +5172 稔 +5173 漬 +5174 蜒 +5175 裾 +5176 瀘 +5177 暫 +5178 嚎 +5179 蚧 +5180 匆 +5181 掖 +5182 铱 +5183 詢 +5184 擋 +5185 燉 +5186 壺 +5187 販 +5188 爻 +5189 蜥 +5190 翦 +5191 仄 +5192 螂 +5193 砧 +5194 厮 +5195 粑 +5196 匝 +5197 吁 +5198 豎 +5199 蝴 +5200 蛀 +5201 剌 +5202 歳 +5203 遜 +5204 咚 +5205 渦 +5206 讴 +5207 谤 +5208 抠 +5209 僮 +5210 俑 +5211 廂 +5212 撥 +5213 芨 +5214 诩 +5215 芫 +5216 巽 +5217 苣 +5218 茴 +5219 荏 +5220 苴 +5221 賤 +5222 鹹 +5223 祕 +5224 逮 +5225 薏 +5226 矗 +5227 ǐ +5228 禍 +5229 瘡 +5230 緻 +5231 涪 +5232 唬 +5233 イ +5234 钡 +5235 雹 +5236 們 +5237 兇 +5238 兌 +5239 勛 +5240 剝 +5241 揮 +5242 擼 +5243 敘 +5244 殤 +5245 灑 +5246 烜 +5247 揪 +5248 綜 +5249 拣 +5250 絞 +5251 柬 +5252 秸 +5253 緒 +5254 埂 +5255 逛 +5256 逞 +5257 滁 +5258 麽 +5259 揍 +5260 岘 +5261 袄 +5262 坷 +5263 繞 +5264 瞒 +5265 聰 +5266 髋 +5267 屌 +5268 颁 +5269 啄 +5270 傘 +5271 疵 +5272 嬅 +5273 崂 +5274 徙 +5275 呐 +5276 噻 +5277 彗 +5278 闱 +5279 寥 +5280 嚓 +5281 潢 +5282 瞄 +5283 婺 +5284 骜 +5285 骠 +5286 纨 +5287 鈎 +5288 嵬 +5289 阆 +5290 庠 +5291 悯 +5292 剁 +5293 瞧 +5294 缜 +5295 酋 +5296 癲 +5297 叼 +5298 バ +5299 疸 +5300 楝 +5301 闊 +5302 搔 +5303 瑷 +5304 ト +5305 戗 +5306 陝 +5307 娛 +5308 柺 +5309 蔥 +5310 爰 +5311 獒 +5312 蠕 +5313 杳 +5314 脲 +5315 閑 +5316 孰 +5317 薊 +5318 橄 +5319 褥 +5320 胪 +5321 腱 +5322 仍 +5323 膈 +5324 赊 +5325 竑 +5326 刪 +5327 孖 +5328 擁 +5329 坍 +5330 壩 +5331 捨 +5332 锉 +5333 跋 +5334 ハ +5335 熄 +5336 沓 +5337 湍 +5338 惕 +5339 焖 +5340 钏 +5341 钴 +5342 馅 +5343 発 +5344 凪 +5345 曬 +5346 癜 +5347 耦 +5348 窈 +5349 奄 +5350 簾 +5351 蠓 +5352 螭 +5353 臾 +5354 吱 +5355 鯊 +5356 氛 +5357 咋 +5358 徹 +5359 噩 +5360 乜 +5361 孬 +5362 揖 +5363 鼐 +5364 醪 +5365 撼 +5366 蚰 +5367 蛎 +5368 鲟 +5369 帚 +5370 蔗 +5371 厍 +5372 鬱 +5373 诣 +5374 羯 +5375 蜓 +5376 盅 +5377 誕 +5378 蜻 +5379 剡 +5380 簌 +5381 筵 +5382 酊 +5383 怔 +5384 贿 +5385 み +5386 忒 +5387 叻 +5388 吒 +5389 撷 +5390 遞 +5391 廁 +5392 俚 +5393 贇 +5394 勖 +5395 夔 +5396 苋 +5397 诤 +5398 塾 +5399 賠 +5400 谲 +5401 淵 +5402 鼾 +5403 莼 +5404 輯 +5405 菰 +5406 滯 +5407 薮 +5408 揆 +5409 辯 +5410 髯 +5411 瑠 +5412 皑 +5413 盎 +5414 哎 +5415 祷 +5416 ウ +5417 償 +5418 厭 +5419 嘆 +5420 嚇 +5421 嬿 +5422 嶽 +5423 憑 +5424 憲 +5425 攤 +5426 桜 +5427 檯 +5428 渾 +5429 湉 +5430 澀 +5431 綉 +5432 綸 +5433 緯 +5434 疚 +5435 倔 +5436 笹 +5437 硃 +5438 瀉 +5439 妨 +5440 ム +5441 栢 +5442 猥 +5443 膩 +5444 悌 +5445 鉆 +5446 悚 +5447 屆 +5448 铆 +5449 崮 +5450 嗦 +5451 箩 +5452 屡 +5453 饷 +5454 涿 +5455 娲 +5456 娓 +5457 娈 +5458 姊 +5459 撈 +5460 拈 +5461 鎂 +5462 讫 +5463 録 +5464 嵊 +5465 猶 +5466 吝 +5467 霹 +5468 溱 +5469 羨 +5470 琵 +5471 恂 +5472 琤 +5473 疊 +5474 凜 +5475 堑 +5476 珲 +5477 甦 +5478 梆 +5479 筐 +5480 穰 +5481 瓠 +5482 饒 +5483 鸪 +5484 疱 +5485 鹉 +5486 猩 +5487 痂 +5488 嘘 +5489 瘀 +5490 閨 +5491 閩 +5492 惦 +5493 侩 +5494 敕 +5495 桠 +5496 赉 +5497 伺 +5498 殓 +5499 犟 +5500 唆 +5501 雛 +5502 淄 +5503 勍 +5504 レ +5505 飕 +5506 獭 +5507 蘿 +5508 讹 +5509 ワ +5510 飨 +5511 頑 +5512 趟 +5513 侮 +5514 蝕 +5515 惋 +5516 碛 +5517 熵 +5518 钤 +5519 硒 +5520 飏 +5521 蟬 +5522 睑 +5523 稞 +5524 盞 +5525 擬 +5526 勸 +5527 擇 +5528 駝 +5529 窠 +5530 耒 +5531 裱 +5532 ず +5533 憾 +5534 曈 +5535 蜃 +5536 ヒ +5537 簸 +5538 憎 +5539 鰲 +5540 敝 +5541 謂 +5542 柞 +5543 醴 +5544 蠹 +5545 蚶 +5546 翕 +5547 雎 +5548 雒 +5549 跖 +5550 啬 +5551 誦 +5552 铀 +5553 蜷 +5554 蹊 +5555 蹼 +5556 誇 +5557 蜢 +5558 跷 +5559 謙 +5560 咱 +5561 伫 +5562 ミ +5563 呓 +5564 诒 +5565 倏 +5566 鄱 +5567 倜 +5568 芾 +5569 茆 +5570 阪 +5571 谄 +5572 谙 +5573 芡 +5574 隗 +5575 芎 +5576 茯 +5577 荇 +5578 濾 +5579 龐 +5580 菘 +5581 菟 +5582 齋 +5583 蕲 +5584 掬 +5585 扪 +5586 轟 +5587 燭 +5588 捶 +5589 幢 +5590 ǎ +5591 鳕 +5592 皺 +5593 縛 +5594 扛 +5595 穂 +5596 ゴ +5597 セ +5598 ギ +5599 噹 +5600 墳 +5601 奬 +5602 姍 +5603 嫄 +5604 慮 +5605 様 +5606 灝 +5607 槛 +5608 伎 +5609 綁 +5610 澗 +5611 痉 +5612 剿 +5613 撅 +5614 緋 +5615 睫 +5616 筍 +5617 舶 +5618 菠 +5619 矇 +5620 怖 +5621 猖 +5622 ǔ +5623 郴 +5624 椽 +5625 オ +5626 暘 +5627 獣 +5628 羔 +5629 庒 +5630 掂 +5631 鉀 +5632 灞 +5633 鍛 +5634 颗 +5635 麂 +5636 浯 +5637 鋅 +5638 鋸 +5639 寞 +5640 併 +5641 銜 +5642 峒 +5643 喙 +5644 嗯 +5645 忏 +5646 滏 +5647 繡 +5648 沌 +5649 臘 +5650 沭 +5651 阈 +5652 姒 +5653 苺 +5654 滂 +5655 淙 +5656 汩 +5657 媾 +5658 艶 +5659 嫱 +5660 莆 +5661 曝 +5662 錐 +5663 撂 +5664 逄 +5665 逑 +5666 馏 +5667 囿 +5668 嘀 +5669 弭 +5670 啮 +5671 皿 +5672 泺 +5673 纏 +5674 噗 +5675 歉 +5676 玎 +5677 悄 +5678 珙 +5679 缬 +5680 缭 +5681 擠 +5682 愷 +5683 恍 +5684 鸩 +5685 餌 +5686 鹑 +5687 蠶 +5688 疖 +5689 瘕 +5690 榈 +5691 椤 +5692 闇 +5693 辫 +5694 瑭 +5695 氪 +5696 榫 +5697 昴 +5698 昝 +5699 拭 +5700 殒 +5701 腈 +5702 枞 +5703 枋 +5704 隧 +5705 腩 +5706 妊 +5707 蓆 +5708 楮 +5709 枸 +5710 辇 +5711 臊 +5712 窮 +5713 琯 +5714 禛 +5715 恙 +5716 ネ +5717 捅 +5718 飓 +5719 眺 +5720 虧 +5721 勵 +5722 顛 +5723 螞 +5724 飽 +5725 幌 +5726 蟻 +5727 搪 +5728 砣 +5729 镫 +5730 晤 +5731 蘊 +5732 萄 +5733 蘋 +5734 碣 +5735 頤 +5736 诬 +5737 镗 +5738 梟 +5739 瘿 +5740 蚜 +5741 衲 +5742 聃 +5743 馮 +5744 駒 +5745 颀 +5746 蟆 +5747 螽 +5748 螈 +5749 哟 +5750 堯 +5751 滘 +5752 颞 +5753 颚 +5754 颛 +5755 衄 +5756 徳 +5757 炘 +5758 該 +5759 詳 +5760 囍 +5761 孵 +5762 鯉 +5763 諜 +5764 亟 +5765 蛄 +5766 蚺 +5767 袅 +5768 衾 +5769 踵 +5770 斟 +5771 孛 +5772 箧 +5773 羟 +5774 笏 +5775 蛏 +5776 跛 +5777 鴉 +5778 蛭 +5779 鱿 +5780 蹴 +5781 仵 +5782 暨 +5783 蜈 +5784 酐 +5785 鲑 +5786 髒 +5787 篩 +5788 觚 +5789 鯛 +5790 瀝 +5791 摺 +5792 哝 +5793 呦 +5794 喏 +5795 哌 +5796 咻 +5797 瀟 +5798 髻 +5799 俣 +5800 賺 +5801 贈 +5802 滬 +5803 郄 +5804 蹤 +5805 墉 +5806 俟 +5807 傩 +5808 偎 +5809 凼 +5810 荜 +5811 陟 +5812 贛 +5813 隍 +5814 邛 +5815 垡 +5816 荠 +5817 摧 +5818 萁 +5819 莨 +5820 蒌 +5821 嶼 +5822 稗 +5823 掇 +5824 蕈 +5825 鳢 +5826 鞣 +5827 鞅 +5828 瑋 +5829 竊 +5830 籤 +5831 蛔 +5832 猾 +5833 粄 +5834 が +5835 ジ +5836 * +5837 伕 +5838 厠 +5839 嘯 +5840 姮 +5841 廬 +5842 搾 +5843 潑 +5844 讥 +5845 絳 +5846 喚 +5847 铰 +5848 硷 +5849 絢 +5850 す +5851 搀 +5852 掺 +5853 硯 +5854 毆 +5855 濁 +5856 峄 +5857 幛 +5858 哩 +5859 喋 +5860 啵 +5861 婪 +5862 烩 +5863 猝 +5864 迸 +5865 ヤ +5866 洹 +5867 鋭 +5868 撃 +5869 拇 +5870 膿 +5871 臍 +5872 鉤 +5873 悻 +5874 嗑 +5875 嗖 +5876 喑 +5877 饬 +5878 琶 +5879 懵 +5880 噫 +5881 忡 +5882 怵 +5883 孀 +5884 姘 +5885 潦 +5886 怆 +5887 砰 +5888 蔫 +5889 藐 +5890 乒 +5891 嫫 +5892 骓 +5893 孢 +5894 纡 +5895 孪 +5896 沆 +5897 泔 +5898 錘 +5899 怏 +5900 庹 +5901 抡 +5902 銹 +5903 巅 +5904 恸 +5905 遒 +5906 遨 +5907 狞 +5908 淏 +5909 癱 +5910 绡 +5911 纭 +5912 扦 +5913 玢 +5914 缢 +5915 缥 +5916 珧 +5917 躯 +5918 畿 +5919 鸫 +5920 鸱 +5921 鸨 +5922 樞 +5923 懈 +5924 衅 +5925 鹗 +5926 惺 +5927 餓 +5928 蠱 +5929 痱 +5930 匈 +5931 榉 +5932 楦 +5933 ど +5934 よ +5935 龋 +5936 戢 +5937 笆 +5938 雫 +5939 隴 +5940 擘 +5941 杓 +5942 牍 +5943 嚷 +5944 樯 +5945 砷 +5946 轭 +5947 栉 +5948 觑 +5949 闖 +5950 柩 +5951 腭 +5952 捎 +5953 樨 +5954 枰 +5955 鑄 +5956 閥 +5957 滷 +5958 焗 +5959 嘔 +5960 蛻 +5961 胳 +5962 勳 +5963 歙 +5964 蘚 +5965 瞰 +5966 螢 +5967 わ +5968 蠔 +5969 斫 +5970 砭 +5971 旃 +5972 钇 +5973 褪 +5974 烊 +5975 淌 +5976 铍 +5977 铐 +5978 鸵 +5979 熨 +5980 铤 +5981 铢 +5982 镔 +5983 顆 +5984 癒 +5985 僱 +5986 媗 +5987 琇 +5988 嘗 +5989 竦 +5990 癀 +5991 秆 +5992 衿 +5993 竜 +5994 螃 +5995 蟮 +5996 罂 +5997 螳 +5998 傭 +5999 夠 +6000 蝼 +6001 驕 +6002 噎 +6003 ぴ +6004 侖 +6005 訾 +6006 嬛 +6007 謠 +6008 蜘 +6009 酢 +6010 趸 +6011 醍 +6012 フ +6013 汎 +6014 匕 +6015 氐 +6016 蚓 +6017 蚬 +6018 鲢 +6019 諧 +6020 蚴 +6021 訣 +6022 綦 +6023 謊 +6024 鳩 +6025 驢 +6026 蛳 +6027 窒 +6028 瘴 +6029 笳 +6030 鲵 +6031 嘱 +6032 貘 +6033 睾 +6034 佤 +6035 詐 +6036 篾 +6037 蛸 +6038 貔 +6039 簋 +6040 窺 +6041 卻 +6042 唏 +6043 咧 +6044 慣 +6045 歎 +6046 烔 +6047 鷺 +6048 べ +6049 贅 +6050 刍 +6051 蹟 +6052 黒 +6053 艽 +6054 堀 +6055 鷄 +6056 垅 +6057 勰 +6058 坭 +6059 谔 +6060 凫 +6061 賜 +6062 谠 +6063 俸 +6064 垓 +6065 黴 +6066 邴 +6067 圪 +6068 賦 +6069 荥 +6070 剋 +6071 僕 +6072 陛 +6073 較 +6074 莅 +6075 荨 +6076 茛 +6077 菖 +6078 轄 +6079 薹 +6080 捺 +6081 骰 +6082 掸 +6083 禎 +6084 々 +6085 腑 +6086 竅 +6087 玙 +6088 玕 +6089 ご +6090 う +6091 せ +6092 ぎ +6093 グ +6094 倖 +6095 厲 +6096 唸 +6097 姪 +6098 姉 +6099 寢 +6100 崟 +6101 悽 +6102 柊 +6103 棧 +6104 殯 +6105 湊 +6106 湜 +6107 潰 +6108 骷 +6109 紳 +6110 ソ +6111 粳 +6112 紹 +6113 綢 +6114 綴 +6115 叢 +6116 洸 +6117 膊 +6118 惭 +6119 豺 +6120 姵 +6121 躇 +6122 癮 +6123 溼 +6124 岬 +6125 釆 +6126 鬣 +6127 啜 +6128 喱 +6129 喽 +6130 だ +6131 ダ +6132 麾 +6133 猗 +6134 搂 +6135 艙 +6136 悒 +6137 愕 +6138 懊 +6139 睹 +6140 脣 +6141 慵 +6142 悴 +6143 懦 +6144 涑 +6145 晾 +6146 噌 +6147 噤 +6148 忖 +6149 饴 +6150 馀 +6151 饽 +6152 遽 +6153 邃 +6154 迥 +6155 淅 +6156 闩 +6157 肅 +6158 嘈 +6159 鎬 +6160 苾 +6161 嗳 +6162 迳 +6163 汨 +6164 闼 +6165 媪 +6166 粕 +6167 骝 +6168 嬲 +6169 孳 +6170 辔 +6171 挛 +6172 狹 +6173 逖 +6174 阊 +6175 嶂 +6176 帏 +6177 釵 +6178 罵 +6179 鄒 +6180 嘹 +6181 恻 +6182 阗 +6183 醃 +6184 沚 +6185 诅 +6186 佺 +6187 曠 +6188 绗 +6189 绂 +6190 谴 +6191 菈 +6192 缌 +6193 缗 +6194 缑 +6195 缟 +6196 鏢 +6197 荳 +6198 嬸 +6199 衹 +6200 衆 +6201 衎 +6202 鸷 +6203 痿 +6204 戦 +6205 椋 +6206 瞪 +6207 ド +6208 蒨 +6209 煽 +6210 苫 +6211 啥 +6212 鑲 +6213 吶 +6214 瓤 +6215 榷 +6216 戡 +6217 闌 +6218 隸 +6219 氙 +6220 ニ +6221 吮 +6222 藴 +6223 榧 +6224 虢 +6225 椴 +6226 瘸 +6227 慑 +6228 栲 +6229 肓 +6230 隻 +6231 蔆 +6232 殡 +6233 槲 +6234 晌 +6235 轱 +6236 桁 +6237 杼 +6238 蔵 +6239 觐 +6240 扔 +6241 桷 +6242 牯 +6243 牒 +6244 胗 +6245 艘 +6246 蔬 +6247 鳃 +6248 棂 +6249 闢 +6250 棹 +6251 贽 +6252 膻 +6253 瀏 +6254 僅 +6255 昳 +6256 漣 +6257 婭 +6258 愆 +6259 恚 +6260 虓 +6261 黝 +6262 铟 +6263 蝸 +6264 黠 +6265 秣 +6266 飼 +6267 餃 +6268 罹 +6269 磴 +6270 砻 +6271 锑 +6272 頰 +6273 锢 +6274 礴 +6275 頒 +6276 煨 +6277 绦 +6278 焓 +6279 頜 +6280 砗 +6281 碓 +6282 眦 +6283 碇 +6284 迢 +6285 镉 +6286 秭 +6287 镞 +6288 誊 +6289 钯 +6290 睨 +6291 欽 +6292 鱧 +6293 礙 +6294 玪 +6295 瘪 +6296 餮 +6297 衽 +6298 唁 +6299 衩 +6300 袢 +6301 耜 +6302 鸯 +6303 疡 +6304 馭 +6305 峯 +6306 ズ +6307 氦 +6308 び +6309 踊 +6310 虻 +6311 颦 +6312 颏 +6313 颔 +6314 滓 +6315 遏 +6316 濒 +6317 ピ +6318 鲎 +6319 龈 +6320 霎 +6321 醌 +6322 誡 +6323 伧 +6324 馗 +6325 廿 +6326 蚣 +6327 蹙 +6328 虺 +6329 笈 +6330 蜞 +6331 裟 +6332 剽 +6333 蚱 +6334 築 +6335 褻 +6336 蛐 +6337 鲳 +6338 鲂 +6339 菏 +6340 糸 +6341 羧 +6342 仉 +6343 笪 +6344 繇 +6345 靥 +6346 赳 +6347 鲅 +6348 粼 +6349 糁 +6350 粽 +6351 鲶 +6352 稣 +6353 伢 +6354 踹 +6355 鰐 +6356 蝙 +6357 螯 +6358 糍 +6359 佧 +6360 鰻 +6361 淩 +6362 濺 +6363 弒 +6364 楽 +6365 嬤 +6366 呔 +6367 卟 +6368 擢 +6369 哏 +6370 哧 +6371 呤 +6372 咄 +6373 咛 +6374 璽 +6375 盪 +6376 囤 +6377 讪 +6378 诳 +6379 诜 +6380 岿 +6381 鵡 +6382 俦 +6383 鹼 +6384 麩 +6385 踐 +6386 郇 +6387 埙 +6388 郯 +6389 ボ +6390 茏 +6391 艿 +6392 俳 +6393 阱 +6394 侏 +6395 俾 +6396 茚 +6397 茕 +6398 偈 +6399 苌 +6400 荑 +6401 貶 +6402 脔 +6403 壅 +6404 鷓 +6405 谶 +6406 鷗 +6407 邳 +6408 羸 +6409 垸 +6410 苜 +6411 鸚 +6412 佥 +6413 荦 +6414 苻 +6415 搗 +6416 鱔 +6417 穀 +6418 龑 +6419 荽 +6420 輿 +6421 葶 +6422 蓍 +6423 蓦 +6424 菅 +6425 蓥 +6426 憐 +6427 婠 +6428 蘖 +6429 轎 +6430 抻 +6431 掾 +6432 捋 +6433 辻 +6434 鱷 +6435 鱘 +6436 谆 +6437 瑢 +6438 蹈 +6439 祤 +6440 瘉 +6441 縷 +6442 繃 +6443 窅 +6444 竇 +6445 玘 +6446 玗 +6447 粧 +6448 秾 +6449 ┳ +6450 畝 +6451 ざ +6452 ザ +6453 ケ +6454 摈 +6455 伝 +6456 嚒 +6457 墮 +6458 妺 +6459 幀 +6460 廯 +6461 彙 +6462 摯 +6463 枊 +6464 櫥 +6465 檸 +6466 汙 +6467 潯 +6468 煒 +6469 煖 +6470 そ +6471 骶 +6472 緝 +6473 締 +6474 緬 +6475 紺 +6476 厩 +6477 経 +6478 鞑 +6479 げ +6480 澆 +6481 谗 +6482 掣 +6483 踌 +6484 侈 +6485 烝 +6486 尓 +6487 曖 +6488 翛 +6489 圜 +6490 嘧 +6491 喟 +6492 喹 +6493 嗷 +6494 唰 +6495 垃 +6496 纜 +6497 诲 +6498 樺 +6499 甯 +6500 泞 +6501 т +6502 婁 +6503 浍 +6504 浠 +6505 浈 +6506 淖 +6507 с +6508 愦 +6509 惆 +6510 憧 +6511 惴 +6512 悭 +6513 銑 +6514 髅 +6515 聾 +6516 沤 +6517 憔 +6518 涔 +6519 洳 +6520 溧 +6521 汜 +6522 汊 +6523 崆 +6524 溏 +6525 譬 +6526 彘 +6527 逅 +6528 氖 +6529 渌 +6530 驸 +6531 驽 +6532 沏 +6533 骀 +6534 骟 +6535 嬗 +6536 苪 +6537 尜 +6538 纣 +6539 鉬 +6540 鈍 +6541 犸 +6542 嗤 +6543 囔 +6544 囝 +6545 馔 +6546 逋 +6547 屐 +6548 孱 +6549 銲 +6550 涮 +6551 鉑 +6552 溲 +6553 狍 +6554 嘤 +6555 庥 +6556 砒 +6557 潴 +6558 湔 +6559 抿 +6560 阏 +6561 罷 +6562 狷 +6563 帑 +6564 恹 +6565 妁 +6566 潸 +6567 澌 +6568 馁 +6569 錠 +6570 鄖 +6571 鋤 +6572 徂 +6573 窿 +6574 廪 +6575 妣 +6576 奀 +6577 岀 +6578 绺 +6579 髑 +6580 で +6581 缃 +6582 顼 +6583 莖 +6584 泅 +6585 荘 +6586 莙 +6587 鸶 +6588 皈 +6589 鸲 +6590 喰 +6591 疴 +6592 鹈 +6593 痤 +6594 鹧 +6595 瘊 +6596 汹 +6597 疔 +6598 饃 +6599 濫 +6600 榀 +6601 楂 +6602 楫 +6603 閲 +6604 閻 +6605 磋 +6606 杌 +6607 璁 +6608 瑁 +6609 冴 +6610 掙 +6611 氷 +6612 辍 +6613 闿 +6614 蕪 +6615 纂 +6616 毽 +6617 氅 +6618 氘 +6619 槁 +6620 枘 +6621 薬 +6622 肼 +6623 桄 +6624 鐲 +6625 薈 +6626 栊 +6627 墒 +6628 觇 +6629 闕 +6630 觎 +6631 薔 +6632 橐 +6633 暝 +6634 胍 +6635 嗽 +6636 胫 +6637 辂 +6638 犍 +6639 挈 +6640 膣 +6641 檩 +6642 噁 +6643 濬 +6644 唄 +6645 琲 +6646 痠 +6647 歩 +6648 黜 +6649 悫 +6650 碜 +6651 矸 +6652 欖 +6653 殳 +6654 旄 +6655 欹 +6656 毂 +6657 诽 +6658 兲 +6659 虜 +6660 咁 +6661 瞽 +6662 睽 +6663 撐 +6664 澪 +6665 碉 +6666 囷 +6667 飢 +6668 锘 +6669 蠅 +6670 蠍 +6671 磲 +6672 顱 +6673 屉 +6674 紊 +6675 韌 +6676 眄 +6677 盹 +6678 镬 +6679 镂 +6680 颙 +6681 煅 +6682 斡 +6683 钫 +6684 秕 +6685 秫 +6686 哮 +6687 睐 +6688 钲 +6689 睚 +6690 瀕 +6691 駛 +6692 ぱ +6693 駁 +6694 駄 +6695 嘜 +6696 満 +6697 蟥 +6698 簟 +6699 吭 +6700 吩 +6701 雩 +6702 霰 +6703 鰱 +6704 ぶ +6705 ブ +6706 謹 +6707 戸 +6708 醮 +6709 醅 +6710 蹩 +6711 ふ +6712 澱 +6713 铡 +6714 諒 +6715 卅 +6716 囟 +6717 貍 +6718 鼋 +6719 鼍 +6720 罄 +6721 舐 +6722 蝈 +6723 鲣 +6724 鬲 +6725 乩 +6726 笄 +6727 蜱 +6728 翮 +6729 郧 +6730 笕 +6731 蜩 +6732 蛩 +6733 鲩 +6734 錾 +6735 蹶 +6736 騁 +6737 箜 +6738 鲮 +6739 跆 +6740 仨 +6741 赝 +6742 豊 +6743 匮 +6744 涸 +6745 笥 +6746 粢 +6747 赧 +6748 瞩 +6749 跤 +6750 睁 +6751 伉 +6752 襯 +6753 诌 +6754 筚 +6755 筌 +6756 騏 +6757 豉 +6758 糗 +6759 剀 +6760 瀞 +6761 嘢 +6762 呋 +6763 邏 +6764 吲 +6765 咂 +6766 唠 +6767 吆 +6768 哔 +6769 啕 +6770 甌 +6771 讦 +6772 诟 +6773 殆 +6774 鼯 +6775 侪 +6776 ほ +6777 郓 +6778 诶 +6779 谀 +6780 倮 +6781 黉 +6782 黙 +6783 坻 +6784 兖 +6785 莛 +6786 苄 +6787 貳 +6788 贖 +6789 陬 +6790 谖 +6791 偻 +6792 兕 +6793 傥 +6794 畚 +6795 鶯 +6796 隈 +6797 谥 +6798 谪 +6799 鵲 +6800 僭 +6801 赑 +6802 谮 +6803 嘩 +6804 畑 +6805 攜 +6806 癥 +6807 価 +6808 莠 +6809 荩 +6810 萜 +6811 軼 +6812 媄 +6813 薨 +6814 薤 +6815 蕖 +6816 藁 +6817 迖 +6818 藿 +6819 蘼 +6820 奁 +6821 揄 +6822 尬 +6823 拶 +6824 燴 +6825 狛 +6826 磡 +6827 磧 +6828 磯 +6829 ǒ +6830 鳔 +6831 鳟 +6832 鳏 +6833 鳎 +6834 鲀 +6835 鲹 +6836 瑣 +6837 唉 +6838 皜 +6839 皞 +6840 惮 +6841 郸 +6842 祘 +6843 揩 +6844 繚 +6845 袱 +6846 珝 +6847 珰 +6848 禱 +6849 畬 +6850 ぐ +6851 睏 +6852 乚 +6853 倓 +6854 倞 +6855 僞 +6856 儷 +6857 儘 +6858 啫 +6859 嚮 +6860 噯 +6861 埇 +6862 埗 +6863 垵 +6864 塢 +6865 奭 +6866 妠 +6867 帰 +6868 恵 +6869 憤 +6870 挾 +6871 摳 +6872 攪 +6873 暦 +6874 暐 +6875 柈 +6876 枂 +6877 棲 +6878 棨 +6879 樁 +6880 槓 +6881 檳 +6882 毐 +6883 洑 +6884 湲 +6885 潁 +6886 瀆 +6887 讣 +6888 ゼ +6889 嫉 +6890 絵 +6891 饯 +6892 ゾ +6893 壘 +6894 絃 +6895 抉 +6896 絆 +6897 嫻 +6898 箏 +6899 箓 +6900 剐 +6901 徊 +6902 槻 +6903 碴 +6904 搽 +6905 诧 +6906 伋 +6907 矯 +6908 矻 +6909 瞅 +6910 堿 +6911 啰 +6912 瘁 +6913 岽 +6914 嶙 +6915 岌 +6916 岜 +6917 釗 +6918 啻 +6919 嗫 +6920 啷 +6921 斃 +6922 猬 +6923 夥 +6924 舛 +6925 猊 +6926 鈦 +6927 髀 +6928 跺 +6929 涘 +6930 遄 +6931 澶 +6932 浥 +6933 炁 +6934 浞 +6935 銨 +6936 洧 +6937 髂 +6938 ツ +6939 臑 +6940 泖 +6941 慊 +6942 ッ +6943 娒 +6944 辆 +6945 嗉 +6946 谰 +6947 峁 +6948 夤 +6949 岵 +6950 獠 +6951 獬 +6952 愠 +6953 崃 +6954 拎 +6955 崤 +6956 忉 +6957 怃 +6958 遴 +6959 迓 +6960 娩 +6961 羈 +6962 嗲 +6963 馇 +6964 囵 +6965 庑 +6966 漯 +6967 麈 +6968 挎 +6969 屾 +6970 涙 +6971 妫 +6972 髌 +6973 テ +6974 徠 +6975 芣 +6976 茀 +6977 鄀 +6978 嚅 +6979 忤 +6980 潆 +6981 瞥 +6982 艱 +6983 嫘 +6984 骘 +6985 苨 +6986 翯 +6987 犴 +6988 馓 +6989 怙 +6990 逦 +6991 沩 +6992 囫 +6993 怦 +6994 羼 +6995 嵋 +6996 嵴 +6997 繭 +6998 囹 +6999 圉 +7000 鈕 +7001 怛 +7002 乓 +7003 咆 +7004 阒 +7005 鉗 +7006 徇 +7007 鉚 +7008 嶷 +7009 豳 +7010 咙 +7011 您 +7012 彷 +7013 妪 +7014 漭 +7015 噢 +7016 戕 +7017 鉅 +7018 鰾 +7019 旵 +7020 麋 +7021 绱 +7022 纰 +7023 鐐 +7024 莀 +7025 菂 +7026 橇 +7027 锹 +7028 缡 +7029 鏘 +7030 鏜 +7031 鏽 +7032 甾 +7033 哾 +7034 昫 +7035 饑 +7036 ば +7037 鸺 +7038 鹁 +7039 鹌 +7040 鹩 +7041 鹨 +7042 餡 +7043 疠 +7044 橢 +7045 鏖 +7046 撻 +7047 閪 +7048 榘 +7049 椹 +7050 魃 +7051 囪 +7052 鑵 +7053 鑼 +7054 锜 +7055 溉 +7056 痊 +7057 颧 +7058 葷 +7059 応 +7060 旮 +7061 辚 +7062 陞 +7063 蕗 +7064 忞 +7065 膑 +7066 胱 +7067 氚 +7068 氲 +7069 牖 +7070 霂 +7071 霑 +7072 魉 +7073 瓴 +7074 殁 +7075 赡 +7076 桎 +7077 赈 +7078 肱 +7079 脘 +7080 槠 +7081 肫 +7082 閏 +7083 菴 +7084 桤 +7085 枨 +7086 槭 +7087 樗 +7088 桕 +7089 觌 +7090 腴 +7091 樘 +7092 雑 +7093 闘 +7094 隠 +7095 雖 +7096 萵 +7097 蕁 +7098 橛 +7099 轵 +7100 栌 +7101 纫 +7102 桴 +7103 桫 +7104 柝 +7105 朐 +7106 薙 +7107 橼 +7108 甥 +7109 辄 +7110 脍 +7111 蕎 +7112 甪 +7113 単 +7114 実 +7115 昰 +7116 窯 +7117 旼 +7118 沨 +7119 岺 +7120 濰 +7121 塱 +7122 汭 +7123 疙 +7124 婍 +7125 戆 +7126 怼 +7127 砜 +7128 砀 +7129 頃 +7130 魍 +7131 懲 +7132 戩 +7133 撿 +7134 齑 +7135 熳 +7136 鞏 +7137 囂 +7138 虤 +7139 滎 +7140 瞑 +7141 钭 +7142 畎 +7143 畋 +7144 顎 +7145 魑 +7146 戯 +7147 铯 +7148 铫 +7149 檄 +7150 蠄 +7151 旒 +7152 锛 +7153 砟 +7154 酞 +7155 炝 +7156 炻 +7157 钹 +7158 罾 +7159 盥 +7160 铼 +7161 锪 +7162 戽 +7163 嚏 +7164 硇 +7165 黻 +7166 黼 +7167 铊 +7168 铌 +7169 镪 +7170 锸 +7171 頗 +7172 盱 +7173 铑 +7174 钕 +7175 镱 +7176 飆 +7177 腆 +7178 祢 +7179 祧 +7180 詈 +7181 铗 +7182 镏 +7183 颯 +7184 蝨 +7185 禳 +7186 钶 +7187 淆 +7188 牺 +7189 恓 +7190 玨 +7191 鱈 +7192 攏 +7193 嘚 +7194 黢 +7195 я +7196 褡 +7197 窨 +7198 窕 +7199 駱 +7200 囱 +7201 襦 +7202 裥 +7203 讶 +7204 耋 +7205 耵 +7206 裨 +7207 聒 +7208 褙 +7209 褓 +7210 馴 +7211 慜 +7212 浐 +7213 蟀 +7214 髙 +7215 ビ +7216 売 +7217 を +7218 勻 +7219 蚩 +7220 蚨 +7221 驍 +7222 舀 +7223 覓 +7224 黥 +7225 籀 +7226 臬 +7227 魟 +7228 詭 +7229 岞 +7230 霪 +7231 隹 +7232 龇 +7233 髦 +7234 е +7235 愔 +7236 懼 +7237 謡 +7238 貅 +7239 醺 +7240 酴 +7241 髡 +7242 崭 +7243 鴣 +7244 鴦 +7245 へ +7246 ヘ +7247 踅 +7248 蠊 +7249 龉 +7250 醭 +7251 驛 +7252 筲 +7253 襞 +7254 鯽 +7255 踽 +7256 锗 +7257 跄 +7258 蹉 +7259 芈 +7260 蹑 +7261 跗 +7262 跚 +7263 麴 +7264 鮀 +7265 誅 +7266 仞 +7267 驟 +7268 鬍 +7269 鲭 +7270 蹰 +7271 跎 +7272 仃 +7273 蝾 +7274 酝 +7275 読 +7276 跸 +7277 叵 +7278 驤 +7279 髄 +7280 貉 +7281 跹 +7282 蠛 +7283 篝 +7284 篪 +7285 筘 +7286 蝮 +7287 蛴 +7288 蝤 +7289 蜇 +7290 龊 +7291 鲋 +7292 鲽 +7293 鮫 +7294 蘸 +7295 跻 +7296 豸 +7297 踉 +7298 踟 +7299 狰 +7300 赜 +7301 刎 +7302 驪 +7303 鬚 +7304 鲞 +7305 骉 +7306 喳 +7307 篤 +7308 訶 +7309 髖 +7310 蜉 +7311 蹀 +7312 佝 +7313 乸 +7314 沺 +7315 琍 +7316 琎 +7317 巖 +7318 禦 +7319 ╯ +7320 淪 +7321 咦 +7322 擀 +7323 甙 +7324 呖 +7325 咝 +7326 哞 +7327 哽 +7328 哓 +7329 呲 +7330 哕 +7331 咿 +7332 ╰ +7333 漷 +7334 禩 +7335 檜 +7336 鷲 +7337 髭 +7338 囑 +7339 诂 +7340 凇 +7341 诨 +7342 侉 +7343 佻 +7344 伲 +7345 鸞 +7346 鄞 +7347 郫 +7348 鄯 +7349 鼩 +7350 ぼ +7351 軀 +7352 墀 +7353 転 +7354 酃 +7355 籴 +7356 倥 +7357 坩 +7358 坼 +7359 垆 +7360 茑 +7361 鹀 +7362 矍 +7363 坌 +7364 谑 +7365 陔 +7366 匍 +7367 茈 +7368 陲 +7369 傧 +7370 茼 +7371 芟 +7372 鵰 +7373 谘 +7374 亳 +7375 垩 +7376 隰 +7377 谡 +7378 邙 +7379 袤 +7380 儆 +7381 酆 +7382 鹮 +7383 賁 +7384 賃 +7385 儋 +7386 圮 +7387 苘 +7388 賄 +7389 鸛 +7390 埝 +7391 坜 +7392 鱒 +7393 乂 +7394 朧 +7395 沄 +7396 痺 +7397 穢 +7398 譞 +7399 擷 +7400 ポ +7401 嬢 +7402 葑 +7403 莴 +7404 莩 +7405 菝 +7406 蒺 +7407 蓐 +7408 菔 +7409 輓 +7410 蒹 +7411 蒴 +7412 輛 +7413 軻 +7414 齲 +7415 傀 +7416 拮 +7417 薜 +7418 蕻 +7419 轅 +7420 蓿 +7421 捩 +7422 摒 +7423 奘 +7424 匏 +7425 揿 +7426 尴 +7427 抟 +7428 摁 +7429 辮 +7430 挹 +7431 搦 +7432 辺 +7433 谞 +7434 睜 +7435 搐 +7436 鳜 +7437 鱸 +7438 骺 +7439 鞲 +7440 鳙 +7441 鲉 +7442 鲘 +7443 鳉 +7444 鳑 +7445 讐 +7446 瑝 +7447 瑨 +7448 镑 +7449 皚 +7450 皦 +7451 盁 +7452 祙 +7453 痋 +7454 瘈 +7455 瘍 +7456 瘓 +7457 璣 +7458 瓏 +7459 瓘 +7460 笒 +7461 珖 +7462 豢 +7463 籟 +7464 粬 +7465 ё +7466 禵 +7467 ┛ +7468 ┃ +7469 甡 +7470 ぷ +7471 ぁ +7472 ぇ +7473 ガ +7474 + +7475 狈 +7476 盶 +7477 眬 +7478 睒 +7479 侘 +7480 亅 +7481 仮 +7482 亶 +7483 仏 +7484 偓 +7485 値 +7486 倻 +7487 儉 +7488 叡 +7489 厳 +7490 啱 +7491 噠 +7492 嚕 +7493 嘰 +7494 噭 +7495 噸 +7496 垈 +7497 垕 +7498 墾 +7499 墎 +7500 墘 +7501 姸 +7502 姈 +7503 嫲 +7504 嫆 +7505 嫊 +7506 嫋 +7507 崁 +7508 崈 +7509 崚 +7510 崢 +7511 幍 +7512 帯 +7513 巻 +7514 彫 +7515 弶 +7516 悪 +7517 慬 +7518 懇 +7519 憙 +7520 憫 +7521 挻 +7522 拝 +7523 斂 +7524 攢 +7525 攬 +7526 昞 +7527 暠 +7528 晝 +7529 晧 +7530 晸 +7531 杺 +7532 梶 +7533 梼 +7534 槺 +7535 槃 +7536 槑 +7537 櫞 +7538 櫚 +7539 殭 +7540 殲 +7541 洣 +7542 浛 +7543 洨 +7544 湰 +7545 湴 +7546 湳 +7547 淸 +7548 渼 +7549 漖 +7550 瀧 +7551 瀮 +7552 煢 +7553 焼 +7554 ぜ +7555 兗 +7556 惣 +7557 紓 +7558 紘 +7559 紜 +7560 紮 +7561 鹘 +7562 絹 +7563 綑 +7564 ň +7565 肪 +7566 ぞ +7567 溇 +7568 綻 +7569 継 +7570 緞 +7571 糰 +7572 侥 +7573 続 +7574 綽 +7575 綝 +7576 攫 +7577 絜 +7578 緈 +7579 絪 +7580 綰 +7581 紈 +7582 絎 +7583 紉 +7584 惫 +7585 児 +7586 姽 +7587 暪 +7588 筧 +7589 恫 +7590 ゲ +7591 瞋 +7592 睬 +7593 瞞 +7594 睺 +7595 硚 +7596 碁 +7597 砳 +7598 砕 +7599 珹 +7600 癆 +7601 嚜 +7602 惇 +7603 潽 +7604 嗎 +7605 幄 +7606 釁 +7607 釐 +7608 髁 +7609 劻 +7610 屃 +7611 浟 +7612 羱 +7613 郷 +7614 喁 +7615 唷 +7616 鄘 +7617 喈 +7618 鄲 +7619 骼 +7620 咐 +7621 惱 +7622 繽 +7623 纻 +7624 缷 +7625 罈 +7626 佲 +7627 団 +7628 夆 +7629 猕 +7630 飧 +7631 鈿 +7632 鉄 +7633 屄 +7634 嵚 +7635 聳 +7636 ゅ +7637 ュ +7638 嬪 +7639 濞 +7640 寤 +7641 瀹 +7642 鍊 +7643 鍙 +7644 冧 +7645 変 +7646 庝 +7647 鋇 +7648 洇 +7649 浃 +7650 洌 +7651 鋰 +7652 镊 +7653 臏 +7654 ャ +7655 悝 +7656 惬 +7657 銖 +7658 溍 +7659 谩 +7660 脅 +7661 峋 +7662 浼 +7663 徉 +7664 徨 +7665 郃 +7666 噱 +7667 釧 +7668 邂 +7669 洫 +7670 溽 +7671 悛 +7672 忝 +7673 徭 +7674 怄 +7675 馄 +7676 鈉 +7677 徘 +7678 鈊 +7679 銬 +7680 鎌 +7681 ㄆ +7682 芵 +7683 苼 +7684 苽 +7685 茋 +7686 崛 +7687 嗌 +7688 馐 +7689 馑 +7690 彖 +7691 咫 +7692 涫 +7693 婀 +7694 驺 +7695 芻 +7696 媲 +7697 骛 +7698 苃 +7699 骖 +7700 骢 +7701 苧 +7702 苭 +7703 鎧 +7704 罠 +7705 舎 +7706 镣 +7707 犰 +7708 犷 +7709 嗵 +7710 忪 +7711 錳 +7712 泐 +7713 阄 +7714 銶 +7715 狁 +7716 腘 +7717 狒 +7718 狨 +7719 狲 +7720 嘭 +7721 怍 +7722 怩 +7723 溆 +7724 湓 +7725 郟 +7726 翾 +7727 猄 +7728 噙 +7729 氵 +7730 狴 +7731 狳 +7732 帔 +7733 噘 +7734 儡 +7735 滠 +7736 猇 +7737 狺 +7738 癇 +7739 礳 +7740 昪 +7741 渃 +7742 瀋 +7743 – +7744 廋 +7745 х +7746 ょ +7747 ョ +7748 冪 +7749 绔 +7750 缁 +7751 绁 +7752 暻 +7753 菉 +7754 菫 +7755 玷 +7756 缛 +7757 鏗 +7758 荌 +7759 缣 +7760 莔 +7761 缰 +7762 缯 +7763 鏟 +7764 缵 +7765 莢 +7766 鐮 +7767 痙 +7768 俤 +7769 揹 +7770 梔 +7771 疍 +7772 黟 +7773 婐 +7774 氿 +7775 鸬 +7776 稹 +7777 皤 +7778 穑 +7779 饋 +7780 饹 +7781 餍 +7782 π +7783 袆 +7784 鹆 +7785 鹇 +7786 鹋 +7787 疰 +7788 痃 +7789 鹕 +7790 鹚 +7791 痦 +7792 痼 +7793 鹪 +7794 瘌 +7795 餚 +7796 餛 +7797 疬 +7798 饅 +7799 瘙 +7800 杄 +7801 珽 +7802 夐 +7803 涢 +7804 楱 +7805 椁 +7806 棰 +7807 閬 +7808 熒 +7809 嗚 +7810 欒 +7811 韪 +7812 鑰 +7813 铚 +7814 葇 +7815 涥 +7816 滉 +7817 戋 +7818 検 +7819 浭 +7820 澥 +7821 蔪 +7822 囯 +7823 氹 +7824 氆 +7825 毵 +7826 耄 +7827 毳 +7828 氡 +7829 査 +7830 薦 +7831 藠 +7832 氩 +7833 氤 +7834 槊 +7835 杪 +7836 桡 +7837 蔭 +7838 曷 +7839 脬 +7840 閎 +7841 萩 +7842 晷 +7843 殚 +7844 殛 +7845 呻 +7846 菶 +7847 杷 +7848 隕 +7849 蒄 +7850 镚 +7851 閔 +7852 雋 +7853 轫 +7854 娠 +7855 鐸 +7856 柰 +7857 腠 +7858 胛 +7859 腼 +7860 薺 +7861 轳 +7862 蔔 +7863 胙 +7864 蒾 +7865 轹 +7866 檎 +7867 牦 +7868 媵 +7869 膂 +7870 胝 +7871 胴 +7872 檫 +7873 雝 +7874 蓇 +7875 曩 +7876 犒 +7877 臌 +7878 関 +7879 蒟 +7880 挲 +7881 胼 +7882 辋 +7883 檗 +7884 巉 +7885 昮 +7886 碏 +7887 嶧 +7888 済 +7889 滸 +7890 籬 +7891 琀 +7892 獺 +7893 ╥ +7894 璱 +7895 峣 +7896 抜 +7897 渋 +7898 璉 +7899 ы +7900 図 +7901 栱 +7902 眵 +7903 韡 +7904 懑 +7905 韮 +7906 韾 +7907 蓖 +7908 呁 +7909 浲 +7910 滌 +7911 彀 +7912 欤 +7913 鞕 +7914 ヌ +7915 斕 +7916 蹋 +7917 ь +7918 嗩 +7919 畹 +7920 罘 +7921 顒 +7922 顓 +7923 顗 +7924 顥 +7925 埼 +7926 蝲 +7927 氾 +7928 颼 +7929 锃 +7930 罴 +7931 锝 +7932 砉 +7933 锕 +7934 砝 +7935 礤 +7936 礓 +7937 藺 +7938 钽 +7939 蠲 +7940 锱 +7941 镦 +7942 锲 +7943 锨 +7944 钚 +7945 顳 +7946 焐 +7947 嗡 +7948 颋 +7949 蟞 +7950 蘄 +7951 挝 +7952 镲 +7953 頼 +7954 硌 +7955 頽 +7956 锺 +7957 眙 +7958 椭 +7959 眭 +7960 碚 +7961 碡 +7962 祗 +7963 铙 +7964 锖 +7965 镌 +7966 镓 +7967 頡 +7968 碲 +7969 禊 +7970 颱 +7971 蟯 +7972 蟄 +7973 韜 +7974 頦 +7975 蝀 +7976 蟈 +7977 磔 +7978 忑 +7979 颶 +7980 磙 +7981 忐 +7982 燹 +7983 蠣 +7984 玧 +7985 獼 +7986 甁 +7987 禟 +7988 桲 +7989 譴 +7990 祃 +7991 窵 +7992 琺 +7993 俶 +7994 昺 +7995 渕 +7996 ∕ +7997 鱉 +7998 廙 +7999 琻 +8000 玭 +8001 ﹏ +8002 縉 +8003 烴 +8004 聍 +8005 癔 +8006 瘛 +8007 瘵 +8008 瘠 +8009 駙 +8010 駟 +8011 喲 +8012 癃 +8013 皴 +8014 裢 +8015 耧 +8016 裊 +8017 褛 +8018 聩 +8019 褊 +8020 褫 +8021 颃 +8022 媜 +8023 昽 +8024 梠 +8025 ㎡ +8026 嚭 +8027 埡 +8028 簕 +8029 簫 +8030 黧 +8031 篦 +8032 笞 +8033 蟋 +8034 蟑 +8035 螬 +8036 髪 +8037 в +8038 虼 +8039 颥 +8040 蚍 +8041 蚋 +8042 驊 +8043 驎 +8044 円 +8045 捯 +8046 曇 +8047 眶 +8048 滛 +8049 烎 +8050 魘 +8051 艋 +8052 舢 +8053 魷 +8054 詮 +8055 婗 +8056 滝 +8057 龃 +8058 鲼 +8059 觥 +8060 龌 +8061 鰭 +8062 謬 +8063 鮜 +8064 酽 +8065 醢 +8066 醯 +8067 酡 +8068 鯇 +8069 鯖 +8070 辗 +8071 眨 +8072 圾 +8073 髫 +8074 卮 +8075 丨 +8076 艟 +8077 黾 +8078 艄 +8079 虿 +8080 龀 +8081 罅 +8082 箦 +8083 蜮 +8084 鲠 +8085 鲥 +8086 雠 +8087 誥 +8088 趵 +8089 趼 +8090 蹂 +8091 趺 +8092 嘏 +8093 蜴 +8094 鲦 +8095 襜 +8096 諦 +8097 箸 +8098 笮 +8099 襠 +8100 笊 +8101 箅 +8102 蜿 +8103 鍪 +8104 鏊 +8105 亻 +8106 豨 +8107 鯤 +8108 箪 +8109 筇 +8110 箢 +8111 蛲 +8112 蝻 +8113 籼 +8114 諭 +8115 鲱 +8116 躅 +8117 仂 +8118 諮 +8119 簁 +8120 鯧 +8121 謐 +8122 誰 +8123 鳯 +8124 訫 +8125 豈 +8126 蝰 +8127 粞 +8128 鯪 +8129 鲴 +8130 鮪 +8131 笤 +8132 笾 +8133 蝌 +8134 螋 +8135 蝓 +8136 趄 +8137 糌 +8138 鲇 +8139 鲆 +8140 鲻 +8141 鲺 +8142 鲐 +8143 躞 +8144 貊 +8145 伥 +8146 魆 +8147 鰍 +8148 鮭 +8149 鮍 +8150 髎 +8151 諷 +8152 鳶 +8153 筮 +8154 騮 +8155 詔 +8156 鯰 +8157 鮰 +8158 筅 +8159 篼 +8160 蝥 +8161 蜊 +8162 糅 +8163 酎 +8164 踮 +8165 刿 +8166 諺 +8167 鬢 +8168 骕 +8169 鴛 +8170 糨 +8171 鳊 +8172 巔 +8173 噐 +8174 攔 +8175 丷 +8176 烺 +8177 眘 +8178 譙 +8179 疭 +8180 丼 +8181 奡 +8182 н +8183 ┻ +8184 邨 +8185 哚 +8186 呃 +8187 咤 +8188 呙 +8189 逨 +8190 哳 +8191 呶 +8192 唢 +8193 哂 +8194 啁 +8195 咣 +8196 唿 +8197 玹 +8198 眛 +8199 匱 +8200 噓 +8201 嶶 +8202 鼹 +8203 掹 +8204 鷥 +8205 蹿 +8206 ぺ +8207 広 +8208 讧 +8209 趨 +8210 鶉 +8211 鶗 +8212 ベ +8213 佴 +8214 佾 +8215 鼷 +8216 堺 +8217 燐 +8218 麀 +8219 鸰 +8220 ホ +8221 鄣 +8222 郛 +8223 郏 +8224 鼽 +8225 慄 +8226 嬡 +8227 屲 +8228 堞 +8229 堠 +8230 劬 +8231 芄 +8232 鄄 +8233 艹 +8234 谂 +8235 诿 +8236 貯 +8237 劾 +8238 茔 +8239 鹍 +8240 陉 +8241 訇 +8242 鬯 +8243 荛 +8244 苁 +8245 鵪 +8246 鸊 +8247 偬 +8248 厶 +8249 賚 +8250 垴 +8251 贠 +8252 谝 +8253 邗 +8254 儇 +8255 苤 +8256 冫 +8257 圹 +8258 埸 +8259 黿 +8260 邶 +8261 埤 +8262 茌 +8263 谵 +8264 贍 +8265 瑅 +8266 眞 +8267 亀 +8268 坵 +8269 檞 +8270 玏 +8271 沇 +8272 縴 +8273 凖 +8274 淉 +8275 齷 +8276 龕 +8277 傒 +8278 栞 +8279 蓣 +8280 荸 +8281 荬 +8282 轂 +8283 萋 +8284 萏 +8285 菹 +8286 蓠 +8287 蒡 +8288 葜 +8289 甍 +8290 軽 +8291 軾 +8292 瓃 +8293 倆 +8294 巣 +8295 玓 +8296 淶 +8297 慆 +8298 兀 +8299 м +8300 掁 +8301 栟 +8302 迍 +8303 蘧 +8304 轆 +8305 蕺 +8306 蘩 +8307 掴 +8308 捭 +8309 耷 +8310 掼 +8311 拊 +8312 拚 +8313 捃 +8314 込 +8315 盃 +8316 疇 +8317 偁 +8318 燻 +8319 牤 +8320 牘 +8321 犽 +8322 犢 +8323 磥 +8324 磦 +8325 磻 +8326 磾 +8327 ▕ +8328 ▽ +8329 鼢 +8330 鳓 +8331 靼 +8332 鞯 +8333 鲃 +8334 鲊 +8335 鲏 +8336 鲖 +8337 鲙 +8338 鲯 +8339 鲾 +8340 鳀 +8341 鳠 +8342 讃 +8343 譆 +8344 讎 +8345 讖 +8346 瑒 +8347 瑧 +8348 瑫 +8349 瑮 +8350 瑱 +8351 瑸 +8352 癭 +8353 皛 +8354 癩 +8355 皰 +8356 皸 +8357 礬 +8358 祼 +8359 禇 +8360 痎 +8361 瘄 +8362 瘆 +8363 瘣 +8364 瘧 +8365 瘨 +8366 瘺 +8367 瓔 +8368 瓚 +8369 瓛 +8370 瓟 +8371 縹 +8372 繄 +8373 繅 +8374 繕 +8375 穇 +8376 穫 +8377 窊 +8378 窓 +8379 窣 +8380 笉 +8381 笣 +8382 玚 +8383 玔 +8384 珄 +8385 珌 +8386 珎 +8387 珛 +8388 珵 +8389 粙 +8390 粨 +8391 粩 +8392 Т +8393 О +8394 Л +8395 Э +8396 Б +8397 秬 +8398 稈 +8399 ┗ +8400 ━ +8401 畤 +8402 畯 +8403 ぉ +8404 ぃ +8405 プ +8406 ィ +8407 ゥ +8408 眧 +8409 盷 +8410 眴 +8411 亊 +8412 伒 +8413 亇 +8414 仱 +8415 亜 +8416 亹 +8417 仐 +8418 仚 +8419 偞 +8420 偍 +8421 倕 +8422 倗 +8423 倧 +8424 倴 +8425 倵 +8426 倶 +8427 儈 +8428 僝 +8429 僜 +8430 儔 +8431 儚 +8432 劏 +8433 劵 +8434 劊 +8435 劌 +8436 剺 +8437 叇 +8438 叆 +8439 厔 +8440 厙 +8441 厷 +8442 喛 +8443 啣 +8444 啈 +8445 圇 +8446 嚢 +8447 嚨 +8448 嚟 +8449 噲 +8450 噵 +8451 噽 +8452 嚀 +8453 嚐 +8454 堊 +8455 垿 +8456 埈 +8457 埆 +8458 垻 +8459 垾 +8460 垏 +8461 垝 +8462 垞 +8463 垱 +8464 垳 +8465 夨 +8466 塤 +8467 壆 +8468 墐 +8469 娮 +8470 娀 +8471 妧 +8472 妶 +8473 姀 +8474 嫤 +8475 嫰 +8476 嫽 +8477 媭 +8478 媱 +8479 嫀 +8480 嫃 +8481 嫏 +8482 嫑 +8483 嫕 +8484 嫙 +8485 尨 +8486 宍 +8487 尅 +8488 尪 +8489 孿 +8490 寔 +8491 寗 +8492 寭 +8493 寯 +8494 寳 +8495 対 +8496 専 +8497 峘 +8498 崀 +8499 嶗 +8500 嵂 +8501 崼 +8502 嵒 +8503 崍 +8504 崐 +8505 巂 +8506 巸 +8507 巹 +8508 巿 +8509 帀 +8510 帡 +8511 帩 +8512 彯 +8513 惙 +8514 惢 +8515 悧 +8516 悩 +8517 悰 +8518 悵 +8519 慇 +8520 憚 +8521 憣 +8522 憭 +8523 掲 +8524 抃 +8525 拏 +8526 拠 +8527 拤 +8528 拫 +8529 拵 +8530 拸 +8531 挏 +8532 挐 +8533 挓 +8534 摽 +8535 摜 +8536 摫 +8537 搥 +8538 摑 +8539 敻 +8540 攣 +8541 攱 +8542 攲 +8543 攽 +8544 敐 +8545 暱 +8546 晙 +8547 晛 +8548 晫 +8549 晳 +8550 暁 +8551 暅 +8552 枴 +8553 柅 +8554 枹 +8555 枺 +8556 朶 +8557 枌 +8558 枒 +8559 枓 +8560 枙 +8561 枟 +8562 枦 +8563 枱 +8564 棸 +8565 椏 +8566 梋 +8567 棫 +8568 棻 +8569 梾 +8570 棅 +8571 樋 +8572 槱 +8573 榎 +8574 榿 +8575 槀 +8576 槇 +8577 槈 +8578 槉 +8579 槏 +8580 槖 +8581 槜 +8582 槝 +8583 橿 +8584 檁 +8585 櫌 +8586 檵 +8587 檻 +8588 櫆 +8589 櫈 +8590 毎 +8591 歔 +8592 殢 +8593 洦 +8594 泘 +8595 泑 +8596 洶 +8597 洴 +8598 浉 +8599 洰 +8600 洢 +8601 泚 +8602 洈 +8603 湋 +8604 湙 +8605 湚 +8606 湞 +8607 潿 +8608 澂 +8609 澔 +8610 潄 +8611 潏 +8612 潟 +8613 灤 +8614 灕 +8615 瀅 +8616 瀰 +8617 瀼 +8618 灃 +8619 灄 +8620 煬 +8621 煾 +8622 煡 +8623 煓 +8624 嚰 +8625 糬 +8626 ń +8627 幗 +8628 摻 +8629 絔 +8630 綋 +8631 綎 +8632 痪 +8633 樉 +8634 緙 +8635 緱 +8636 緲 +8637 糀 +8638 紸 +8639 綣 +8640 紂 +8641 綬 +8642 鞴 +8643 肮 +8644 柟 +8645 箋 +8646 箖 +8647 箠 +8648 筯 +8649 筶 +8650 筼 +8651 刽 +8652 筜 +8653 嚥 +8654 嵅 +8655 毑 +8656 瞼 +8657 瞾 +8658 矅 +8659 矖 +8660 矚 +8661 瞐 +8662 睞 +8663 瞕 +8664 瞤 +8665 С +8666 嫵 +8667 槼 +8668 砢 +8669 硞 +8670 硤 +8671 硨 +8672 硵 +8673 矰 +8674 盿 +8675 巃 +8676 焌 +8677 礎 +8678 禔 +8679 朅 +8680 楢 +8681 И +8682 嫪 +8683 敧 +8684 獦 +8685 п +8686 屺 +8687 岣 +8688 岖 +8689 幞 +8690 醽 +8691 醾 +8692 醿 +8693 釄 +8694 釈 +8695 Я +8696 伭 +8697 偭 +8698 熈 +8699 羶 +8700 羾 +8701 翃 +8702 翚 +8703 о +8704 傕 +8705 愢 +8706 曕 +8707 涏 +8708 鄚 +8709 唳 +8710 喾 +8711 啖 +8712 鄴 +8713 尷 +8714 椑 +8715 熇 +8716 繾 +8717 繹 +8718 纮 +8719 缊 +8720 罌 +8721 罍 +8722 呪 +8723 炩 +8724 熲 +8725 鈄 +8726 猞 +8727 獍 +8728 猸 +8729 狻 +8730 饪 +8731 饣 +8732 鈡 +8733 猢 +8734 猡 +8735 獗 +8736 鈷 +8737 β +8738 吢 +8739 埪 +8740 徛 +8741 毬 +8742 浡 +8743 灺 +8744 赂 +8745 聤 +8746 聴 +8747 麇 +8748 у +8749 傚 +8750 冨 +8751 堝 +8752 撳 +8753 欏 +8754 氬 +8755 滃 +8756 濆 +8757 錨 +8758 瀣 +8759 錩 +8760 濉 +8761 鍎 +8762 鍔 +8763 鍖 +8764 鍘 +8765 鍢 +8766 鍥 +8767 づ +8768 偱 +8769 壟 +8770 惻 +8771 斉 +8772 舺 +8773 艅 +8774 艎 +8775 嶄 +8776 曚 +8777 澉 +8778 涠 +8779 洎 +8780 洚 +8781 鋯 +8782 鋶 +8783 鋹 +8784 偰 +8785 縻 +8786 娿 +8787 阕 +8788 悱 +8789 愀 +8790 悃 +8791 惝 +8792 惚 +8793 銆 +8794 銍 +8795 銓 +8796 銚 +8797 銥 +8798 γ +8799 吤 +8800 幟 +8801 捗 +8802 脇 +8803 脛 +8804 脩 +8805 脳 +8806 脷 +8807 饨 +8808 褰 +8809 愎 +8810 岢 +8811 宄 +8812 徜 +8813 崦 +8814 噔 +8815 嗄 +8816 嚆 +8817 辶 +8818 謇 +8819 邋 +8820 迮 +8821 迕 +8822 渑 +8823 淠 +8824 溷 +8825 胊 +8826 憷 +8827 隳 +8828 崞 +8829 嗥 +8830 鉨 +8831 纁 +8832 淝 +8833 鉉 +8834 罝 +8835 狃 +8836 嵝 +8837 錮 +8838 腄 +8839 傛 +8840 忔 +8841 鎊 +8842 妯 +8843 妗 +8844 鎭 +8845 鏃 +8846 伷 +8847 吰 +8848 嬈 +8849 澠 +8850 芧 +8851 芠 +8852 苳 +8853 茖 +8854 茤 +8855 茪 +8856 徼 +8857 彡 +8858 犭 +8859 噼 +8860 嚯 +8861 翬 +8862 忾 +8863 迨 +8864 抨 +8865 渖 +8866 娌 +8867 芶 +8868 胬 +8869 鍮 +8870 媸 +8871 嫠 +8872 骒 +8873 鍺 +8874 骣 +8875 鍼 +8876 苢 +8877 鎡 +8878 芓 +8879 鎢 +8880 迄 +8881 纥 +8882 鎣 +8883 芛 +8884 纩 +8885 孓 +8886 臚 +8887 鄃 +8888 釭 +8889 臜 +8890 嵛 +8891 嵯 +8892 鄆 +8893 囗 +8894 忭 +8895 忸 +8896 馕 +8897 庀 +8898 屣 +8899 渫 +8900 撵 +8901 湎 +8902 阃 +8903 醞 +8904 郕 +8905 羕 +8906 鈑 +8907 臠 +8908 胠 +8909 猰 +8910 狽 +8911 嵫 +8912 郚 +8913 嘌 +8914 臢 +8915 漶 +8916 狯 +8917 嶝 +8918 鄌 +8919 圄 +8920 嗾 +8921 怫 +8922 搴 +8923 逡 +8924 逶 +8925 遑 +8926 鉶 +8927 阌 +8928 阋 +8929 錚 +8930 鉷 +8931 罳 +8932 鉸 +8933 釹 +8934 舠 +8935 銼 +8936 酺 +8937 羣 +8938 纓 +8939 羥 +8940 猁 +8941 彳 +8942 帙 +8943 嘬 +8944 傈 +8945 廑 +8946 舥 +8947 遘 +8948 潲 +8949 溘 +8950 膙 +8951 錡 +8952 醁 +8953 鉞 +8954 醂 +8955 猃 +8956 帻 +8957 廨 +8958 遢 +8959 爿 +8960 繸 +8961 鈀 +8962 舲 +8963 脄 +8964 肸 +8965 赁 +8966 呸 +8967 譩 +8968 璪 +8969 卋 +8970 媌 +8971 揵 +8972 渂 +8973 獴 +8974 濨 +8975 縞 +8976 玞 +8977 塩 +8978 礐 +8979 瑿 +8980 滳 +8981 濩 +8982 嶇 +8983 旂 +8984 栫 +8985 熺 +8986 绋 +8987 缱 +8988 绲 +8989 缈 +8990 绌 +8991 鐓 +8992 鐜 +8993 鐢 +8994 鐫 +8995 デ +8996 喦 +8997 柷 +8998 溓 +8999 荾 +9000 莧 +9001 菋 +9002 菍 +9003 缂 +9004 绻 +9005 缒 +9006 荄 +9007 缧 +9008 莐 +9009 鏻 +9010 莑 +9011 荖 +9012 鏞 +9013 荗 +9014 缫 +9015 荙 +9016 鏤 +9017 缳 +9018 莦 +9019 譫 +9020 礵 +9021 穌 +9022 卍 +9023 坉 +9024 奷 +9025 檇 +9026 沝 +9027 鱀 +9028 窪 +9029 籇 +9030 丏 +9031 嘍 +9032 塂 +9033 廌 +9034 涴 +9035 滒 +9036 燄 +9037 瘅 +9038 瓞 +9039 鸹 +9040 饈 +9041 饗 +9042 饞 +9043 饤 +9044 饸 +9045 饾 +9046 佇 +9047 傂 +9048 椥 +9049 衸 +9050 鸸 +9051 痄 +9052 蠧 +9053 鹎 +9054 痖 +9055 痍 +9056 衒 +9057 餒 +9058 鹜 +9059 鹛 +9060 鹣 +9061 酗 +9062 餸 +9063 瘐 +9064 蠷 +9065 餾 +9066 餞 +9067 磂 +9068 縠 +9069 乪 +9070 僥 +9071 漞 +9072 瀍 +9073 礒 +9074 僂 +9075 楨 +9076 冮 +9077 嗛 +9078 忛 +9079 旈 +9080 氶 +9081 滈 +9082 轺 +9083 楗 +9084 閭 +9085 閶 +9086 閹 +9087 闀 +9088 闆 +9089 娚 +9090 樕 +9091 炆 +9092 蓕 +9093 蓢 +9094 蓪 +9095 蓯 +9096 ㈣ +9097 庤 +9098 氳 +9099 滆 +9100 鑷 +9101 鑾 +9102 钑 +9103 铏 +9104 铻 +9105 壢 +9106 嬋 +9107 斎 +9108 毴 +9109 萚 +9110 萛 +9111 葎 +9112 葖 +9113 葦 +9114 夑 +9115 婈 +9116 甏 +9117 犄 +9118 軎 +9119 戥 +9120 辎 +9121 辏 +9122 陘 +9123 険 +9124 蔃 +9125 蕣 +9126 蕫 +9127 蕶 +9128 侂 +9129 撾 +9130 涬 +9131 熾 +9132 毹 +9133 氍 +9134 雱 +9135 霙 +9136 吽 +9137 喫 +9138 毸 +9139 藘 +9140 藟 +9141 藦 +9142 藨 +9143 昃 +9144 刖 +9145 旯 +9146 璩 +9147 瓿 +9148 锳 +9149 槔 +9150 殄 +9151 殂 +9152 栳 +9153 枇 +9154 枥 +9155 赅 +9156 赍 +9157 氇 +9158 朊 +9159 赕 +9160 菳 +9161 镃 +9162 榍 +9163 赙 +9164 肭 +9165 鐳 +9166 菵 +9167 腽 +9168 殍 +9169 栝 +9170 梃 +9171 觊 +9172 觋 +9173 閒 +9174 雊 +9175 薌 +9176 蔴 +9177 薍 +9178 橥 +9179 菼 +9180 觏 +9181 薸 +9182 镴 +9183 鐺 +9184 蒻 +9185 萂 +9186 檠 +9187 梏 +9188 枵 +9189 蕂 +9190 旰 +9191 暌 +9192 曛 +9193 牾 +9194 擞 +9195 腧 +9196 蓀 +9197 薖 +9198 蓂 +9199 閟 +9200 鑣 +9201 柽 +9202 脒 +9203 閡 +9204 轾 +9205 檑 +9206 柃 +9207 柢 +9208 闡 +9209 蕌 +9210 犏 +9211 贶 +9212 僳 +9213 蔞 +9214 薠 +9215 蓎 +9216 椠 +9217 閆 +9218 闋 +9219 蔂 +9220 薁 +9221 琭 +9222 奻 +9223 渇 +9224 鱂 +9225 禙 +9226 丗 +9227 朏 +9228 桭 +9229 汧 +9230 磄 +9231 縢 +9232 眊 +9233 俫 +9234 峠 +9235 璆 +9236 咷 +9237 圙 +9238 楪 +9239 欸 +9240 甴 +9241 奾 +9242 媓 +9243 榟 +9244 渉 +9245 疕 +9246 璈 +9247 奌 +9248 桯 +9249 礽 +9250 唅 +9251 沬 +9252 両 +9253 朓 +9254 愴 +9255 韃 +9256 恝 +9257 恁 +9258 頊 +9259 囃 +9260 埻 +9261 娡 +9262 樛 +9263 蚡 +9264 蛯 +9265 蛺 +9266 蜆 +9267 嶌 +9268 靆 +9269 歃 +9270 臁 +9271 靰 +9272 飑 +9273 霡 +9274 欷 +9275 膦 +9276 靄 +9277 靺 +9278 魈 +9279 嬏 +9280 藹 +9281 虁 +9282 虒 +9283 栴 +9284 燁 +9285 瞀 +9286 畀 +9287 瞌 +9288 瞟 +9289 瞍 +9290 睥 +9291 頫 +9292 囄 +9293 嬑 +9294 屛 +9295 嵨 +9296 櫸 +9297 蝃 +9298 螄 +9299 螆 +9300 庯 +9301 橈 +9302 濓 +9303 锇 +9304 锆 +9305 锔 +9306 锒 +9307 飩 +9308 飪 +9309 屜 +9310 徬 +9311 愊 +9312 撓 +9313 浵 +9314 蟶 +9315 蟷 +9316 蠂 +9317 蠑 +9318 蠙 +9319 砑 +9320 罱 +9321 锞 +9322 罟 +9323 觳 +9324 畛 +9325 锍 +9326 礅 +9327 砼 +9328 豌 +9329 靉 +9330 烀 +9331 瞠 +9332 盍 +9333 钅 +9334 顰 +9335 镡 +9336 锫 +9337 镢 +9338 礞 +9339 炱 +9340 鞡 +9341 砬 +9342 蘡 +9343 扃 +9344 镧 +9345 蘢 +9346 祓 +9347 镳 +9348 砩 +9349 硎 +9350 硭 +9351 礻 +9352 铋 +9353 钍 +9354 顴 +9355 螮 +9356 镩 +9357 镨 +9358 蟜 +9359 颎 +9360 蟝 +9361 鞨 +9362 頷 +9363 螲 +9364 蚷 +9365 硗 +9366 硖 +9367 祆 +9368 钐 +9369 铒 +9370 颕 +9371 蚃 +9372 頹 +9373 韐 +9374 蟢 +9375 鞮 +9376 蝜 +9377 眈 +9378 霳 +9379 硪 +9380 煸 +9381 熘 +9382 蝟 +9383 钣 +9384 铘 +9385 铞 +9386 飋 +9387 蟧 +9388 矬 +9389 矧 +9390 镆 +9391 鞶 +9392 頠 +9393 磉 +9394 爝 +9395 靦 +9396 飔 +9397 碹 +9398 蘵 +9399 燠 +9400 燔 +9401 铥 +9402 飖 +9403 稂 +9404 镘 +9405 靨 +9406 飗 +9407 蛍 +9408 颳 +9409 靬 +9410 蘗 +9411 蟳 +9412 镙 +9413 靂 +9414 蘘 +9415 蝂 +9416 籮 +9417 媕 +9418 巎 +9419 杍 +9420 榡 +9421 匤 +9422 旿 +9423 汮 +9424 媖 +9425 宬 +9426 昸 +9427 鱇 +9428 圞 +9429 痩 +9430 禠 +9431 甃 +9432 凩 +9433 奓 +9434 昄 +9435 玬 +9436 弇 +9437 杕 +9438 禡 +9439 僊 +9440 慚 +9441 磏 +9442 祅 +9443 籲 +9444 俷 +9445 卬 +9446 坣 +9447 礜 +9448 玁 +9449 瘩 +9450 侎 +9451 傫 +9452 烋 +9453 瘭 +9454 癍 +9455 窀 +9456 瘰 +9457 穸 +9458 瘼 +9459 瘢 +9460 駡 +9461 駭 +9462 а +9463 毖 +9464 炑 +9465 熝 +9466 褆 +9467 褕 +9468 褢 +9469 褣 +9470 褦 +9471 褯 +9472 窬 +9473 癯 +9474 襻 +9475 窭 +9476 疋 +9477 皲 +9478 馼 +9479 馚 +9480 馜 +9481 耩 +9482 袷 +9483 袼 +9484 裎 +9485 裣 +9486 耨 +9487 耱 +9488 裰 +9489 襁 +9490 裈 +9491 聱 +9492 顸 +9493 褴 +9494 裋 +9495 馱 +9496 裏 +9497 唎 +9498 弌 +9499 恛 +9500 渙 +9501 窸 +9502 籓 +9503 労 +9504 椇 +9505 樅 +9506 熀 +9507 簞 +9508 簠 +9509 簷 +9510 Ⅲ +9511 嶓 +9512 蠖 +9513 螅 +9514 螗 +9515 蟊 +9516 髣 +9517 髴 +9518 鬄 +9519 饔 +9520 営 +9521 捰 +9522 臃 +9523 訏 +9524 訑 +9525 訕 +9526 訚 +9527 黩 +9528 岒 +9529 嶒 +9530 栻 +9531 颟 +9532 虮 +9533 騾 +9534 驀 +9535 驁 +9536 驃 +9537 呉 +9538 喴 +9539 襖 +9540 覚 +9541 傯 +9542 掫 +9543 汈 +9544 濘 +9545 舁 +9546 舻 +9547 舣 +9548 艨 +9549 舴 +9550 舾 +9551 舳 +9552 嬙 +9553 懺 +9554 捲 +9555 斣 +9556 詛 +9557 詝 +9558 詡 +9559 詣 +9560 詰 +9561 詼 +9562 匂 +9563 庼 +9564 楒 +9565 欥 +9566 汌 +9567 龅 +9568 鯻 +9569 鯷 +9570 龆 +9571 觫 +9572 謦 +9573 鰟 +9574 鰣 +9575 鰤 +9576 鰥 +9577 鰩 +9578 鰶 +9579 喼 +9580 壷 +9581 娭 +9582 撝 +9583 曋 +9584 氈 +9585 謄 +9586 諤 +9587 謨 +9588 謫 +9589 謳 +9590 侕 +9591 凊 +9592 婖 +9593 曱 +9594 濙 +9595 醑 +9596 醐 +9597 醣 +9598 鯔 +9599 栒 +9600 椪 +9601 誣 +9602 諏 +9603 咗 +9604 岠 +9605 戻 +9606 鴃 +9607 鴂 +9608 鴯 +9609 鴳 +9610 鴷 +9611 ω +9612 堌 +9613 愗 +9614 懾 +9615 椮 +9616 炟 +9617 亍 +9618 鼗 +9619 貐 +9620 蠼 +9621 蚪 +9622 艏 +9623 艚 +9624 跫 +9625 豕 +9626 蟛 +9627 舨 +9628 蟪 +9629 骯 +9630 螵 +9631 筢 +9632 恿 +9633 筻 +9634 襛 +9635 颡 +9636 蚵 +9637 蜾 +9638 詀 +9639 訟 +9640 袈 +9641 鲡 +9642 趿 +9643 踱 +9644 蹁 +9645 剜 +9646 劁 +9647 劂 +9648 詁 +9649 魾 +9650 蚯 +9651 鮟 +9652 蹒 +9653 冂 +9654 覦 +9655 騞 +9656 訢 +9657 粜 +9658 諨 +9659 誨 +9660 跣 +9661 鴇 +9662 鳧 +9663 谼 +9664 覧 +9665 箝 +9666 箨 +9667 笫 +9668 羰 +9669 鯡 +9670 跞 +9671 跏 +9672 詆 +9673 訥 +9674 谿 +9675 笸 +9676 蛱 +9677 綮 +9678 粝 +9679 鲰 +9680 鮥 +9681 跬 +9682 躏 +9683 仡 +9684 匚 +9685 仫 +9686 簀 +9687 覬 +9688 鮦 +9689 鰈 +9690 鮨 +9691 鮈 +9692 覯 +9693 鰉 +9694 諱 +9695 鳰 +9696 鬘 +9697 躐 +9698 伛 +9699 阂 +9700 髈 +9701 篌 +9702 篥 +9703 蛞 +9704 蝣 +9705 蛑 +9706 蛘 +9707 趔 +9708 趑 +9709 趱 +9710 豇 +9711 拄 +9712 鮋 +9713 踔 +9714 跽 +9715 鴒 +9716 豋 +9717 仳 +9718 卣 +9719 覲 +9720 驫 +9721 観 +9722 騫 +9723 謖 +9724 謗 +9725 鴕 +9726 骃 +9727 魋 +9728 髏 +9729 鮐 +9730 鳷 +9731 篣 +9732 螓 +9733 蜍 +9734 魎 +9735 訳 +9736 謚 +9737 鲒 +9738 鳆 +9739 鲔 +9740 鳇 +9741 鲕 +9742 誹 +9743 踬 +9744 觖 +9745 踯 +9746 刭 +9747 觱 +9748 鮒 +9749 髕 +9750 觴 +9751 諼 +9752 豖 +9753 騳 +9754 験 +9755 襶 +9756 酏 +9757 鲚 +9758 踺 +9759 骦 +9760 鴝 +9761 鳽 +9762 蜣 +9763 肄 +9764 剞 +9765 訝 +9766 鱬 +9767 卲 +9768 梡 +9769 廝 +9770 ╭ +9771 琿 +9772 弎 +9773 梣 +9774 禥 +9775 慟 +9776 峳 +9777 璕 +9778 擱 +9779 祍 +9780 峴 +9781 泂 +9782 渟 +9783 漵 +9784 禨 +9785 婼 +9786 擲 +9787 昐 +9788 鬟 +9789 忂 +9790 攮 +9791 攉 +9792 撙 +9793 撺 +9794 邅 +9795 邩 +9796 呒 +9797 哙 +9798 唣 +9799 哐 +9800 咭 +9801 啭 +9802 遅 +9803 遹 +9804 唼 +9805 痶 +9806 籺 +9807 籘 +9808 盩 +9809 俆 +9810 嘥 +9811 昑 +9812 桾 +9813 漈 +9814 畊 +9815 僽 +9816 坲 +9817 塽 +9818 妘 +9819 奤 +9820 孶 +9821 朥 +9822 Ⅹ +9823 夲 +9824 燏 +9825 鷟 +9826 鷂 +9827 鷞 +9828 鷨 +9829 鷯 +9830 鷸 +9831 鷿 +9832 и +9833 ペ +9834 佢 +9835 嗂 +9836 氌 +9837 诖 +9838 诎 +9839 诙 +9840 诋 +9841 冖 +9842 跂 +9843 跅 +9844 跐 +9845 咘 +9846 圌 +9847 滪 +9848 鵠 +9849 鵟 +9850 鶒 +9851 鶘 +9852 ① +9853 佡 +9854 嬞 +9855 俅 +9856 俜 +9857 贄 +9858 凔 +9859 曽 +9860 鸤 +9861 髹 +9862 娵 +9863 撣 +9864 鄹 +9865 邾 +9866 郾 +9867 蹐 +9868 蹓 +9869 蹠 +9870 蹣 +9871 圏 +9872 婞 +9873 孅 +9874 欬 +9875 黐 +9876 鬈 +9877 冘 +9878 嗆 +9879 嵻 +9880 愜 +9881 栜 +9882 炣 +9883 塄 +9884 墁 +9885 芑 +9886 芏 +9887 鼙 +9888 軋 +9889 軏 +9890 軛 +9891 诮 +9892 劢 +9893 诓 +9894 诔 +9895 讵 +9896 卺 +9897 诹 +9898 诼 +9899 哿 +9900 麬 +9901 苈 +9902 苠 +9903 倨 +9904 貰 +9905 阽 +9906 偾 +9907 鷇 +9908 賒 +9909 鷈 +9910 阼 +9911 匐 +9912 踖 +9913 鷉 +9914 鹔 +9915 陧 +9916 垤 +9917 埏 +9918 巯 +9919 芴 +9920 躪 +9921 鹠 +9922 鵑 +9923 傺 +9924 鹡 +9925 鸑 +9926 苎 +9927 鶲 +9928 貽 +9929 僬 +9930 埚 +9931 埘 +9932 埒 +9933 圬 +9934 躉 +9935 芤 +9936 茇 +9937 趐 +9938 躊 +9939 踧 +9940 跶 +9941 鹯 +9942 跼 +9943 賡 +9944 龠 +9945 賂 +9946 鹴 +9947 邡 +9948 蠃 +9949 埴 +9950 茺 +9951 鶺 +9952 赪 +9953 黈 +9954 鶻 +9955 踴 +9956 谳 +9957 鵼 +9958 圯 +9959 鸝 +9960 鸂 +9961 鼱 +9962 鱲 +9963 璿 +9964 憊 +9965 泇 +9966 玍 +9967 嶸 +9968 烿 +9969 皐 +9970 竪 +9971 玾 +9972 杦 +9973 泈 +9974 鱓 +9975 媁 +9976 榃 +9977 淲 +9978 玿 +9979 亁 +9980 碭 +9981 癤 +9982 疿 +9983 揦 +9984 榅 +9985 鱵 +9986 瑈 +9987 儁 +9988 漼 +9989 玒 +9990 甕 +9991 塝 +9992 榊 +9993 圐 +9994 岧 +9995 汖 +9996 齶 +9997 齙 +9998 龁 +9999 龔 +10000 龘 +10001 龢 +10002 冚 +10003 嗇 +10004 堓 +10005 壿 +10006 澼 +10007 炤 +10008 莸 +10009 輻 +10010 輾 +10011 轁 +10012 蒎 +10013 鼴 +10014 菪 +10015 萑 +10016 輋 +10017 軫 +10018 蓊 +10019 菸 +10020 齦 +10021 蒗 +10022 葚 +10023 齪 +10024 輗 +10025 葸 +10026 蔸 +10027 蔹 +10028 輜 +10029 輞 +10030 葺 +10031 輟 +10032 縵 +10033 儂 +10034 唞 +10035 妟 +10036 媧 +10037 瀦 +10038 鱖 +10039 礪 +10040 俍 +10041 嘮 +10042 怹 +10043 堽 +10044 嶠 +10045 橚 +10046 汘 +10047 勣 +10048 堔 +10049 搠 +10050 迀 +10051 薷 +10052 辿 +10053 薅 +10054 蕞 +10055 轡 +10056 迏 +10057 迵 +10058 蕹 +10059 廾 +10060 辀 +10061 掮 +10062 揲 +10063 揸 +10064 揠 +10065 辡 +10066 轍 +10067 尥 +10068 摅 +10069 搛 +10070 搋 +10071 轘 +10072 搡 +10073 摞 +10074 摭 +10075 揶 +10076 鳡 +10077 笭 +10078 厾 +10079 棤 +10080 湢 +10081 犠 +10082 牠 +10083 牁 +10084 牂 +10085 燼 +10086 犼 +10087 燜 +10088 爊 +10089 爍 +10090 爕 +10091 犧 +10092 碻 +10093 碸 +10094 磣 +10095 碷 +10096 磠 +10097 磢 +10098 碄 +10099 碶 +10100 磩 +10101 磪 +10102 磭 +10103 磰 +10104 磱 +10105 磳 +10106 磵 +10107 磶 +10108 磹 +10109 磼 +10110 磿 +10111 礀 +10112 礂 +10113 礃 +10114 礄 +10115 礆 +10116 礇 +10117 礈 +10118 礉 +10119 礊 +10120 礋 +10121 ` +10122 ^ +10123 ╜ +10124 ╚ +10125 ▇ +10126 ㄗ +10127 ▅ +10128 ǖ +10129 ╛ +10130 ǚ +10131 ǜ +10132 ɑ +10133 ╗ +10134 ╙ +10135 ▄ +10136 ▆ +10137 ǘ +10138 ˊ +10139 ╘ +10140 █ +10141 ▉ +10142 ▊ +10143 ▋ +10144 ▌ +10145 ▍ +10146 ▎ +10147 ▏ +10148 ▓ +10149 ▔ +10150 ▼ +10151 ◢ +10152 ◣ +10153 ◤ +10154 ◥ +10155 ☉ +10156 ⊕ +10157 〒 +10158 〝 +10159 〞 +10160 ~ +10161 鞔 +10162 鱜 +10163 鱚 +10164 鱺 +10165 鱛 +10166 鱙 +10167 鱹 +10168 鞫 +10169 鰼 +10170 鱻 +10171 鱽 +10172 鱾 +10173 鲄 +10174 鲌 +10175 鲓 +10176 鲗 +10177 鲝 +10178 鲪 +10179 鲬 +10180 鲿 +10181 鳁 +10182 鳂 +10183 鳈 +10184 鳒 +10185 鳚 +10186 鳛 +10187 譤 +10188 讄 +10189 譥 +10190 譡 +10191 譢 +10192 讉 +10193 讋 +10194 讌 +10195 讑 +10196 讒 +10197 讔 +10198 讕 +10199 讙 +10200 讛 +10201 讜 +10202 讞 +10203 讟 +10204 讬 +10205 讱 +10206 讻 +10207 诇 +10208 诐 +10209 诪 +10210 谉 +10211 < +10212 = +10213 > +10214 琡 +10215 琟 +10216 瑍 +10217 琠 +10218 琜 +10219 琞 +10220 瑊 +10221 瑌 +10222 珸 +10223 琝 +10224 瑎 +10225 瑏 +10226 瑐 +10227 瑑 +10228 瑓 +10229 瑔 +10230 瑖 +10231 瑘 +10232 瑡 +10233 瑥 +10234 瑦 +10235 瑬 +10236 瑲 +10237 瑳 +10238 瑴 +10239 瑵 +10240 瑹 +10241 | +10242 Υ +10243 Θ +10244 ψ +10245 Μ +10246 Ζ +10247 Π +10248 Φ +10249 Ο +10250 Ν +10251 Α +10252 Ψ +10253 Ω +10254 Λ +10255 Η +10256 Χ +10257 Ι +10258 Ξ +10259 Δ +10260 Β +10261 Γ +10262 Ε +10263 Ρ +10264 癪 +10265 皘 +10266 癧 +10267 癅 +10268 癨 +10269 皝 +10270 皟 +10271 皠 +10272 皡 +10273 皢 +10274 皣 +10275 皥 +10276 皧 +10277 皨 +10278 皪 +10279 皫 +10280 皬 +10281 皭 +10282 皯 +10283 皳 +10284 皵 +10285 皶 +10286 皷 +10287 皹 +10288 皻 +10289 皼 +10290 皽 +10291 皾 +10292 盀 +10293 礰 +10294 礮 +10295 祣 +10296 礫 +10297 礭 +10298 祡 +10299 礍 +10300 祦 +10301 祩 +10302 祪 +10303 祫 +10304 祬 +10305 祮 +10306 祰 +10307 祱 +10308 祲 +10309 祳 +10310 祴 +10311 祵 +10312 祶 +10313 祹 +10314 祻 +10315 祽 +10316 祾 +10317 禂 +10318 禃 +10319 禆 +10320 禈 +10321 禉 +10322 禋 +10323 禌 +10324 禐 +10325 禑 +10326 _ +10327 痐 +10328 瘇 +10329 痏 +10330 痆 +10331 痌 +10332 瘂 +10333 疈 +10334 瘎 +10335 瘏 +10336 瘑 +10337 瘒 +10338 瘔 +10339 瘖 +10340 瘚 +10341 瘜 +10342 瘝 +10343 瘞 +10344 瘬 +10345 瘮 +10346 瘯 +10347 瘲 +10348 瘶 +10349 瘷 +10350 瘹 +10351 瘻 +10352 瘽 +10353 癁 +10354 璥 +10355 瓇 +10356 瓅 +10357 璤 +10358 璢 +10359 瑻 +10360 璡 +10361 瓈 +10362 瓉 +10363 瓌 +10364 瓍 +10365 瓎 +10366 瓐 +10367 瓑 +10368 瓓 +10369 瓕 +10370 瓖 +10371 瓙 +10372 瓝 +10373 瓡 +10374 瓥 +10375 瓧 +10376 瓨 +10377 瓩 +10378 瓪 +10379 瓫 +10380 瓬 +10381 瓭 +10382 瓱 +10383 - +10384 , +10385 ; +10386 : +10387 ! +10388 〈 +10389 〃 +10390 △ +10391 ∽ +10392 ‖ +10393 ˇ +10394 “ +10395 〉 +10396 ’ +10397 … +10398   +10399 】 +10400 》 +10401 「 +10402 ~ +10403 』 +10404 ¨ +10405 《 +10406 ‘ +10407 · +10408 、 +10409 。 +10410 ˉ +10411 ” +10412 ? +10413 縙 +10414 縚 +10415 縘 +10416 縶 +10417 縸 +10418 縗 +10419 縺 +10420 縼 +10421 縿 +10422 繀 +10423 繂 +10424 繈 +10425 繉 +10426 繊 +10427 繋 +10428 繌 +10429 繍 +10430 繎 +10431 繏 +10432 繐 +10433 繑 +10434 繒 +10435 繓 +10436 繖 +10437 繗 +10438 繘 +10439 繙 +10440 繛 +10441 繜 +10442 / +10443 穈 +10444 穅 +10445 穨 +10446 穦 +10447 穄 +10448 穧 +10449 稝 +10450 穃 +10451 穪 +10452 穬 +10453 穭 +10454 穮 +10455 穱 +10456 穲 +10457 穵 +10458 穻 +10459 穼 +10460 穽 +10461 穾 +10462 窂 +10463 窇 +10464 窉 +10465 窋 +10466 窌 +10467 窎 +10468 窏 +10469 窐 +10470 窔 +10471 窙 +10472 窚 +10473 窛 +10474 窞 +10475 窡 +10476 竈 +10477 竳 +10478 竉 +10479 竲 +10480 竆 +10481 竴 +10482 竵 +10483 竷 +10484 竸 +10485 竻 +10486 竼 +10487 竾 +10488 笀 +10489 笁 +10490 笂 +10491 笅 +10492 笇 +10493 笌 +10494 笍 +10495 笎 +10496 笐 +10497 笓 +10498 笖 +10499 笗 +10500 笘 +10501 笚 +10502 笜 +10503 笝 +10504 笟 +10505 笡 +10506 笢 +10507 笧 +10508 笩 +10509 ' +10510 " +10511 珇 +10512 珆 +10513 珋 +10514 珒 +10515 珓 +10516 珔 +10517 珕 +10518 珗 +10519 珘 +10520 珚 +10521 珜 +10522 珟 +10523 珡 +10524 珢 +10525 珤 +10526 珦 +10527 珨 +10528 珫 +10529 珬 +10530 珯 +10531 珳 +10532 珴 +10533 珶 +10534 粇 +10535 粅 +10536 籣 +10537 粆 +10538 粈 +10539 粊 +10540 粋 +10541 粌 +10542 粍 +10543 粎 +10544 粏 +10545 粐 +10546 粓 +10547 粔 +10548 粖 +10549 粚 +10550 粛 +10551 粠 +10552 粡 +10553 粣 +10554 粦 +10555 粫 +10556 粭 +10557 粯 +10558 粰 +10559 粴 +10560 粶 +10561 粷 +10562 粸 +10563 粺 +10564 ( +10565 ) +10566 [ +10567 ] +10568 { +10569 } +10570 Ж +10571 К +10572 Е +10573 У +10574 Н +10575 А +10576 Х +10577 Ц +10578 Й +10579 Щ +10580 Ё +10581 Ф +10582 З +10583 М +10584 Г +10585 В +10586 Д +10587 П +10588 禴 +10589 秪 +10590 秥 +10591 禰 +10592 秢 +10593 秨 +10594 禓 +10595 秮 +10596 秱 +10597 秲 +10598 秳 +10599 秴 +10600 秵 +10601 秶 +10602 秷 +10603 秹 +10604 秺 +10605 秼 +10606 秿 +10607 稁 +10608 稄 +10609 稉 +10610 稊 +10611 稌 +10612 稏 +10613 稐 +10614 稑 +10615 稒 +10616 稓 +10617 稕 +10618 稖 +10619 稘 +10620 稙 +10621 稛 +10622 ┐ +10623 ┄ +10624 ﹡ +10625 ┈ +10626 ﹟ +10627 │ +10628 ┌ +10629 ┑ +10630 ┋ +10631 ┉ +10632 ┓ +10633 └ +10634 ┇ +10635 ﹞ +10636 ﹠ +10637 ┒ +10638 ┅ +10639 ┊ +10640 〡 +10641 ─ +10642 ‐ +10643 ┍ +10644 ﹢ +10645 ﹣ +10646 ﹤ +10647 ﹥ +10648 ﹦ +10649 ﹨ +10650 ﹩ +10651 ﹪ +10652 ﹫ +10653 〇 +10654 甞 +10655 畘 +10656 畖 +10657 甠 +10658 甗 +10659 甝 +10660 畕 +10661 畗 +10662 甛 +10663 畞 +10664 畟 +10665 畠 +10666 畡 +10667 畣 +10668 畧 +10669 畨 +10670 畩 +10671 畮 +10672 畱 +10673 畳 +10674 畵 +10675 畷 +10676 畺 +10677 畻 +10678 畼 +10679 畽 +10680 畾 +10681 疀 +10682 疁 +10683 疂 +10684 疄 +10685 @ +10686 ぅ +10687 ⒋ +10688 ⅷ +10689 Ⅶ +10690 ⒆ +10691 ⅵ +10692 ⒌ +10693 ⅰ +10694 ⒖ +10695 ⒎ +10696 ⒏ +10697 ⒒ +10698 ⅶ +10699 ⒍ +10700 ⅸ +10701 ⅳ +10702 ⅱ +10703 ⅲ +10704 ⅴ +10705 ⒈ +10706 ( +10707 W +10708 , +10709 & +10710 5 +10711 / +10712 - +10713 ! +10714 ? +10715 + +10716 ; +10717 ' +10718 ) +10719 . +10720 ¥ +10721 " +10722 # +10723 % +10724 ァ +10725 ェ +10726 ォ +10727 \ +10728 盽 +10729 盺 +10730 眫 +10731 盻 +10732 盵 +10733 眥 +10734 眪 +10735 盄 +10736 眮 +10737 眰 +10738 眱 +10739 眲 +10740 眳 +10741 眹 +10742 眻 +10743 眽 +10744 眿 +10745 睂 +10746 睄 +10747 睅 +10748 睆 +10749 睈 +10750 睉 +10751 睊 +10752 睋 +10753 睌 +10754 睍 +10755 睎 +10756 睓 +10757 睔 +10758 睕 +10759 睖 +10760 睗 +10761 睘 +10762 睙 +10763  +10764  +10765  +10766  +10767  +10768  +10769  +10770 +10771 +10772  +10773  +10774  +10775  +10776  +10777  +10778  +10779  +10780  +10781  +10782  +10783  +10784  +10785  +10786  +10787  +10788  +10789  +10790  +10791 伌 +10792 乣 +10793 乛 +10794 仺 +10795 伂 +10796 仸 +10797 伆 +10798 乢 +10799 伅 +10800 伃 +10801 仭 +10802 伩 +10803 伔 +10804 伀 +10805 乕 +10806 亄 +10807 仹 +10808 伓 +10809 仼 +10810 伄 +10811 丂 +10812 仯 +10813 仴 +10814 乗 +10815 伇 +10816 亐 +10817 亖 +10818 亗 +10819 亙 +10820 亝 +10821 亣 +10822 亪 +10823 亯 +10824 亰 +10825 亱 +10826 亴 +10827 亷 +10828 亸 +10829 亼 +10830 亽 +10831 亾 +10832 仈 +10833 仌 +10834 仒 +10835 仛 +10836 仜 +10837 仠 +10838 仢 +10839 仦 +10840 仧 +10841 俙 +10842 俕 +10843 傋 +10844 倈 +10845 偊 +10846 偘 +10847 偟 +10848 俖 +10849 偗 +10850 偔 +10851 偂 +10852 偪 +10853 偡 +10854 偢 +10855 偒 +10856 偦 +10857 俒 +10858 俔 +10859 倇 +10860 偋 +10861 偠 +10862 偐 +10863 偖 +10864 侤 +10865 偆 +10866 偄 +10867 偅 +10868 俓 +10869 偙 +10870 倎 +10871 倐 +10872 倛 +10873 倝 +10874 倠 +10875 倢 +10876 倣 +10877 倯 +10878 倰 +10879 倱 +10880 倲 +10881 倳 +10882 倷 +10883 倸 +10884 倹 +10885 倽 +10886 倿 +10887 偀 +10888 僠 +10889 儴 +10890 凎 +10891 冏 +10892 儸 +10893 儼 +10894 兊 +10895 僟 +10896 儻 +10897 儹 +10898 儭 +10899 兛 +10900 兎 +10901 兏 +10902 兓 +10903 僛 +10904 儃 +10905 儅 +10906 儳 +10907 儵 +10908 儺 +10909 傽 +10910 儰 +10911 儮 +10912 儯 +10913 儱 +10914 儽 +10915 儊 +10916 儌 +10917 儍 +10918 儎 +10919 儏 +10920 儐 +10921 儑 +10922 儓 +10923 儕 +10924 儖 +10925 儗 +10926 儙 +10927 儛 +10928 儜 +10929 儞 +10930 儠 +10931 儢 +10932 儣 +10933 儤 +10934 儥 +10935 儦 +10936 儧 +10937 儨 +10938 儩 +10939 儫 +10940 劥 +10941 刞 +10942 刕 +10943 剘 +10944 匃 +10945 劕 +10946 剕 +10947 劙 +10948 劦 +10949 刜 +10950 劘 +10951 劖 +10952 効 +10953 劮 +10954 劯 +10955 劔 +10956 刐 +10957 刔 +10958 剓 +10959 剗 +10960 劎 +10961 劧 +10962 劗 +10963 凘 +10964 劋 +10965 刓 +10966 劚 +10967 剙 +10968 剚 +10969 剟 +10970 剠 +10971 剢 +10972 剣 +10973 剤 +10974 剦 +10975 剨 +10976 剫 +10977 剬 +10978 剭 +10979 剮 +10980 剰 +10981 剱 +10982 剳 +10983 剴 +10984 剶 +10985 剷 +10986 剸 +10987 剹 +10988 剻 +10989 剼 +10990 剾 +10991 劀 +10992 劄 +10993 劅 +10994 叴 +10995 卄 +10996 叏 +10997 厏 +10998 咓 +10999 呑 +11000 叕 +11001 厊 +11002 叞 +11003 叺 +11004 卂 +11005 叝 +11006 叚 +11007 叀 +11008 吙 +11009 叿 +11010 吀 +11011 叓 +11012 吇 +11013 匸 +11014 匽 +11015 厈 +11016 厎 +11017 収 +11018 叾 +11019 叐 +11020 叜 +11021 匑 +11022 叅 +11023 叄 +11024 匼 +11025 厐 +11026 厑 +11027 厒 +11028 厓 +11029 厖 +11030 厗 +11031 厛 +11032 厜 +11033 厞 +11034 厡 +11035 厤 +11036 厧 +11037 厪 +11038 厫 +11039 厬 +11040 厯 +11041 厰 +11042 厱 +11043 厵 +11044 厸 +11045 厹 +11046 厺 +11047 厼 +11048 厽 +11049 哷 +11050 哵 +11051 唦 +11052 嗺 +11053 喿 +11054 啲 +11055 啨 +11056 啺 +11057 喌 +11058 哶 +11059 啹 +11060 啳 +11061 喎 +11062 喐 +11063 喕 +11064 哰 +11065 哴 +11066 唟 +11067 唥 +11068 啩 +11069 喍 +11070 啯 +11071 啴 +11072 咢 +11073 啢 +11074 啠 +11075 哱 +11076 啽 +11077 唨 +11078 唩 +11079 唫 +11080 唭 +11081 唲 +11082 唴 +11083 唵 +11084 唶 +11085 唹 +11086 唺 +11087 唻 +11088 唽 +11089 啀 +11090 啂 +11091 啅 +11092 啇 +11093 啋 +11094 啌 +11095 啍 +11096 啎 +11097 啑 +11098 啒 +11099 啔 +11100 啗 +11101 啘 +11102 啙 +11103 啚 +11104 啛 +11105 嚧 +11106 嘸 +11107 嘵 +11108 嚚 +11109 噡 +11110 囎 +11111 嚞 +11112 噟 +11113 嚘 +11114 嘷 +11115 嚡 +11116 嚳 +11117 嚪 +11118 嚫 +11119 嚝 +11120 嚙 +11121 嚩 +11122 嚛 +11123 嚠 +11124 嚖 +11125 嚔 +11126 嚗 +11127 嚤 +11128 噣 +11129 噥 +11130 噦 +11131 噧 +11132 噮 +11133 噰 +11134 噳 +11135 噷 +11136 噺 +11137 噾 +11138 噿 +11139 嚁 +11140 嚂 +11141 嚃 +11142 嚄 +11143 嚈 +11144 嚉 +11145 嚊 +11146 嚌 +11147 埓 +11148 坄 +11149 坁 +11150 埁 +11151 垀 +11152 堶 +11153 坾 +11154 埌 +11155 埖 +11156 坃 +11157 埊 +11158 垺 +11159 埧 +11160 埛 +11161 埜 +11162 埢 +11163 圼 +11164 圿 +11165 坽 +11166 坿 +11167 埀 +11168 埄 +11169 埉 +11170 垽 +11171 垼 +11172 圽 +11173 埍 +11174 垁 +11175 垇 +11176 垉 +11177 垊 +11178 垍 +11179 垎 +11180 垐 +11181 垑 +11182 垔 +11183 垖 +11184 垗 +11185 垘 +11186 垙 +11187 垜 +11188 垥 +11189 垨 +11190 垪 +11191 垬 +11192 垯 +11193 垰 +11194 垶 +11195 垷 +11196 壌 +11197 塦 +11198 塣 +11199 墌 +11200 壸 +11201 壃 +11202 壈 +11203 壍 +11204 壄 +11205 墶 +11206 壙 +11207 壐 +11208 壂 +11209 壔 +11210 塠 +11211 墈 +11212 墋 +11213 墽 +11214 壎 +11215 墿 +11216 堾 +11217 墹 +11218 墷 +11219 墸 +11220 墺 +11221 塡 +11222 壉 +11223 墍 +11224 墏 +11225 墑 +11226 墔 +11227 墕 +11228 墖 +11229 増 +11230 墛 +11231 墝 +11232 墠 +11233 墡 +11234 墢 +11235 墣 +11236 墤 +11237 墥 +11238 墦 +11239 墧 +11240 墪 +11241 墫 +11242 墬 +11243 墭 +11244 墯 +11245 墰 +11246 墱 +11247 墲 +11248 墴 +11249 姶 +11250 奰 +11251 姩 +11252 妦 +11253 婘 +11254 妡 +11255 姲 +11256 姷 +11257 奯 +11258 姱 +11259 姯 +11260 姟 +11261 娍 +11262 姺 +11263 姼 +11264 姭 +11265 奫 +11266 妢 +11267 姧 +11268 姰 +11269 夽 +11270 姢 +11271 姠 +11272 姡 +11273 姤 +11274 姳 +11275 妬 +11276 妭 +11277 妰 +11278 妱 +11279 妳 +11280 妴 +11281 妵 +11282 妷 +11283 妸 +11284 妼 +11285 妽 +11286 妿 +11287 姁 +11288 姂 +11289 姃 +11290 姄 +11291 姅 +11292 姇 +11293 姌 +11294 姎 +11295 姏 +11296 姕 +11297 姖 +11298 姙 +11299 姛 +11300 嫶 +11301 媊 +11302 媈 +11303 嫧 +11304 媬 +11305 嬜 +11306 嫭 +11307 媩 +11308 嫷 +11309 媉 +11310 嫮 +11311 嫛 +11312 嬁 +11313 嫹 +11314 嫺 +11315 嫬 +11316 媅 +11317 媇 +11318 媫 +11319 嫥 +11320 嫸 +11321 嫨 +11322 嫯 +11323 婡 +11324 嫟 +11325 嫝 +11326 嫞 +11327 嫢 +11328 媆 +11329 嫳 +11330 媮 +11331 媯 +11332 媰 +11333 媴 +11334 媶 +11335 媷 +11336 媹 +11337 媺 +11338 媻 +11339 媼 +11340 媿 +11341 嫅 +11342 嫇 +11343 嫈 +11344 嫍 +11345 嫎 +11346 嫐 +11347 嫓 +11348 嫗 +11349 宆 +11350 尐 +11351 寏 +11352 岟 +11353 屪 +11354 尙 +11355 寍 +11356 尠 +11357 尩 +11358 宊 +11359 尟 +11360 尛 +11361 尶 +11362 尫 +11363 尭 +11364 尗 +11365 尰 +11366 宂 +11367 寋 +11368 寎 +11369 尒 +11370 尞 +11371 孈 +11372 尌 +11373 尡 +11374 寑 +11375 寕 +11376 寖 +11377 寘 +11378 寙 +11379 寚 +11380 寛 +11381 寜 +11382 寠 +11383 寣 +11384 寪 +11385 寱 +11386 寲 +11387 寴 +11388 寷 +11389 寽 +11390 尀 +11391 嵈 +11392 峖 +11393 崹 +11394 嵶 +11395 崿 +11396 峾 +11397 崷 +11398 嵃 +11399 嵉 +11400 峗 +11401 嵀 +11402 崱 +11403 嵖 +11404 嵎 +11405 嵏 +11406 峓 +11407 峕 +11408 峿 +11409 崸 +11410 嵍 +11411 崺 +11412 嵁 +11413 岪 +11414 崵 +11415 崲 +11416 崳 +11417 崶 +11418 峔 +11419 嵄 +11420 崄 +11421 崅 +11422 崉 +11423 崊 +11424 崋 +11425 崌 +11426 崏 +11427 崑 +11428 崒 +11429 崓 +11430 崕 +11431 崘 +11432 崙 +11433 崜 +11434 崝 +11435 崠 +11436 崡 +11437 崣 +11438 崥 +11439 崨 +11440 崪 +11441 崫 +11442 崬 +11443 崯 +11444 幋 +11445 帹 +11446 巭 +11447 庽 +11448 巪 +11449 帵 +11450 幇 +11451 幆 +11452 幁 +11453 幙 +11454 幏 +11455 幐 +11456 帿 +11457 幓 +11458 嶿 +11459 巤 +11460 巬 +11461 幎 +11462 帺 +11463 幃 +11464 嶡 +11465 帲 +11466 帴 +11467 嶾 +11468 幈 +11469 巰 +11470 巵 +11471 巶 +11472 巺 +11473 巼 +11474 帄 +11475 帇 +11476 帉 +11477 帊 +11478 帋 +11479 帍 +11480 帎 +11481 帒 +11482 帓 +11483 帗 +11484 帞 +11485 帟 +11486 帠 +11487 帢 +11488 帣 +11489 帤 +11490 帨 +11491 帪 +11492 彺 +11493 彣 +11494 弤 +11495 忳 +11496 徸 +11497 彟 +11498 彴 +11499 彽 +11500 廮 +11501 彲 +11502 彮 +11503 徔 +11504 徃 +11505 彨 +11506 徎 +11507 廩 +11508 弡 +11509 弣 +11510 彠 +11511 彾 +11512 廆 +11513 彜 +11514 彚 +11515 彛 +11516 彞 +11517 廫 +11518 彵 +11519 弨 +11520 弫 +11521 弬 +11522 弮 +11523 弰 +11524 弲 +11525 弳 +11526 弴 +11527 弸 +11528 弻 +11529 弽 +11530 弾 +11531 弿 +11532 彁 +11533 彂 +11534 彃 +11535 彄 +11536 彅 +11537 彆 +11538 彇 +11539 彉 +11540 彋 +11541 彍 +11542 彑 +11543 惔 +11544 恅 +11545 恀 +11546 惃 +11547 悀 +11548 愾 +11549 愖 +11550 惉 +11551 恷 +11552 惁 +11553 惏 +11554 惖 +11555 恄 +11556 惎 +11557 惌 +11558 悺 +11559 惪 +11560 惛 +11561 惈 +11562 怺 +11563 怾 +11564 恾 +11565 惂 +11566 惗 +11567 惄 +11568 惍 +11569 怈 +11570 悿 +11571 悾 +11572 惀 +11573 怽 +11574 惐 +11575 悁 +11576 悂 +11577 悆 +11578 悇 +11579 悈 +11580 悊 +11581 悋 +11582 悎 +11583 悏 +11584 悐 +11585 悑 +11586 悓 +11587 悕 +11588 悗 +11589 悘 +11590 悙 +11591 悜 +11592 悞 +11593 悡 +11594 悢 +11595 悤 +11596 悥 +11597 悮 +11598 悳 +11599 悷 +11600 懘 +11601 慲 +11602 慯 +11603 懆 +11604 憕 +11605 戺 +11606 懽 +11607 懍 +11608 憒 +11609 懄 +11610 懓 +11611 懙 +11612 慱 +11613 懐 +11614 懎 +11615 憽 +11616 懣 +11617 懛 +11618 懜 +11619 懌 +11620 懟 +11621 憓 +11622 懅 +11623 懚 +11624 懏 +11625 懁 +11626 憿 +11627 懀 +11628 懃 +11629 慭 +11630 懕 +11631 憖 +11632 憗 +11633 憘 +11634 憛 +11635 憜 +11636 憞 +11637 憟 +11638 憠 +11639 憡 +11640 憢 +11641 憥 +11642 憦 +11643 憪 +11644 憮 +11645 憯 +11646 憰 +11647 憱 +11648 憳 +11649 憴 +11650 憵 +11651 憸 +11652 憹 +11653 憺 +11654 憻 +11655 抈 +11656 抆 +11657 挩 +11658 拁 +11659 捵 +11660 挰 +11661 抾 +11662 挦 +11663 挵 +11664 挼 +11665 抇 +11666 挴 +11667 挱 +11668 挕 +11669 捒 +11670 挿 +11671 捀 +11672 挮 +11673 捇 +11674 抂 +11675 抅 +11676 抺 +11677 拀 +11678 挧 +11679 挬 +11680 挳 +11681 扏 +11682 挙 +11683 挗 +11684 挘 +11685 挜 +11686 挶 +11687 拃 +11688 拑 +11689 拕 +11690 拞 +11691 拡 +11692 拪 +11693 拰 +11694 拲 +11695 拹 +11696 拺 +11697 拻 +11698 挀 +11699 挃 +11700 挄 +11701 挅 +11702 挆 +11703 挊 +11704 挋 +11705 挍 +11706 挒 +11707 揯 +11708 摠 +11709 搤 +11710 擏 +11711 撟 +11712 摤 +11713 搢 +11714 摝 +11715 摪 +11716 摰 +11717 揰 +11718 摨 +11719 摥 +11720 摗 +11721 摲 +11722 摣 +11723 摶 +11724 揫 +11725 搟 +11726 搣 +11727 摟 +11728 摱 +11729 摡 +11730 摦 +11731 揁 +11732 摛 +11733 摙 +11734 摚 +11735 揬 +11736 搧 +11737 搨 +11738 搩 +11739 搫 +11740 搮 +11741 搯 +11742 搰 +11743 搱 +11744 搲 +11745 搳 +11746 搵 +11747 搷 +11748 搸 +11749 搹 +11750 搻 +11751 搼 +11752 摀 +11753 摂 +11754 摃 +11755 摉 +11756 摋 +11757 摌 +11758 摍 +11759 摎 +11760 摏 +11761 摐 +11762 摓 +11763 摕 +11764 敶 +11765 擿 +11766 擽 +11767 敤 +11768 旝 +11769 斪 +11770 敩 +11771 攟 +11772 敠 +11773 敯 +11774 敮 +11775 敪 +11776 敺 +11777 敨 +11778 敾 +11779 攞 +11780 攠 +11781 敡 +11782 敹 +11783 敥 +11784 敭 +11785 擛 +11786 敜 +11787 敚 +11788 敟 +11789 擻 +11790 敱 +11791 攦 +11792 攧 +11793 攨 +11794 攩 +11795 攭 +11796 攰 +11797 攷 +11798 攺 +11799 攼 +11800 敀 +11801 敁 +11802 敂 +11803 敃 +11804 敄 +11805 敆 +11806 敇 +11807 敊 +11808 敋 +11809 敍 +11810 敎 +11811 敒 +11812 敓 +11813 暣 +11814 昤 +11815 昢 +11816 暔 +11817 晘 +11818 曶 +11819 暚 +11820 晐 +11821 暒 +11822 暟 +11823 暤 +11824 昣 +11825 暞 +11826 暛 +11827 暋 +11828 暩 +11829 暙 +11830 暬 +11831 昜 +11832 昡 +11833 晎 +11834 晑 +11835 暓 +11836 暥 +11837 暕 +11838 暜 +11839 旲 +11840 暏 +11841 暍 +11842 暎 +11843 晜 +11844 晠 +11845 晢 +11846 晣 +11847 晥 +11848 晩 +11849 晪 +11850 晬 +11851 晱 +11852 晲 +11853 晵 +11854 晹 +11855 晻 +11856 晼 +11857 晽 +11858 晿 +11859 暀 +11860 暃 +11861 暆 +11862 柎 +11863 朻 +11864 朸 +11865 枿 +11866 杶 +11867 桏 +11868 栕 +11869 柆 +11870 枽 +11871 柕 +11872 朹 +11873 柉 +11874 柇 +11875 柨 +11876 柗 +11877 柛 +11878 柣 +11879 朳 +11880 朷 +11881 杮 +11882 杴 +11883 枾 +11884 柖 +11885 柀 +11886 朄 +11887 枻 +11888 枼 +11889 柋 +11890 杸 +11891 杹 +11892 杻 +11893 杽 +11894 枀 +11895 枃 +11896 枅 +11897 枆 +11898 枈 +11899 枍 +11900 枎 +11901 枏 +11902 枑 +11903 枔 +11904 枖 +11905 枛 +11906 枠 +11907 枡 +11908 枤 +11909 枩 +11910 枬 +11911 枮 +11912 棿 +11913 梎 +11914 梌 +11915 棬 +11916 梸 +11917 椬 +11918 棳 +11919 棪 +11920 椀 +11921 梍 +11922 棷 +11923 棴 +11924 棥 +11925 椃 +11926 椄 +11927 椈 +11928 梉 +11929 梴 +11930 梷 +11931 椂 +11932 棭 +11933 棶 +11934 棦 +11935 棩 +11936 梊 +11937 梹 +11938 梺 +11939 梻 +11940 梽 +11941 梿 +11942 棁 +11943 棃 +11944 棆 +11945 棇 +11946 棈 +11947 棊 +11948 棌 +11949 棎 +11950 棏 +11951 棐 +11952 棑 +11953 棓 +11954 棔 +11955 棖 +11956 棙 +11957 棛 +11958 棜 +11959 棝 +11960 棞 +11961 棡 +11962 棢 +11963 槾 +11964 榒 +11965 榐 +11966 槰 +11967 榽 +11968 橑 +11969 樧 +11970 槵 +11971 榺 +11972 槮 +11973 槹 +11974 樀 +11975 榑 +11976 槸 +11977 槶 +11978 槨 +11979 樃 +11980 槴 +11981 樆 +11982 榌 +11983 榏 +11984 榹 +11985 榼 +11986 槯 +11987 槷 +11988 楡 +11989 槫 +11990 槩 +11991 槪 +11992 槬 +11993 榾 +11994 槂 +11995 槄 +11996 槅 +11997 槆 +11998 槒 +11999 槕 +12000 槗 +12001 槙 +12002 槚 +12003 槞 +12004 槡 +12005 槢 +12006 槣 +12007 槤 +12008 槥 +12009 槦 +12010 櫒 +12011 欦 +12012 櫖 +12013 檤 +12014 櫐 +12015 櫟 +12016 櫙 +12017 櫗 +12018 櫋 +12019 櫩 +12020 櫡 +12021 櫢 +12022 櫕 +12023 橾 +12024 檣 +12025 檥 +12026 櫑 +12027 櫠 +12028 櫓 +12029 櫘 +12030 橜 +12031 櫎 +12032 櫍 +12033 櫏 +12034 橽 +12035 櫛 +12036 檧 +12037 檨 +12038 檪 +12039 檭 +12040 檮 +12041 檰 +12042 檱 +12043 檲 +12044 檴 +12045 檶 +12046 檷 +12047 檹 +12048 檺 +12049 檼 +12050 檽 +12051 檾 +12052 檿 +12053 櫀 +12054 櫁 +12055 櫂 +12056 櫄 +12057 櫅 +12058 櫉 +12059 毚 +12060 歗 +12061 毃 +12062 殈 +12063 汍 +12064 毈 +12065 殀 +12066 殾 +12067 毜 +12068 歘 +12069 毌 +12070 毉 +12071 殹 +12072 毧 +12073 毞 +12074 毟 +12075 毇 +12076 毣 +12077 歖 +12078 歿 +12079 殅 +12080 毝 +12081 毊 +12082 欯 +12083 殻 +12084 殽 +12085 歕 +12086 殌 +12087 殎 +12088 殏 +12089 殐 +12090 殑 +12091 殕 +12092 殗 +12093 殙 +12094 殜 +12095 殝 +12096 殞 +12097 殟 +12098 殠 +12099 殣 +12100 殥 +12101 殦 +12102 殧 +12103 殨 +12104 殩 +12105 殫 +12106 殬 +12107 殮 +12108 殰 +12109 殱 +12110 殶 +12111 洿 +12112 沗 +12113 沕 +12114 涽 +12115 涀 +12116 洭 +12117 浀 +12118 洯 +12119 洝 +12120 浄 +12121 洬 +12122 浕 +12123 沎 +12124 泏 +12125 泒 +12126 洤 +12127 浂 +12128 洡 +12129 洟 +12130 洠 +12131 沑 +12132 洷 +12133 泙 +12134 泜 +12135 泝 +12136 泟 +12137 泤 +12138 泦 +12139 泧 +12140 泩 +12141 泬 +12142 泭 +12143 泲 +12144 泴 +12145 泹 +12146 泿 +12147 洀 +12148 洂 +12149 洃 +12150 洅 +12151 洆 +12152 洉 +12153 洊 +12154 洍 +12155 洏 +12156 洐 +12157 洓 +12158 洔 +12159 洕 +12160 洖 +12161 洘 +12162 湸 +12163 渀 +12164 淾 +12165 湪 +12166 渵 +12167 滣 +12168 溩 +12169 渱 +12170 湨 +12171 湹 +12172 淿 +12173 湱 +12174 湣 +12175 溈 +12176 湻 +12177 湼 +12178 溁 +12179 淽 +12180 渰 +12181 渳 +12182 湩 +12183 湬 +12184 淍 +12185 湦 +12186 湤 +12187 湥 +12188 湵 +12189 渶 +12190 渷 +12191 渹 +12192 渻 +12193 渽 +12194 渿 +12195 湀 +12196 湁 +12197 湂 +12198 湅 +12199 湆 +12200 湇 +12201 湈 +12202 湌 +12203 湏 +12204 湐 +12205 湑 +12206 湒 +12207 湕 +12208 湗 +12209 湝 +12210 湠 +12211 湡 +12212 澊 +12213 漙 +12214 潹 +12215 濛 +12216 澴 +12217 潀 +12218 潶 +12219 澃 +12220 澋 +12221 漘 +12222 澘 +12223 澐 +12224 澑 +12225 潾 +12226 漑 +12227 潷 +12228 澏 +12229 潻 +12230 澁 +12231 滰 +12232 潳 +12233 潱 +12234 潵 +12235 漒 +12236 澅 +12237 潃 +12238 潅 +12239 潈 +12240 潉 +12241 潊 +12242 潌 +12243 潎 +12244 潐 +12245 潒 +12246 潓 +12247 潕 +12248 潖 +12249 潗 +12250 潙 +12251 潚 +12252 潝 +12253 潠 +12254 潡 +12255 潣 +12256 潥 +12257 潧 +12258 潨 +12259 潩 +12260 潪 +12261 潫 +12262 瀪 +12263 烑 +12264 灛 +12265 灠 +12266 灥 +12267 瀇 +12268 灟 +12269 灜 +12270 灐 +12271 灴 +12272 灧 +12273 灨 +12274 灚 +12275 灮 +12276 灖 +12277 灦 +12278 濦 +12279 灒 +12280 灔 +12281 瀄 +12282 灡 +12283 瀫 +12284 瀭 +12285 瀯 +12286 瀱 +12287 瀲 +12288 瀳 +12289 瀴 +12290 瀶 +12291 瀷 +12292 瀸 +12293 瀺 +12294 瀻 +12295 瀽 +12296 瀿 +12297 灀 +12298 灁 +12299 灂 +12300 灅 +12301 灆 +12302 灇 +12303 灈 +12304 灉 +12305 灊 +12306 灋 +12307 灍 +12308 煷 +12309 焋 +12310 焇 +12311 焴 +12312 燋 +12313 熥 +12314 焲 +12315 煱 +12316 煹 +12317 煰 +12318 煭 +12319 煛 +12320 熆 +12321 煼 +12322 煫 +12323 焄 +12324 焆 +12325 焮 +12326 焳 +12327 煣 +12328 煻 +12329 煯 +12330 煠 +12331 煝 +12332 煟 +12333 焅 +12334 煴 +12335 焵 +12336 焷 +12337 焸 +12338 焹 +12339 焺 +12340 焻 +12341 焽 +12342 焾 +12343 焿 +12344 煀 +12345 煁 +12346 煂 +12347 煃 +12348 煄 +12349 煆 +12350 煇 +12351 煈 +12352 煋 +12353 煍 +12354 煏 +12355 煐 +12356 煑 +12357 煔 +12358 煕 +12359 煗 +12360 煘 +12361 〖 +12362 Ъ +12363 ┘ +12364 ⒓ +12365 < +12366 伡 +12367 偧 +12368 劶 +12369 吋 +12370 喖 +12371 埣 +12372 壖 +12373 娂 +12374 嫾 +12375 尲 +12376 嵓 +12377 幖 +12378 徏 +12379 懠 +12380 捈 +12381 摷 +12382 敿 +12383 暭 +12384 柤 +12385 椉 +12386 樇 +12387 櫦 +12388 毤 +12389 浖 +12390 溂 +12391 澕 +12392 灱 +12393 熂 +12394 紎 +12395 糭 +12396 紏 +12397 糪 +12398 紑 +12399 紒 +12400 紕 +12401 紖 +12402 紝 +12403 紞 +12404 紟 +12405 紣 +12406 紤 +12407 紥 +12408 紦 +12409 紨 +12410 紩 +12411 紪 +12412 紬 +12413 紭 +12414 紱 +12415 紲 +12416 紴 +12417 紵 +12418 〗 +12419 Ы +12420 ┙ +12421 ⒔ +12422 = +12423 伣 +12424 偨 +12425 兘 +12426 劷 +12427 吔 +12428 喗 +12429 嚱 +12430 埥 +12431 壗 +12432 娊 +12433 嫿 +12434 尳 +12435 嵔 +12436 惤 +12437 懡 +12438 捊 +12439 斀 +12440 暯 +12441 柦 +12442 椊 +12443 樈 +12444 櫧 +12445 浗 +12446 溄 +12447 澖 +12448 灲 +12449 絗 +12450 絴 +12451 絖 +12452 絒 +12453 紷 +12454 絓 +12455 絸 +12456 絺 +12457 絻 +12458 絼 +12459 絽 +12460 絾 +12461 絿 +12462 綀 +12463 綂 +12464 綃 +12465 綄 +12466 綅 +12467 綆 +12468 綇 +12469 綈 +12470 綊 +12471 綌 +12472 綍 +12473 綐 +12474 綒 +12475 綔 +12476 綕 +12477 綖 +12478 綗 +12479 【 +12480 Ь +12481 ┚ +12482 ⒕ +12483 > +12484 伨 +12485 偩 +12486 兙 +12487 劸 +12488 吘 +12489 嚲 +12490 埦 +12491 娋 +12492 嬀 +12493 尵 +12494 嵕 +12495 幘 +12496 従 +12497 惥 +12498 懢 +12499 捑 +12500 摼 +12501 斁 +12502 暰 +12503 柧 +12504 椌 +12505 櫨 +12506 毦 +12507 浘 +12508 灳 +12509 熅 +12510 綹 +12511 緗 +12512 綶 +12513 緖 +12514 緘 +12515 綷 +12516 緛 +12517 緜 +12518 緟 +12519 緡 +12520 緢 +12521 緤 +12522 緥 +12523 緦 +12524 緧 +12525 緪 +12526 緫 +12527 緭 +12528 緮 +12529 緰 +12530 緳 +12531 緵 +12532 緶 +12533 緷 +12534 緸 +12535 絘 +12536 綼 +12537 糱 +12538 糂 +12539 絙 +12540 綛 +12541 糲 +12542 糃 +12543 絚 +12544 糳 +12545 糄 +12546 絛 +12547 紻 +12548 糴 +12549 糆 +12550 紼 +12551 緀 +12552 綞 +12553 糵 +12554 糉 +12555 絝 +12556 紽 +12557 緁 +12558 綟 +12559 糶 +12560 紾 +12561 緂 +12562 糷 +12563 糎 +12564 絟 +12565 紿 +12566 緃 +12567 綡 +12568 糹 +12569 絠 +12570 絀 +12571 緄 +12572 糺 +12573 糐 +12574 絁 +12575 緅 +12576 糼 +12577 糑 +12578 緆 +12579 綤 +12580 糽 +12581 糒 +12582 絣 +12583 緇 +12584 糓 +12585 絤 +12586 糿 +12587 糔 +12588 絅 +12589 緉 +12590 綨 +12591 糘 +12592 綩 +12593 紁 +12594 糚 +12595 絧 +12596 絇 +12597 綪 +12598 糛 +12599 絈 +12600 緌 +12601 紃 +12602 糝 +12603 絩 +12604 絉 +12605 緍 +12606 絊 +12607 緎 +12608 糡 +12609 絫 +12610 総 +12611 綯 +12612 紆 +12613 糢 +12614 絬 +12615 緐 +12616 紇 +12617 糣 +12618 絭 +12619 絍 +12620 糤 +12621 絯 +12622 糥 +12623 絰 +12624 絏 +12625 緓 +12626 綳 +12627 糦 +12628 緔 +12629 紌 +12630 絑 +12631 綵 +12632 紶 +12633 綘 +12634 緺 +12635 」 +12636 Ч +12637 ┕ +12638 ⒐ +12639 伖 +12640 偣 +12641 劰 +12642 吂 +12643 喒 +12644 嚬 +12645 埞 +12646 壒 +12647 尮 +12648 幑 +12649 徆 +12650 惞 +12651 懝 +12652 捁 +12653 摴 +12654 敼 +12655 椆 +12656 樄 +12657 櫣 +12658 毠 +12659 浌 +12660 湽 +12661 澒 +12662 灩 +12663 煿 +12664 筦 +12665 筤 +12666 箌 +12667 筥 +12668 筟 +12669 筣 +12670 箎 +12671 笯 +12672 筡 +12673 箑 +12674 箒 +12675 箘 +12676 箙 +12677 箚 +12678 箛 +12679 箞 +12680 箟 +12681 箣 +12682 箤 +12683 箥 +12684 箮 +12685 箯 +12686 箰 +12687 箲 +12688 箳 +12689 箵 +12690 箶 +12691 箷 +12692 箹 +12693 箺 +12694 箻 +12695 箼 +12696 箽 +12697 箾 +12698 箿 +12699 篂 +12700 篃 +12701 笰 +12702 筨 +12703 笲 +12704 筩 +12705 笴 +12706 筪 +12707 笵 +12708 筫 +12709 笶 +12710 筬 +12711 笷 +12712 筭 +12713 笻 +12714 筰 +12715 笽 +12716 筳 +12717 笿 +12718 筴 +12719 筀 +12720 筁 +12721 筸 +12722 筂 +12723 筺 +12724 筃 +12725 筄 +12726 筽 +12727 筿 +12728 筈 +12729 箁 +12730 筊 +12731 箂 +12732 箃 +12733 筎 +12734 箄 +12735 筓 +12736 箆 +12737 筕 +12738 箇 +12739 筗 +12740 箈 +12741 筙 +12742 箉 +12743 箊 +12744 筞 +12745 Σ +12746 〔 +12747 Р +12748 ┎ +12749 ⒉ +12750 伈 +12751 偛 +12752 儾 +12753 劜 +12754 啿 +12755 埐 +12756 壊 +12757 姴 +12758 嫴 +12759 尣 +12760 幉 +12761 彶 +12762 惒 +12763 懖 +12764 挷 +12765 摬 +12766 敳 +12767 暡 +12768 柌 +12769 棽 +12770 櫜 +12771 湶 +12772 灢 +12773 煵 +12774 瞏 +12775 瞊 +12776 瞇 +12777 瞉 +12778 瞷 +12779 瞹 +12780 瞈 +12781 矀 +12782 矁 +12783 矂 +12784 矃 +12785 矄 +12786 矆 +12787 矉 +12788 矊 +12789 矋 +12790 矌 +12791 矐 +12792 矑 +12793 矒 +12794 矓 +12795 矔 +12796 矕 +12797 矘 +12798 矙 +12799 矝 +12800 矞 +12801 矟 +12802 矠 +12803 矡 +12804 瞓 +12805 睟 +12806 睠 +12807 睤 +12808 瞖 +12809 睧 +12810 瞗 +12811 睩 +12812 睪 +12813 瞙 +12814 睭 +12815 瞚 +12816 睮 +12817 瞛 +12818 睯 +12819 瞜 +12820 睰 +12821 瞝 +12822 睱 +12823 睲 +12824 瞡 +12825 睳 +12826 睴 +12827 睵 +12828 瞦 +12829 睶 +12830 瞨 +12831 睷 +12832 瞫 +12833 睸 +12834 瞭 +12835 瞮 +12836 睻 +12837 瞯 +12838 睼 +12839 瞱 +12840 瞲 +12841 瞂 +12842 瞴 +12843 瞃 +12844 瞶 +12845 瞆 +12846 矤 +12847 鞒 +12848 Τ +12849 〕 +12850 ┏ +12851 ⒊ +12852 偝 +12853 兂 +12854 劤 +12855 叧 +12856 喅 +12857 嚦 +12858 埑 +12859 壋 +12860 尦 +12861 嵆 +12862 幊 +12863 彸 +12864 惓 +12865 懗 +12866 挸 +12867 摮 +12868 柍 +12869 棾 +12870 櫝 +12871 毘 +12872 湷 +12873 澇 +12874 煶 +12875 砠 +12876 硘 +12877 砡 +12878 砙 +12879 砞 +12880 硔 +12881 硙 +12882 矦 +12883 砛 +12884 硛 +12885 硜 +12886 硠 +12887 硡 +12888 硢 +12889 硣 +12890 硥 +12891 硦 +12892 硧 +12893 硩 +12894 硰 +12895 硲 +12896 硳 +12897 硴 +12898 硶 +12899 硸 +12900 硹 +12901 硺 +12902 硽 +12903 硾 +12904 硿 +12905 碀 +12906 碂 +12907 砤 +12908 矨 +12909 砨 +12910 砪 +12911 砫 +12912 砮 +12913 矱 +12914 砯 +12915 矲 +12916 砱 +12917 矴 +12918 矵 +12919 矷 +12920 砵 +12921 矹 +12922 砶 +12923 矺 +12924 砽 +12925 砿 +12926 矼 +12927 硁 +12928 砃 +12929 硂 +12930 砄 +12931 砅 +12932 硄 +12933 砆 +12934 硆 +12935 砇 +12936 硈 +12937 砈 +12938 硉 +12939 砊 +12940 硊 +12941 砋 +12942 硋 +12943 砎 +12944 硍 +12945 砏 +12946 硏 +12947 砐 +12948 硑 +12949 砓 +12950 硓 +12951 碃 +12952 ╝ +12953 鱝 +12954 譨 +12955 琣 +12956 礱 +12957 痑 +12958 璦 +12959 穉 +12960 竌 +12961 玜 +12962 籥 +12963 禷 +12964 ゛ +12965 乤 +12966 俛 +12967 僡 +12968 刟 +12969 卆 +12970 哸 +12971 坅 +12972 塧 +12973 奱 +12974 媋 +12975 宎 +12976 峚 +12977 廰 +12978 慳 +12979 抋 +12980 揳 +12981 攁 +12982 昦 +12983 朼 +12984 梐 +12985 榓 +12986 檃 +12987 歛 +12988 沘 +12989 渁 +12990 漚 +12991 ˋ +12992 鰽 +12993 譇 +12994 疉 +12995 緼 +12996 稟 +12997 獳 +12998 籄 +12999 〢 +13000 瓵 +13001 盇 +13002 丄 +13003 侫 +13004 凙 +13005 咥 +13006 嘇 +13007 婣 +13008 孉 +13009 岮 +13010 嶢 +13011 廇 +13012 怉 +13013 慉 +13014 扐 +13015 揂 +13016 旳 +13017 桝 +13018 橝 +13019 欰 +13020 汚 +13021 淎 +13022 滱 +13023 濧 +13024 鳘 +13025 Κ +13026 — +13027 ┆ +13028 ⅹ +13029 * +13030 仾 +13031 偑 +13032 儶 +13033 劒 +13034 叒 +13035 埅 +13036 壀 +13037 崻 +13038 帾 +13039 挭 +13040 摢 +13041 柂 +13042 棯 +13043 槳 +13044 櫔 +13045 湭 +13046 灙 +13047 煪 +13048 猔 +13049 猑 +13050 獈 +13051 獆 +13052 猒 +13053 猍 +13054 狜 +13055 猏 +13056 獉 +13057 獊 +13058 獋 +13059 獌 +13060 獏 +13061 獓 +13062 獔 +13063 獕 +13064 獖 +13065 獘 +13066 獙 +13067 獚 +13068 獛 +13069 獜 +13070 獝 +13071 獞 +13072 獟 +13073 獡 +13074 獤 +13075 獥 +13076 獧 +13077 獩 +13078 獪 +13079 獫 +13080 獮 +13081 獰 +13082 ㄡ +13083 ︶ +13084 ♂ +13085 ┽ +13086 ⑨ +13087 佱 +13088 傖 +13089 冡 +13090 勧 +13091 呩 +13092 囜 +13093 堘 +13094 夅 +13095 娽 +13096 嬦 +13097 屷 +13098 嶀 +13099 忈 +13100 愥 +13101 戓 +13102 掅 +13103 斸 +13104 栣 +13105 樶 +13106 欋 +13107 氠 +13108 涐 +13109 炨 +13110 醏 +13111 醊 +13112 醻 +13113 岈 +13114 醸 +13115 醎 +13116 醄 +13117 醈 +13118 醹 +13119 岍 +13120 醆 +13121 醼 +13122 釂 +13123 釃 +13124 釅 +13125 釒 +13126 釓 +13127 釔 +13128 釕 +13129 釖 +13130 釙 +13131 釚 +13132 釛 +13133 釟 +13134 釠 +13135 釡 +13136 釢 +13137 釤 +13138 α +13139 × +13140 ┝ +13141 ⒘ +13142 A +13143 吜 +13144 喠 +13145 嚵 +13146 埩 +13147 壛 +13148 娏 +13149 嬃 +13150 嵙 +13151 幜 +13152 徚 +13153 惲 +13154 懥 +13155 捔 +13156 摿 +13157 斄 +13158 柫 +13159 椓 +13160 樍 +13161 櫫 +13162 毩 +13163 溋 +13164 澚 +13165 灹 +13166 羆 +13167 羄 +13168 羭 +13169 羀 +13170 羃 +13171 羮 +13172 罖 +13173 羂 +13174 羴 +13175 羵 +13176 羺 +13177 羻 +13178 翂 +13179 翄 +13180 翆 +13181 翈 +13182 翉 +13183 翋 +13184 翍 +13185 翏 +13186 翐 +13187 翑 +13188 翓 +13189 翖 +13190 翗 +13191 翙 +13192 翜 +13193 翝 +13194 翞 +13195 翢 +13196 ㄠ +13197 ︵ +13198 ∴ +13199 ┼ +13200 ⑧ +13201 ` +13202 佮 +13203 冟 +13204 勦 +13205 呧 +13206 嗋 +13207 囙 +13208 堗 +13209 夃 +13210 娻 +13211 嬥 +13212 屶 +13213 嵿 +13214 庎 +13215 忇 +13216 戉 +13217 掄 +13218 撪 +13219 椸 +13220 樴 +13221 氞 +13222 溹 +13223 澿 +13224 炧 +13225 熰 +13226 郶 +13227 鄜 +13228 郲 +13229 鄛 +13230 郂 +13231 郳 +13232 鄝 +13233 鄠 +13234 鄨 +13235 鄩 +13236 鄪 +13237 鄫 +13238 鄮 +13239 鄳 +13240 鄵 +13241 鄶 +13242 鄷 +13243 鄸 +13244 鄺 +13245 鄼 +13246 鄽 +13247 鄾 +13248 鄿 +13249 酂 +13250 ɡ +13251 ± +13252 Ю +13253 ├ +13254 ⒗ +13255 @ +13256 伬 +13257 偫 +13258 劺 +13259 吚 +13260 喞 +13261 埨 +13262 壚 +13263 娎 +13264 嬂 +13265 嵗 +13266 幚 +13267 徖 +13268 捓 +13269 摾 +13270 暲 +13271 柪 +13272 樌 +13273 櫪 +13274 毨 +13275 浝 +13276 溊 +13277 澙 +13278 灷 +13279 纞 +13280 繻 +13281 纚 +13282 繺 +13283 纴 +13284 纼 +13285 绖 +13286 绤 +13287 绬 +13288 绹 +13289 缐 +13290 缞 +13291 缹 +13292 缻 +13293 缼 +13294 缽 +13295 缾 +13296 缿 +13297 罀 +13298 罁 +13299 罃 +13300 罆 +13301 罇 +13302 罉 +13303 罊 +13304 罋 +13305 罎 +13306 罏 +13307 罒 +13308 ㄢ +13309 ︹ +13310 ♀ +13311 р +13312 ┾ +13313 ⑩ +13314 傗 +13315 冣 +13316 勨 +13317 嗏 +13318 堚 +13319 娾 +13320 嬧 +13321 屸 +13322 庘 +13323 忊 +13324 愨 +13325 戔 +13326 掆 +13327 撯 +13328 斺 +13329 曗 +13330 栤 +13331 椻 +13332 樷 +13333 欌 +13334 涒 +13335 溾 +13336 獯 +13337 鈤 +13338 怊 +13339 鈢 +13340 鈅 +13341 鈁 +13342 鈃 +13343 猱 +13344 釦 +13345 猓 +13346 鈂 +13347 鈥 +13348 鈧 +13349 鈨 +13350 鈩 +13351 鈪 +13352 鈫 +13353 鈭 +13354 鈮 +13355 鈯 +13356 鈰 +13357 鈱 +13358 鈲 +13359 鈳 +13360 鈵 +13361 鈶 +13362 鈸 +13363 鈹 +13364 鈻 +13365 鈼 +13366 鈽 +13367 鈾 +13368 鉁 +13369 鉂 +13370 鉃 +13371 ÷ +13372 ┞ +13373 ぢ +13374 ⒙ +13375 B +13376 ヂ +13377 甭 +13378 伮 +13379 偮 +13380 兟 +13381 喡 +13382 嚶 +13383 壜 +13384 娐 +13385 嬄 +13386 幝 +13387 惵 +13388 懧 +13389 捖 +13390 撀 +13391 斅 +13392 暵 +13393 柭 +13394 椔 +13395 樎 +13396 櫬 +13397 溌 +13398 澛 +13399 熉 +13400 耟 +13401 耝 +13402 聕 +13403 耞 +13404 耓 +13405 耛 +13406 翤 +13407 聙 +13408 聛 +13409 聜 +13410 聝 +13411 聟 +13412 聠 +13413 聡 +13414 聢 +13415 聣 +13416 聥 +13417 聦 +13418 聧 +13419 聨 +13420 聫 +13421 聬 +13422 聭 +13423 聮 +13424 聵 +13425 聸 +13426 聹 +13427 聺 +13428 聻 +13429 聼 +13430 ㄥ +13431 ﹀ +13432 ″ +13433 ╁ +13434 ㈠ +13435 佸 +13436 勫 +13437 呭 +13438 嗗 +13439 夊 +13440 婂 +13441 屽 +13442 嶅 +13443 庡 +13444 忓 +13445 愬 +13446 戝 +13447 掑 +13448 斿 +13449 曞 +13450 栧 +13451 楀 +13452 炲 +13453 熷 +13454 錪 +13455 鍉 +13456 鬻 +13457 鍇 +13458 瀵 +13459 錧 +13460 鍆 +13461 鍈 +13462 錊 +13463 鍌 +13464 鍏 +13465 鍐 +13466 鍑 +13467 鍒 +13468 鍓 +13469 鍕 +13470 鍗 +13471 鍚 +13472 鍜 +13473 鍝 +13474 鍞 +13475 鍟 +13476 鍠 +13477 鍡 +13478 鍣 +13479 鍤 +13480 鍦 +13481 鍧 +13482 鍨 +13483 鍩 +13484 ㄅ +13485 ε +13486 ∨ +13487 ┡ +13488 ⑴ +13489 E +13490 ヅ +13491 伵 +13492 吪 +13493 喤 +13494 埮 +13495 娕 +13496 嵟 +13497 徟 +13498 懪 +13499 捙 +13500 撆 +13501 暸 +13502 椗 +13503 櫯 +13504 毰 +13505 溑 +13506 澟 +13507 熍 +13508 臽 +13509 臹 +13510 舼 +13511 臸 +13512 臷 +13513 艀 +13514 艁 +13515 艃 +13516 艈 +13517 艌 +13518 艐 +13519 艑 +13520 艓 +13521 艔 +13522 艕 +13523 艗 +13524 艛 +13525 艝 +13526 艞 +13527 艤 +13528 艧 +13529 ㄤ +13530 ︿ +13531 ′ +13532 ╀ +13533 佷 +13534 勪 +13535 呬 +13536 嗕 +13537 囦 +13538 堜 +13539 嬩 +13540 屼 +13541 忎 +13542 愪 +13543 戜 +13544 掍 +13545 斾 +13546 栦 +13547 椾 +13548 欎 +13549 涗 +13550 滀 +13551 濅 +13552 炰 +13553 熶 +13554 鋊 +13555 鋨 +13556 鋦 +13557 鋉 +13558 鋄 +13559 鋥 +13560 鋩 +13561 鋫 +13562 鋬 +13563 鋮 +13564 鋱 +13565 鋲 +13566 鋳 +13567 鋷 +13568 鋺 +13569 鋻 +13570 鋽 +13571 鋾 +13572 鋿 +13573 錀 +13574 錁 +13575 錂 +13576 錅 +13577 錆 +13578 錇 +13579 錈 +13580 δ +13581 ∧ +13582 ┠ +13583 ⒛ +13584 D +13585 伳 +13586 勀 +13587 吥 +13588 喣 +13589 嚹 +13590 埬 +13591 娔 +13592 嬆 +13593 屇 +13594 嵞 +13595 幠 +13596 惸 +13597 懩 +13598 捘 +13599 斈 +13600 暷 +13601 柲 +13602 椖 +13603 毮 +13604 浤 +13605 溎 +13606 澞 +13607 熌 +13608 腵 +13609 腲 +13610 膥 +13611 膢 +13612 腯 +13613 膡 +13614 膤 +13615 腬 +13616 膧 +13617 膫 +13618 膬 +13619 膮 +13620 膯 +13621 膰 +13622 膲 +13623 膴 +13624 膵 +13625 膶 +13626 膷 +13627 膸 +13628 膹 +13629 膼 +13630 膾 +13631 臄 +13632 臅 +13633 臈 +13634 臋 +13635 臐 +13636 臒 +13637 ㄣ +13638 ︺ +13639 ° +13640 ┿ +13641 ゃ +13642 冦 +13643 勩 +13644 呫 +13645 嗐 +13646 囥 +13647 堛 +13648 夈 +13649 嬨 +13650 屻 +13651 嶃 +13652 庛 +13653 忋 +13654 愩 +13655 戙 +13656 掋 +13657 撱 +13658 斻 +13659 曘 +13660 栥 +13661 椼 +13662 欍 +13663 氥 +13664 涖 +13665 溿 +13666 濄 +13667 炪 +13668 熴 +13669 鉦 +13670 銃 +13671 鉥 +13672 鉡 +13673 銄 +13674 鉢 +13675 銇 +13676 銈 +13677 銉 +13678 銊 +13679 銋 +13680 銏 +13681 銕 +13682 銗 +13683 銙 +13684 銛 +13685 銝 +13686 銞 +13687 銟 +13688 銠 +13689 銡 +13690 銣 +13691 銤 +13692 銦 +13693 ∶ +13694 ┟ +13695 ⒚ +13696 C +13697 伱 +13698 偯 +13699 兠 +13700 劽 +13701 喢 +13702 嚸 +13703 埫 +13704 壝 +13705 嵜 +13706 徝 +13707 惷 +13708 懨 +13709 撁 +13710 斆 +13711 暶 +13712 柮 +13713 椕 +13714 樏 +13715 櫭 +13716 毭 +13717 浢 +13718 澝 +13719 灻 +13720 熋 +13721 胉 +13722 胇 +13723 脋 +13724 胈 +13725 肹 +13726 胅 +13727 肻 +13728 脌 +13729 脕 +13730 脗 +13731 脙 +13732 脜 +13733 脝 +13734 脟 +13735 脠 +13736 脡 +13737 脢 +13738 脤 +13739 脥 +13740 脦 +13741 脧 +13742 脨 +13743 脪 +13744 脭 +13745 脮 +13746 脰 +13747 脴 +13748 脵 +13749 脺 +13750 脻 +13751 脼 +13752 脽 +13753 饧 +13754 饩 +13755 宀 +13756 猘 +13757 狝 +13758 醓 +13759 酇 +13760 羇 +13761 罙 +13762 郺 +13763 繟 +13764 嗬 +13765 鈇 +13766 耡 +13767 翧 +13768 猹 +13769 忄 +13770 饫 +13771 忮 +13772 錋 +13773 臿 +13774 鋋 +13775 銩 +13776 腶 +13777 腁 +13778 溻 +13779 滗 +13780 鉧 +13781 肁 +13782 懔 +13783 汔 +13784 彐 +13785 猙 +13786 狟 +13787 醔 +13788 酈 +13789 罛 +13790 郆 +13791 纀 +13792 繠 +13793 鈈 +13794 釨 +13795 翨 +13796 錬 +13797 舃 +13798 臖 +13799 鋌 +13800 銪 +13801 腷 +13802 腂 +13803 鉈 +13804 胋 +13805 肂 +13806 猚 +13807 狢 +13808 醕 +13809 酑 +13810 羉 +13811 罜 +13812 郼 +13813 郈 +13814 釩 +13815 耤 +13816 錭 +13817 錍 +13818 臗 +13819 鋍 +13820 銫 +13821 腃 +13822 鉩 +13823 胏 +13824 猟 +13825 狣 +13826 醖 +13827 酓 +13828 羋 +13829 郿 +13830 郉 +13831 纃 +13832 繢 +13833 釪 +13834 翫 +13835 錎 +13836 屦 +13837 膁 +13838 鉊 +13839 胐 +13840 肈 +13841 猠 +13842 狤 +13843 ㄦ +13844 ︽ +13845 ℃ +13846 ф +13847 ╂ +13848 ㈡ +13849 佹 +13850 冩 +13851 勬 +13852 呮 +13853 嗘 +13854 囨 +13855 堟 +13856 夋 +13857 婃 +13858 嬫 +13859 嶆 +13860 庢 +13861 愭 +13862 戞 +13863 掓 +13864 撴 +13865 旀 +13866 曟 +13867 栨 +13868 楁 +13869 樻 +13870 欐 +13871 氭 +13872 濇 +13873 炴 +13874 熸 +13875 鎩 +13876 鎋 +13877 鎨 +13878 鍬 +13879 鎈 +13880 姹 +13881 鎯 +13882 鎰 +13883 鎱 +13884 鎲 +13885 鎳 +13886 鎴 +13887 鎵 +13888 鎶 +13889 鎷 +13890 鎸 +13891 鎹 +13892 鎺 +13893 鎻 +13894 鎼 +13895 鎽 +13896 鎾 +13897 鎿 +13898 鏀 +13899 鏁 +13900 鏂 +13901 鏄 +13902 鏅 +13903 鏆 +13904 鏇 +13905 鏉 +13906 鏋 +13907 鏌 +13908 ζ +13909 ∑ +13910 ┢ +13911 ⑵ +13912 F +13913 兤 +13914 勂 +13915 喥 +13916 嚻 +13917 埰 +13918 壠 +13919 娖 +13920 嵠 +13921 幤 +13922 惼 +13923 捚 +13924 斊 +13925 柶 +13926 椘 +13927 樒 +13928 櫰 +13929 毱 +13930 浧 +13931 溒 +13932 炂 +13933 熎 +13934 苸 +13935 苵 +13936 芲 +13937 苶 +13938 芢 +13939 苿 +13940 茊 +13941 茍 +13942 茐 +13943 茒 +13944 茓 +13945 茘 +13946 茙 +13947 茝 +13948 茞 +13949 茟 +13950 茠 +13951 茡 +13952 茢 +13953 茣 +13954 茥 +13955 茦 +13956 茩 +13957 茮 +13958 茰 +13959 茷 +13960 茻 +13961 醗 +13962 酔 +13963 羍 +13964 罞 +13965 崾 +13966 郋 +13967 纄 +13968 繣 +13969 嗍 +13970 釫 +13971 耬 +13972 馊 +13973 錏 +13974 臙 +13975 鋏 +13976 銭 +13977 腅 +13978 鉫 +13979 鉋 +13980 胑 +13981 肊 +13982 闶 +13983 鎍 +13984 鍭 +13985 艫 +13986 驵 +13987 芺 +13988 鎐 +13989 艭 +13990 鎑 +13991 鍰 +13992 芼 +13993 鎒 +13994 鍱 +13995 芿 +13996 艵 +13997 鎓 +13998 苀 +13999 鎔 +14000 鍳 +14001 苂 +14002 鎕 +14003 鍴 +14004 艸 +14005 苅 +14006 艻 +14007 鎗 +14008 鍶 +14009 苆 +14010 艼 +14011 鎘 +14012 鍷 +14013 苉 +14014 芀 +14015 鎙 +14016 鍸 +14017 苐 +14018 芁 +14019 鎚 +14020 鍹 +14021 苖 +14022 鎛 +14023 苙 +14024 芅 +14025 嫜 +14026 鎜 +14027 鍻 +14028 苚 +14029 芆 +14030 嬖 +14031 鎝 +14032 苝 +14033 芇 +14034 鎞 +14035 鍽 +14036 芉 +14037 鎟 +14038 芌 +14039 鎠 +14040 鍿 +14041 芐 +14042 鎀 +14043 苩 +14044 芔 +14045 纟 +14046 孥 +14047 苬 +14048 芕 +14049 鎃 +14050 芖 +14051 鎄 +14052 苮 +14053 芚 +14054 鎦 +14055 鎅 +14056 苰 +14057 苲 +14058 芞 +14059 鏍 +14060 茽 +14061 猣 +14062 狥 +14063 醘 +14064 酕 +14065 羏 +14066 鄁 +14067 纅 +14068 繤 +14069 鈌 +14070 釬 +14071 耭 +14072 翭 +14073 鋐 +14074 銯 +14075 膄 +14076 腇 +14077 鉌 +14078 胒 +14079 肍 +14080 猤 +14081 狦 +14082 醙 +14083 酖 +14084 羐 +14085 罣 +14086 郍 +14087 纆 +14088 耮 +14089 錱 +14090 錑 +14091 舋 +14092 臛 +14093 銰 +14094 膅 +14095 腉 +14096 鉭 +14097 鉍 +14098 胓 +14099 肎 +14100 猦 +14101 狧 +14102 酘 +14103 羑 +14104 鄅 +14105 纇 +14106 繦 +14107 釮 +14108 耯 +14109 翲 +14110 錒 +14111 銱 +14112 膆 +14113 腍 +14114 鉮 +14115 鉎 +14116 胔 +14117 肏 +14118 猧 +14119 狪 +14120 醝 +14121 酙 +14122 羒 +14123 罥 +14124 郔 +14125 纈 +14126 繧 +14127 嘞 +14128 鈏 +14129 耰 +14130 翴 +14131 錓 +14132 舏 +14133 臝 +14134 屙 +14135 鋓 +14136 膇 +14137 漤 +14138 滹 +14139 鉯 +14140 鉏 +14141 胕 +14142 肐 +14143 猨 +14144 狫 +14145 酛 +14146 羓 +14147 罦 +14148 鄇 +14149 繨 +14150 鈐 +14151 釰 +14152 耲 +14153 翵 +14154 舑 +14155 臞 +14156 膉 +14157 腏 +14158 鉰 +14159 鉐 +14160 胘 +14161 肑 +14162 猭 +14163 狵 +14164 醟 +14165 酜 +14166 罧 +14167 鄈 +14168 郖 +14169 纊 +14170 釱 +14171 耴 +14172 翶 +14173 錵 +14174 錕 +14175 舓 +14176 鋕 +14177 銴 +14178 膋 +14179 腒 +14180 鉱 +14181 胟 +14182 肒 +14183 猯 +14184 狶 +14185 醠 +14186 酟 +14187 羖 +14188 罫 +14189 郘 +14190 纋 +14191 鈒 +14192 釲 +14193 耹 +14194 翷 +14195 錖 +14196 舕 +14197 鋖 +14198 膌 +14199 腖 +14200 鉲 +14201 鉒 +14202 肔 +14203 醡 +14204 酠 +14205 羗 +14206 罬 +14207 鄊 +14208 郙 +14209 鈓 +14210 耺 +14211 翸 +14212 錷 +14213 臡 +14214 鋗 +14215 膍 +14216 腗 +14217 鉳 +14218 鉓 +14219 胢 +14220 肕 +14221 猲 +14222 醤 +14223 酦 +14224 羘 +14225 罭 +14226 纍 +14227 繬 +14228 釴 +14229 耼 +14230 庋 +14231 錸 +14232 舗 +14233 鋘 +14234 膎 +14235 鉵 +14236 鉔 +14237 胣 +14238 肗 +14239 猳 +14240 狾 +14241 醥 +14242 酧 +14243 羙 +14244 罯 +14245 郞 +14246 纎 +14247 嘁 +14248 嘣 +14249 圊 +14250 耾 +14251 翺 +14252 夂 +14253 庳 +14254 錙 +14255 舘 +14256 臤 +14257 弪 +14258 艴 +14259 逭 +14260 耪 +14261 屮 +14262 鋙 +14263 膐 +14264 腛 +14265 胦 +14266 肙 +14267 阍 +14268 沲 +14269 猵 +14270 狿 +14271 醦 +14272 酨 +14273 羛 +14274 鄍 +14275 繮 +14276 鈖 +14277 聀 +14278 翽 +14279 舙 +14280 鋚 +14281 膒 +14282 腜 +14283 鉖 +14284 胮 +14285 肞 +14286 猀 +14287 醧 +14288 酫 +14289 羜 +14290 鄎 +14291 郠 +14292 繯 +14293 鈗 +14294 釷 +14295 聁 +14296 錻 +14297 錛 +14298 舚 +14299 臦 +14300 鋛 +14301 銺 +14302 膓 +14303 腝 +14304 胵 +14305 肣 +14306 猺 +14307 猂 +14308 醨 +14309 酭 +14310 羠 +14311 鄏 +14312 郣 +14313 纑 +14314 繰 +14315 釸 +14316 聄 +14317 翿 +14318 錼 +14319 錜 +14320 舝 +14321 鋜 +14322 銻 +14323 膔 +14324 腞 +14325 鉹 +14326 胷 +14327 肦 +14328 猻 +14329 醩 +14330 酳 +14331 羢 +14332 罶 +14333 鄐 +14334 郤 +14335 纒 +14336 鈙 +14337 聅 +14338 耂 +14339 錽 +14340 錝 +14341 臩 +14342 鋝 +14343 膕 +14344 腟 +14345 鉺 +14346 鉙 +14347 胹 +14348 肧 +14349 猼 +14350 猅 +14351 鄑 +14352 郥 +14353 繲 +14354 鈚 +14355 釺 +14356 聇 +14357 耇 +14358 錿 +14359 錞 +14360 舤 +14361 臫 +14362 鋞 +14363 膖 +14364 腡 +14365 胻 +14366 肨 +14367 猽 +14368 猆 +14369 酻 +14370 罸 +14371 郩 +14372 纔 +14373 帱 +14374 鈛 +14375 聈 +14376 耈 +14377 廒 +14378 廛 +14379 鍀 +14380 錟 +14381 臮 +14382 鋟 +14383 銾 +14384 膗 +14385 腢 +14386 鉼 +14387 胾 +14388 肬 +14389 丬 +14390 獀 +14391 醰 +14392 酼 +14393 羦 +14394 罺 +14395 郪 +14396 纕 +14397 繴 +14398 鈜 +14399 釼 +14400 耉 +14401 舦 +14402 臯 +14403 鋠 +14404 銿 +14405 腣 +14406 鉽 +14407 胿 +14408 肰 +14409 獁 +14410 猈 +14411 醱 +14412 醀 +14413 罻 +14414 鄔 +14415 郬 +14416 繵 +14417 耊 +14418 鍂 +14419 舧 +14420 臰 +14421 鋡 +14422 鋀 +14423 腤 +14424 鉾 +14425 鉝 +14426 脀 +14427 肳 +14428 獂 +14429 猉 +14430 醲 +14431 罼 +14432 鄕 +14433 郮 +14434 纗 +14435 繶 +14436 釾 +14437 聏 +14438 耎 +14439 鍃 +14440 舩 +14441 臱 +14442 鋢 +14443 膞 +14444 鉿 +14445 脁 +14446 肵 +14447 獃 +14448 猋 +14449 醳 +14450 羪 +14451 罽 +14452 郰 +14453 纘 +14454 噍 +14455 鈟 +14456 釿 +14457 聐 +14458 耏 +14459 鍄 +14460 錣 +14461 舮 +14462 臲 +14463 鋣 +14464 鋂 +14465 膟 +14466 腨 +14467 鉟 +14468 脃 +14469 肶 +14470 猌 +14471 羫 +14472 罿 +14473 鄗 +14474 郱 +14475 纙 +14476 聑 +14477 耑 +14478 鍅 +14479 錤 +14480 臵 +14481 鋃 +14482 銁 +14483 鉠 +14484 翣 +14485 酄 +14486 罓 +14487 鍫 +14488 錉 +14489 臓 +14490 銧 +14491 脿 +14492 碽 +14493 ╞ +14494 癰 +14495 礲 +14496 痓 +14497 縝 +14498 穊 +14499 竍 +14500 玝 +14501 籦 +14502 禸 +14503 ゜ +14504 乥 +14505 僢 +14506 刡 +14507 哹 +14508 嘼 +14509 坆 +14510 塨 +14511 奲 +14512 宐 +14513 峛 +14514 巄 +14515 廱 +14516 恇 +14517 慴 +14518 抌 +14519 攂 +14520 昩 +14521 朾 +14522 梑 +14523 榖 +14524 檅 +14525 歜 +14526 漛 +14527 瀊 +14528 焍 +14529 碆 +14530 ˙ +14531 譈 +14532 礏 +14533 瑽 +14534 稡 +14535 窧 +14536 禕 +14537 〣 +14538 瓸 +14539 盉 +14540 侭 +14541 傿 +14542 凚 +14543 匓 +14544 咮 +14545 嘊 +14546 圔 +14547 塀 +14548 夿 +14549 婤 +14550 孊 +14551 嶣 +14552 怋 +14553 払 +14554 揃 +14555 擝 +14556 旴 +14557 朆 +14558 桞 +14559 楤 +14560 橞 +14561 欱 +14562 汢 +14563 烞 +14564 碿 +14565 ╟ +14566 鱟 +14567 譪 +14568 琧 +14569 痗 +14570 璫 +14571 穋 +14572 竎 +14573 籧 +14574 禼 +14575 ヽ +14576 甤 +14577 眂 +14578 乧 +14579 俢 +14580 僣 +14581 刢 +14582 卌 +14583 哻 +14584 嘽 +14585 坈 +14586 奵 +14587 媍 +14588 宑 +14589 峜 +14590 巆 +14591 廲 +14592 恈 +14593 抍 +14594 揷 +14595 攃 +14596 朿 +14597 梒 +14598 榗 +14599 檆 +14600 沜 +14601 漜 +14602 焎 +14603 碈 +14604 珻 +14605 癈 +14606 疌 +14607 緾 +14608 稢 +14609 禖 +14610 〤 +14611 盋 +14612 丆 +14613 侰 +14614 匔 +14615 咰 +14616 嘋 +14617 圕 +14618 塁 +14619 婥 +14620 孋 +14621 岰 +14622 嶤 +14623 怌 +14624 慍 +14625 扖 +14626 揅 +14627 朇 +14628 桟 +14629 楥 +14630 欳 +14631 汣 +14632 淐 +14633 烠 +14634 ㄧ +14635 ︾ +14636 $ +14637 ╃ +14638 ㈢ +14639 傜 +14640 勭 +14641 呯 +14642 嗙 +14643 囩 +14644 堢 +14645 夌 +14646 婄 +14647 嬬 +14648 庣 +14649 忕 +14650 愮 +14651 戠 +14652 掔 +14653 撶 +14654 楃 +14655 樼 +14656 欑 +14657 氱 +14658 涚 +14659 濈 +14660 炵 +14661 鏯 +14662 鐍 +14663 鐋 +14664 绨 +14665 鏮 +14666 鏪 +14667 鐊 +14668 鐌 +14669 缍 +14670 绠 +14671 鏫 +14672 鐎 +14673 鐏 +14674 鐑 +14675 鐒 +14676 鐔 +14677 鐕 +14678 鐖 +14679 鐗 +14680 鐙 +14681 鐚 +14682 鐛 +14683 鐝 +14684 鐞 +14685 鐟 +14686 鐠 +14687 鐡 +14688 鐣 +14689 鐤 +14690 鐥 +14691 鐦 +14692 鐧 +14693 鐨 +14694 鐩 +14695 鐪 +14696 鐬 +14697 鐭 +14698 ㄇ +14699 η +14700 ∏ +14701 ┣ +14702 ⑶ +14703 G +14704 伹 +14705 偳 +14706 兦 +14707 勄 +14708 埱 +14709 壡 +14710 娗 +14711 嬊 +14712 屒 +14713 嵡 +14714 幥 +14715 徢 +14716 惽 +14717 捛 +14718 撉 +14719 椙 +14720 櫱 +14721 浨 +14722 澢 +14723 炃 +14724 熐 +14725 莁 +14726 荿 +14727 莮 +14728 莬 +14729 荹 +14730 莭 +14731 茾 +14732 荺 +14733 莯 +14734 莵 +14735 莻 +14736 莾 +14737 莿 +14738 菃 +14739 菄 +14740 菆 +14741 菎 +14742 菐 +14743 菑 +14744 菒 +14745 菕 +14746 菗 +14747 菙 +14748 菚 +14749 菛 +14750 菞 +14751 菢 +14752 菣 +14753 菤 +14754 菦 +14755 菧 +14756 菨 +14757 菬 +14758 鏰 +14759 莂 +14760 茿 +14761 绐 +14762 缋 +14763 缏 +14764 鏱 +14765 鏐 +14766 莃 +14767 荁 +14768 鏑 +14769 莄 +14770 荂 +14771 鏳 +14772 鏒 +14773 莇 +14774 鏴 +14775 莈 +14776 荅 +14777 鏵 +14778 荈 +14779 鏕 +14780 莋 +14781 鏷 +14782 莌 +14783 荋 +14784 鏸 +14785 莍 +14786 鏹 +14787 鏙 +14788 莏 +14789 荍 +14790 鏺 +14791 鏚 +14792 荎 +14793 鏛 +14794 荓 +14795 鏼 +14796 荕 +14797 鏝 +14798 莕 +14799 鏾 +14800 缲 +14801 莗 +14802 鐀 +14803 鏠 +14804 鐁 +14805 莚 +14806 荝 +14807 莝 +14808 荢 +14809 鐃 +14810 鏣 +14811 莟 +14812 荰 +14813 鐄 +14814 莡 +14815 荱 +14816 鐅 +14817 荲 +14818 鐆 +14819 鏦 +14820 莣 +14821 鏧 +14822 莤 +14823 荴 +14824 鏨 +14825 莥 +14826 荵 +14827 巛 +14828 鐉 +14829 鏩 +14830 荶 +14831 菭 +14832 磀 +14833 ╠ +14834 鱠 +14835 琩 +14836 璬 +14837 縟 +14838 竏 +14839 ヾ +14840 眃 +14841 乨 +14842 僤 +14843 刣 +14844 嘾 +14845 塪 +14846 媎 +14847 宒 +14848 峝 +14849 巇 +14850 恉 +14851 慸 +14852 抎 +14853 攄 +14854 杁 +14855 榙 +14856 歞 +14857 渄 +14858 漝 +14859 瀌 +14860 焏 +14861 ― +14862 譊 +14863 珼 +14864 癉 +14865 礑 +14866 璂 +14867 緿 +14868 稤 +14869 獶 +14870 禗 +14871 〥 +14872 盌 +14873 侱 +14874 僁 +14875 凞 +14876 匘 +14877 奃 +14878 孌 +14879 岲 +14880 嶥 +14881 怐 +14882 慏 +14883 扗 +14884 揇 +14885 朌 +14886 桪 +14887 楧 +14888 橠 +14889 欴 +14890 汥 +14891 滵 +14892 濪 +14893 烡 +14894 ︷ +14895 ○ +14896 ю +14897 ゐ +14898 ヰ +14899 侌 +14900 傪 +14901 凁 +14902 勷 +14903 咅 +14904 嗮 +14905 囸 +14906 堭 +14907 夝 +14908 岎 +14909 嶐 +14910 庰 +14911 忦 +14912 掟 +14913 擆 +14914 旔 +14915 曫 +14916 栶 +14917 楌 +14918 橉 +14919 欚 +14920 濔 +14921 烉 +14922 餪 +14923 饉 +14924 鹱 +14925 餧 +14926 饆 +14927 餈 +14928 餦 +14929 饊 +14930 饌 +14931 饍 +14932 饎 +14933 饏 +14934 饐 +14935 饘 +14936 饙 +14937 饚 +14938 饜 +14939 饟 +14940 饠 +14941 饡 +14942 饢 +14943 饦 +14944 饳 +14945 饻 +14946 馂 +14947 ㄐ +14948 ⌒ +14949 ┬ +14950 ⑿ +14951 P +14952 冃 +14953 勑 +14954 呅 +14955 埿 +14956 壭 +14957 娦 +14958 嬓 +14959 屝 +14960 嵭 +14961 幮 +14962 徯 +14963 愋 +14964 捫 +14965 撔 +14966 斝 +14967 曅 +14968 栃 +14969 櫺 +14970 毿 +14971 浶 +14972 溞 +14973 澬 +14974 炐 +14975 熜 +14976 衊 +14977 衈 +14978 衺 +14979 衉 +14980 衃 +14981 衇 +14982 衶 +14983 蠤 +14984 衻 +14985 衼 +14986 袀 +14987 袃 +14988 袇 +14989 袉 +14990 袊 +14991 袌 +14992 袎 +14993 袏 +14994 袐 +14995 袑 +14996 袓 +14997 袔 +14998 袕 +14999 袗 +15000 袘 +15001 袙 +15002 袚 +15003 袛 +15004 袝 +15005 袞 +15006 袟 +15007 袠 +15008 袡 +15009 袥 +15010 袦 +15011 袧 +15012 袨 +15013 袩 +15014 餉 +15015 衋 +15016 蠥 +15017 餬 +15018 蠦 +15019 餋 +15020 衏 +15021 衐 +15022 蠨 +15023 餎 +15024 衑 +15025 餱 +15026 蠪 +15027 餲 +15028 餑 +15029 蠫 +15030 餳 +15031 衕 +15032 衖 +15033 蠭 +15034 餵 +15035 餔 +15036 衘 +15037 蠮 +15038 餶 +15039 餕 +15040 衚 +15041 蠯 +15042 餷 +15043 餖 +15044 蠰 +15045 餗 +15046 衜 +15047 餹 +15048 蠳 +15049 瘃 +15050 餺 +15051 餙 +15052 衞 +15053 餻 +15054 衟 +15055 蠵 +15056 衠 +15057 餽 +15058 餜 +15059 衦 +15060 衧 +15061 蠸 +15062 餿 +15063 衪 +15064 蠺 +15065 饀 +15066 餟 +15067 衭 +15068 疒 +15069 瘗 +15070 瘥 +15071 饁 +15072 餠 +15073 衯 +15074 蠽 +15075 衱 +15076 蠾 +15077 餢 +15078 衳 +15079 蠿 +15080 衴 +15081 衁 +15082 餤 +15083 衵 +15084 衂 +15085 馉 +15086 袪 +15087 ╡ +15088 鱡 +15089 琫 +15090 癳 +15091 礶 +15092 痚 +15093 璭 +15094 竐 +15095 玡 +15096 籩 +15097 秂 +15098 〆 +15099 甧 +15100 眅 +15101 俥 +15102 卐 +15103 唀 +15104 噀 +15105 坋 +15106 塭 +15107 奺 +15108 媏 +15109 宔 +15110 峞 +15111 巈 +15112 廵 +15113 恊 +15114 慹 +15115 抏 +15116 揺 +15117 攅 +15118 昬 +15119 梕 +15120 榚 +15121 檈 +15122 歟 +15123 沞 +15124 渆 +15125 焑 +15126 碋 +15127 ‥ +15128 鱁 +15129 癊 +15130 疎 +15131 璄 +15132 縀 +15133 稥 +15134 窫 +15135 獷 +15136 籈 +15137 禘 +15138 瓻 +15139 盓 +15140 丒 +15141 侲 +15142 凟 +15143 匛 +15144 咵 +15145 嘐 +15146 圗 +15147 塃 +15148 奅 +15149 婨 +15150 孍 +15151 岴 +15152 嶦 +15153 廍 +15154 怑 +15155 慐 +15156 扙 +15157 揈 +15158 擡 +15159 旹 +15160 朎 +15161 桬 +15162 欵 +15163 汦 +15164 淓 +15165 滶 +15166 烢 +15167 ㄩ +15168 ﹂ +15169 ¢ +15170 ч +15171 ╅ +15172 ㈤ +15173 侀 +15174 傞 +15175 勯 +15176 呴 +15177 囬 +15178 堥 +15179 婇 +15180 嬮 +15181 岄 +15182 嶉 +15183 庨 +15184 愰 +15185 掗 +15186 曢 +15187 栭 +15188 楅 +15189 橀 +15190 欓 +15191 濋 +15192 熼 +15193 榇 +15194 閌 +15195 閊 +15196 閇 +15197 閧 +15198 椐 +15199 锧 +15200 閈 +15201 閫 +15202 閮 +15203 閯 +15204 閰 +15205 閳 +15206 閴 +15207 閵 +15208 閷 +15209 閸 +15210 閺 +15211 閼 +15212 閽 +15213 閾 +15214 閿 +15215 闁 +15216 闂 +15217 闃 +15218 闄 +15219 闅 +15220 闈 +15221 闉 +15222 ㄉ +15223 ι +15224 ∩ +15225 ┥ +15226 ⑸ +15227 I +15228 伾 +15229 勆 +15230 吷 +15231 喩 +15232 嚿 +15233 埳 +15234 壣 +15235 屔 +15236 嵣 +15237 徤 +15238 惿 +15239 懮 +15240 捝 +15241 撋 +15242 斏 +15243 暽 +15244 柹 +15245 椛 +15246 櫳 +15247 毶 +15248 浬 +15249 溕 +15250 蒦 +15251 蓗 +15252 蓔 +15253 蒧 +15254 蒣 +15255 蒥 +15256 蓒 +15257 葽 +15258 蒤 +15259 蓘 +15260 蓙 +15261 蓚 +15262 蓛 +15263 蓜 +15264 蓞 +15265 蓡 +15266 蓤 +15267 蓧 +15268 蓨 +15269 蓫 +15270 蓭 +15271 蓱 +15272 蓲 +15273 蓳 +15274 蓴 +15275 蓵 +15276 蓶 +15277 蓷 +15278 蓸 +15279 蓹 +15280 蓺 +15281 蓻 +15282 蓽 +15283 蓾 +15284 蔀 +15285 蔁 +15286 ㄨ +15287 ﹁ +15288 ¤ +15289 ц +15290 ╄ +15291 ヨ +15292 佽 +15293 傝 +15294 冭 +15295 勮 +15296 呰 +15297 堣 +15298 夎 +15299 婅 +15300 嬭 +15301 岃 +15302 嶈 +15303 忚 +15304 愯 +15305 戣 +15306 掕 +15307 撹 +15308 旇 +15309 曡 +15310 栬 +15311 楄 +15312 樿 +15313 涜 +15314 濊 +15315 炶 +15316 熻 +15317 鑐 +15318 鑯 +15319 鑭 +15320 鑏 +15321 杩 +15322 璺 +15323 鑋 +15324 鑍 +15325 鑮 +15326 鑌 +15327 鑱 +15328 鑳 +15329 鑴 +15330 鑶 +15331 鑸 +15332 鑹 +15333 鑺 +15334 鑻 +15335 鑿 +15336 钀 +15337 钁 +15338 钂 +15339 钃 +15340 钄 +15341 钖 +15342 钘 +15343 铇 +15344 铓 +15345 铔 +15346 铦 +15347 ㄈ +15348 θ +15349 ∪ +15350 ┤ +15351 ⑷ +15352 H +15353 伻 +15354 勅 +15355 喨 +15356 嚾 +15357 埲 +15358 娙 +15359 屓 +15360 幦 +15361 徣 +15362 惾 +15363 懭 +15364 捜 +15365 暼 +15366 柸 +15367 椚 +15368 樔 +15369 櫲 +15370 浫 +15371 溔 +15372 澣 +15373 炄 +15374 熑 +15375 萡 +15376 萟 +15377 萠 +15378 萞 +15379 葅 +15380 葈 +15381 菮 +15382 葊 +15383 葋 +15384 葌 +15385 葍 +15386 葏 +15387 葐 +15388 葒 +15389 葓 +15390 葔 +15391 葕 +15392 葘 +15393 葝 +15394 葟 +15395 葠 +15396 葢 +15397 葤 +15398 葥 +15399 葧 +15400 葨 +15401 葪 +15402 葮 +15403 葰 +15404 葲 +15405 葴 +15406 葹 +15407 葻 +15408 ﹃ +15409 £ +15410 ш +15411 ╆ +15412 ㈥ +15413 侁 +15414 勱 +15415 呹 +15416 嗞 +15417 囮 +15418 堦 +15419 岅 +15420 嶊 +15421 庩 +15422 愱 +15423 戧 +15424 撽 +15425 旉 +15426 曣 +15427 栮 +15428 楆 +15429 橁 +15430 欔 +15431 濌 +15432 炾 +15433 阘 +15434 阇 +15435 陗 +15436 陓 +15437 阓 +15438 攴 +15439 闧 +15440 陒 +15441 陖 +15442 甓 +15443 戤 +15444 闬 +15445 陙 +15446 陚 +15447 陜 +15448 陠 +15449 陥 +15450 陦 +15451 陫 +15452 陭 +15453 陮 +15454 陱 +15455 陹 +15456 陻 +15457 陼 +15458 陾 +15459 陿 +15460 隀 +15461 隁 +15462 隂 +15463 隃 +15464 隄 +15465 隇 +15466 隉 +15467 ㄊ +15468 κ +15469 ∈ +15470 ┦ +15471 ⑹ +15472 J +15473 伿 +15474 偸 +15475 兪 +15476 勈 +15477 吺 +15478 囀 +15479 埵 +15480 嬍 +15481 屖 +15482 嵤 +15483 幨 +15484 徥 +15485 愂 +15486 懯 +15487 捠 +15488 斒 +15489 暿 +15490 樖 +15491 櫴 +15492 毷 +15493 炇 +15494 蔨 +15495 蕕 +15496 蕓 +15497 蔩 +15498 蔧 +15499 蕒 +15500 蕔 +15501 蔦 +15502 蕘 +15503 蕚 +15504 蕛 +15505 蕜 +15506 蕝 +15507 蕟 +15508 蕠 +15509 蕡 +15510 蕢 +15511 蕥 +15512 蕦 +15513 蕧 +15514 蕬 +15515 蕮 +15516 蕯 +15517 蕰 +15518 蕱 +15519 蕳 +15520 蕷 +15521 蕸 +15522 蕼 +15523 蕽 +15524 蕿 +15525 薀 +15526 ﹄ +15527 ‰ +15528 щ +15529 ╇ +15530 ㈦ +15531 傠 +15532 冸 +15533 勲 +15534 呺 +15535 嗠 +15536 堧 +15537 夒 +15538 婋 +15539 岆 +15540 庪 +15541 愲 +15542 戨 +15543 旊 +15544 曤 +15545 栯 +15546 楇 +15547 橂 +15548 滊 +15549 濍 +15550 炿 +15551 雦 +15552 隷 +15553 氕 +15554 搿 +15555 肟 +15556 敫 +15557 雥 +15558 雧 +15559 攵 +15560 隌 +15561 毪 +15562 隲 +15563 雬 +15564 雭 +15565 雮 +15566 雰 +15567 雴 +15568 雵 +15569 雸 +15570 雺 +15571 雼 +15572 雽 +15573 雿 +15574 霃 +15575 霅 +15576 霊 +15577 霋 +15578 霌 +15579 霐 +15580 霒 +15581 霔 +15582 霗 +15583 霚 +15584 霛 +15585 霝 +15586 霟 +15587 ㄋ +15588 λ +15589 ∷ +15590 ┧ +15591 ⑺ +15592 K +15593 佀 +15594 偹 +15595 兯 +15596 勊 +15597 囁 +15598 埶 +15599 壦 +15600 娝 +15601 嬎 +15602 屗 +15603 嵥 +15604 幩 +15605 徦 +15606 愃 +15607 懰 +15608 捤 +15609 撍 +15610 斔 +15611 曀 +15612 椝 +15613 櫵 +15614 浰 +15615 溗 +15616 澦 +15617 炈 +15618 熕 +15619 薫 +15620 薧 +15621 藒 +15622 藎 +15623 薣 +15624 藑 +15625 薂 +15626 薥 +15627 藔 +15628 藗 +15629 藙 +15630 藚 +15631 藞 +15632 藡 +15633 藢 +15634 藣 +15635 藧 +15636 藪 +15637 藫 +15638 藭 +15639 藮 +15640 藯 +15641 藰 +15642 藱 +15643 藲 +15644 藳 +15645 藵 +15646 藶 +15647 藷 +15648 蒩 +15649 葾 +15650 榱 +15651 萢 +15652 阛 +15653 闍 +15654 蔄 +15655 赆 +15656 赇 +15657 隺 +15658 薃 +15659 脶 +15660 肜 +15661 脞 +15662 锽 +15663 蒪 +15664 葿 +15665 萣 +15666 阞 +15667 蔮 +15668 蔅 +15669 隑 +15670 薭 +15671 薆 +15672 蒫 +15673 蒀 +15674 鑓 +15675 阠 +15676 蔯 +15677 隿 +15678 隒 +15679 薱 +15680 閐 +15681 镈 +15682 蒬 +15683 蒁 +15684 鑔 +15685 萪 +15686 桊 +15687 阣 +15688 闐 +15689 蔰 +15690 蔇 +15691 牮 +15692 雂 +15693 隓 +15694 薲 +15695 薉 +15696 镋 +15697 蒭 +15698 蒃 +15699 萫 +15700 阤 +15701 闑 +15702 蔱 +15703 蔈 +15704 雃 +15705 薳 +15706 肷 +15707 腙 +15708 胨 +15709 蒮 +15710 鑖 +15711 菷 +15712 阥 +15713 闒 +15714 蔲 +15715 蔉 +15716 雈 +15717 隖 +15718 薴 +15719 薋 +15720 蒅 +15721 鑗 +15722 鐶 +15723 萭 +15724 菺 +15725 阦 +15726 闓 +15727 蔳 +15728 蔊 +15729 薵 +15730 镠 +15731 蒱 +15732 蒆 +15733 鐷 +15734 萮 +15735 菻 +15736 阧 +15737 闔 +15738 蔋 +15739 薶 +15740 镮 +15741 蒳 +15742 蒊 +15743 殪 +15744 萯 +15745 阨 +15746 蔍 +15747 晡 +15748 雐 +15749 隝 +15750 胩 +15751 胂 +15752 閖 +15753 蒵 +15754 蒍 +15755 鑚 +15756 鐹 +15757 萰 +15758 菾 +15759 阩 +15760 蔶 +15761 蔎 +15762 隞 +15763 薐 +15764 閗 +15765 镵 +15766 蒶 +15767 蒏 +15768 鑛 +15769 萲 +15770 菿 +15771 阫 +15772 闗 +15773 蔾 +15774 蔏 +15775 隟 +15776 薻 +15777 蒐 +15778 鑜 +15779 鐻 +15780 萳 +15781 萀 +15782 阬 +15783 蔿 +15784 雔 +15785 薼 +15786 薒 +15787 閙 +15788 镸 +15789 蒑 +15790 鐼 +15791 萴 +15792 阭 +15793 蕀 +15794 蔒 +15795 隡 +15796 薽 +15797 薓 +15798 镹 +15799 蒒 +15800 鑞 +15801 鐽 +15802 萅 +15803 阯 +15804 闚 +15805 隢 +15806 薾 +15807 镺 +15808 蒓 +15809 轷 +15810 鐿 +15811 萶 +15812 萇 +15813 柙 +15814 阰 +15815 蔕 +15816 牿 +15817 犋 +15818 雘 +15819 隣 +15820 薿 +15821 薕 +15822 閜 +15823 镻 +15824 蒔 +15825 鑠 +15826 鑀 +15827 萷 +15828 阷 +15829 蕄 +15830 蔖 +15831 隤 +15832 藀 +15833 閝 +15834 镼 +15835 鑡 +15836 鑁 +15837 萹 +15838 萉 +15839 阸 +15840 蕅 +15841 蔘 +15842 雚 +15843 隥 +15844 藂 +15845 薗 +15846 閞 +15847 蓃 +15848 蒖 +15849 鑢 +15850 萺 +15851 阹 +15852 闞 +15853 蕆 +15854 蔙 +15855 隦 +15856 藃 +15857 薘 +15858 镾 +15859 蓅 +15860 蒘 +15861 鑃 +15862 萻 +15863 萐 +15864 阺 +15865 闟 +15866 蕇 +15867 蔛 +15868 藄 +15869 赀 +15870 閠 +15871 蒚 +15872 鑤 +15873 萾 +15874 萒 +15875 阾 +15876 闠 +15877 蕋 +15878 蔜 +15879 隩 +15880 藅 +15881 薚 +15882 閁 +15883 蒛 +15884 辁 +15885 鑅 +15886 萿 +15887 萓 +15888 棼 +15889 椟 +15890 陁 +15891 蔝 +15892 贳 +15893 藆 +15894 薝 +15895 膪 +15896 脎 +15897 胲 +15898 閂 +15899 蓈 +15900 蒝 +15901 葀 +15902 萔 +15903 陃 +15904 蕍 +15905 雟 +15906 藇 +15907 薞 +15908 蒞 +15909 葁 +15910 萕 +15911 陊 +15912 蔠 +15913 雡 +15914 隬 +15915 藈 +15916 薟 +15917 閤 +15918 閄 +15919 蓌 +15920 鑨 +15921 鑈 +15922 葂 +15923 萖 +15924 陎 +15925 闤 +15926 蕏 +15927 蔢 +15928 隭 +15929 藊 +15930 閅 +15931 蒠 +15932 鑩 +15933 鑉 +15934 葃 +15935 萗 +15936 陏 +15937 闥 +15938 蕐 +15939 隮 +15940 藋 +15941 薡 +15942 閦 +15943 蓏 +15944 蒢 +15945 鑪 +15946 鑊 +15947 葄 +15948 萙 +15949 陑 +15950 闦 +15951 蕑 +15952 蔤 +15953 雤 +15954 藌 +15955 薢 +15956 柁 +15957 锠 +15958 葼 +15959 霠 +15960 藸 +15961 磃 +15962 ╢ +15963 鱢 +15964 譮 +15965 癴 +15966 礷 +15967 痜 +15968 璮 +15969 縡 +15970 玣 +15971 籪 +15972 秄 +15973 ゝ +15974 眆 +15975 乫 +15976 俧 +15977 僨 +15978 刦 +15979 唂 +15980 坒 +15981 塮 +15982 媐 +15983 宖 +15984 峟 +15985 廸 +15986 恌 +15987 慺 +15988 抐 +15989 揻 +15990 攆 +15991 杅 +15992 梖 +15993 榝 +15994 檉 +15995 歠 +15996 沠 +15997 漟 +15998 瀎 +15999 焒 +16000 ‵ +16001 譌 +16002 癋 +16003 礔 +16004 疐 +16005 璅 +16006 縁 +16007 稦 +16008 籉 +16009 侳 +16010 凢 +16011 匜 +16012 咶 +16013 嘑 +16014 塅 +16015 奆 +16016 婩 +16017 孎 +16018 岶 +16019 廎 +16020 怓 +16021 慒 +16022 扚 +16023 揊 +16024 擣 +16025 楩 +16026 橣 +16027 欶 +16028 淔 +16029 烣 +16030 ╣ +16031 鱣 +16032 癵 +16033 礸 +16034 痝 +16035 穏 +16036 竒 +16037 玤 +16038 籫 +16039 秅 +16040 ゞ +16041 甮 +16042 乬 +16043 僩 +16044 刧 +16045 唃 +16046 噂 +16047 坓 +16048 塯 +16049 奼 +16050 媑 +16051 廹 +16052 恎 +16053 慻 +16054 抔 +16055 揼 +16056 杇 +16057 梘 +16058 榞 +16059 檊 +16060 漡 +16061 焔 +16062 碐 +16063 ℅ +16064 鱃 +16065 譍 +16066 珿 +16067 癎 +16068 礕 +16069 疓 +16070 縂 +16071 稧 +16072 獹 +16073 籊 +16074 盙 +16075 侴 +16076 僄 +16077 凣 +16078 匞 +16079 嘒 +16080 塆 +16081 奊 +16082 婫 +16083 孏 +16084 岹 +16085 嶨 +16086 廏 +16087 怗 +16088 慓 +16089 扜 +16090 揋 +16091 擥 +16092 桮 +16093 橤 +16094 汫 +16095 淕 +16096 濭 +16097 烥 +16098 磆 +16099 ╤ +16100 鱤 +16101 琱 +16102 癶 +16103 礹 +16104 痟 +16105 穐 +16106 竓 +16107 秇 +16108 ﹉ +16109 県 +16110 乭 +16111 俬 +16112 僪 +16113 卙 +16114 噃 +16115 坔 +16116 塰 +16117 宧 +16118 峢 +16119 巋 +16120 廻 +16121 恏 +16122 慼 +16123 抙 +16124 揾 +16125 攈 +16126 昲 +16127 杊 +16128 梙 +16129 檋 +16130 歨 +16131 瀐 +16132 碒 +16133 ℉ +16134 鱄 +16135 譎 +16136 縃 +16137 稨 +16138 窰 +16139 籋 +16140 禜 +16141 瓾 +16142 盚 +16143 丠 +16144 凥 +16145 匟 +16146 咹 +16147 嘓 +16148 圚 +16149 塇 +16150 孒 +16151 廐 +16152 怘 +16153 慔 +16154 扝 +16155 揌 +16156 擧 +16157 旽 +16158 朒 +16159 楬 +16160 橦 +16161 欻 +16162 汬 +16163 淗 +16164 滺 +16165 磇 +16166 鱥 +16167 譱 +16168 癷 +16169 縤 +16170 竔 +16171 籭 +16172 秈 +16173 ﹊ +16174 甶 +16175 眎 +16176 乮 +16177 俰 +16178 僫 +16179 刬 +16180 卛 +16181 噄 +16182 坕 +16183 奿 +16184 媔 +16185 宨 +16186 巌 +16187 廼 +16188 恑 +16189 慽 +16190 搃 +16191 杋 +16192 梚 +16193 榠 +16194 檌 +16195 瀒 +16196 焛 +16197 碔 +16198 ↖ +16199 鱅 +16200 譏 +16201 琁 +16202 癐 +16203 礗 +16204 疘 +16205 縄 +16206 窱 +16207 禝 +16208 ㊣ +16209 甀 +16210 侷 +16211 僆 +16212 処 +16213 匢 +16214 咺 +16215 圛 +16216 塈 +16217 奍 +16218 岻 +16219 嶪 +16220 廔 +16221 怚 +16222 扞 +16223 揑 +16224 旾 +16225 桰 +16226 橧 +16227 欼 +16228 滻 +16229 烮 +16230 № +16231 ╉ +16232 ㈨ +16233 冺 +16234 勴 +16235 呿 +16236 堩 +16237 夗 +16238 嬳 +16239 岉 +16240 嶍 +16241 庬 +16242 忢 +16243 戫 +16244 掜 +16245 旐 +16246 曧 +16247 楉 +16248 橅 +16249 欗 +16250 氻 +16251 涰 +16252 滍 +16253 濏 +16254 烅 +16255 燀 +16256 泶 +16257 韣 +16258 憝 +16259 慝 +16260 砘 +16261 韂 +16262 韠 +16263 韢 +16264 鞞 +16265 恧 +16266 韁 +16267 肀 +16268 韤 +16269 韥 +16270 韨 +16271 韯 +16272 韰 +16273 韱 +16274 韲 +16275 韷 +16276 韸 +16277 韹 +16278 韺 +16279 韽 +16280 頀 +16281 頄 +16282 頇 +16283 頉 +16284 頋 +16285 頍 +16286 ㄍ +16287 ν +16288 ⊥ +16289 ┩ +16290 ⑼ +16291 M +16292 佂 +16293 偼 +16294 兺 +16295 喭 +16296 壨 +16297 屚 +16298 嵧 +16299 愅 +16300 捦 +16301 撏 +16302 斖 +16303 曂 +16304 柾 +16305 椡 +16306 櫷 +16307 毻 +16308 溚 +16309 澩 +16310 炌 +16311 熗 +16312 蚟 +16313 蛜 +16314 蛗 +16315 蚠 +16316 蚚 +16317 蚞 +16318 蛖 +16319 蛚 +16320 虭 +16321 蚛 +16322 蛡 +16323 蛢 +16324 蛣 +16325 蛥 +16326 蛦 +16327 蛧 +16328 蛨 +16329 蛪 +16330 蛫 +16331 蛬 +16332 蛶 +16333 蛷 +16334 蛼 +16335 蛽 +16336 蛿 +16337 蜁 +16338 蜄 +16339 蜅 +16340 蜋 +16341 蜌 +16342 蜎 +16343 蜏 +16344 蜑 +16345 蜔 +16346 § +16347 ъ +16348 ╈ +16349 ㈧ +16350 侅 +16351 傡 +16352 冹 +16353 呾 +16354 嗢 +16355 囲 +16356 堨 +16357 夓 +16358 婌 +16359 嬱 +16360 岇 +16361 忟 +16362 愳 +16363 旍 +16364 曥 +16365 栰 +16366 楈 +16367 橃 +16368 氺 +16369 涭 +16370 濎 +16371 烄 +16372 熿 +16373 靱 +16374 靯 +16375 靇 +16376 旆 +16377 靃 +16378 靅 +16379 靮 +16380 靲 +16381 靵 +16382 靷 +16383 靸 +16384 靹 +16385 靻 +16386 靽 +16387 靾 +16388 靿 +16389 鞀 +16390 鞁 +16391 鞃 +16392 鞄 +16393 鞆 +16394 鞈 +16395 鞉 +16396 鞊 +16397 鞌 +16398 鞎 +16399 鞐 +16400 鞓 +16401 鞖 +16402 鞗 +16403 鞙 +16404 鞚 +16405 鞛 +16406 鞜 +16407 ㄌ +16408 μ +16409 √ +16410 ┨ +16411 ぬ +16412 ⑻ +16413 L +16414 佁 +16415 偺 +16416 勌 +16417 吿 +16418 壧 +16419 娞 +16420 屘 +16421 嵦 +16422 幪 +16423 徧 +16424 愄 +16425 懱 +16426 捥 +16427 撎 +16428 曁 +16429 柼 +16430 椞 +16431 樚 +16432 櫶 +16433 浱 +16434 溙 +16435 澨 +16436 炋 +16437 熖 +16438 蘞 +16439 蘜 +16440 虀 +16441 蘾 +16442 蘝 +16443 蘙 +16444 蘽 +16445 虂 +16446 虃 +16447 虅 +16448 虆 +16449 虇 +16450 虈 +16451 虉 +16452 虊 +16453 虋 +16454 虌 +16455 虖 +16456 虗 +16457 虘 +16458 虙 +16459 虝 +16460 虠 +16461 虡 +16462 虣 +16463 虥 +16464 虦 +16465 虨 +16466 虩 +16467 ︻ +16468 ☆ +16469 ╊ +16470 ゎ +16471 ㈩ +16472 ヮ +16473 侇 +16474 傤 +16475 冾 +16476 囶 +16477 堫 +16478 夘 +16479 婎 +16480 嬵 +16481 岊 +16482 嶎 +16483 庮 +16484 忣 +16485 愵 +16486 戭 +16487 掝 +16488 擃 +16489 旑 +16490 曨 +16491 橆 +16492 氼 +16493 涱 +16494 濐 +16495 烆 +16496 頯 +16497 瞵 +16498 顋 +16499 頮 +16500 罨 +16501 頪 +16502 頬 +16503 頏 +16504 顐 +16505 顑 +16506 顕 +16507 顖 +16508 顙 +16509 顚 +16510 顜 +16511 顝 +16512 顟 +16513 顠 +16514 顡 +16515 顢 +16516 顣 +16517 顦 +16518 顩 +16519 顬 +16520 顭 +16521 ㄎ +16522 ξ +16523 ∥ +16524 ┪ +16525 ⑽ +16526 N +16527 佄 +16528 兾 +16529 勎 +16530 娢 +16531 幬 +16532 徫 +16533 愇 +16534 懳 +16535 斘 +16536 曃 +16537 栁 +16538 椢 +16539 樜 +16540 毼 +16541 浳 +16542 溛 +16543 炍 +16544 熚 +16545 蝋 +16546 蝆 +16547 蝵 +16548 蝊 +16549 蝅 +16550 蝱 +16551 蝳 +16552 蜙 +16553 蝄 +16554 蝹 +16555 蝺 +16556 蝿 +16557 螀 +16558 螁 +16559 螇 +16560 螉 +16561 螊 +16562 螌 +16563 螎 +16564 螑 +16565 螒 +16566 螔 +16567 螕 +16568 螖 +16569 螘 +16570 螙 +16571 螚 +16572 螛 +16573 螝 +16574 螠 +16575 螡 +16576 螣 +16577 ︼ +16578 ★ +16579 э +16580 ╋ +16581 侊 +16582 傦 +16583 冿 +16584 勶 +16585 咃 +16586 嗭 +16587 堬 +16588 夛 +16589 婏 +16590 嬶 +16591 岋 +16592 嶏 +16593 忥 +16594 愶 +16595 掞 +16596 擄 +16597 旓 +16598 曪 +16599 栵 +16600 楋 +16601 欙 +16602 涳 +16603 滐 +16604 烇 +16605 燂 +16606 锎 +16607 颺 +16608 铷 +16609 飤 +16610 铴 +16611 锏 +16612 颻 +16613 铩 +16614 锓 +16615 铽 +16616 颷 +16617 飡 +16618 飣 +16619 铹 +16620 颸 +16621 飥 +16622 飫 +16623 飬 +16624 飭 +16625 飮 +16626 飱 +16627 飳 +16628 飴 +16629 飵 +16630 飶 +16631 飸 +16632 飺 +16633 餀 +16634 餁 +16635 餂 +16636 餄 +16637 ㄏ +16638 ο +16639 ∠ +16640 ┫ +16641 ⑾ +16642 O +16643 佅 +16644 傁 +16645 兿 +16646 勏 +16647 呄 +16648 喯 +16649 囅 +16650 埾 +16651 壪 +16652 娤 +16653 嬒 +16654 嵪 +16655 幭 +16656 懴 +16657 捪 +16658 斚 +16659 曄 +16660 栂 +16661 椣 +16662 櫹 +16663 毾 +16664 澫 +16665 炏 +16666 熛 +16667 蟕 +16668 蟐 +16669 蟸 +16670 蟔 +16671 蟏 +16672 蟵 +16673 螥 +16674 蟎 +16675 蟺 +16676 蟼 +16677 蟽 +16678 蟿 +16679 蠀 +16680 蠁 +16681 蠆 +16682 蠇 +16683 蠈 +16684 蠉 +16685 蠋 +16686 蠌 +16687 蠎 +16688 蠏 +16689 蠐 +16690 蠒 +16691 蠗 +16692 蠘 +16693 蠚 +16694 蠜 +16695 蠠 +16696 锊 +16697 韆 +16698 鞟 +16699 蚢 +16700 虯 +16701 愍 +16702 砹 +16703 霢 +16704 蘟 +16705 灬 +16706 爨 +16707 炷 +16708 蝍 +16709 蜛 +16710 钆 +16711 颽 +16712 蟖 +16713 螦 +16714 镟 +16715 镥 +16716 镤 +16717 锬 +16718 锩 +16719 铈 +16720 韇 +16721 蚥 +16722 虰 +16723 靊 +16724 霣 +16725 蘠 +16726 藼 +16727 蝏 +16728 蜝 +16729 颾 +16730 蟗 +16731 韈 +16732 鞢 +16733 蚦 +16734 虲 +16735 靋 +16736 霤 +16737 藽 +16738 頲 +16739 蝐 +16740 蜟 +16741 钋 +16742 颿 +16743 顲 +16744 蟘 +16745 螩 +16746 韉 +16747 鞤 +16748 蚫 +16749 虳 +16750 眇 +16751 靌 +16752 霥 +16753 藾 +16754 頳 +16755 蝑 +16756 蜠 +16757 铕 +16758 飀 +16759 螪 +16760 镄 +16761 韊 +16762 鞥 +16763 蚭 +16764 虴 +16765 黹 +16766 靍 +16767 霦 +16768 蘣 +16769 蘀 +16770 頴 +16771 蝒 +16772 蜤 +16773 钌 +16774 飁 +16775 蟚 +16776 锼 +16777 鞦 +16778 蚮 +16779 虵 +16780 靎 +16781 蘤 +16782 蘁 +16783 頵 +16784 蝔 +16785 飂 +16786 螰 +16787 鞧 +16788 蚲 +16789 虶 +16790 靏 +16791 霨 +16792 蘥 +16793 蘂 +16794 頖 +16795 蜧 +16796 飃 +16797 螱 +16798 韍 +16799 蚳 +16800 虷 +16801 靐 +16802 霩 +16803 蘦 +16804 蘃 +16805 蝖 +16806 蜨 +16807 颒 +16808 韎 +16809 鞩 +16810 虸 +16811 眍 +16812 靑 +16813 霫 +16814 蘨 +16815 煳 +16816 蝘 +16817 蜪 +16818 钔 +16819 飅 +16820 蟟 +16821 螴 +16822 锿 +16823 锾 +16824 韏 +16825 鞪 +16826 蚸 +16827 靔 +16828 霬 +16829 蘪 +16830 蝚 +16831 蜫 +16832 螶 +16833 鞬 +16834 蚹 +16835 蚄 +16836 靕 +16837 霮 +16838 蘫 +16839 頺 +16840 頚 +16841 蝛 +16842 蜬 +16843 飇 +16844 颣 +16845 螷 +16846 蚻 +16847 蚅 +16848 靗 +16849 霯 +16850 蜭 +16851 飈 +16852 蟣 +16853 韒 +16854 蚼 +16855 蚆 +16856 靘 +16857 霱 +16858 蘉 +16859 蝝 +16860 蜯 +16861 飉 +16862 颩 +16863 蟤 +16864 螹 +16865 鞱 +16866 蚽 +16867 蚇 +16868 蘮 +16869 頝 +16870 蝞 +16871 蜰 +16872 飊 +16873 颪 +16874 蟦 +16875 螻 +16876 镅 +16877 韔 +16878 鞳 +16879 蚾 +16880 眢 +16881 眚 +16882 霴 +16883 蘯 +16884 煺 +16885 頾 +16886 頞 +16887 蜲 +16888 钪 +16889 钬 +16890 螼 +16891 镎 +16892 韕 +16893 鞵 +16894 蚿 +16895 蚉 +16896 靝 +16897 蘰 +16898 頿 +16899 頟 +16900 蝡 +16901 蜳 +16902 飌 +16903 蟨 +16904 螾 +16905 韖 +16906 蛁 +16907 蚎 +16908 靟 +16909 蝢 +16910 蜵 +16911 飍 +16912 颭 +16913 蟩 +16914 螿 +16915 韗 +16916 鞷 +16917 蛂 +16918 蚏 +16919 靣 +16920 霷 +16921 蘲 +16922 顁 +16923 蜶 +16924 蟫 +16925 蟁 +16926 韘 +16927 鞸 +16928 蛃 +16929 蚐 +16930 靤 +16931 霺 +16932 蘳 +16933 顂 +16934 頢 +16935 蝧 +16936 飐 +16937 蟂 +16938 钸 +16939 韙 +16940 鞹 +16941 蛅 +16942 蚑 +16943 霻 +16944 蘴 +16945 蘐 +16946 頣 +16947 蜹 +16948 颰 +16949 蟭 +16950 蟃 +16951 韚 +16952 鞺 +16953 蛈 +16954 蚒 +16955 睃 +16956 碥 +16957 靧 +16958 霼 +16959 禚 +16960 顄 +16961 蝩 +16962 蜺 +16963 稆 +16964 稃 +16965 韛 +16966 鞻 +16967 蛌 +16968 蚔 +16969 霽 +16970 蘶 +16971 蘓 +16972 蝪 +16973 蜼 +16974 颲 +16975 蟰 +16976 蟅 +16977 蚖 +16978 靪 +16979 霿 +16980 蘷 +16981 蘔 +16982 蝫 +16983 蜽 +16984 蟇 +16985 韝 +16986 鞽 +16987 蛒 +16988 蚗 +16989 靫 +16990 靀 +16991 蘹 +16992 蘕 +16993 顇 +16994 飜 +16995 颴 +16996 韞 +16997 鞾 +16998 蛓 +16999 蚘 +17000 靁 +17001 蘺 +17002 顈 +17003 頨 +17004 蝭 +17005 蝁 +17006 飝 +17007 颵 +17008 蟉 +17009 韟 +17010 鞿 +17011 蛕 +17012 蚙 +17013 靭 +17014 蘻 +17015 顉 +17016 頩 +17017 蝯 +17018 飠 +17019 蟴 +17020 蟌 +17021 铪 +17022 钷 +17023 頎 +17024 蜖 +17025 鞝 +17026 虪 +17027 顮 +17028 螤 +17029 餇 +17030 磈 +17031 ╦ +17032 鱦 +17033 譲 +17034 琷 +17035 癹 +17036 礿 +17037 痡 +17038 璲 +17039 縥 +17040 穓 +17041 竕 +17042 秊 +17043 ﹋ +17044 甹 +17045 眏 +17046 乯 +17047 俲 +17048 僯 +17049 刯 +17050 卝 +17051 唈 +17052 坖 +17053 塲 +17054 妀 +17055 宩 +17056 峧 +17057 廽 +17058 抝 +17059 搄 +17060 攋 +17061 昷 +17062 梛 +17063 檍 +17064 歫 +17065 沯 +17066 渏 +17067 漥 +17068 瀓 +17069 碕 +17070 ↗ +17071 鱆 +17072 譐 +17073 琂 +17074 癑 +17075 礘 +17076 疛 +17077 璊 +17078 稪 +17079 窲 +17080 禞 +17081 ㎎ +17082 盝 +17083 丣 +17084 侸 +17085 僇 +17086 凧 +17087 咼 +17088 嘕 +17089 圝 +17090 塉 +17091 奐 +17092 婮 +17093 孞 +17094 岼 +17095 嶫 +17096 廕 +17097 怞 +17098 慗 +17099 扟 +17100 揓 +17101 擩 +17102 朖 +17103 桱 +17104 楯 +17105 橨 +17106 淛 +17107 滼 +17108 濲 +17109 烰 +17110 磌 +17111 ╧ +17112 譳 +17113 琸 +17114 祂 +17115 痥 +17116 縦 +17117 穔 +17118 竗 +17119 籯 +17120 秌 +17121 ﹌ +17122 甼 +17123 眐 +17124 乲 +17125 俴 +17126 僰 +17127 刱 +17128 卥 +17129 唊 +17130 噆 +17131 坘 +17132 塳 +17133 妅 +17134 峩 +17135 巏 +17136 弅 +17137 恔 +17138 慿 +17139 択 +17140 搆 +17141 杒 +17142 梜 +17143 榢 +17144 檏 +17145 歬 +17146 沰 +17147 渒 +17148 漦 +17149 瀔 +17150 焝 +17151 ↘ +17152 琄 +17153 疜 +17154 璌 +17155 縆 +17156 稫 +17157 窴 +17158 獽 +17159 籏 +17160 ㎏ +17161 甂 +17162 侹 +17163 僈 +17164 凨 +17165 匥 +17166 咾 +17167 嘖 +17168 奒 +17169 婯 +17170 孠 +17171 岾 +17172 嶬 +17173 廗 +17174 怟 +17175 扠 +17176 揔 +17177 擪 +17178 昁 +17179 朘 +17180 楰 +17181 欿 +17182 汯 +17183 淜 +17184 滽 +17185 濳 +17186 烱 +17187 磍 +17188 ╨ +17189 鱨 +17190 琹 +17191 璴 +17192 縧 +17193 竘 +17194 籰 +17195 秎 +17196 ﹍ +17197 甽 +17198 眑 +17199 乴 +17200 俵 +17201 刲 +17202 卨 +17203 唋 +17204 噇 +17205 坙 +17206 塴 +17207 妉 +17208 宭 +17209 峫 +17210 巐 +17211 弆 +17212 恖 +17213 憀 +17214 抣 +17215 搇 +17216 昹 +17217 杔 +17218 榣 +17219 檒 +17220 歭 +17221 沴 +17222 渓 +17223 漧 +17224 焞 +17225 碙 +17226 ↙ +17227 譒 +17228 癓 +17229 礚 +17230 疞 +17231 璍 +17232 稬 +17233 獿 +17234 籐 +17235 ㎜ +17236 盠 +17237 丩 +17238 侺 +17239 僉 +17240 匧 +17241 哃 +17242 圠 +17243 塋 +17244 婰 +17245 孡 +17246 峀 +17247 嶭 +17248 廘 +17249 怢 +17250 慙 +17251 扡 +17252 揕 +17253 擫 +17254 朙 +17255 桳 +17256 楲 +17257 橪 +17258 歀 +17259 汱 +17260 淟 +17261 濴 +17262 烲 +17263 磎 +17264 ╩ +17265 鱩 +17266 譵 +17267 癿 +17268 祄 +17269 痬 +17270 璵 +17271 穖 +17272 竚 +17273 籱 +17274 秏 +17275 ﹎ +17276 甿 +17277 眒 +17278 乵 +17279 僲 +17280 刴 +17281 卪 +17282 唌 +17283 噈 +17284 坢 +17285 妋 +17286 媘 +17287 峬 +17288 巑 +17289 恗 +17290 抦 +17291 搈 +17292 攎 +17293 梞 +17294 榤 +17295 檓 +17296 歮 +17297 沵 +17298 漨 +17299 瀖 +17300 焟 +17301 譓 +17302 琈 +17303 癕 +17304 礛 +17305 疢 +17306 璏 +17307 縈 +17308 稭 +17309 窶 +17310 玀 +17311 籑 +17312 ㎝ +17313 甅 +17314 丮 +17315 侻 +17316 匨 +17317 哅 +17318 嘙 +17319 圡 +17320 塎 +17321 奙 +17322 孧 +17323 峂 +17324 嶮 +17325 怣 +17326 扢 +17327 揗 +17328 昅 +17329 朚 +17330 桵 +17331 楳 +17332 歁 +17333 汳 +17334 淢 +17335 濵 +17336 烳 +17337 ╪ +17338 譶 +17339 皀 +17340 痭 +17341 璶 +17342 縩 +17343 穘 +17344 竛 +17345 秐 +17346 畁 +17347 乶 +17348 僴 +17349 刵 +17350 唍 +17351 噉 +17352 塶 +17353 妌 +17354 媙 +17355 宯 +17356 峮 +17357 巒 +17358 弉 +17359 恘 +17360 抧 +17361 搉 +17362 昻 +17363 杗 +17364 榥 +17365 歯 +17366 沶 +17367 渘 +17368 焠 +17369 碞 +17370 ∟ +17371 鱊 +17372 譔 +17373 琋 +17374 癗 +17375 疦 +17376 璑 +17377 窷 +17378 籒 +17379 禢 +17380 ㎞ +17381 甆 +17382 丯 +17383 侼 +17384 凬 +17385 匩 +17386 哊 +17387 圢 +17388 塏 +17389 奛 +17390 婲 +17391 孨 +17392 峃 +17393 嶯 +17394 怤 +17395 慛 +17396 扤 +17397 揘 +17398 擭 +17399 朜 +17400 桸 +17401 楴 +17402 歂 +17403 汵 +17404 淣 +17405 漀 +17406 濶 +17407 ︸ +17408 ● +17409 ゑ +17410 Ⅰ +17411 ヱ +17412 凂 +17413 咇 +17414 嗰 +17415 囻 +17416 堮 +17417 夞 +17418 婑 +17419 嬹 +17420 岏 +17421 嶑 +17422 庱 +17423 忨 +17424 戱 +17425 旕 +17426 栺 +17427 楍 +17428 橊 +17429 欛 +17430 汃 +17431 涶 +17432 滖 +17433 燅 +17434 馺 +17435 馸 +17436 駘 +17437 瘳 +17438 駖 +17439 馹 +17440 馵 +17441 馌 +17442 駞 +17443 駢 +17444 駥 +17445 駦 +17446 駨 +17447 駩 +17448 駪 +17449 駬 +17450 駮 +17451 駰 +17452 駴 +17453 駵 +17454 駶 +17455 駸 +17456 ㄑ +17457 ρ +17458 ⊙ +17459 ┭ +17460 ⒀ +17461 Q +17462 パ +17463 傃 +17464 冄 +17465 勓 +17466 呇 +17467 囇 +17468 堁 +17469 娧 +17470 嬔 +17471 屟 +17472 嵮 +17473 幯 +17474 徰 +17475 愌 +17476 捬 +17477 撗 +17478 栄 +17479 椦 +17480 樠 +17481 氀 +17482 浹 +17483 溠 +17484 澭 +17485 裛 +17486 裗 +17487 褈 +17488 裑 +17489 裖 +17490 褅 +17491 袬 +17492 裓 +17493 褉 +17494 褋 +17495 褌 +17496 褍 +17497 褎 +17498 褏 +17499 褑 +17500 褔 +17501 褖 +17502 褗 +17503 褘 +17504 褜 +17505 褝 +17506 褞 +17507 褟 +17508 褤 +17509 褧 +17510 褨 +17511 褩 +17512 褬 +17513 褭 +17514 褱 +17515 褳 +17516 褵 +17517 窆 +17518 馎 +17519 袮 +17520 窳 +17521 衤 +17522 袯 +17523 馽 +17524 馛 +17525 裞 +17526 裠 +17527 袲 +17528 馿 +17529 馝 +17530 袳 +17531 耖 +17532 耔 +17533 耠 +17534 馞 +17535 裦 +17536 袴 +17537 馟 +17538 裧 +17539 袵 +17540 駂 +17541 馠 +17542 裩 +17543 袶 +17544 駃 +17545 馡 +17546 裪 +17547 袸 +17548 耥 +17549 耢 +17550 裉 +17551 馢 +17552 袹 +17553 駅 +17554 馣 +17555 裬 +17556 袺 +17557 駆 +17558 馤 +17559 裭 +17560 袻 +17561 駇 +17562 馦 +17563 裮 +17564 袽 +17565 駈 +17566 馧 +17567 裯 +17568 袾 +17569 駉 +17570 袿 +17571 裼 +17572 裵 +17573 裀 +17574 駋 +17575 馫 +17576 裶 +17577 裃 +17578 駌 +17579 裷 +17580 裄 +17581 裺 +17582 裇 +17583 駎 +17584 裻 +17585 駏 +17586 馯 +17587 裿 +17588 駑 +17589 褀 +17590 裌 +17591 褁 +17592 裍 +17593 駓 +17594 褃 +17595 駔 +17596 褄 +17597 裐 +17598 駹 +17599 褷 +17600 磑 +17601 ╫ +17602 鱫 +17603 琽 +17604 皁 +17605 祇 +17606 痮 +17607 璷 +17608 穙 +17609 玱 +17610 籵 +17611 秓 +17612 ﹐ +17613 畂 +17614 眔 +17615 乷 +17616 俹 +17617 僶 +17618 刼 +17619 卭 +17620 坥 +17621 塷 +17622 妎 +17623 宱 +17624 巓 +17625 憃 +17626 抩 +17627 搊 +17628 攐 +17629 杘 +17630 榦 +17631 歰 +17632 沷 +17633 漮 +17634 碠 +17635 ∣ +17636 譕 +17637 琌 +17638 癘 +17639 礝 +17640 疧 +17641 璒 +17642 縊 +17643 稯 +17644 玂 +17645 禣 +17646 丱 +17647 侽 +17648 凮 +17649 匫 +17650 哋 +17651 圤 +17652 塐 +17653 奜 +17654 峅 +17655 嶰 +17656 廜 +17657 怬 +17658 扥 +17659 揙 +17660 擮 +17661 昈 +17662 朞 +17663 桹 +17664 橭 +17665 歄 +17666 汷 +17667 淥 +17668 濷 +17669 烵 +17670 骱 +17671 『 +17672 Ш +17673 ┖ +17674 ⒑ +17675 : +17676 伜 +17677 偤 +17678 吅 +17679 喓 +17680 姾 +17681 嫼 +17682 尯 +17683 嵑 +17684 幒 +17685 徍 +17686 懞 +17687 捄 +17688 摵 +17689 柡 +17690 櫤 +17691 毢 +17692 澓 +17693 灪 +17694 篳 +17695 篰 +17696 簙 +17697 簗 +17698 篲 +17699 篬 +17700 篯 +17701 簘 +17702 篅 +17703 篭 +17704 簚 +17705 簛 +17706 簜 +17707 簝 +17708 簣 +17709 簤 +17710 簥 +17711 簨 +17712 簩 +17713 簬 +17714 簭 +17715 簮 +17716 簯 +17717 簰 +17718 簱 +17719 簲 +17720 簳 +17721 簴 +17722 簵 +17723 簶 +17724 簹 +17725 簺 +17726 簻 +17727 簼 +17728 ◇ +17729 侒 +17730 傮 +17731 凅 +17732 勼 +17733 咉 +17734 圀 +17735 夡 +17736 婓 +17737 嬻 +17738 岓 +17739 庴 +17740 忬 +17741 愺 +17742 戵 +17743 掦 +17744 擉 +17745 旙 +17746 曮 +17747 栿 +17748 楏 +17749 橌 +17750 欝 +17751 汅 +17752 涹 +17753 滙 +17754 濗 +17755 烍 +17756 燇 +17757 骭 +17758 蟓 +17759 骩 +17760 骫 +17761 髛 +17762 螫 +17763 髝 +17764 髠 +17765 髢 +17766 髤 +17767 髥 +17768 髧 +17769 髨 +17770 髩 +17771 髬 +17772 髱 +17773 髲 +17774 髳 +17775 髵 +17776 髶 +17777 髸 +17778 髺 +17779 髼 +17780 髽 +17781 髾 +17782 髿 +17783 鬀 +17784 鬂 +17785 鬅 +17786 ㄓ +17787 τ +17788 ∮ +17789 ┯ +17790 ⒂ +17791 S +17792 佊 +17793 傆 +17794 冇 +17795 呌 +17796 囉 +17797 堄 +17798 娪 +17799 嵱 +17800 幱 +17801 徲 +17802 懹 +17803 撚 +17804 斢 +17805 栍 +17806 椨 +17807 櫽 +17808 氂 +17809 浻 +17810 溣 +17811 澯 +17812 炗 +17813 熡 +17814 觍 +17815 觺 +17816 觃 +17817 覿 +17818 觷 +17819 觹 +17820 觻 +17821 觽 +17822 觾 +17823 觿 +17824 訁 +17825 訃 +17826 訄 +17827 訆 +17828 訉 +17829 訋 +17830 訌 +17831 訍 +17832 訐 +17833 訒 +17834 訔 +17835 訖 +17836 託 +17837 訛 +17838 訜 +17839 ︱ +17840 ◎ +17841 Ⅱ +17842 ヲ +17843 侐 +17844 凃 +17845 咈 +17846 嗱 +17847 囼 +17848 婒 +17849 嬺 +17850 庲 +17851 忩 +17852 愹 +17853 掤 +17854 擈 +17855 楎 +17856 欜 +17857 汄 +17858 涷 +17859 濖 +17860 烌 +17861 騸 +17862 騶 +17863 騕 +17864 騗 +17865 騵 +17866 虍 +17867 駺 +17868 騖 +17869 騹 +17870 騺 +17871 騻 +17872 騼 +17873 騿 +17874 驂 +17875 驄 +17876 驆 +17877 驇 +17878 驉 +17879 驌 +17880 驑 +17881 驒 +17882 驓 +17883 驔 +17884 驖 +17885 驘 +17886 ㄒ +17887 σ +17888 ∫ +17889 б +17890 ┮ +17891 ⒁ +17892 R +17893 佉 +17894 勔 +17895 囈 +17896 壱 +17897 娨 +17898 嬕 +17899 屢 +17900 嵰 +17901 幰 +17902 徱 +17903 愐 +17904 撘 +17905 斠 +17906 栆 +17907 椧 +17908 樢 +17909 櫼 +17910 氁 +17911 浺 +17912 溡 +17913 澮 +17914 炓 +17915 襚 +17916 襘 +17917 襼 +17918 襹 +17919 襙 +17920 襕 +17921 襗 +17922 襺 +17923 褸 +17924 襽 +17925 襾 +17926 覀 +17927 覂 +17928 覅 +17929 覇 +17930 覈 +17931 覉 +17932 覊 +17933 覌 +17934 覎 +17935 覐 +17936 覑 +17937 覒 +17938 覔 +17939 覕 +17940 覗 +17941 覘 +17942 覙 +17943 覛 +17944 覜 +17945 覝 +17946 覞 +17947 覟 +17948 覠 +17949 ︳ +17950 ◆ +17951 Ⅳ +17952 ヴ +17953 侓 +17954 勽 +17955 咊 +17956 嗶 +17957 圁 +17958 堲 +17959 婔 +17960 嬼 +17961 岕 +17962 嶔 +17963 庺 +17964 忯 +17965 愻 +17966 桇 +17967 楐 +17968 橍 +17969 欞 +17970 涺 +17971 鬬 +17972 鬪 +17973 糇 +17974 舭 +17975 鬫 +17976 舡 +17977 鬩 +17978 魗 +17979 魙 +17980 鬇 +17981 簦 +17982 鬨 +17983 舯 +17984 魛 +17985 魜 +17986 魝 +17987 魞 +17988 魠 +17989 魡 +17990 魢 +17991 魣 +17992 魤 +17993 魥 +17994 魦 +17995 魧 +17996 魨 +17997 魩 +17998 魪 +17999 魫 +18000 魬 +18001 魭 +18002 魮 +18003 魰 +18004 魲 +18005 魳 +18006 魴 +18007 魶 +18008 魸 +18009 魹 +18010 魺 +18011 ㄔ +18012 髟 +18013 υ +18014 ≡ +18015 г +18016 ┰ +18017 ⒃ +18018 T +18019 佋 +18020 傇 +18021 勗 +18022 呍 +18023 囋 +18024 壴 +18025 娫 +18026 屧 +18027 嵲 +18028 幵 +18029 愒 +18030 栐 +18031 椩 +18032 樤 +18033 櫾 +18034 氃 +18035 浽 +18036 溤 +18037 澰 +18038 熢 +18039 訿 +18040 詜 +18041 訽 +18042 訹 +18043 訞 +18044 詟 +18045 詤 +18046 詥 +18047 詧 +18048 詨 +18049 詪 +18050 詫 +18051 詬 +18052 詯 +18053 詴 +18054 詵 +18055 詶 +18056 詷 +18057 詸 +18058 詺 +18059 詻 +18060 詾 +18061 詿 +18062 ■ +18063 Ⅵ +18064 ヶ +18065 傱 +18066 咑 +18067 嗹 +18068 圅 +18069 夦 +18070 嬾 +18071 忲 +18072 愽 +18073 戹 +18074 掱 +18075 旜 +18076 曵 +18077 桍 +18078 橏 +18079 濚 +18080 烐 +18081 鯺 +18082 鰚 +18083 鯹 +18084 鰗 +18085 鰙 +18086 觯 +18087 鯸 +18088 鰛 +18089 鰜 +18090 鰝 +18091 鰞 +18092 鰠 +18093 鰡 +18094 鰢 +18095 鰦 +18096 鰧 +18097 鰨 +18098 鰪 +18099 鰮 +18100 鰯 +18101 鰰 +18102 鰳 +18103 鰴 +18104 鰵 +18105 鰷 +18106 鰹 +18107 鰺 +18108 ㄖ +18109 χ +18110 ≈ +18111 ┲ +18112 ⒅ +18113 V +18114 佒 +18115 冎 +18116 勚 +18117 呏 +18118 堉 +18119 屩 +18120 嵵 +18121 徶 +18122 捴 +18123 斨 +18124 栔 +18125 椫 +18126 樦 +18127 欀 +18128 浿 +18129 溨 +18130 澲 +18131 炛 +18132 熤 +18133 謃 +18134 謁 +18135 謢 +18136 謤 +18137 謥 +18138 謧 +18139 謩 +18140 謪 +18141 謮 +18142 謯 +18143 謰 +18144 謱 +18145 謵 +18146 謶 +18147 謷 +18148 謸 +18149 謺 +18150 謻 +18151 謼 +18152 謽 +18153 謾 +18154 謿 +18155 譀 +18156 譁 +18157 譂 +18158 譃 +18159 譄 +18160 黪 +18161 ︴ +18162 □ +18163 Ⅴ +18164 ヵ +18165 傰 +18166 匁 +18167 咍 +18168 嗸 +18169 圂 +18170 堳 +18171 夣 +18172 嬽 +18173 岝 +18174 嶕 +18175 庻 +18176 愼 +18177 掯 +18178 旛 +18179 桋 +18180 楑 +18181 橎 +18182 欟 +18183 汋 +18184 涻 +18185 滜 +18186 鮚 +18187 酲 +18188 鮺 +18189 鮸 +18190 鮗 +18191 鮙 +18192 鮷 +18193 鮹 +18194 酾 +18195 醵 +18196 魼 +18197 鮘 +18198 鮻 +18199 鮽 +18200 鮿 +18201 鯀 +18202 鯁 +18203 鯃 +18204 鯄 +18205 鯆 +18206 鯈 +18207 鯋 +18208 鯍 +18209 鯎 +18210 鯏 +18211 鯐 +18212 鯑 +18213 鯒 +18214 鯓 +18215 鯕 +18216 鯗 +18217 鯙 +18218 鯚 +18219 ㄕ +18220 φ +18221 ≌ +18222 д +18223 ┱ +18224 ⒄ +18225 U +18226 佌 +18227 傉 +18228 冋 +18229 呎 +18230 喺 +18231 囌 +18232 堈 +18233 壵 +18234 娬 +18235 嬚 +18236 屨 +18237 嵳 +18238 幷 +18239 徴 +18240 愓 +18241 懻 +18242 捳 +18243 撜 +18244 斦 +18245 樥 +18246 氄 +18247 浾 +18248 溦 +18249 炚 +18250 熣 +18251 諂 +18252 諀 +18253 誟 +18254 誁 +18255 諃 +18256 諄 +18257 諅 +18258 諆 +18259 諉 +18260 諌 +18261 諍 +18262 諎 +18263 諑 +18264 諓 +18265 諔 +18266 諕 +18267 諗 +18268 諘 +18269 諙 +18270 諛 +18271 諝 +18272 諞 +18273 諟 +18274 諠 +18275 諡 +18276 諢 +18277 ▲ +18278 Ⅷ +18279 侙 +18280 傴 +18281 凐 +18282 匄 +18283 嗻 +18284 堷 +18285 夬 +18286 婙 +18287 孁 +18288 嶘 +18289 庿 +18290 忴 +18291 慀 +18292 掵 +18293 擑 +18294 桒 +18295 楕 +18296 橒 +18297 欨 +18298 涾 +18299 滧 +18300 濜 +18301 烒 +18302 燌 +18303 鴡 +18304 鴟 +18305 鳾 +18306 鴀 +18307 鴞 +18308 鴠 +18309 鳣 +18310 鴢 +18311 鴤 +18312 鴥 +18313 鴫 +18314 鴬 +18315 鴰 +18316 鴱 +18317 鴴 +18318 鴶 +18319 鴸 +18320 鴹 +18321 鴽 +18322 鴾 +18323 鵀 +18324 鵁 +18325 ㄘ +18326 ∝ +18327 ж +18328 ┴ +18329 X +18330 佖 +18331 傌 +18332 冐 +18333 勜 +18334 呚 +18335 嗀 +18336 囏 +18337 娯 +18338 嬝 +18339 嵷 +18340 庁 +18341 捸 +18342 撠 +18343 曍 +18344 栘 +18345 権 +18346 欂 +18347 氊 +18348 涁 +18349 澵 +18350 豟 +18351 豝 +18352 貇 +18353 貄 +18354 豞 +18355 丿 +18356 豙 +18357 豜 +18358 貃 +18359 貆 +18360 谸 +18361 丌 +18362 豛 +18363 乇 +18364 貎 +18365 貏 +18366 貑 +18367 貒 +18368 貕 +18369 貗 +18370 貙 +18371 貚 +18372 貛 +18373 貜 +18374 貟 +18375 貣 +18376 貤 +18377 貥 +18378 丶 +18379 篴 +18380 篈 +18381 觓 +18382 覣 +18383 竽 +18384 騛 +18385 褹 +18386 鬭 +18387 鬉 +18388 舄 +18389 鯝 +18390 謅 +18391 諥 +18392 鮝 +18393 魽 +18394 誂 +18395 酹 +18396 鴄 +18397 鳤 +18398 豠 +18399 谹 +18400 劐 +18401 羝 +18402 銎 +18403 劓 +18404 篵 +18405 骲 +18406 驜 +18407 觔 +18408 覤 +18409 騜 +18410 鬮 +18411 鬊 +18412 鯾 +18413 鮞 +18414 誃 +18415 鴅 +18416 谺 +18417 篶 +18418 篊 +18419 骳 +18420 觕 +18421 覥 +18422 騝 +18423 襝 +18424 鬰 +18425 鬋 +18426 訡 +18427 鯿 +18428 鯟 +18429 謈 +18430 鲧 +18431 魿 +18432 誧 +18433 誄 +18434 鴆 +18435 鳦 +18436 谻 +18437 篸 +18438 篋 +18439 骴 +18440 驞 +18441 觗 +18442 鬌 +18443 詃 +18444 鯠 +18445 謉 +18446 鮠 +18447 躔 +18448 豥 +18449 匦 +18450 篹 +18451 篍 +18452 骵 +18453 觘 +18454 騟 +18455 襡 +18456 褽 +18457 鬳 +18458 詄 +18459 絷 +18460 鰁 +18461 鋈 +18462 鮡 +18463 鮁 +18464 誩 +18465 誆 +18466 鴈 +18467 豦 +18468 谽 +18469 厣 +18470 骹 +18471 觙 +18472 騠 +18473 騀 +18474 襢 +18475 褾 +18476 鬴 +18477 鬎 +18478 詅 +18479 訤 +18480 鰂 +18481 鯢 +18482 謋 +18483 諪 +18484 谾 +18485 篻 +18486 篏 +18487 骻 +18488 驡 +18489 觛 +18490 覩 +18491 騡 +18492 襣 +18493 褿 +18494 鬵 +18495 鬐 +18496 鰃 +18497 謌 +18498 諫 +18499 鮣 +18500 鮃 +18501 鴊 +18502 鳪 +18503 篽 +18504 篐 +18505 骽 +18506 觝 +18507 騂 +18508 襤 +18509 襀 +18510 鬶 +18511 鬑 +18512 詇 +18513 訦 +18514 鰄 +18515 謍 +18516 鮤 +18517 鮄 +18518 誋 +18519 豩 +18520 豀 +18521 篿 +18522 篒 +18523 骾 +18524 驣 +18525 觟 +18526 騣 +18527 騃 +18528 襥 +18529 襂 +18530 鬷 +18531 鬒 +18532 詉 +18533 訧 +18534 敉 +18535 纛 +18536 鰅 +18537 鯥 +18538 鐾 +18539 鮅 +18540 蹯 +18541 鴌 +18542 鳬 +18543 豂 +18544 篔 +18545 骿 +18546 騤 +18547 騄 +18548 襧 +18549 襃 +18550 訨 +18551 鰆 +18552 鯦 +18553 謏 +18554 鮆 +18555 誮 +18556 鴍 +18557 鳭 +18558 豭 +18559 豃 +18560 篕 +18561 髃 +18562 驥 +18563 觡 +18564 覭 +18565 騅 +18566 襅 +18567 鬹 +18568 鬕 +18569 詋 +18570 訩 +18571 鰇 +18572 諯 +18573 鮧 +18574 鮇 +18575 誎 +18576 鴎 +18577 鳮 +18578 豮 +18579 豄 +18580 簂 +18581 篖 +18582 驦 +18583 騦 +18584 騆 +18585 襆 +18586 鬺 +18587 鬖 +18588 詌 +18589 謑 +18590 諰 +18591 誏 +18592 鴏 +18593 豯 +18594 簃 +18595 髆 +18596 驧 +18597 觤 +18598 騧 +18599 鬽 +18600 鬗 +18601 詍 +18602 誱 +18603 誐 +18604 鴐 +18605 豰 +18606 簄 +18607 篘 +18608 髇 +18609 觧 +18610 覰 +18611 篑 +18612 笱 +18613 騨 +18614 騈 +18615 襫 +18616 襈 +18617 鬾 +18618 詎 +18619 訬 +18620 鰊 +18621 謓 +18622 諲 +18623 鮊 +18624 誑 +18625 鴑 +18626 鳱 +18627 豱 +18628 篛 +18629 驩 +18630 篚 +18631 騩 +18632 騉 +18633 襬 +18634 鬿 +18635 鬙 +18636 詏 +18637 艉 +18638 鰋 +18639 鯫 +18640 謔 +18641 諳 +18642 誳 +18643 誒 +18644 鹾 +18645 躜 +18646 鳲 +18647 豲 +18648 刂 +18649 簆 +18650 篜 +18651 觩 +18652 騪 +18653 騊 +18654 襭 +18655 魀 +18656 訮 +18657 鰌 +18658 鯬 +18659 謕 +18660 諴 +18661 鮌 +18662 誴 +18663 誔 +18664 鴓 +18665 豍 +18666 簈 +18667 篞 +18668 髊 +18669 觪 +18670 騋 +18671 襮 +18672 襋 +18673 鬛 +18674 詑 +18675 訯 +18676 鯭 +18677 諵 +18678 誵 +18679 豵 +18680 簉 +18681 篟 +18682 髍 +18683 驲 +18684 觬 +18685 覴 +18686 騬 +18687 襌 +18688 魊 +18689 詒 +18690 訰 +18691 鯮 +18692 諶 +18693 鮎 +18694 誶 +18695 誖 +18696 鳵 +18697 豶 +18698 簊 +18699 篠 +18700 觭 +18701 覵 +18702 騭 +18703 騍 +18704 襰 +18705 襍 +18706 詓 +18707 鰏 +18708 鮯 +18709 鮏 +18710 誷 +18711 豷 +18712 簍 +18713 篢 +18714 觮 +18715 襎 +18716 魌 +18717 誸 +18718 豻 +18719 簎 +18720 髐 +18721 骍 +18722 覷 +18723 簏 +18724 鬠 +18725 糈 +18726 鯱 +18727 諹 +18728 鮱 +18729 踣 +18730 鴘 +18731 鳸 +18732 豼 +18733 豒 +18734 刳 +18735 簐 +18736 骎 +18737 騐 +18738 襳 +18739 襐 +18740 魐 +18741 鬡 +18742 詖 +18743 鰒 +18744 鮲 +18745 誚 +18746 鴙 +18747 豽 +18748 豓 +18749 簑 +18750 篧 +18751 骔 +18752 觲 +18753 覹 +18754 騱 +18755 騑 +18756 襴 +18757 襑 +18758 魒 +18759 鰓 +18760 謜 +18761 鮳 +18762 鮓 +18763 鴚 +18764 鳺 +18765 豾 +18766 簒 +18767 篨 +18768 騲 +18769 騒 +18770 襵 +18771 襒 +18772 魓 +18773 鬤 +18774 詘 +18775 鰔 +18776 鯴 +18777 鮴 +18778 鮔 +18779 誜 +18780 鳻 +18781 豿 +18782 骙 +18783 觵 +18784 覻 +18785 簖 +18786 魕 +18787 訷 +18788 鰕 +18789 鯵 +18790 諽 +18791 鮕 +18792 誽 +18793 鴜 +18794 鳼 +18795 貀 +18796 豗 +18797 簔 +18798 篫 +18799 髗 +18800 觶 +18801 覼 +18802 騔 +18803 魖 +18804 鬦 +18805 訸 +18806 謟 +18807 鮶 +18808 鮖 +18809 誾 +18810 貁 +18811 豘 +18812 酤 +18813 鳋 +18814 觜 +18815 籂 +18816 覡 +18817 魻 +18818 誀 +18819 譅 +18820 鵂 +18821 貭 +18822 磒 +18823 ╬ +18824 譸 +18825 琾 +18826 皃 +18827 祊 +18828 痯 +18829 璸 +18830 穚 +18831 竝 +18832 玴 +18833 籶 +18834 秔 +18835 ﹑ +18836 畃 +18837 眕 +18838 俻 +18839 僷 +18840 刾 +18841 唒 +18842 噋 +18843 坧 +18844 塸 +18845 妏 +18846 媝 +18847 宲 +18848 峱 +18849 弍 +18850 恜 +18851 憄 +18852 抪 +18853 昿 +18854 杙 +18855 檖 +18856 歱 +18857 漰 +18858 瀙 +18859 焢 +18860 碢 +18861 ≒ +18862 鱌 +18863 譖 +18864 癙 +18865 礟 +18866 疨 +18867 璓 +18868 縋 +18869 稰 +18870 窹 +18871 玃 +18872 籔 +18873 禤 +18874 ㏄ +18875 丳 +18876 侾 +18877 働 +18878 匬 +18879 哖 +18880 嘝 +18881 圥 +18882 塒 +18883 奝 +18884 婸 +18885 孭 +18886 峆 +18887 嶱 +18888 怭 +18889 慞 +18890 扨 +18891 擯 +18892 朠 +18893 桺 +18894 楶 +18895 歅 +18896 汸 +18897 淧 +18898 漃 +18899 濸 +18900 烶 +18901 磓 +18902 譹 +18903 皅 +18904 祋 +18905 痲 +18906 璹 +18907 縬 +18908 穛 +18909 竡 +18910 玵 +18911 籷 +18912 秖 +18913 ﹒ +18914 畄 +18915 眖 +18916 乹 +18917 俼 +18918 僸 +18919 剄 +18920 卶 +18921 唓 +18922 噏 +18923 坬 +18924 塹 +18925 妐 +18926 宷 +18927 峲 +18928 巕 +18929 恞 +18930 憅 +18931 抭 +18932 搎 +18933 攓 +18934 晀 +18935 杚 +18936 榪 +18937 檘 +18938 泀 +18939 渜 +18940 瀜 +18941 焣 +18942 碤 +18943 ≦ +18944 鱍 +18945 癚 +18946 礠 +18947 疩 +18948 璔 +18949 縌 +18950 玅 +18951 籕 +18952 ㏎ +18953 盦 +18954 丵 +18955 俀 +18956 僎 +18957 凲 +18958 匭 +18959 哘 +18960 嘠 +18961 圦 +18962 塓 +18963 奞 +18964 婹 +18965 孮 +18966 峇 +18967 嶲 +18968 廞 +18969 怮 +18970 扱 +18971 擰 +18972 昋 +18973 朡 +18974 桻 +18975 楺 +18976 歈 +18977 漄 +18978 濹 +18979 烸 +18980 磖 +18981 ╮ +18982 鱮 +18983 譺 +18984 皉 +18985 祌 +18986 璻 +18987 縭 +18988 穜 +18989 竢 +18990 玶 +18991 籸 +18992 秗 +18993 ﹔ +18994 畆 +18995 眗 +18996 乺 +18997 俽 +18998 剅 +18999 卹 +19000 唕 +19001 坮 +19002 塺 +19003 妑 +19004 媟 +19005 宺 +19006 弐 +19007 恟 +19008 憆 +19009 抮 +19010 搑 +19011 杛 +19012 梤 +19013 榬 +19014 檙 +19015 渞 +19016 漴 +19017 焤 +19018 碦 +19019 ≧ +19020 鱎 +19021 琑 +19022 癛 +19023 礡 +19024 疪 +19025 稲 +19026 窻 +19027 玆 +19028 籖 +19029 ㏑ +19030 俁 +19031 僐 +19032 凴 +19033 哛 +19034 嘡 +19035 圧 +19036 塕 +19037 奟 +19038 婻 +19039 峈 +19040 嶳 +19041 怰 +19042 慠 +19043 扲 +19044 揜 +19045 昍 +19046 朢 +19047 桼 +19048 楻 +19049 歊 +19050 汻 +19051 漅 +19052 磗 +19053 鱯 +19054 瑂 +19055 皊 +19056 痵 +19057 穝 +19058 竤 +19059 玸 +19060 籹 +19061 秙 +19062 ﹕ +19063 畇 +19064 乻 +19065 俿 +19066 僺 +19067 剆 +19068 唖 +19069 噑 +19070 坰 +19071 塻 +19072 妔 +19073 媠 +19074 宻 +19075 巗 +19076 恠 +19077 憇 +19078 抯 +19079 搒 +19080 攕 +19081 晄 +19082 杝 +19083 梥 +19084 檚 +19085 歴 +19086 焥 +19087 碨 +19088 ⊿ +19089 鱏 +19090 琒 +19091 癝 +19092 璖 +19093 縎 +19094 稴 +19095 窼 +19096 玈 +19097 籗 +19098 ㏒ +19099 盨 +19100 凷 +19101 匰 +19102 哠 +19103 圫 +19104 塖 +19105 孲 +19106 峉 +19107 嶴 +19108 怱 +19109 慡 +19110 扴 +19111 揝 +19112 朣 +19113 桽 +19114 橲 +19115 歋 +19116 汼 +19117 烻 +19118 ㄟ +19119 ∵ +19120 ⑦ +19121 _ +19122 佭 +19123 傔 +19124 冞 +19125 勥 +19126 呥 +19127 嗊 +19128 囘 +19129 堖 +19130 夁 +19131 娺 +19132 屵 +19133 嵾 +19134 庍 +19135 愡 +19136 戇 +19137 撨 +19138 斶 +19139 曔 +19140 栠 +19141 椷 +19142 樳 +19143 欉 +19144 氝 +19145 涍 +19146 溸 +19147 澾 +19148 炦 +19149 熯 +19150 擗 +19151 攥 +19152 遾 +19153 擐 +19154 擤 +19155 邆 +19156 邇 +19157 邉 +19158 邌 +19159 邍 +19160 邎 +19161 邐 +19162 邒 +19163 邔 +19164 邖 +19165 邘 +19166 邚 +19167 邜 +19168 邞 +19169 邟 +19170 邠 +19171 邤 +19172 邥 +19173 邧 +19174 邫 +19175 邭 +19176 邲 +19177 邷 +19178 邼 +19179 邽 +19180 邿 +19181 遖 +19182 逜 +19183 哜 +19184 吣 +19185 遚 +19186 逤 +19187 逥 +19188 遝 +19189 逧 +19190 遟 +19191 逩 +19192 逪 +19193 遡 +19194 逫 +19195 遤 +19196 逬 +19197 遦 +19198 逰 +19199 遧 +19200 遪 +19201 逳 +19202 遫 +19203 逴 +19204 唪 +19205 咴 +19206 啧 +19207 遬 +19208 逷 +19209 遯 +19210 逹 +19211 遰 +19212 逺 +19213 遱 +19214 逽 +19215 逿 +19216 遳 +19217 遀 +19218 遶 +19219 遆 +19220 遈 +19221 啐 +19222 郀 +19223 磘 +19224 譼 +19225 瑃 +19226 皌 +19227 縯 +19228 穞 +19229 竧 +19230 秚 +19231 ﹖ +19232 畉 +19233 乼 +19234 倀 +19235 僼 +19236 卼 +19237 唗 +19238 噒 +19239 坱 +19240 塼 +19241 妕 +19242 媡 +19243 宼 +19244 峵 +19245 巘 +19246 弔 +19247 恡 +19248 憈 +19249 抰 +19250 搕 +19251 攖 +19252 晅 +19253 杢 +19254 梩 +19255 榯 +19256 檛 +19257 泃 +19258 渢 +19259 焧 +19260 ═ +19261 鱐 +19262 琓 +19263 癟 +19264 礣 +19265 疶 +19266 璗 +19267 縏 +19268 稵 +19269 窽 +19270 玊 +19271 ㏕ +19272 乀 +19273 僒 +19274 凾 +19275 圱 +19276 奣 +19277 婽 +19278 孴 +19279 峊 +19280 嶵 +19281 廡 +19282 怲 +19283 扵 +19284 揟 +19285 朤 +19286 楾 +19287 橳 +19288 歍 +19289 汿 +19290 淭 +19291 濼 +19292 烼 +19293 ╱ +19294 皍 +19295 祏 +19296 痷 +19297 璾 +19298 縰 +19299 穟 +19300 竨 +19301 玼 +19302 籾 +19303 秛 +19304 ﹗ +19305 眜 +19306 乽 +19307 倁 +19308 剈 +19309 卽 +19310 唘 +19311 媢 +19312 寀 +19313 巙 +19314 弖 +19315 憉 +19316 抲 +19317 攗 +19318 晆 +19319 杣 +19320 梪 +19321 榰 +19322 泆 +19323 瀠 +19324 碪 +19325 ║ +19326 鱑 +19327 譛 +19328 琔 +19329 癠 +19330 礥 +19331 疷 +19332 縐 +19333 稶 +19334 窾 +19335 玌 +19336 籙 +19337 ︰ +19338 甎 +19339 乁 +19340 俇 +19341 刄 +19342 匲 +19343 哢 +19344 嘦 +19345 圲 +19346 塙 +19347 婾 +19348 峌 +19349 怳 +19350 慤 +19351 扷 +19352 揢 +19353 昒 +19354 楿 +19355 橴 +19356 沀 +19357 淯 +19358 漊 +19359 濽 +19360 烾 +19361 → +19362 侜 +19363 傶 +19364 凓 +19365 匉 +19366 咜 +19367 嗿 +19368 堹 +19369 婜 +19370 岤 +19371 嶛 +19372 忷 +19373 慂 +19374 扂 +19375 旡 +19376 曻 +19377 桗 +19378 楘 +19379 橔 +19380 欪 +19381 汑 +19382 淂 +19383 滫 +19384 鷃 +19385 鷁 +19386 鷡 +19387 鷀 +19388 鶣 +19389 鶿 +19390 鷢 +19391 鷤 +19392 鷧 +19393 鷩 +19394 鷪 +19395 鷫 +19396 鷬 +19397 鷭 +19398 鷰 +19399 鷳 +19400 鷴 +19401 鷵 +19402 鷷 +19403 鷽 +19404 鷾 +19405 鸀 +19406 鸁 +19407 ㄚ +19408 ≮ +19409 ② +19410 Z +19411 傏 +19412 冓 +19413 呞 +19414 堏 +19415 壼 +19416 娳 +19417 嬟 +19418 屭 +19419 徻 +19420 愙 +19421 戁 +19422 捼 +19423 撢 +19424 斱 +19425 曏 +19426 栚 +19427 椱 +19428 涄 +19429 溭 +19430 澸 +19431 炡 +19432 赻 +19433 赹 +19434 赱 +19435 赸 +19436 趠 +19437 贎 +19438 讠 +19439 赲 +19440 趢 +19441 趤 +19442 趦 +19443 趧 +19444 趩 +19445 趪 +19446 趫 +19447 趬 +19448 趭 +19449 趮 +19450 趯 +19451 趰 +19452 趲 +19453 趶 +19454 趷 +19455 趹 +19456 趻 +19457 趽 +19458 跀 +19459 跁 +19460 跇 +19461 跈 +19462 跉 +19463 跍 +19464 跒 +19465 跓 +19466 ※ +19467 Ⅸ +19468 侚 +19469 凒 +19470 匇 +19471 嗼 +19472 堸 +19473 夰 +19474 婛 +19475 孂 +19476 嶚 +19477 廀 +19478 忶 +19479 慁 +19480 戼 +19481 擓 +19482 旟 +19483 曺 +19484 桖 +19485 楖 +19486 橓 +19487 欩 +19488 汏 +19489 淁 +19490 濝 +19491 烓 +19492 燍 +19493 鵿 +19494 鵞 +19495 鵾 +19496 鶀 +19497 鵃 +19498 鶂 +19499 鶄 +19500 鶅 +19501 鶆 +19502 鶇 +19503 鶊 +19504 鶋 +19505 鶏 +19506 鶑 +19507 鶓 +19508 鶔 +19509 鶕 +19510 鶖 +19511 鶙 +19512 鶚 +19513 鶜 +19514 鶞 +19515 鶠 +19516 鶡 +19517 ㄙ +19518 ≠ +19519 з +19520 ┵ +19521 Y +19522 傎 +19523 冑 +19524 呝 +19525 嗁 +19526 囐 +19527 堎 +19528 壻 +19529 娰 +19530 嵸 +19531 庂 +19532 徺 +19533 愘 +19534 捹 +19535 撡 +19536 斮 +19537 栙 +19538 椯 +19539 樫 +19540 欃 +19541 涃 +19542 澷 +19543 炠 +19544 熧 +19545 賎 +19546 侔 +19547 賍 +19548 賋 +19549 賩 +19550 賫 +19551 貮 +19552 賮 +19553 賯 +19554 賰 +19555 賱 +19556 賲 +19557 賳 +19558 賵 +19559 賶 +19560 賷 +19561 賸 +19562 賹 +19563 賻 +19564 賾 +19565 賿 +19566 贁 +19567 贋 +19568 ← +19569 Ⅺ +19570 { +19571 侞 +19572 匊 +19573 咞 +19574 嘂 +19575 圎 +19576 夳 +19577 婝 +19578 孄 +19579 岥 +19580 嶜 +19581 忹 +19582 扄 +19583 掻 +19584 擕 +19585 旣 +19586 桘 +19587 楙 +19588 橕 +19589 欫 +19590 汒 +19591 淃 +19592 濢 +19593 烕 +19594 鸴 +19595 鸧 +19596 鸃 +19597 麁 +19598 麃 +19599 麄 +19600 麅 +19601 麆 +19602 麉 +19603 麊 +19604 麌 +19605 麍 +19606 麎 +19607 麏 +19608 麐 +19609 麑 +19610 麔 +19611 麕 +19612 麖 +19613 麘 +19614 麙 +19615 麚 +19616 麛 +19617 麜 +19618 麞 +19619 麠 +19620 麡 +19621 麢 +19622 麣 +19623 麤 +19624 麧 +19625 麨 +19626 ㄛ +19627 ≯ +19628 й +19629 ┷ +19630 ③ +19631 [ +19632 佦 +19633 傐 +19634 冔 +19635 勠 +19636 呟 +19637 嗃 +19638 囒 +19639 堐 +19640 嬠 +19641 屰 +19642 嵺 +19643 庅 +19644 徾 +19645 戂 +19646 捽 +19647 斲 +19648 曐 +19649 栛 +19650 椲 +19651 樭 +19652 欅 +19653 氎 +19654 涆 +19655 溮 +19656 澺 +19657 熪 +19658 踾 +19659 踻 +19660 郐 +19661 踎 +19662 踇 +19663 踋 +19664 踼 +19665 跕 +19666 踈 +19667 踿 +19668 蹃 +19669 蹅 +19670 蹆 +19671 蹌 +19672 蹍 +19673 蹎 +19674 蹏 +19675 蹔 +19676 蹕 +19677 蹖 +19678 蹗 +19679 蹘 +19680 蹚 +19681 蹛 +19682 蹜 +19683 蹝 +19684 蹞 +19685 蹡 +19686 蹢 +19687 蹧 +19688 蹨 +19689 蹫 +19690 ↑ +19691 Ⅻ +19692 | +19693 侟 +19694 傸 +19695 凕 +19696 匋 +19697 咟 +19698 堻 +19699 夵 +19700 岦 +19701 嶞 +19702 廃 +19703 忺 +19704 扅 +19705 掽 +19706 擖 +19707 旤 +19708 朁 +19709 桙 +19710 楛 +19711 橖 +19712 汓 +19713 淈 +19714 滭 +19715 濣 +19716 烖 +19717 燑 +19718 鼅 +19719 鼃 +19720 黖 +19721 黓 +19722 鼂 +19723 鼄 +19724 麫 +19725 鼆 +19726 鼇 +19727 鼈 +19728 鼉 +19729 鼊 +19730 鼌 +19731 鼏 +19732 鼑 +19733 鼒 +19734 鼔 +19735 鼕 +19736 鼖 +19737 鼘 +19738 鼚 +19739 鼛 +19740 鼜 +19741 鼝 +19742 鼟 +19743 鼡 +19744 鼣 +19745 鼥 +19746 鼦 +19747 鼧 +19748 鼪 +19749 鼫 +19750 鼮 +19751 ㄜ +19752 ≤ +19753 к +19754 ┸ +19755 ④ +19756 \ +19757 佨 +19758 呠 +19759 囓 +19760 堒 +19761 娷 +19762 庈 +19763 徿 +19764 戃 +19765 捾 +19766 斳 +19767 曑 +19768 椳 +19769 樮 +19770 欆 +19771 氒 +19772 溰 +19773 澻 +19774 熫 +19775 躟 +19776 躝 +19777 堋 +19778 躿 +19779 堙 +19780 墚 +19781 堍 +19782 埽 +19783 躙 +19784 軃 +19785 軄 +19786 軆 +19787 軉 +19788 軐 +19789 軓 +19790 軔 +19791 軕 +19792 軗 +19793 軘 +19794 軚 +19795 軞 +19796 軡 +19797 軣 +19798 鶤 +19799 赼 +19800 卩 +19801 阝 +19802 阢 +19803 鵄 +19804 賏 +19805 汆 +19806 馘 +19807 鸻 +19808 踑 +19809 跘 +19810 坫 +19811 躠 +19812 蹵 +19813 塥 +19814 芰 +19815 苊 +19816 冁 +19817 鶥 +19818 赽 +19819 贐 +19820 鵥 +19821 鵅 +19822 鸼 +19823 鸅 +19824 踒 +19825 跙 +19826 黚 +19827 麭 +19828 躡 +19829 蹷 +19830 鷆 +19831 赾 +19832 贑 +19833 谇 +19834 鵆 +19835 賑 +19836 鸆 +19837 踓 +19838 跜 +19839 麮 +19840 蹸 +19841 鶧 +19842 赿 +19843 陴 +19844 鵧 +19845 貲 +19846 踕 +19847 垧 +19848 黡 +19849 麯 +19850 躣 +19851 鶨 +19852 趀 +19853 贓 +19854 鵨 +19855 鵈 +19856 勹 +19857 鹐 +19858 鸈 +19859 坶 +19860 凵 +19861 廴 +19862 黣 +19863 麰 +19864 躤 +19865 蹺 +19866 鶩 +19867 趂 +19868 贔 +19869 鵩 +19870 鵉 +19871 賔 +19872 鹒 +19873 鸉 +19874 踗 +19875 跢 +19876 黤 +19877 躥 +19878 蹻 +19879 鶪 +19880 趃 +19881 鵊 +19882 賕 +19883 貵 +19884 鹓 +19885 踘 +19886 黦 +19887 躦 +19888 蹽 +19889 鷋 +19890 鶫 +19891 趆 +19892 鵋 +19893 賖 +19894 跦 +19895 麳 +19896 躧 +19897 蹾 +19898 鷌 +19899 鶬 +19900 趇 +19901 贗 +19902 賗 +19903 亠 +19904 鹖 +19905 鸌 +19906 跧 +19907 垲 +19908 黫 +19909 躨 +19910 鷍 +19911 鶭 +19912 趈 +19913 贘 +19914 鵭 +19915 賘 +19916 鹙 +19917 鸍 +19918 踛 +19919 跩 +19920 黬 +19921 麶 +19922 躩 +19923 躂 +19924 鷎 +19925 鶮 +19926 趉 +19927 鵎 +19928 賙 +19929 貹 +19930 鹝 +19931 鸎 +19932 踜 +19933 跭 +19934 黭 +19935 麷 +19936 躃 +19937 鷏 +19938 趌 +19939 鵯 +19940 鵏 +19941 貺 +19942 鹟 +19943 踠 +19944 跮 +19945 黮 +19946 麹 +19947 躭 +19948 躄 +19949 鷐 +19950 趍 +19951 鵐 +19952 賛 +19953 鸐 +19954 踡 +19955 跰 +19956 黰 +19957 麺 +19958 鶱 +19959 趎 +19960 贜 +19961 踤 +19962 跱 +19963 黱 +19964 麼 +19965 躰 +19966 趏 +19967 鵒 +19968 賝 +19969 裒 +19970 僦 +19971 鹢 +19972 鸒 +19973 踥 +19974 跲 +19975 墼 +19976 黲 +19977 麿 +19978 躱 +19979 鶳 +19980 鵓 +19981 鹥 +19982 鸓 +19983 踦 +19984 跴 +19985 黳 +19986 黀 +19987 鷔 +19988 趒 +19989 赒 +19990 鵴 +19991 鵔 +19992 賟 +19993 鸔 +19994 黁 +19995 躋 +19996 鷕 +19997 鶵 +19998 趓 +19999 赗 +20000 鵕 +20001 鸕 +20002 踨 +20003 黵 +20004 黂 +20005 躌 +20006 鷖 +20007 鵶 +20008 鵖 +20009 鹲 +20010 鸖 +20011 黶 +20012 躶 +20013 趖 +20014 赥 +20015 鵷 +20016 鸗 +20017 踭 +20018 跿 +20019 黷 +20020 黅 +20021 躷 +20022 躎 +20023 鷘 +20024 鶸 +20025 趗 +20026 赨 +20027 谫 +20028 鵸 +20029 鵘 +20030 氽 +20031 冱 +20032 鸘 +20033 踰 +20034 踀 +20035 埯 +20036 黸 +20037 黆 +20038 躸 +20039 躑 +20040 茳 +20041 鷙 +20042 鶹 +20043 趘 +20044 赩 +20045 鵹 +20046 鵙 +20047 鸙 +20048 踲 +20049 黺 +20050 黇 +20051 躹 +20052 躒 +20053 鷚 +20054 鵺 +20055 鵚 +20056 賥 +20057 賅 +20058 鹷 +20059 踳 +20060 黽 +20061 躻 +20062 躓 +20063 鷛 +20064 趚 +20065 赬 +20066 鵻 +20067 鹸 +20068 踃 +20069 黊 +20070 躼 +20071 鷜 +20072 鶼 +20073 趛 +20074 赮 +20075 鵜 +20076 賧 +20077 鸜 +20078 踶 +20079 踄 +20080 鼀 +20081 黋 +20082 躖 +20083 鷝 +20084 鶽 +20085 趜 +20086 赯 +20087 鵽 +20088 賨 +20089 鹺 +20090 踷 +20091 踆 +20092 鼁 +20093 黌 +20094 躾 +20095 跔 +20096 鶢 +20097 麪 +20098 蹱 +20099 軤 +20100 ╲ +20101 譾 +20102 皏 +20103 祐 +20104 痸 +20105 穠 +20106 玽 +20107 籿 +20108 秜 +20109 ﹙ +20110 畍 +20111 眝 +20112 乿 +20113 倂 +20114 僾 +20115 剉 +20116 卾 +20117 唙 +20118 噕 +20119 坴 +20120 塿 +20121 妚 +20122 媣 +20123 寁 +20124 峷 +20125 巚 +20126 弙 +20127 恦 +20128 抳 +20129 攙 +20130 晇 +20131 杤 +20132 梫 +20133 榲 +20134 檝 +20135 渧 +20136 漹 +20137 瀡 +20138 焩 +20139 碫 +20140 ╒ +20141 琕 +20142 疺 +20143 縑 +20144 稸 +20145 籚 +20146 禫 +20147 ¬ +20148 甐 +20149 盫 +20150 俈 +20151 僔 +20152 刅 +20153 匳 +20154 哣 +20155 嘨 +20156 圴 +20157 奦 +20158 媀 +20159 峍 +20160 怴 +20161 慥 +20162 扸 +20163 揤 +20164 擵 +20165 昖 +20166 梀 +20167 橵 +20168 歏 +20169 淰 +20170 漋 +20171 磜 +20172 ╳ +20173 鱳 +20174 譿 +20175 瑆 +20176 祑 +20177 瓀 +20178 縲 +20179 穡 +20180 粀 +20181 秝 +20182 ﹚ +20183 畐 +20184 倃 +20185 僿 +20186 厀 +20187 唚 +20188 噖 +20189 墂 +20190 妛 +20191 媤 +20192 寃 +20193 峸 +20194 巜 +20195 弚 +20196 恮 +20197 抴 +20198 搘 +20199 晈 +20200 梬 +20201 榳 +20202 渨 +20203 漺 +20204 焪 +20205 碬 +20206 ╓ +20207 譝 +20208 琖 +20209 礧 +20210 疻 +20211 璚 +20212 稺 +20213 竁 +20214 籛 +20215 禬 +20216 ¦ +20217 甒 +20218 盬 +20219 乄 +20220 俉 +20221 刉 +20222 匴 +20223 哤 +20224 圵 +20225 塛 +20226 峎 +20227 廤 +20228 怶 +20229 慦 +20230 扺 +20231 揥 +20232 昗 +20233 朩 +20234 梂 +20235 橶 +20236 漌 +20237 濿 +20238 焀 +20239 磝 +20240 ▁ +20241 鱴 +20242 瑇 +20243 皒 +20244 祒 +20245 痻 +20246 瓁 +20247 縳 +20248 竫 +20249 粁 +20250 秞 +20251 ﹛ +20252 眡 +20253 倄 +20254 厁 +20255 唜 +20256 噚 +20257 坸 +20258 墄 +20259 妜 +20260 媥 +20261 寈 +20262 峹 +20263 巟 +20264 弜 +20265 恱 +20266 憍 +20267 抶 +20268 搙 +20269 攛 +20270 杧 +20271 梮 +20272 榵 +20273 檟 +20274 歺 +20275 泋 +20276 渪 +20277 漻 +20278 瀤 +20279 焫 +20280 ╔ +20281 琗 +20282 礨 +20283 璛 +20284 縓 +20285 稾 +20286 竂 +20287 玐 +20288 禭 +20289 甔 +20290 盭 +20291 乆 +20292 俋 +20293 僗 +20294 刋 +20295 匵 +20296 哫 +20297 嘪 +20298 圶 +20299 塜 +20300 奨 +20301 媂 +20302 孹 +20303 峏 +20304 廥 +20305 怷 +20306 扻 +20307 昘 +20308 梄 +20309 橷 +20310 歑 +20311 沊 +20312 淴 +20313 漍 +20314 瀀 +20315 焁 +20316 磞 +20317 ▂ +20318 讁 +20319 皔 +20320 祔 +20321 痽 +20322 瓂 +20323 穣 +20324 珁 +20325 粂 +20326 秠 +20327 ﹜ +20328 畒 +20329 眣 +20330 倅 +20331 剏 +20332 厃 +20333 唝 +20334 噛 +20335 坹 +20336 墆 +20337 媦 +20338 寉 +20339 峺 +20340 巠 +20341 弝 +20342 恲 +20343 抷 +20344 搚 +20345 晊 +20346 杫 +20347 梱 +20348 榶 +20349 檡 +20350 歽 +20351 泍 +20352 瀥 +20353 焬 +20354 碮 +20355 ╕ +20356 譟 +20357 琘 +20358 礩 +20359 痀 +20360 璝 +20361 縔 +20362 竃 +20363 籝 +20364 ℡ +20365 盰 +20366 乊 +20367 俌 +20368 僘 +20369 刌 +20370 匶 +20371 哬 +20372 嘫 +20373 圷 +20374 奩 +20375 媃 +20376 孻 +20377 峐 +20378 嶻 +20379 廦 +20380 怸 +20381 慪 +20382 扽 +20383 揧 +20384 擸 +20385 昚 +20386 朰 +20387 梇 +20388 橸 +20389 沋 +20390 漎 +20391 瀁 +20392 焂 +20393 ↓ +20394 } +20395 傹 +20396 匌 +20397 咠 +20398 嘄 +20399 堼 +20400 夶 +20401 婟 +20402 孆 +20403 嶟 +20404 廄 +20405 忼 +20406 慅 +20407 扆 +20408 掿 +20409 擙 +20410 旪 +20411 朂 +20412 桚 +20413 楜 +20414 橗 +20415 欭 +20416 滮 +20417 烗 +20418 齕 +20419 齗 +20420 齵 +20421 鼲 +20422 齖 +20423 齹 +20424 齺 +20425 齻 +20426 齼 +20427 齽 +20428 齾 +20429 龂 +20430 龎 +20431 龏 +20432 龒 +20433 龓 +20434 龖 +20435 龗 +20436 龝 +20437 龞 +20438 龡 +20439 郎 +20440 凉 +20441 裏 +20442 ㄝ +20443 鬏 +20444 ≥ +20445 л +20446 ぽ +20447 ⑤ +20448 ] +20449 佪 +20450 呡 +20451 娸 +20452 屳 +20453 嵼 +20454 庉 +20455 忀 +20456 愝 +20457 戄 +20458 捿 +20459 撦 +20460 斴 +20461 曒 +20462 椵 +20463 樰 +20464 欇 +20465 涊 +20466 溳 +20467 熭 +20468 輅 +20469 莰 +20470 輣 +20471 輀 +20472 輂 +20473 輠 +20474 輢 +20475 荮 +20476 軥 +20477 輁 +20478 輥 +20479 輦 +20480 輧 +20481 輫 +20482 輬 +20483 輭 +20484 輮 +20485 輲 +20486 輳 +20487 輴 +20488 輵 +20489 輶 +20490 輹 +20491 輽 +20492 轀 +20493 轃 +20494 菥 +20495 莶 +20496 齛 +20497 鼳 +20498 齜 +20499 齝 +20500 鼵 +20501 輈 +20502 軨 +20503 鼶 +20504 軩 +20505 齟 +20506 鼸 +20507 輊 +20508 萆 +20509 鼺 +20510 輌 +20511 軬 +20512 齢 +20513 齣 +20514 輎 +20515 軮 +20516 齤 +20517 齁 +20518 輏 +20519 齥 +20520 齂 +20521 輐 +20522 軰 +20523 齃 +20524 輑 +20525 軱 +20526 齧 +20527 齅 +20528 輒 +20529 軲 +20530 齨 +20531 齆 +20532 軳 +20533 齇 +20534 軴 +20535 蔌 +20536 齈 +20537 齫 +20538 齉 +20539 輖 +20540 齬 +20541 軷 +20542 輘 +20543 齮 +20544 齌 +20545 輙 +20546 軹 +20547 齯 +20548 齍 +20549 輚 +20550 軺 +20551 葙 +20552 蓰 +20553 蒇 +20554 蒈 +20555 齰 +20556 齎 +20557 齱 +20558 齏 +20559 蔟 +20560 齴 +20561 軿 +20562 蒉 +20563 隣 +20564 磟 +20565 ▃ +20566 瑉 +20567 皕 +20568 痾 +20569 穤 +20570 竮 +20571 珃 +20572 粃 +20573 秡 +20574 ﹝ +20575 眤 +20576 亃 +20577 剒 +20578 厇 +20579 噝 +20580 坺 +20581 墇 +20582 寊 +20583 峼 +20584 弞 +20585 恴 +20586 抸 +20587 搝 +20588 晍 +20589 杬 +20590 梲 +20591 歾 +20592 泎 +20593 渮 +20594 漽 +20595 焭 +20596 碯 +20597 ╖ +20598 譠 +20599 琙 +20600 癦 +20601 痁 +20602 縕 +20603 竄 +20604 籞 +20605 ㈱ +20606 甖 +20607 盳 +20608 乑 +20609 僙 +20610 刏 +20611 哯 +20612 圸 +20613 塟 +20614 孼 +20615 峑 +20616 廧 +20617 慫 +20618 抁 +20619 揨 +20620 昛 +20621 朲 +20622 梈 +20623 橺 +20624 歓 +20625 沍 +20626 瀂 +20627 焃 +20628 齄 +20629 〓 +20630  ̄ +20631 侢 +20632 傼 +20633 凗 +20634 咡 +20635 嘅 +20636 圑 +20637 夻 +20638 孇 +20639 岨 +20640 廅 +20641 怇 +20642 扊 +20643 揀 +20644 旫 +20645 桛 +20646 楟 +20647 欮 +20648 淊 +20649 烚 +20650 燓 +20651 ㄞ +20652 ∞ +20653 ⑥ +20654 ^ +20655 佫 +20656 傓 +20657 冝 +20658 呣 +20659 嗈 +20660 囖 +20661 夀 +20662 娹 +20663 嬣 +20664 屴 +20665 嵽 +20666 庌 +20667 忁 +20668 愞 +20669 戅 +20670 撧 +20671 斵 +20672 曓 +20673 椶 +20674 樲 +20675 欈 +20676 氜 +20677 涋 +20678 溵 +20679 澽 +20680 炥 +20681 熮 +20682 轥 +20683 轣 +20684 迆 +20685 轤 +20686 瞢 +20687 轢 +20688 迃 +20689 迉 +20690 迊 +20691 迋 +20692 迌 +20693 迒 +20694 迗 +20695 迚 +20696 迠 +20697 迡 +20698 迣 +20699 迧 +20700 迬 +20701 迯 +20702 迱 +20703 迲 +20704 迶 +20705 迺 +20706 迻 +20707 迼 +20708 迾 +20709 迿 +20710 逇 +20711 逈 +20712 逌 +20713 逎 +20714 逓 +20715 逕 +20716 嗀 +20717 轪 +20718 掎 +20719 掊 +20720 轇 +20721 﨏 +20722 辌 +20723 﨑 +20724 辒 +20725 扌 +20726 辝 +20727 﨔 +20728 辠 +20729 轋 +20730 礼 +20731 轌 +20732 辢 +20733 辤 +20734 尢 +20735 揞 +20736 揎 +20737 﨡 +20738 辥 +20739 轐 +20740 﨤 +20741 辧 +20742 轑 +20743 﨧 +20744 辪 +20745 轒 +20746 辬 +20747 轓 +20748 轔 +20749 搌 +20750 挢 +20751 轕 +20752 轗 +20753 辳 +20754 捱 +20755 轙 +20756 辵 +20757 轚 +20758 撄 +20759 辷 +20760 辸 +20761 轝 +20762 掭 +20763 撖 +20764 逘 +20765 礌 +20766 瑺 +20767 禒 +20768 癄 +20769 繝 +20770 窢 +20771 珷 +20772 粻 +20773 稜 +20774 仩 +20775 儬 +20776 劆 +20777 啝 +20778 嚑 +20779 垹 +20780 墵 +20781 姞 +20782 尃 +20783 崰 +20784 帬 +20785 彔 +20786 悹 +20787 憼 +20788 挔 +20789 摖 +20790 敔 +20791 暊 +20792 枲 +20793 槧 +20794 櫊 +20795 殸 +20796 洜 +20797 潬 +20798 灎 +20799 煚 +20800 牬 +20801 燸 +20802 牗 +20803 爚 +20804 狑 +20805 牞 +20806 爘 +20807 牔 +20808 牥 +20809 牭 +20810 牎 +20811 牱 +20812 牳 +20813 牜 +20814 牷 +20815 燵 +20816 爗 +20817 爙 +20818 牕 +20819 牰 +20820 牣 +20821 燖 +20822 牑 +20823 牏 +20824 牐 +20825 牓 +20826 燶 +20827 牨 +20828 爜 +20829 爞 +20830 爟 +20831 爠 +20832 爡 +20833 爢 +20834 爣 +20835 爤 +20836 爥 +20837 爧 +20838 爩 +20839 爫 +20840 爮 +20841 爯 +20842 爳 +20843 爴 +20844 爼 +20845 牀 +20846 牃 +20847 牅 +20848 牉 +20849 牊 +20850 牸 +20851 牻 +20852 牼 +20853 牴 +20854 牪 +20855 牫 +20856 燗 +20857 牚 +20858 犪 +20859 犩 +20860 犂 +20861 犫 +20862 犅 +20863 犲 +20864 犱 +20865 犮 +20866 犆 +20867 犳 +20868 犉 +20869 燽 +20870 燘 +20871 燾 +20872 犵 +20873 犌 +20874 燿 +20875 狆 +20876 犘 +20877 爀 +20878 燛 +20879 犻 +20880 犐 +20881 犺 +20882 犎 +20883 爁 +20884 爂 +20885 燝 +20886 爃 +20887 燞 +20888 爄 +20889 犿 +20890 犾 +20891 犖 +20892 狅 +20893 犗 +20894 爅 +20895 燡 +20896 爇 +20897 燢 +20898 爈 +20899 爉 +20900 狇 +20901 犙 +20902 燨 +20903 牶 +20904 狊 +20905 犛 +20906 狉 +20907 犚 +20908 狋 +20909 犜 +20910 狏 +20911 狌 +20912 犝 +20913 狓 +20914 爌 +20915 爎 +20916 燫 +20917 燬 +20918 犨 +20919 燯 +20920 狕 +20921 狔 +20922 狖 +20923 犤 +20924 狘 +20925 犥 +20926 燰 +20927 爓 +20928 燱 +20929 爔 +20930 燳 +20931 狚 +20932 犦 +20933 爖 +20934 牋 +20935 OOV_NUM +20936 OOV_ALPHA +20937 OOV_ALNUM +20938 OOV_HANZ +20940 OOV diff --git a/examples/sequence_tagging/downloads.py b/examples/sequence_tagging/downloads.py new file mode 100644 index 0000000000000000000000000000000000000000..b61c3e779cca3f3900d8e0bc0eb209f8fa2f9389 --- /dev/null +++ b/examples/sequence_tagging/downloads.py @@ -0,0 +1,148 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Download script, download dataset and pretrain models. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import io +import os +import sys +import time +import hashlib +import tarfile +import requests + +FILE_INFO = { + 'BASE_URL': 'https://baidu-nlp.bj.bcebos.com/', + 'DATA': { + 'name': 'lexical_analysis-dataset-2.0.0.tar.gz', + 'md5': '71e4a9a36d0f0177929a1bccedca7dba' + }, + 'MODEL': { + 'name': 'sequence_tagging_dy.tar.gz', + 'md5': "1125d374c03c8218b6e47325dcf607e3" + }, +} + + +def usage(): + desc = ("\nDownload datasets and pretrained models for sequence tagging.\n" + "Usage:\n" + " 1. python download.py all\n" + " 2. python download.py dataset\n" + " 3. python download.py model\n") + print(desc) + + +def md5file(fname): + hash_md5 = hashlib.md5() + with io.open(fname, "rb") as fin: + for chunk in iter(lambda: fin.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + +def extract(fname, dir_path): + """ + Extract tar.gz file + """ + try: + tar = tarfile.open(fname, "r:gz") + file_names = tar.getnames() + for file_name in file_names: + tar.extract(file_name, dir_path) + print(file_name) + tar.close() + except Exception as e: + raise e + + +def _download(url, filename, md5sum): + """ + Download file and check md5 + """ + retry = 0 + retry_limit = 3 + chunk_size = 4096 + while not (os.path.exists(filename) and md5file(filename) == md5sum): + if retry < retry_limit: + retry += 1 + else: + raise RuntimeError( + "Cannot download dataset ({0}) with retry {1} times.".format( + url, retry_limit)) + try: + start = time.time() + size = 0 + res = requests.get(url, stream=True) + filesize = int(res.headers['content-length']) + if res.status_code == 200: + print("[Filesize]: %0.2f MB" % (filesize / 1024 / 1024)) + # save by chunk + with io.open(filename, "wb") as fout: + for chunk in res.iter_content(chunk_size=chunk_size): + if chunk: + fout.write(chunk) + size += len(chunk) + pr = '>' * int(size * 50 / filesize) + print( + '\r[Process ]: %s%.2f%%' % + (pr, float(size / filesize * 100)), + end='') + end = time.time() + print("\n[CostTime]: %.2f s" % (end - start)) + except Exception as e: + print(e) + + +def download(name, dir_path): + url = FILE_INFO['BASE_URL'] + FILE_INFO[name]['name'] + file_path = os.path.join(dir_path, FILE_INFO[name]['name']) + + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + # download data + print("Downloading : %s" % name) + _download(url, file_path, FILE_INFO[name]['md5']) + + # extract data + print("Extracting : %s" % file_path) + extract(file_path, dir_path) + os.remove(file_path) + + +if __name__ == '__main__': + if len(sys.argv) != 2: + usage() + sys.exit(1) + pwd = os.path.join(os.path.dirname(__file__), './') + ernie_dir = os.path.join(os.path.dirname(__file__), './pretrained') + + if sys.argv[1] == 'all': + download('DATA', pwd) + download('MODEL', pwd) + + if sys.argv[1] == "dataset": + download('DATA', pwd) + + elif sys.argv[1] == "model": + download('MODEL', pwd) + + else: + usage() diff --git a/examples/sequence_tagging/downloads.sh b/examples/sequence_tagging/downloads.sh new file mode 100644 index 0000000000000000000000000000000000000000..a6c49878cf37fef01216fd450cf9de29958cb899 --- /dev/null +++ b/examples/sequence_tagging/downloads.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# download baseline model file to ./model_baseline/ +if [ -d ./model_baseline/ ] +then + echo "./model_baseline/ directory already existed, ignore download" +else + wget --no-check-certificate https://baidu-nlp.bj.bcebos.com/sequence_tagging_dy.tar.gz + tar xvf sequence_tagging_dy.tar.gz + /bin/rm sequence_tagging_dy.tar.gz +fi + +# download dataset file to ./data/ +if [ -d ./data/ ] +then + echo "./data/ directory already existed, ignore download" +else + wget --no-check-certificate https://baidu-nlp.bj.bcebos.com/lexical_analysis-dataset-2.0.0.tar.gz + tar xvf lexical_analysis-dataset-2.0.0.tar.gz + /bin/rm lexical_analysis-dataset-2.0.0.tar.gz +fi + diff --git a/examples/sequence_tagging/eval.py b/examples/sequence_tagging/eval.py new file mode 100644 index 0000000000000000000000000000000000000000..ff3e7b9865064289f73b19756d4c1b5a271e11d2 --- /dev/null +++ b/examples/sequence_tagging/eval.py @@ -0,0 +1,99 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +SequenceTagging network structure +""" + +from __future__ import division +from __future__ import print_function + +import io +import os +import sys +import math +import argparse +import numpy as np + +from train import SeqTagging +from utils.configure import PDConfig +from utils.check import check_gpu, check_version +from utils.metrics import chunk_count +from reader import LacDataset, create_lexnet_data_generator, create_dataloader + +work_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.join(work_dir, "../")) +from hapi.model import set_device, Input + +import paddle.fluid as fluid +from paddle.fluid.optimizer import AdamOptimizer +from paddle.fluid.layers.utils import flatten + + +def main(args): + place = set_device(args.device) + fluid.enable_dygraph(place) if args.dynamic else None + + inputs = [Input([None, None], 'int64', name='words'), + Input([None], 'int64', name='length')] + + feed_list = None if args.dynamic else [x.forward() for x in inputs] + dataset = LacDataset(args) + eval_path = args.test_file + + chunk_evaluator = fluid.metrics.ChunkEvaluator() + chunk_evaluator.reset() + + eval_generator = create_lexnet_data_generator( + args, reader=dataset, file_name=eval_path, place=place, mode="test") + + eval_dataset = create_dataloader( + eval_generator, place, feed_list=feed_list) + + vocab_size = dataset.vocab_size + num_labels = dataset.num_labels + model = SeqTagging(args, vocab_size, num_labels) + + optim = AdamOptimizer( + learning_rate=args.base_learning_rate, + parameter_list=model.parameters()) + + model.mode = "test" + model.prepare(inputs=inputs) + model.load(args.init_from_checkpoint, skip_mismatch=True) + + for data in eval_dataset(): + if len(data) == 1: + batch_data = data[0] + targets = np.array(batch_data[2]) + else: + batch_data = data + targets = batch_data[2].numpy() + inputs_data = [batch_data[0], batch_data[1]] + crf_decode, length = model.test(inputs=inputs_data) + num_infer_chunks, num_label_chunks, num_correct_chunks = chunk_count(crf_decode, targets, length, dataset.id2label_dict) + chunk_evaluator.update(num_infer_chunks, num_label_chunks, num_correct_chunks) + + precision, recall, f1 = chunk_evaluator.eval() + print("[test] P: %.5f, R: %.5f, F1: %.5f" % (precision, recall, f1)) + + +if __name__ == '__main__': + args = PDConfig(yaml_file="sequence_tagging.yaml") + args.build() + args.Print() + + use_gpu = True if args.device == "gpu" else False + check_gpu(use_gpu) + check_version() + main(args) diff --git a/examples/sequence_tagging/images/gru-crf-model.png b/examples/sequence_tagging/images/gru-crf-model.png new file mode 100644 index 0000000000000000000000000000000000000000..1ca7d5cf0c5381294915dc13bfb4fbd2bf7e99dc Binary files /dev/null and b/examples/sequence_tagging/images/gru-crf-model.png differ diff --git a/examples/sequence_tagging/predict.py b/examples/sequence_tagging/predict.py new file mode 100644 index 0000000000000000000000000000000000000000..ac4a50ad30c494a4d433682d04fffa23cc4d1c03 --- /dev/null +++ b/examples/sequence_tagging/predict.py @@ -0,0 +1,94 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +SequenceTagging network structure +""" + +from __future__ import division +from __future__ import print_function + +import io +import os +import sys +import math +import argparse +import numpy as np + +from train import SeqTagging +from utils.check import check_gpu, check_version +from utils.configure import PDConfig +from reader import LacDataset, create_lexnet_data_generator, create_dataloader + +work_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.join(work_dir, "../")) +from hapi.model import set_device, Input + +import paddle.fluid as fluid +from paddle.fluid.optimizer import AdamOptimizer +from paddle.fluid.layers.utils import flatten + + +def main(args): + place = set_device(args.device) + fluid.enable_dygraph(place) if args.dynamic else None + + inputs = [Input([None, None], 'int64', name='words'), + Input([None], 'int64', name='length')] + + feed_list = None if args.dynamic else [x.forward() for x in inputs] + dataset = LacDataset(args) + predict_path = args.predict_file + + predict_generator = create_lexnet_data_generator( + args, reader=dataset, file_name=predict_path, place=place, mode="predict") + + predict_dataset = create_dataloader( + predict_generator, place, feed_list=feed_list) + + vocab_size = dataset.vocab_size + num_labels = dataset.num_labels + model = SeqTagging(args, vocab_size, num_labels) + + optim = AdamOptimizer( + learning_rate=args.base_learning_rate, + parameter_list=model.parameters()) + + model.mode = "test" + model.prepare(inputs=inputs) + + model.load(args.init_from_checkpoint, skip_mismatch=True) + + f = open(args.output_file, "wb") + for data in predict_dataset(): + if len(data) == 1: + input_data = data[0] + else: + input_data = data + results, length = model.test(inputs=flatten(input_data)) + for i in range(len(results)): + word_len = length[i] + word_ids = results[i][: word_len] + tags = [dataset.id2label_dict[str(id)] for id in word_ids] + f.write("\002".join(tags) + "\n") + + +if __name__ == '__main__': + args = PDConfig(yaml_file="sequence_tagging.yaml") + args.build() + args.Print() + + use_gpu = True if args.device == "gpu" else False + check_gpu(use_gpu) + check_version() + main(args) diff --git a/examples/sequence_tagging/reader.py b/examples/sequence_tagging/reader.py new file mode 100644 index 0000000000000000000000000000000000000000..02719407e8771d46a17f1c7b5583e69c11a6cd73 --- /dev/null +++ b/examples/sequence_tagging/reader.py @@ -0,0 +1,259 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +SequenceTagging dataset +""" + +from __future__ import division +from __future__ import print_function + +import io +import numpy as np + +import paddle + + +class LacDataset(object): + """ + Load lexical analysis dataset + """ + + def __init__(self, args): + self.word_dict_path = args.word_dict_path + self.label_dict_path = args.label_dict_path + self.word_rep_dict_path = args.word_rep_dict_path + self._load_dict() + + def _load_dict(self): + self.word2id_dict = self.load_kv_dict( + self.word_dict_path, reverse=True, value_func=np.int64) + self.id2word_dict = self.load_kv_dict(self.word_dict_path) + self.label2id_dict = self.load_kv_dict( + self.label_dict_path, reverse=True, value_func=np.int64) + self.id2label_dict = self.load_kv_dict(self.label_dict_path) + if self.word_rep_dict_path is None: + self.word_replace_dict = dict() + else: + self.word_replace_dict = self.load_kv_dict(self.word_rep_dict_path) + + def load_kv_dict(self, + dict_path, + reverse=False, + delimiter="\t", + key_func=None, + value_func=None): + """ + Load key-value dict from file + """ + result_dict = {} + for line in io.open(dict_path, "r", encoding='utf8'): + terms = line.strip("\n").split(delimiter) + if len(terms) != 2: + continue + if reverse: + value, key = terms + else: + key, value = terms + if key in result_dict: + raise KeyError("key duplicated with [%s]" % (key)) + if key_func: + key = key_func(key) + if value_func: + value = value_func(value) + result_dict[key] = value + return result_dict + + @property + def vocab_size(self): + return max(self.word2id_dict.values()) + 1 + + @property + def num_labels(self): + return max(self.label2id_dict.values()) + 1 + + def get_num_examples(self, filename): + """num of line of file""" + return sum(1 for line in io.open(filename, "r", encoding='utf8')) + + def word_to_ids(self, words): + """convert word to word index""" + word_ids = [] + for word in words: + word = self.word_replace_dict.get(word, word) + if word not in self.word2id_dict: + word = "OOV" + word_id = self.word2id_dict[word] + word_ids.append(word_id) + + return word_ids + + def label_to_ids(self, labels): + """convert label to label index""" + label_ids = [] + for label in labels: + if label not in self.label2id_dict: + label = "O" + label_id = self.label2id_dict[label] + label_ids.append(label_id) + return label_ids + + def file_reader(self, + filename, + mode="train", + batch_size=32, + max_seq_len=126): + """ + yield (word_idx, target_idx) one by one from file, + or yield (word_idx, ) in `infer` mode + """ + + def wrapper(): + fread = io.open(filename, "r", encoding="utf-8") + if mode == "train": + headline = next(fread) + headline = headline.strip().split('\t') + assert len(headline) == 2 and headline[0] == "text_a" and headline[ + 1] == "label" + buf = [] + for line in fread: + words, labels = line.strip("\n").split("\t") + if len(words) < 1: + continue + word_ids = self.word_to_ids(words.split("\002")) + label_ids = self.label_to_ids(labels.split("\002")) + assert len(word_ids) == len(label_ids) + words_len = np.int64(len(word_ids)) + + word_ids = word_ids[0:max_seq_len] + words_len = np.int64(len(word_ids)) + word_ids += [0 for _ in range(max_seq_len - words_len)] + label_ids = label_ids[0:max_seq_len] + label_ids += [0 for _ in range(max_seq_len - words_len)] + assert len(word_ids) == len(label_ids) + yield word_ids, label_ids, words_len + elif mode == "test": + headline = next(fread) + headline = headline.strip().split('\t') + assert len(headline) == 2 and headline[0] == "text_a" and headline[ + 1] == "label" + buf = [] + for line in fread: + words, labels = line.strip("\n").split("\t") + if len(words) < 1: + continue + word_ids = self.word_to_ids(words.split("\002")) + label_ids = self.label_to_ids(labels.split("\002")) + assert len(word_ids) == len(label_ids) + words_len = np.int64(len(word_ids)) + yield word_ids, label_ids, words_len + else: + for line in fread: + words = line.strip("\n").split('\t')[0] + if words == u"text_a": + continue + if "\002" not in words: + word_ids = self.word_to_ids(words) + else: + word_ids = self.word_to_ids(words.split("\002")) + words_len = np.int64(len(word_ids)) + yield word_ids, words_len + + fread.close() + + return wrapper + + +def create_lexnet_data_generator(args, reader, file_name, place, mode="train"): + def padding_data(max_len, batch_data): + padding_batch_data = [] + for data in batch_data: + data += [0 for _ in range(max_len - len(data))] + padding_batch_data.append(data) + return padding_batch_data + + def wrapper(): + if mode == "train": + batch_words, batch_labels, seq_lens = [], [], [] + for epoch in xrange(args.epoch): + for instance in reader.file_reader( + file_name, mode, max_seq_len=args.max_seq_len)(): + words, labels, words_len = instance + if len(seq_lens) < args.batch_size: + batch_words.append(words) + batch_labels.append(labels) + seq_lens.append(words_len) + if len(seq_lens) == args.batch_size: + yield batch_words, seq_lens, batch_labels, batch_labels + batch_words, batch_labels, seq_lens = [], [], [] + + if len(seq_lens) > 0: + yield batch_words, seq_lens, batch_labels, batch_labels + elif mode == "test": + batch_words, batch_labels, seq_lens, max_len = [], [], [], 0 + for instance in reader.file_reader( + file_name, mode, max_seq_len=args.max_seq_len)(): + words, labels, words_len = instance + max_len = words_len if words_len > max_len else max_len + if len(seq_lens) < args.batch_size: + batch_words.append(words) + seq_lens.append(words_len) + batch_labels.append(labels) + if len(seq_lens) == args.batch_size: + padding_batch_words = padding_data(max_len, batch_words) + padding_batch_labels = padding_data(max_len, batch_labels) + yield padding_batch_words, seq_lens, padding_batch_labels, padding_batch_labels + batch_words, batch_labels, seq_lens, max_len = [], [], [], 0 + if len(seq_lens) > 0: + padding_batch_words = padding_data(max_len, batch_words) + padding_batch_labels = padding_data(max_len, batch_labels) + yield padding_batch_words, seq_lens, padding_batch_labels, padding_batch_labels + + else: + batch_words, seq_lens, max_len = [], [], 0 + for instance in reader.file_reader( + file_name, mode, max_seq_len=args.max_seq_len)(): + words, words_len = instance + if len(seq_lens) < args.batch_size: + batch_words.append(words) + seq_lens.append(words_len) + max_len = words_len if words_len > max_len else max_len + if len(seq_lens) == args.batch_size: + padding_batch_words = padding_data(max_len, batch_words) + yield padding_batch_words, seq_lens + batch_words, seq_lens, max_len = [], [], 0 + if len(seq_lens) > 0: + padding_batch_words = padding_data(max_len, batch_words) + yield padding_batch_words, seq_lens + + return wrapper + + +def create_dataloader(generator, place, feed_list=None): + if not feed_list: + data_loader = paddle.io.DataLoader.from_generator( + capacity=50, + use_double_buffer=True, + iterable=True, + return_list=True) + else: + data_loader = paddle.io.DataLoader.from_generator( + feed_list=feed_list, + capacity=50, + use_double_buffer=True, + iterable=True, + return_list=True) + data_loader.set_batch_generator(generator, places=place) + return data_loader + + diff --git a/examples/sequence_tagging/sequence_tagging.yaml b/examples/sequence_tagging/sequence_tagging.yaml new file mode 100644 index 0000000000000000000000000000000000000000..feb0ce13c20aac64af5ddd85980de32c86b7a1d0 --- /dev/null +++ b/examples/sequence_tagging/sequence_tagging.yaml @@ -0,0 +1,25 @@ +word_dict_path: "./conf/word.dic" +label_dict_path: "./conf/tag.dic" +word_rep_dict_path: "./conf/q2b.dic" +device: "cpu" +dynamic: True +epoch: 10 +base_learning_rate: 0.001 +word_emb_dim: 128 +grnn_hidden_dim: 128 +bigru_num: 2 +emb_learning_rate: 1.0 +crf_learning_rate: 1.0 +batch_size: 300 +max_seq_len: 126 +num_devices: 1 +save_dir: "model" +init_from_checkpoint: "model_baseline/params" +init_from_pretrain_model: "" +save_freq: 1 +eval_freq: 1 +output_file: "predict.result" +test_file: "./data/test.tsv" +train_file: "./data/train.tsv" +predict_file: "./data/infer.tsv" +mode: "train" diff --git a/examples/sequence_tagging/train.py b/examples/sequence_tagging/train.py new file mode 100644 index 0000000000000000000000000000000000000000..947bf370e9de22ddde4127c22431baf1a8b0248d --- /dev/null +++ b/examples/sequence_tagging/train.py @@ -0,0 +1,271 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +SequenceTagging network structure +""" + +from __future__ import division +from __future__ import print_function + +import io +import os +import sys +import math +import argparse +import numpy as np + +work_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.join(work_dir, "../")) + + +from hapi.metrics import Metric +from hapi.model import Model, Input, Loss, set_device +from hapi.text.text import SequenceTagging + +from utils.check import check_gpu, check_version +from utils.configure import PDConfig +from reader import LacDataset, create_lexnet_data_generator, create_dataloader + +import paddle.fluid as fluid +from paddle.fluid.optimizer import AdamOptimizer + + +class SeqTagging(Model): + def __init__(self, args, vocab_size, num_labels, length=None): + super(SeqTagging, self).__init__() + """ + define the lexical analysis network structure + word: stores the input of the model + for_infer: a boolean value, indicating if the model to be created is for training or predicting. + + return: + for infer: return the prediction + otherwise: return the prediction + """ + self.mode_type = args.mode + self.word_emb_dim = args.word_emb_dim + self.vocab_size = vocab_size + self.num_labels = num_labels + self.grnn_hidden_dim = args.grnn_hidden_dim + self.emb_lr = args.emb_learning_rate if 'emb_learning_rate' in dir( + args) else 1.0 + self.crf_lr = args.emb_learning_rate if 'crf_learning_rate' in dir( + args) else 1.0 + self.bigru_num = args.bigru_num + self.batch_size = args.batch_size + self.init_bound = 0.1 + self.length=length + + self.sequence_tagging = SequenceTagging( + vocab_size=self.vocab_size, + num_labels=self.num_labels, + batch_size=self.batch_size, + word_emb_dim=self.word_emb_dim, + grnn_hidden_dim=self.grnn_hidden_dim, + emb_learning_rate=self.emb_lr, + crf_learning_rate=self.crf_lr, + bigru_num=self.bigru_num, + init_bound=self.init_bound, + length=self.length) + + def forward(self, *inputs): + """ + Configure the network + """ + word = inputs[0] + lengths = inputs[1] + if self.mode_type == "train" or self.mode_type == "test": + target = inputs[2] + outputs = self.sequence_tagging(word, lengths, target) + else: + outputs = self.sequence_tagging(word, lengths) + return outputs + + +class Chunk_eval(fluid.dygraph.Layer): + def __init__(self, + num_chunk_types, + chunk_scheme, + excluded_chunk_types=None): + super(Chunk_eval, self).__init__() + self.num_chunk_types = num_chunk_types + self.chunk_scheme = chunk_scheme + self.excluded_chunk_types = excluded_chunk_types + + def forward(self, input, label, seq_length=None): + precision = self._helper.create_variable_for_type_inference( + dtype="float32") + recall = self._helper.create_variable_for_type_inference( + dtype="float32") + f1_score = self._helper.create_variable_for_type_inference( + dtype="float32") + num_infer_chunks = self._helper.create_variable_for_type_inference( + dtype="int64") + num_label_chunks = self._helper.create_variable_for_type_inference( + dtype="int64") + num_correct_chunks = self._helper.create_variable_for_type_inference( + dtype="int64") + this_input = {"Inference": input, "Label": label} + if seq_length is not None: + this_input["SeqLength"] = seq_length + self._helper.append_op( + type='chunk_eval', + inputs=this_input, + outputs={ + "Precision": [precision], + "Recall": [recall], + "F1-Score": [f1_score], + "NumInferChunks": [num_infer_chunks], + "NumLabelChunks": [num_label_chunks], + "NumCorrectChunks": [num_correct_chunks] + }, + attrs={ + "num_chunk_types": self.num_chunk_types, + "chunk_scheme": self.chunk_scheme, + "excluded_chunk_types": self.excluded_chunk_types or [] + }) + return (num_infer_chunks, num_label_chunks, num_correct_chunks) + + +class LacLoss(Loss): + def __init__(self): + super(LacLoss, self).__init__() + pass + + def forward(self, outputs, labels): + avg_cost = outputs[1] + return avg_cost + + +class ChunkEval(Metric): + def __init__(self, num_labels, name=None, *args, **kwargs): + super(ChunkEval, self).__init__(*args, **kwargs) + self._init_name(name) + self.chunk_eval = Chunk_eval( + int(math.ceil((num_labels - 1) / 2.0)), "IOB") + self.reset() + + def add_metric_op(self, *args): + crf_decode = args[0] + lengths = args[2] + label = args[3] + (num_infer_chunks, num_label_chunks, + num_correct_chunks) = self.chunk_eval( + input=crf_decode, label=label, seq_length=lengths) + return [num_infer_chunks, num_label_chunks, num_correct_chunks] + + def update(self, num_infer_chunks, num_label_chunks, num_correct_chunks, + *args, **kwargs): + self.infer_chunks_total += num_infer_chunks + self.label_chunks_total += num_label_chunks + self.correct_chunks_total += num_correct_chunks + precision = float( + num_correct_chunks) / num_infer_chunks if num_infer_chunks else 0 + recall = float( + num_correct_chunks) / num_label_chunks if num_label_chunks else 0 + f1_score = float(2 * precision * recall) / ( + precision + recall) if num_correct_chunks else 0 + return [precision, recall, f1_score] + + def reset(self): + self.infer_chunks_total = 0 + self.label_chunks_total = 0 + self.correct_chunks_total = 0 + + def accumulate(self): + precision = float( + self.correct_chunks_total + ) / self.infer_chunks_total if self.infer_chunks_total else 0 + recall = float( + self.correct_chunks_total + ) / self.label_chunks_total if self.label_chunks_total else 0 + f1_score = float(2 * precision * recall) / ( + precision + recall) if self.correct_chunks_total else 0 + res = [precision, recall, f1_score] + return res + + def _init_name(self, name): + name = name or 'chunk eval' + self._name = ['precision', 'recall', 'F1'] + + def name(self): + return self._name + + +def main(args): + place = set_device(args.device) + fluid.enable_dygraph(place) if args.dynamic else None + + inputs = [Input([None, None], 'int64', name='words'), + Input([None], 'int64', name='length'), + Input([None, None], 'int64', name='target')] + + labels = [Input([None, None], 'int64', name='labels')] + + feed_list = None if args.dynamic else [x.forward() for x in inputs + labels] + dataset = LacDataset(args) + train_path = args.train_file + test_path = args.test_file + + train_generator = create_lexnet_data_generator( + args, reader=dataset, file_name=train_path, place=place, mode="train") + test_generator = create_lexnet_data_generator( + args, reader=dataset, file_name=test_path, place=place, mode="test") + + train_dataset = create_dataloader( + train_generator, place, feed_list=feed_list) + test_dataset = create_dataloader( + test_generator, place, feed_list=feed_list) + + vocab_size = dataset.vocab_size + num_labels = dataset.num_labels + model = SeqTagging(args, vocab_size, num_labels) + + optim = AdamOptimizer( + learning_rate=args.base_learning_rate, + parameter_list=model.parameters()) + + model.prepare( + optim, + LacLoss(), + ChunkEval(num_labels), + inputs=inputs, + labels=labels, + device=args.device) + + if args.init_from_checkpoint: + model.load(args.init_from_checkpoint) + + if args.init_from_pretrain_model: + model.load(args.init_from_pretrain_model, reset_optimizer=True) + + model.fit(train_dataset, + test_dataset, + epochs=args.epoch, + batch_size=args.batch_size, + eval_freq=args.eval_freq, + save_freq=args.save_freq, + save_dir=args.save_dir) + + +if __name__ == '__main__': + args = PDConfig(yaml_file="sequence_tagging.yaml") + args.build() + args.Print() + + use_gpu = True if args.device == "gpu" else False + check_gpu(use_gpu) + check_version() + + main(args) diff --git a/examples/sequence_tagging/utils/__init__.py b/examples/sequence_tagging/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/examples/sequence_tagging/utils/check.py b/examples/sequence_tagging/utils/check.py new file mode 100644 index 0000000000000000000000000000000000000000..79ab4862d3c2082c36039b047be08d4a4b5dcedd --- /dev/null +++ b/examples/sequence_tagging/utils/check.py @@ -0,0 +1,58 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import sys + +import paddle.fluid as fluid + +__all__ = ['check_gpu', 'check_version'] + + +def check_gpu(use_gpu): + """ + Log error and exit when set use_gpu=true in paddlepaddle + cpu version. + """ + err = "Config use_gpu cannot be set as true while you are " \ + "using paddlepaddle cpu version ! \nPlease try: \n" \ + "\t1. Install paddlepaddle-gpu to run model on GPU \n" \ + "\t2. Set use_gpu as false in config file to run " \ + "model on CPU" + + try: + if use_gpu and not fluid.is_compiled_with_cuda(): + print(err) + sys.exit(1) + except Exception as e: + pass + + +def check_version(): + """ + Log error and exit when the installed version of paddlepaddle is + not satisfied. + """ + err = "PaddlePaddle version 1.6 or higher is required, " \ + "or a suitable develop version is satisfied as well. \n" \ + "Please make sure the version is good with your code." \ + + try: + fluid.require_version('1.7.0') + except Exception as e: + print(err) + sys.exit(1) diff --git a/examples/sequence_tagging/utils/configure.py b/examples/sequence_tagging/utils/configure.py new file mode 100644 index 0000000000000000000000000000000000000000..67e601282fee572518435eaed38a4ed8e26fc5f9 --- /dev/null +++ b/examples/sequence_tagging/utils/configure.py @@ -0,0 +1,350 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import argparse +import json +import yaml +import six +import logging + +logging_only_message = "%(message)s" +logging_details = "%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s" + + +class JsonConfig(object): + """ + A high-level api for handling json configure file. + """ + + def __init__(self, config_path): + self._config_dict = self._parse(config_path) + + def _parse(self, config_path): + try: + with open(config_path) as json_file: + config_dict = json.load(json_file) + except: + raise IOError("Error in parsing bert model config file '%s'" % + config_path) + else: + return config_dict + + def __getitem__(self, key): + return self._config_dict[key] + + def print_config(self): + for arg, value in sorted(six.iteritems(self._config_dict)): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + + +class ArgumentGroup(object): + def __init__(self, parser, title, des): + self._group = parser.add_argument_group(title=title, description=des) + + def add_arg(self, name, type, default, help, **kwargs): + type = str2bool if type == bool else type + self._group.add_argument( + "--" + name, + default=default, + type=type, + help=help + ' Default: %(default)s.', + **kwargs) + + +class ArgConfig(object): + """ + A high-level api for handling argument configs. + """ + + def __init__(self): + parser = argparse.ArgumentParser() + + train_g = ArgumentGroup(parser, "training", "training options.") + train_g.add_arg("epoch", int, 3, "Number of epoches for fine-tuning.") + train_g.add_arg("learning_rate", float, 5e-5, + "Learning rate used to train with warmup.") + train_g.add_arg( + "lr_scheduler", + str, + "linear_warmup_decay", + "scheduler of learning rate.", + choices=['linear_warmup_decay', 'noam_decay']) + train_g.add_arg("weight_decay", float, 0.01, + "Weight decay rate for L2 regularizer.") + train_g.add_arg( + "warmup_proportion", float, 0.1, + "Proportion of training steps to perform linear learning rate warmup for." + ) + train_g.add_arg("save_steps", int, 1000, + "The steps interval to save checkpoints.") + train_g.add_arg("use_fp16", bool, False, + "Whether to use fp16 mixed precision training.") + train_g.add_arg( + "loss_scaling", float, 1.0, + "Loss scaling factor for mixed precision training, only valid when use_fp16 is enabled." + ) + train_g.add_arg("pred_dir", str, None, + "Path to save the prediction results") + + log_g = ArgumentGroup(parser, "logging", "logging related.") + log_g.add_arg("skip_steps", int, 10, + "The steps interval to print loss.") + log_g.add_arg("verbose", bool, False, "Whether to output verbose log.") + + run_type_g = ArgumentGroup(parser, "run_type", "running type options.") + run_type_g.add_arg("use_cuda", bool, True, + "If set, use GPU for training.") + run_type_g.add_arg( + "use_fast_executor", bool, False, + "If set, use fast parallel executor (in experiment).") + run_type_g.add_arg( + "num_iteration_per_drop_scope", int, 1, + "Ihe iteration intervals to clean up temporary variables.") + run_type_g.add_arg("do_train", bool, True, + "Whether to perform training.") + run_type_g.add_arg("do_predict", bool, True, + "Whether to perform prediction.") + + custom_g = ArgumentGroup(parser, "customize", "customized options.") + + self.custom_g = custom_g + + self.parser = parser + + def add_arg(self, name, dtype, default, descrip): + self.custom_g.add_arg(name, dtype, default, descrip) + + def build_conf(self): + return self.parser.parse_args() + + +def str2bool(v): + # because argparse does not support to parse "true, False" as python + # boolean directly + return v.lower() in ("true", "t", "1") + + +def print_arguments(args, log=None): + if not log: + print('----------- Configuration Arguments -----------') + for arg, value in sorted(six.iteritems(vars(args))): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + else: + log.info('----------- Configuration Arguments -----------') + for arg, value in sorted(six.iteritems(vars(args))): + log.info('%s: %s' % (arg, value)) + log.info('------------------------------------------------') + + +class PDConfig(object): + """ + A high-level API for managing configuration files in PaddlePaddle. + Can jointly work with command-line-arugment, json files and yaml files. + """ + + def __init__(self, json_file="", yaml_file="", fuse_args=True): + """ + Init funciton for PDConfig. + json_file: the path to the json configure file. + yaml_file: the path to the yaml configure file. + fuse_args: if fuse the json/yaml configs with argparse. + """ + assert isinstance(json_file, str) + assert isinstance(yaml_file, str) + + if json_file != "" and yaml_file != "": + raise Warning( + "json_file and yaml_file can not co-exist for now. please only use one configure file type." + ) + return + + self.args = None + self.arg_config = {} + self.json_config = {} + self.yaml_config = {} + + parser = argparse.ArgumentParser() + + self.default_g = ArgumentGroup(parser, "default", "default options.") + self.yaml_g = ArgumentGroup(parser, "yaml", "options from yaml.") + self.json_g = ArgumentGroup(parser, "json", "options from json.") + self.com_g = ArgumentGroup(parser, "custom", "customized options.") + + self.default_g.add_arg("do_train", bool, False, + "Whether to perform training.") + self.default_g.add_arg("do_predict", bool, False, + "Whether to perform predicting.") + self.default_g.add_arg("do_eval", bool, False, + "Whether to perform evaluating.") + self.default_g.add_arg("do_save_inference_model", bool, False, + "Whether to perform model saving for inference.") + + # NOTE: args for profiler + self.default_g.add_arg("is_profiler", int, 0, "the switch of profiler tools. (used for benchmark)") + self.default_g.add_arg("profiler_path", str, './', "the profiler output file path. (used for benchmark)") + self.default_g.add_arg("max_iter", int, 0, "the max train batch num.(used for benchmark)") + + self.parser = parser + + if json_file != "": + self.load_json(json_file, fuse_args=fuse_args) + + if yaml_file: + self.load_yaml(yaml_file, fuse_args=fuse_args) + + def load_json(self, file_path, fuse_args=True): + + if not os.path.exists(file_path): + raise Warning("the json file %s does not exist." % file_path) + return + + with open(file_path, "r") as fin: + self.json_config = json.loads(fin.read()) + fin.close() + + if fuse_args: + for name in self.json_config: + if isinstance(self.json_config[name], list): + self.json_g.add_arg( + name, + type(self.json_config[name][0]), + self.json_config[name], + "This is from %s" % file_path, + nargs=len(self.json_config[name])) + continue + if not isinstance(self.json_config[name], int) \ + and not isinstance(self.json_config[name], float) \ + and not isinstance(self.json_config[name], str) \ + and not isinstance(self.json_config[name], bool): + + continue + + self.json_g.add_arg(name, + type(self.json_config[name]), + self.json_config[name], + "This is from %s" % file_path) + + def load_yaml(self, file_path, fuse_args=True): + + if not os.path.exists(file_path): + raise Warning("the yaml file %s does not exist." % file_path) + return + + with open(file_path, "r") as fin: + self.yaml_config = yaml.load(fin, Loader=yaml.SafeLoader) + fin.close() + + if fuse_args: + for name in self.yaml_config: + if isinstance(self.yaml_config[name], list): + self.yaml_g.add_arg( + name, + type(self.yaml_config[name][0]), + self.yaml_config[name], + "This is from %s" % file_path, + nargs=len(self.yaml_config[name])) + continue + + if not isinstance(self.yaml_config[name], int) \ + and not isinstance(self.yaml_config[name], float) \ + and not isinstance(self.yaml_config[name], str) \ + and not isinstance(self.yaml_config[name], bool): + + continue + + self.yaml_g.add_arg(name, + type(self.yaml_config[name]), + self.yaml_config[name], + "This is from %s" % file_path) + + def build(self): + self.args = self.parser.parse_args() + self.arg_config = vars(self.args) + + def __add__(self, new_arg): + assert isinstance(new_arg, list) or isinstance(new_arg, tuple) + assert len(new_arg) >= 3 + assert self.args is None + + name = new_arg[0] + dtype = new_arg[1] + dvalue = new_arg[2] + desc = new_arg[3] if len( + new_arg) == 4 else "Description is not provided." + + self.com_g.add_arg(name, dtype, dvalue, desc) + + return self + + def __getattr__(self, name): + if name in self.arg_config: + return self.arg_config[name] + + if name in self.json_config: + return self.json_config[name] + + if name in self.yaml_config: + return self.yaml_config[name] + + raise Warning("The argument %s is not defined." % name) + + def Print(self): + + print("-" * 70) + for name in self.arg_config: + print("%s:\t\t\t\t%s" % (str(name), str(self.arg_config[name]))) + + for name in self.json_config: + if name not in self.arg_config: + print("%s:\t\t\t\t%s" % + (str(name), str(self.json_config[name]))) + + for name in self.yaml_config: + if name not in self.arg_config: + print("%s:\t\t\t\t%s" % + (str(name), str(self.yaml_config[name]))) + + print("-" * 70) + + +if __name__ == "__main__": + """ + pd_config = PDConfig(json_file = "./test/bert_config.json") + pd_config.build() + + print(pd_config.do_train) + print(pd_config.hidden_size) + + pd_config = PDConfig(yaml_file = "./test/bert_config.yaml") + pd_config.build() + + print(pd_config.do_train) + print(pd_config.hidden_size) + """ + + pd_config = PDConfig(yaml_file="./test/bert_config.yaml") + pd_config += ("my_age", int, 18, "I am forever 18.") + pd_config.build() + + print(pd_config.do_train) + print(pd_config.hidden_size) + print(pd_config.my_age) diff --git a/examples/sequence_tagging/utils/metrics.py b/examples/sequence_tagging/utils/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..a7e01b91e1cd7e7cee71f570aef1d722b0c4770b --- /dev/null +++ b/examples/sequence_tagging/utils/metrics.py @@ -0,0 +1,76 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import sys + +import paddle.fluid as fluid + +__all__ = ['chunk_count', "build_chunk"] + + +def build_chunk(data_list, id2label_dict): + """ + Assembly entity + """ + tag_list = [id2label_dict.get(str(id)) for id in data_list] + ner_dict = {} + ner_str = "" + ner_start = 0 + for i in range(len(tag_list)): + tag = tag_list[i] + if tag == u"O": + if i != 0: + key = "%d_%d" % (ner_start, i - 1) + ner_dict[key] = ner_str + ner_start = i + ner_str = tag + elif tag.endswith(u"B"): + if i != 0: + key = "%d_%d" % (ner_start, i - 1) + ner_dict[key] = ner_str + ner_start = i + ner_str = tag.split('-')[0] + elif tag.endswith(u"I"): + if tag.split('-')[0] != ner_str: + if i != 0: + key = "%d_%d" % (ner_start, i - 1) + ner_dict[key] = ner_str + ner_start = i + ner_str = tag.split('-')[0] + return ner_dict + + +def chunk_count(infer_numpy, label_numpy, seq_len, id2label_dict): + """ + calculate num_correct_chunks num_error_chunks total_num for metrics + """ + num_infer_chunks, num_label_chunks, num_correct_chunks = 0, 0, 0 + assert infer_numpy.shape[0] == label_numpy.shape[0] + + for i in range(infer_numpy.shape[0]): + infer_list = infer_numpy[i][: seq_len[i]] + label_list = label_numpy[i][: seq_len[i]] + infer_dict = build_chunk(infer_list, id2label_dict) + num_infer_chunks += len(infer_dict) + label_dict = build_chunk(label_list, id2label_dict) + num_label_chunks += len(label_dict) + for key in infer_dict: + if key in label_dict and label_dict[key] == infer_dict[key]: + num_correct_chunks += 1 + return num_infer_chunks, num_label_chunks, num_correct_chunks + diff --git a/tsm/README.md b/examples/tsm/README.md similarity index 100% rename from tsm/README.md rename to examples/tsm/README.md diff --git a/tsm/check.py b/examples/tsm/check.py similarity index 100% rename from tsm/check.py rename to examples/tsm/check.py diff --git a/tsm/dataset/README.md b/examples/tsm/dataset/README.md similarity index 100% rename from tsm/dataset/README.md rename to examples/tsm/dataset/README.md diff --git a/tsm/dataset/kinetics/generate_label.py b/examples/tsm/dataset/kinetics/generate_label.py similarity index 100% rename from tsm/dataset/kinetics/generate_label.py rename to examples/tsm/dataset/kinetics/generate_label.py diff --git a/tsm/dataset/kinetics/video2pkl.py b/examples/tsm/dataset/kinetics/video2pkl.py similarity index 100% rename from tsm/dataset/kinetics/video2pkl.py rename to examples/tsm/dataset/kinetics/video2pkl.py diff --git a/tsm/images/temporal_shift.png b/examples/tsm/images/temporal_shift.png similarity index 100% rename from tsm/images/temporal_shift.png rename to examples/tsm/images/temporal_shift.png diff --git a/tsm/infer.py b/examples/tsm/infer.py similarity index 97% rename from tsm/infer.py rename to examples/tsm/infer.py index 78dbe2cc6ab92dc2a85fee8f186b1b1ae8d74fdd..a41b667a71ab1188077ad1b44b259841e55a8f4d 100644 --- a/tsm/infer.py +++ b/examples/tsm/infer.py @@ -19,10 +19,10 @@ import os import argparse import numpy as np -from model import Input, set_device -from models import tsm_resnet50 +from hapi.model import Input, set_device from check import check_gpu, check_version +from modeling import tsm_resnet50 from kinetics_dataset import KineticsDataset from transforms import * diff --git a/tsm/kinetics_dataset.py b/examples/tsm/kinetics_dataset.py similarity index 90% rename from tsm/kinetics_dataset.py rename to examples/tsm/kinetics_dataset.py index 7e07543f37392744a2bf82ecc9b038e78d2d5524..123d89814a8c631569cd0503750cafac631cca22 100644 --- a/tsm/kinetics_dataset.py +++ b/examples/tsm/kinetics_dataset.py @@ -26,7 +26,7 @@ except ImportError: import pickle from io import BytesIO -from paddle.fluid.io import Dataset +from paddle.io import Dataset import logging logger = logging.getLogger(__name__) @@ -100,19 +100,12 @@ class KineticsDataset(Dataset): def __getitem__(self, idx): pickle_path = os.path.join(self.pickle_dir, self.pickle_paths[idx]) - try: - if six.PY2: - data = pickle.load(open(pickle_path, 'rb')) - else: - data = pickle.load(open(pickle_path, 'rb'), encoding='bytes') - - vid, label, frames = data - if len(frames) < 1: - logger.error("{} contains no frame".format(pickle_path)) - sys.exit(-1) - except Exception as e: - logger.error("Load {} failed: {}".format(pickle_path, e)) - sys.exit(-1) + if six.PY2: + data = pickle.load(open(pickle_path, 'rb')) + else: + data = pickle.load(open(pickle_path, 'rb'), encoding='bytes') + + vid, label, frames = data if self.label_list is not None: label = self.label_list.index(label) diff --git a/tsm/main.py b/examples/tsm/main.py similarity index 97% rename from tsm/main.py rename to examples/tsm/main.py index 07868dbdc43565341b19ef6fe69c693f812c6258..4a2b8890fdffbb33ce9c776b189d0b4ac14b1816 100644 --- a/tsm/main.py +++ b/examples/tsm/main.py @@ -22,10 +22,10 @@ import numpy as np from paddle import fluid from paddle.fluid.dygraph.parallel import ParallelEnv -from model import Model, CrossEntropy, Input, set_device -from metrics import Accuracy -from models import tsm_resnet50 +from hapi.model import Model, CrossEntropy, Input, set_device +from hapi.metrics import Accuracy +from modeling import tsm_resnet50 from check import check_gpu, check_version from kinetics_dataset import KineticsDataset from transforms import * diff --git a/models/tsm.py b/examples/tsm/modeling.py similarity index 94% rename from models/tsm.py rename to examples/tsm/modeling.py index 91acd16b288e7e0803e0448f0e93a484b0b92c17..c2422ed3f1cf57e9fd029bb01e04e55d5296e918 100644 --- a/models/tsm.py +++ b/examples/tsm/modeling.py @@ -17,8 +17,8 @@ import paddle.fluid as fluid from paddle.fluid.layer_helper import LayerHelper from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear -from model import Model -from .download import get_weights_path +from hapi.model import Model +from hapi.download import get_weights_path __all__ = ["TSM_ResNet", "tsm_resnet50"] @@ -196,9 +196,17 @@ def _tsm_resnet(num_layers, seg_num=8, num_classes=400, pretrained=True): weight_path = get_weights_path(*(pretrain_infos[num_layers])) assert weight_path.endswith('.pdparams'), \ "suffix of weight must be .pdparams" - model.load(weight_path[:-9]) + model.load(weight_path) return model def tsm_resnet50(seg_num=8, num_classes=400, pretrained=True): + """TSM model with 50-layer ResNet as backbone + + Args: + seg_num (int): segment number of each video sample. Default 8. + num_classes (int): video class number. Default 400. + pretrained (bool): If True, returns a model with pre-trained model + on COCO, default True + """ return _tsm_resnet(50, seg_num, num_classes, pretrained) diff --git a/tsm/transforms.py b/examples/tsm/transforms.py similarity index 100% rename from tsm/transforms.py rename to examples/tsm/transforms.py diff --git a/examples/yolov3/.gitignore b/examples/yolov3/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..19ac4b2c67adf15aaa730c8b9823a2b5c2da8780 --- /dev/null +++ b/examples/yolov3/.gitignore @@ -0,0 +1,2 @@ +dataset/voc* +pretrain_weights/darknet53_pretrained.pdparams diff --git a/yolov3/README.md b/examples/yolov3/README.md similarity index 99% rename from yolov3/README.md rename to examples/yolov3/README.md index cc6d302a544f9b8d9cd06fc363b90d053919a5e9..fdbfc569be5040137758407ba5dc2ca26e6cc594 100644 --- a/yolov3/README.md +++ b/examples/yolov3/README.md @@ -101,11 +101,10 @@ YOLOv3 的网络结构由基础特征提取网络、multi-scale特征融合层 ### 模型训练 -数据准备完毕后,可使用`main.py`脚本启动训练和评估,如下脚本会自动每epoch交替进行训练和模型评估,并将checkpoint默认保存在`yolo_checkpoint`目录下。 +数据准备完成后,可使用`main.py`脚本启动训练和评估,如下脚本会自动每epoch交替进行训练和模型评估,并将checkpoint默认保存在`yolo_checkpoint`目录下。 YOLOv3模型训练总batch_size为64训练,以下以使用4卡Tesla P40每卡batch_size为16训练介绍训练方式。对于静态图和动态图,多卡训练中`--batch_size`为每卡上的batch_size,即总batch_size为`--batch_size`乘以卡数。 - `main.py`脚本参数可通过如下命令查询 ```bash diff --git a/yolov3/coco.py b/examples/yolov3/coco.py similarity index 92% rename from yolov3/coco.py rename to examples/yolov3/coco.py index 34809246c1f90d3ad029842c19ae5f2c3eba08b0..50d31cff06692e30fb153983023d4c8ed7476f2c 100644 --- a/yolov3/coco.py +++ b/examples/yolov3/coco.py @@ -18,9 +18,8 @@ from __future__ import print_function import os import cv2 import numpy as np -from pycocotools.coco import COCO -from paddle.fluid.io import Dataset +from paddle.io import Dataset import logging logger = logging.getLogger(__name__) @@ -91,6 +90,7 @@ class COCODataset(Dataset): self._load_roidb_and_cname2cid() def _load_roidb_and_cname2cid(self): + from pycocotools.coco import COCO assert self._anno_path.endswith('.json'), \ 'invalid coco annotation file: ' + anno_path coco = COCO(self._anno_path) @@ -186,30 +186,31 @@ class COCODataset(Dataset): data = np.frombuffer(f.read(), dtype='uint8') im = cv2.imdecode(data, 1) im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) - im_info = np.array([roidb['im_id'][0], roidb['h'], roidb['w']], dtype='int32') + im_id = roidb['im_id'] + im_shape = np.array([roidb['h'], roidb['w']], dtype='int32') gt_bbox = roidb['gt_bbox'] gt_class = roidb['gt_class'] gt_score = roidb['gt_score'] - return im_info, im, gt_bbox, gt_class, gt_score + return im_id, im_shape, im, gt_bbox, gt_class, gt_score def __getitem__(self, idx): - im_info, im, gt_bbox, gt_class, gt_score = self._getitem_by_index(idx) + im_id, im_shape, im, gt_bbox, gt_class, gt_score = self._getitem_by_index(idx) if self._mixup: mixup_idx = idx + np.random.randint(1, self.__len__()) mixup_idx %= self.__len__() - _, mixup_im, mixup_bbox, mixup_class, _ = \ + _, _, mixup_im, mixup_bbox, mixup_class, _ = \ self._getitem_by_index(mixup_idx) - im, gt_bbox, gt_class, gt_score = \ + im_shape, im, gt_bbox, gt_class, gt_score = \ self._mixup_image(im, gt_bbox, gt_class, mixup_im, mixup_bbox, mixup_class) if self._transform: - im_info, im, gt_bbox, gt_class, gt_score = \ - self._transform(im_info, im, gt_bbox, gt_class, gt_score) + im_id, im_shape, im, gt_bbox, gt_class, gt_score = \ + self._transform(im_id, im_shape, im, gt_bbox, gt_class, gt_score) - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] def _mixup_image(self, img1, bbox1, class1, img2, bbox2, class2): factor = np.random.beta(self._alpha, self._beta) @@ -234,7 +235,9 @@ class COCODataset(Dataset): score2 = np.ones_like(class2, dtype="float32") * (1.0 - factor) gt_score = np.concatenate((score1, score2), axis=0) - return img, gt_bbox, gt_class, gt_score + im_shape = np.array([h, w], dtype='int32') + + return im_shape, img, gt_bbox, gt_class, gt_score @property def mixup(self): diff --git a/yolov3/coco_metric.py b/examples/yolov3/coco_metric.py similarity index 100% rename from yolov3/coco_metric.py rename to examples/yolov3/coco_metric.py diff --git a/yolov3/dataset/download_voc.py b/examples/yolov3/dataset/download_voc.py similarity index 97% rename from yolov3/dataset/download_voc.py rename to examples/yolov3/dataset/download_voc.py index 8b064ed4034e5fa1471c8094a78266d531d9c111..9877d7cd6b4946c01f58476b6fe81c328005e711 100644 --- a/yolov3/dataset/download_voc.py +++ b/examples/yolov3/dataset/download_voc.py @@ -17,7 +17,7 @@ import os.path as osp import sys import tarfile -from models.download import _download +from hapi.download import _download import logging logger = logging.getLogger(__name__) diff --git a/yolov3/image/YOLOv3.jpg b/examples/yolov3/image/YOLOv3.jpg similarity index 100% rename from yolov3/image/YOLOv3.jpg rename to examples/yolov3/image/YOLOv3.jpg diff --git a/yolov3/image/YOLOv3_structure.jpg b/examples/yolov3/image/YOLOv3_structure.jpg similarity index 100% rename from yolov3/image/YOLOv3_structure.jpg rename to examples/yolov3/image/YOLOv3_structure.jpg diff --git a/yolov3/image/dog.jpg b/examples/yolov3/image/dog.jpg similarity index 100% rename from yolov3/image/dog.jpg rename to examples/yolov3/image/dog.jpg diff --git a/yolov3/infer.py b/examples/yolov3/infer.py similarity index 90% rename from yolov3/infer.py rename to examples/yolov3/infer.py index f19e86615a0b1c8c57f3469f5a5bdcaa85535e9c..8b0e3abd1843c3413f9756aa7db65cf2de16ef0a 100644 --- a/yolov3/infer.py +++ b/examples/yolov3/infer.py @@ -22,13 +22,13 @@ from PIL import Image from paddle import fluid from paddle.fluid.optimizer import Momentum -from paddle.fluid.io import DataLoader +from paddle.io import DataLoader -from model import Model, Input, set_device -from models import yolov3_darknet53, YoloLoss +from hapi.model import Model, Input, set_device -from coco import COCODataset +from modeling import yolov3_darknet53, YoloLoss from transforms import * + from visualizer import draw_bbox import logging @@ -65,7 +65,8 @@ def main(): device = set_device(FLAGS.device) fluid.enable_dygraph(device) if FLAGS.dynamic else None - inputs = [Input([None, 3], 'int32', name='img_info'), + inputs = [Input([None, 1], 'int64', name='img_id'), + Input([None, 2], 'int32', name='img_shape'), Input([None, 3, None, None], 'float32', name='image')] cat2name = load_labels(FLAGS.label_list, with_background=False) @@ -87,9 +88,10 @@ def main(): img -= np.array(IMAGE_MEAN) img /= np.array(IMAGE_STD) img = img.transpose((2, 0, 1))[np.newaxis, :] - img_info = np.array([0, h, w]).astype('int32')[np.newaxis, :] + img_id = np.array([0]).astype('int64')[np.newaxis, :] + img_shape = np.array([h, w]).astype('int32')[np.newaxis, :] - _, bboxes = model.test([img_info, img]) + _, bboxes = model.test([img_id, img_shape, img]) vis_img = draw_bbox(orig_img, cat2name, bboxes, FLAGS.draw_threshold) save_name = get_save_image_name(FLAGS.output_dir, FLAGS.infer_image) diff --git a/yolov3/main.py b/examples/yolov3/main.py similarity index 94% rename from yolov3/main.py rename to examples/yolov3/main.py index 18c24d196877586475f6aba1f949c3207665fcce..dea9eba5429a2878038aef11a9ca404696b2f7a8 100644 --- a/yolov3/main.py +++ b/examples/yolov3/main.py @@ -23,14 +23,15 @@ import numpy as np from paddle import fluid from paddle.fluid.optimizer import Momentum -from paddle.fluid.io import DataLoader +from paddle.io import DataLoader -from model import Model, Input, set_device -from distributed import DistributedBatchSampler -from models import yolov3_darknet53, YoloLoss +from hapi.model import Model, Input, set_device +from hapi.distributed import DistributedBatchSampler +from hapi.vision.transforms import Compose, BatchCompose -from coco_metric import COCOMetric +from modeling import yolov3_darknet53, YoloLoss from coco import COCODataset +from coco_metric import COCOMetric from transforms import * NUM_MAX_BOXES = 50 @@ -63,7 +64,8 @@ def main(): device = set_device(FLAGS.device) fluid.enable_dygraph(device) if FLAGS.dynamic else None - inputs = [Input([None, 3], 'int32', name='img_info'), + inputs = [Input([None, 1], 'int64', name='img_id'), + Input([None, 2], 'int32', name='img_shape'), Input([None, 3, None, None], 'float32', name='image')] labels = [Input([None, NUM_MAX_BOXES, 4], 'float32', name='gt_bbox'), Input([None, NUM_MAX_BOXES], 'int32', name='gt_label'), @@ -123,7 +125,7 @@ def main(): model_mode='eval' if FLAGS.eval_only else 'train', pretrained=pretrained) - if FLAGS.pretrain_weights is not None: + if FLAGS.pretrain_weights and not FLAGS.eval_only: model.load(FLAGS.pretrain_weights, skip_mismatch=True, reset_optimizer=True) optim = make_optimizer(len(batch_sampler), parameter_list=model.parameters()) @@ -163,7 +165,7 @@ def main(): save_dir="yolo_checkpoint/mixup", save_freq=10) - # do not use image mixup transfrom in laste FLAGS.no_mixup_epoch epoches + # do not use image mixup transfrom in the last FLAGS.no_mixup_epoch epoches dataset.mixup = False model.fit(train_data=loader, epochs=FLAGS.no_mixup_epoch, diff --git a/models/yolov3.py b/examples/yolov3/modeling.py similarity index 75% rename from models/yolov3.py rename to examples/yolov3/modeling.py index c2bbc88ee27cb08269bd2a986ff7b55b4f199999..be462f5afbca8b987775e63e52a7950d2c3d60fd 100644 --- a/models/yolov3.py +++ b/examples/yolov3/modeling.py @@ -16,13 +16,13 @@ from __future__ import division from __future__ import print_function import paddle.fluid as fluid -from paddle.fluid.dygraph.nn import Conv2D +from paddle.fluid.dygraph.nn import Conv2D, BatchNorm from paddle.fluid.param_attr import ParamAttr from paddle.fluid.regularizer import L2Decay -from model import Model, Loss -from .darknet import darknet53, ConvBNLayer -from .download import get_weights_path +from hapi.model import Model, Loss +from hapi.download import get_weights_path +from hapi.vision.models import darknet53 __all__ = ['YoloLoss', 'YOLOv3', 'yolov3_darknet53'] @@ -33,6 +33,46 @@ pretrain_infos = { } +class ConvBNLayer(fluid.dygraph.Layer): + def __init__(self, + ch_in, + ch_out, + filter_size=3, + stride=1, + groups=1, + padding=0, + act="leaky"): + super(ConvBNLayer, self).__init__() + + self.conv = Conv2D( + num_channels=ch_in, + num_filters=ch_out, + filter_size=filter_size, + stride=stride, + padding=padding, + groups=groups, + param_attr=ParamAttr( + initializer=fluid.initializer.Normal(0., 0.02)), + bias_attr=False, + act=None) + self.batch_norm = BatchNorm( + num_channels=ch_out, + param_attr=ParamAttr( + initializer=fluid.initializer.Normal(0., 0.02), + regularizer=L2Decay(0.)), + bias_attr=ParamAttr( + initializer=fluid.initializer.Constant(0.0), + regularizer=L2Decay(0.))) + + self.act = act + + def forward(self, inputs): + out = self.conv(inputs) + out = self.batch_norm(out) + if self.act == 'leaky': + out = fluid.layers.leaky_relu(x=out, alpha=0.1) + return out + class YoloDetectionBlock(fluid.dygraph.Layer): def __init__(self, ch_in, channel): super(YoloDetectionBlock, self).__init__() @@ -88,6 +128,20 @@ class YoloDetectionBlock(fluid.dygraph.Layer): class YOLOv3(Model): + """YOLOv3 model from + `"YOLOv3: An Incremental Improvement" `_ + + Args: + num_classes (int): class number, default 80. + model_mode (str): 'train', 'eval', 'test' mode, network structure + will be diffrent in the output layer and data, in 'train' mode, + no output layer append, in 'eval' and 'test', output feature + map will be decode to predictions by 'fluid.layers.yolo_box', + in 'eval' mode, return feature maps and predictions, in 'test' + mode, only return predictions. Default 'train'. + + """ + def __init__(self, num_classes=80, model_mode='train'): super(YOLOv3, self).__init__() self.num_classes = num_classes @@ -138,7 +192,7 @@ class YOLOv3(Model): act='leaky_relu')) self.route_blocks.append(route) - def forward(self, img_info, inputs): + def forward(self, img_id, img_shape, inputs): outputs = [] boxes = [] scores = [] @@ -163,8 +217,6 @@ class YOLOv3(Model): for m in anchor_mask: mask_anchors.append(self.anchors[2 * m]) mask_anchors.append(self.anchors[2 * m + 1]) - img_shape = fluid.layers.slice(img_info, axes=[1], starts=[1], ends=[3]) - img_id = fluid.layers.slice(img_info, axes=[1], starts=[0], ends=[1]) b, s = fluid.layers.yolo_box( x=block_out, img_size=img_shape, @@ -181,7 +233,7 @@ class YOLOv3(Model): if self.model_mode == 'train': return outputs - preds = [img_id[0, :], + preds = [img_id, fluid.layers.multiclass_nms( bboxes=fluid.layers.concat(boxes, axis=1), scores=fluid.layers.concat(scores, axis=2), @@ -242,9 +294,22 @@ def _yolov3_darknet(num_layers=53, num_classes=80, weight_path = get_weights_path(*(pretrain_infos[num_layers])) assert weight_path.endswith('.pdparams'), \ "suffix of weight must be .pdparams" - model.load(weight_path[:-9]) + model.load(weight_path) return model def yolov3_darknet53(num_classes=80, model_mode='train', pretrained=True): + """YOLOv3 model with 53-layer DarkNet as backbone + + Args: + num_classes (int): class number, default 80. + model_mode (str): 'train', 'eval', 'test' mode, network structure + will be diffrent in the output layer and data, in 'train' mode, + no output layer append, in 'eval' and 'test', output feature + map will be decode to predictions by 'fluid.layers.yolo_box', + in 'eval' mode, return feature maps and predictions, in 'test' + mode, only return predictions. Default 'train'. + pretrained (bool): If True, returns a model with pre-trained model + on COCO, default True + """ return _yolov3_darknet(53, num_classes, model_mode, pretrained) diff --git a/yolov3/transforms.py b/examples/yolov3/transforms.py similarity index 86% rename from yolov3/transforms.py rename to examples/yolov3/transforms.py index a5fbe46cbbfdb39efe3025a351b407b82dbf33c4..8d81c274dfb574bac52855cda95c970e4c8a444f 100644 --- a/yolov3/transforms.py +++ b/examples/yolov3/transforms.py @@ -19,48 +19,18 @@ import cv2 import traceback import numpy as np -import logging -logger = logging.getLogger(__name__) - -__all__ = ['ColorDistort', 'RandomExpand', 'RandomCrop', 'RandomFlip', - 'NormalizeBox', 'PadBox', 'RandomShape', 'NormalizeImage', - 'BboxXYXY2XYWH', 'ResizeImage', 'Compose', 'BatchCompose'] - - -class Compose(object): - def __init__(self, transforms=[]): - self.transforms = transforms - - def __call__(self, *data): - for f in self.transforms: - try: - data = f(*data) - except Exception as e: - stack_info = traceback.format_exc() - logger.info("fail to perform transform [{}] with error: " - "{} and stack:\n{}".format(f, e, str(stack_info))) - raise e - return data - - -class BatchCompose(object): - def __init__(self, transforms=[]): - self.transforms = transforms - - def __call__(self, data): - for f in self.transforms: - try: - data = f(data) - except Exception as e: - stack_info = traceback.format_exc() - logger.info("fail to perform batch transform [{}] with error: " - "{} and stack:\n{}".format(f, e, str(stack_info))) - raise e - - # sample list to batch data - batch = list(zip(*data)) - - return batch +__all__ = [ + 'ColorDistort', + 'RandomExpand', + 'RandomCrop', + 'RandomFlip', + 'NormalizeBox', + 'PadBox', + 'RandomShape', + 'NormalizeImage', + 'BboxXYXY2XYWH', + 'ResizeImage', +] class ColorDistort(object): @@ -145,7 +115,7 @@ class ColorDistort(object): img += delta return img - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): if self.random_apply: distortions = np.random.permutation([ self.apply_brightness, self.apply_contrast, @@ -153,7 +123,7 @@ class ColorDistort(object): ]) for func in distortions: im = func(im) - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] im = self.apply_brightness(im) @@ -165,7 +135,7 @@ class ColorDistort(object): im = self.apply_saturation(im) im = self.apply_hue(im) im = self.apply_contrast(im) - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] class RandomExpand(object): @@ -183,16 +153,16 @@ class RandomExpand(object): self.prob = prob self.fill_value = fill_value - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): if np.random.uniform(0., 1.) < self.prob: - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] height, width, _ = im.shape expand_ratio = np.random.uniform(1., self.ratio) h = int(height * expand_ratio) w = int(width * expand_ratio) if not h > height or not w > width: - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] y = np.random.randint(0, h - height) x = np.random.randint(0, w - width) canvas = np.ones((h, w, 3), dtype=np.uint8) @@ -201,7 +171,7 @@ class RandomExpand(object): gt_bbox += np.array([x, y, x, y], dtype=np.float32) - return [im_info, canvas, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, canvas, gt_bbox, gt_class, gt_score] class RandomCrop(): @@ -232,9 +202,9 @@ class RandomCrop(): self.allow_no_crop = allow_no_crop self.cover_all_box = cover_all_box - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): if len(gt_bbox) == 0: - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] # NOTE Original method attempts to generate one candidate for each # threshold then randomly sample one from the resulting list. @@ -251,7 +221,7 @@ class RandomCrop(): for thresh in thresholds: if thresh == 'no_crop': - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] h, w, _ = im.shape found = False @@ -286,9 +256,9 @@ class RandomCrop(): gt_bbox = np.take(cropped_box, valid_ids, axis=0) gt_class = np.take(gt_class, valid_ids, axis=0) gt_score = np.take(gt_score, valid_ids, axis=0) - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] def _iou_matrix(self, a, b): tl_i = np.maximum(a[:, np.newaxis, :2], b[:, :2]) @@ -334,7 +304,7 @@ class RandomFlip(): isinstance(self.is_normalized, bool)): raise TypeError("{}: input type is invalid.".format(self)) - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): """Filp the image and bounding box. Operators: 1. Flip the image numpy. @@ -363,20 +333,20 @@ class RandomFlip(): m = "{}: invalid box, x2 should be greater than x1".format( self) raise ValueError(m) - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] class NormalizeBox(object): """Transform the bounding box's coornidates to [0,1].""" - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): height, width, _ = im.shape for i in range(gt_bbox.shape[0]): gt_bbox[i][0] = gt_bbox[i][0] / width gt_bbox[i][1] = gt_bbox[i][1] / height gt_bbox[i][2] = gt_bbox[i][2] / width gt_bbox[i][3] = gt_bbox[i][3] / height - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] class PadBox(object): @@ -388,7 +358,7 @@ class PadBox(object): """ self.num_max_boxes = num_max_boxes - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): gt_num = min(self.num_max_boxes, len(gt_bbox)) num_max = self.num_max_boxes @@ -406,7 +376,7 @@ class PadBox(object): if gt_num > 0: pad_score[:gt_num] = gt_score[:gt_num, 0] gt_score = pad_score - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] class BboxXYXY2XYWH(object): @@ -414,10 +384,10 @@ class BboxXYXY2XYWH(object): Convert bbox XYXY format to XYWH format. """ - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): gt_bbox[:, 2:4] = gt_bbox[:, 2:4] - gt_bbox[:, :2] gt_bbox[:, :2] = gt_bbox[:, :2] + gt_bbox[:, 2:4] / 2. - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] class RandomShape(object): @@ -450,13 +420,13 @@ class RandomShape(object): method = np.random.choice(self.interps) if self.random_inter \ else cv2.INTER_NEAREST for i in range(len(samples)): - im = samples[i][1] + im = samples[i][2] h, w = im.shape[:2] scale_x = float(shape) / w scale_y = float(shape) / h im = cv2.resize( im, None, None, fx=scale_x, fy=scale_y, interpolation=method) - samples[i][1] = im + samples[i][2] = im return samples @@ -492,7 +462,7 @@ class NormalizeImage(object): 3. (optional) permute channel """ for i in range(len(samples)): - im = samples[i][1] + im = samples[i][2] im = im.astype(np.float32, copy=False) mean = np.array(self.mean)[np.newaxis, np.newaxis, :] std = np.array(self.std)[np.newaxis, np.newaxis, :] @@ -502,7 +472,7 @@ class NormalizeImage(object): im /= std if self.channel_first: im = im.transpose((2, 0, 1)) - samples[i][1] = im + samples[i][2] = im return samples @@ -595,16 +565,15 @@ class ResizeImage(object): format(type(target_size))) self.target_size = target_size - def __call__(self, im_info, im, gt_bbox, gt_class, gt_score): + def __call__(self, im_id, im_shape, im, gt_bbox, gt_class, gt_score): """ Resize the image numpy. """ if not isinstance(im, np.ndarray): raise TypeError("{}: image type is not numpy.".format(self)) if len(im.shape) != 3: raise ImageError('{}: image is not 3-dimensional.'.format(self)) - im_shape = im.shape - im_scale_x = float(self.target_size) / float(im_shape[1]) - im_scale_y = float(self.target_size) / float(im_shape[0]) + im_scale_x = float(self.target_size) / float(im.shape[1]) + im_scale_y = float(self.target_size) / float(im.shape[0]) resize_w = self.target_size resize_h = self.target_size @@ -616,5 +585,5 @@ class ResizeImage(object): fy=im_scale_y, interpolation=self.interp) - return [im_info, im, gt_bbox, gt_class, gt_score] + return [im_id, im_shape, im, gt_bbox, gt_class, gt_score] diff --git a/yolov3/visualizer.py b/examples/yolov3/visualizer.py similarity index 100% rename from yolov3/visualizer.py rename to examples/yolov3/visualizer.py diff --git a/hapi/__init__.py b/hapi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..eb3f008db4e690a5cf8999862432bedddbf2ef1c --- /dev/null +++ b/hapi/__init__.py @@ -0,0 +1,37 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hapi.configure import Config +from hapi import callbacks +from hapi import datasets +from hapi import distributed +from hapi import download +from hapi import metrics +from hapi import model +from hapi import progressbar +from hapi import text +from hapi import vision + +__all__ = [ + 'Config', + 'callbacks', + 'datasets', + 'distributed', + 'download', + 'metrics', + 'model', + 'progressbar', + 'text', + 'vision', +] diff --git a/callbacks.py b/hapi/callbacks.py similarity index 98% rename from callbacks.py rename to hapi/callbacks.py index 66690cf288efe8ba0d8dcc9eec64031674c8a18b..f02eec1ac7b20fe3d5ec771493378b4e74cc3796 100644 --- a/callbacks.py +++ b/hapi/callbacks.py @@ -15,7 +15,7 @@ import six import copy -from progressbar import ProgressBar +from .progressbar import ProgressBar from paddle.fluid.dygraph.parallel import ParallelEnv @@ -218,8 +218,6 @@ class ProgBarLogger(Callback): # if steps is not None, last step will update in on_epoch_end if self.steps and self.train_step < self.steps: self._updates(logs, 'train') - else: - self._updates(logs, 'train') def on_epoch_end(self, epoch, logs=None): logs = logs or {} @@ -238,7 +236,7 @@ class ProgBarLogger(Callback): def on_eval_batch_end(self, step, logs=None): logs = logs or {} - self.eval_step = step + self.eval_step += 1 samples = logs.get('batch_size', 1) self.evaled_samples += samples diff --git a/hapi/configure.py b/hapi/configure.py new file mode 100644 index 0000000000000000000000000000000000000000..3b4ae0f7363bcaad2c0ec407f868f42678cd485a --- /dev/null +++ b/hapi/configure.py @@ -0,0 +1,289 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import argparse +import json +import yaml +import six +import logging + +logging_only_message = "%(message)s" +logging_details = "%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s" + + +class JsonConfig(object): + """ + A high-level api for handling json configure file. + """ + + def __init__(self, config_path): + self._config_dict = self._parse(config_path) + + def _parse(self, config_path): + try: + with open(config_path) as json_file: + config_dict = json.load(json_file) + except: + raise IOError("Error in parsing bert model config file '%s'" % + config_path) + else: + return config_dict + + def __getitem__(self, key): + return self._config_dict[key] + + def print_config(self): + for arg, value in sorted(six.iteritems(self._config_dict)): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + + +class ArgumentGroup(object): + def __init__(self, parser, title, des): + self._group = parser.add_argument_group(title=title, description=des) + + def add_arg(self, name, type, default, help, **kwargs): + type = str2bool if type == bool else type + self._group.add_argument( + "--" + name, + default=default, + type=type, + help=help + ' Default: %(default)s.', + **kwargs) + + +class ArgConfig(object): + """ + A high-level api for handling argument configs. + """ + + def __init__(self): + parser = argparse.ArgumentParser() + + custom_g = ArgumentGroup(parser, "customize", "customized options.") + + self.custom_g = custom_g + + self.parser = parser + + def add_arg(self, name, dtype, default, descrip): + self.custom_g.add_arg(name, dtype, default, descrip) + + def build_conf(self): + return self.parser.parse_args() + + +def str2bool(v): + # because argparse does not support to parse "true, False" as python + # boolean directly + return v.lower() in ("true", "t", "1") + + +def print_arguments(args, log=None): + if not log: + print('----------- Configuration Arguments -----------') + for arg, value in sorted(six.iteritems(vars(args))): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + else: + log.info('----------- Configuration Arguments -----------') + for arg, value in sorted(six.iteritems(vars(args))): + log.info('%s: %s' % (arg, value)) + log.info('------------------------------------------------') + + +class Config(object): + """ + A high-level API for managing configuration files in PaddlePaddle. + Can jointly work with command-line-arugment, json files and yaml files. + """ + + def __init__(self, json_file="", yaml_file="", fuse_args=True): + """ + Init funciton for PDConfig. + json_file: the path to the json configure file. + yaml_file: the path to the yaml configure file. + fuse_args: if fuse the json/yaml configs with argparse. + """ + assert isinstance(json_file, str) + assert isinstance(yaml_file, str) + + if json_file != "" and yaml_file != "": + raise Warning( + "json_file and yaml_file can not co-exist for now. please only use one configure file type." + ) + return + + self.args = None + self.arg_config = {} + self.json_config = {} + self.yaml_config = {} + + parser = argparse.ArgumentParser() + + self.default_g = ArgumentGroup(parser, "default", "default options.") + self.yaml_g = ArgumentGroup(parser, "yaml", "options from yaml.") + self.json_g = ArgumentGroup(parser, "json", "options from json.") + self.com_g = ArgumentGroup(parser, "custom", "customized options.") + + self.parser = parser + + if json_file != "": + self.load_json(json_file, fuse_args=fuse_args) + + if yaml_file: + self.load_yaml(yaml_file, fuse_args=fuse_args) + + def load_json(self, file_path, fuse_args=True): + + if not os.path.exists(file_path): + raise Warning("the json file %s does not exist." % file_path) + return + + with open(file_path, "r") as fin: + self.json_config = json.loads(fin.read()) + fin.close() + + if fuse_args: + for name in self.json_config: + if isinstance(self.json_config[name], list): + self.json_g.add_arg( + name, + type(self.json_config[name][0]), + self.json_config[name], + "This is from %s" % file_path, + nargs=len(self.json_config[name])) + continue + if not isinstance(self.json_config[name], int) \ + and not isinstance(self.json_config[name], float) \ + and not isinstance(self.json_config[name], str) \ + and not isinstance(self.json_config[name], bool): + + continue + + self.json_g.add_arg(name, + type(self.json_config[name]), + self.json_config[name], + "This is from %s" % file_path) + + def load_yaml(self, file_path, fuse_args=True): + + if not os.path.exists(file_path): + raise Warning("the yaml file %s does not exist." % file_path) + return + + with open(file_path, "r") as fin: + self.yaml_config = yaml.load(fin, Loader=yaml.SafeLoader) + fin.close() + + if fuse_args: + for name in self.yaml_config: + if isinstance(self.yaml_config[name], list): + self.yaml_g.add_arg( + name, + type(self.yaml_config[name][0]), + self.yaml_config[name], + "This is from %s" % file_path, + nargs=len(self.yaml_config[name])) + continue + + if not isinstance(self.yaml_config[name], int) \ + and not isinstance(self.yaml_config[name], float) \ + and not isinstance(self.yaml_config[name], str) \ + and not isinstance(self.yaml_config[name], bool): + + continue + + self.yaml_g.add_arg(name, + type(self.yaml_config[name]), + self.yaml_config[name], + "This is from %s" % file_path) + + def build(self): + self.args = self.parser.parse_args() + self.arg_config = vars(self.args) + + def __add__(self, new_arg): + assert isinstance(new_arg, list) or isinstance(new_arg, tuple) + assert len(new_arg) >= 3 + assert self.args is None + + name = new_arg[0] + dtype = new_arg[1] + dvalue = new_arg[2] + desc = new_arg[3] if len( + new_arg) == 4 else "Description is not provided." + + self.com_g.add_arg(name, dtype, dvalue, desc) + + return self + + def __getattr__(self, name): + if name in self.arg_config: + return self.arg_config[name] + + if name in self.json_config: + return self.json_config[name] + + if name in self.yaml_config: + return self.yaml_config[name] + + raise Warning("The argument %s is not defined." % name) + + def Print(self): + + print("-" * 70) + for name in self.arg_config: + print("%s:\t\t\t\t%s" % (str(name), str(self.arg_config[name]))) + + for name in self.json_config: + if name not in self.arg_config: + print("%s:\t\t\t\t%s" % + (str(name), str(self.json_config[name]))) + + for name in self.yaml_config: + if name not in self.arg_config: + print("%s:\t\t\t\t%s" % + (str(name), str(self.yaml_config[name]))) + + print("-" * 70) + + +if __name__ == "__main__": + """ + pd_config = PDConfig(json_file = "./test/bert_config.json") + pd_config.build() + + print(pd_config.do_train) + print(pd_config.hidden_size) + + pd_config = PDConfig(yaml_file = "./test/bert_config.yaml") + pd_config.build() + + print(pd_config.do_train) + print(pd_config.hidden_size) + """ + + config = Config(yaml_file="./bert.yaml") + config += ("my_age", int, 18, "I am forever 18.") + config.build() + + print(config.data_dir) + print(config.my_age) diff --git a/hapi/datasets/__init__.py b/hapi/datasets/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fc5df6401992def4bc37329794e534a832924da3 --- /dev/null +++ b/hapi/datasets/__init__.py @@ -0,0 +1,25 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import folder +from . import mnist +from . import flowers + +from .folder import * +from .mnist import * +from .flowers import * + +__all__ = folder.__all__ \ + + mnist.__all__ \ + + flowers.__all__ diff --git a/hapi/datasets/flowers.py b/hapi/datasets/flowers.py new file mode 100644 index 0000000000000000000000000000000000000000..1f4f707888d460260d598826ba15ca3c69455f7b --- /dev/null +++ b/hapi/datasets/flowers.py @@ -0,0 +1,129 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import os +import io +import tarfile +import numpy as np +import scipy.io as scio +from PIL import Image + +from paddle.io import Dataset +from .utils import _check_exists_and_download + +__all__ = ["Flowers"] + +DATA_URL = 'http://paddlemodels.bj.bcebos.com/flowers/102flowers.tgz' +LABEL_URL = 'http://paddlemodels.bj.bcebos.com/flowers/imagelabels.mat' +SETID_URL = 'http://paddlemodels.bj.bcebos.com/flowers/setid.mat' +DATA_MD5 = '52808999861908f626f3c1f4e79d11fa' +LABEL_MD5 = 'e0620be6f572b9609742df49c70aed4d' +SETID_MD5 = 'a5357ecc9cb78c4bef273ce3793fc85c' + +# In official 'readme', tstid is the flag of test data +# and trnid is the flag of train data. But test data is more than train data. +# So we exchange the train data and test data. +MODE_FLAG_MAP = {'train': 'tstid', 'test': 'trnid', 'valid': "valid"} + + +class Flowers(Dataset): + """ + Implement of flowers dataset + + Args: + data_file(str): path to data file, can be set None if + :attr:`download` is True. Default None + label_file(str): path to label file, can be set None if + :attr:`download` is True. Default None + setid_file(str): path to subset index file, can be set + None if :attr:`download` is True. Default None + mode(str): 'train', 'valid' or 'test' mode. Default 'train'. + download(bool): whether auto download mnist dataset if + :attr:`image_path`/:attr:`label_path` unset. Default + True + + Examples: + + .. code-block:: python + + from hapi.vision.datasets import Flowers + + flowers = Flowers(mode='test') + + for i in range(len(flowers)): + sample = flowers[i] + print(sample[0].shape, sample[1]) + + """ + + def __init__(self, + data_file=None, + label_file=None, + setid_file=None, + mode='train', + transform=None, + download=True): + assert mode.lower() in ['train', 'valid', 'test'], \ + "mode should be 'train', 'valid' or 'test', but got {}".format(mode) + self.flag = MODE_FLAG_MAP[mode.lower()] + + self.data_file = data_file + if self.data_file is None: + assert download, "data_file not set and auto download disabled" + self.data_file = _check_exists_and_download( + data_file, DATA_URL, DATA_MD5, 'flowers', download) + + self.label_file = label_file + if self.label_file is None: + assert download, "label_file not set and auto download disabled" + self.label_file = _check_exists_and_download( + label_file, LABEL_URL, LABEL_MD5, 'flowers', download) + + self.setid_file = setid_file + if self.setid_file is None: + assert download, "setid_file not set and auto download disabled" + self.setid_file = _check_exists_and_download( + setid_file, SETID_URL, SETID_MD5, 'flowers', download) + + self.transform = transform + + # read dataset into memory + self._load_anno() + + def _load_anno(self): + self.name2mem = {} + self.data_tar = tarfile.open(self.data_file) + for ele in self.data_tar.getmembers(): + self.name2mem[ele.name] = ele + + self.labels = scio.loadmat(self.label_file)['labels'][0] + self.indexes = scio.loadmat(self.setid_file)[self.flag][0] + + def __getitem__(self, idx): + index = self.indexes[idx] + label = np.array([self.labels[index - 1]]) + img_name = "jpg/image_%05d.jpg" % index + img_ele = self.name2mem[img_name] + image = self.data_tar.extractfile(img_ele).read() + image = np.array(Image.open(io.BytesIO(image))) + + if self.transform is not None: + image, label = self.transform(image, label) + + return image, label + + def __len__(self): + return len(self.indexes) diff --git a/datasets/folder.py b/hapi/datasets/folder.py similarity index 66% rename from datasets/folder.py rename to hapi/datasets/folder.py index e853e7e106cf7a305c79ab900515be6f8febf3a0..23f2c9592915e3e83d596c9cc3679eca306a4bd5 100644 --- a/datasets/folder.py +++ b/hapi/datasets/folder.py @@ -16,7 +16,9 @@ import os import sys import cv2 -from paddle.fluid.io import Dataset +from paddle.io import Dataset + +__all__ = ["DatasetFolder", "ImageFolder"] def has_valid_extension(filename, extensions): @@ -71,14 +73,12 @@ class DatasetFolder(Dataset): Args: root (string): Root directory path. - loader (callable, optional): A function to load a sample given its path. - extensions (tuple[string], optional): A list of allowed extensions. + loader (callable|optional): A function to load a sample given its path. + extensions (tuple[str]|optional): A list of allowed extensions. both extensions and is_valid_file should not be passed. - transform (callable, optional): A function/transform that takes in + transform (callable|optional): A function/transform that takes in a sample and returns a transformed version. - target_transform (callable, optional): A function/transform that takes - in the target and transforms it. - is_valid_file (callable, optional): A function that takes path of a file + is_valid_file (callable|optional): A function that takes path of a file and check if the file is a valid file (used to check of corrupt files) both extensions and is_valid_file should not be passed. @@ -94,9 +94,9 @@ class DatasetFolder(Dataset): loader=None, extensions=None, transform=None, - target_transform=None, is_valid_file=None): self.root = root + self.transform = transform if extensions is None: extensions = IMG_EXTENSIONS classes, class_to_idx = self._find_classes(self.root) @@ -150,9 +150,7 @@ class DatasetFolder(Dataset): path, target = self.samples[index] sample = self.loader(path) if self.transform is not None: - sample = self.transform(sample) - if self.target_transform is not None: - target = self.target_transform(target) + sample, target = self.transform(sample, target) return sample, target @@ -166,3 +164,80 @@ IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', def cv2_loader(path): return cv2.imread(path) + + +class ImageFolder(Dataset): + """A generic data loader where the samples are arranged in this way: + + root/1.ext + root/2.ext + root/sub_dir/3.ext + + Args: + root (string): Root directory path. + loader (callable, optional): A function to load a sample given its path. + extensions (tuple[string], optional): A list of allowed extensions. + both extensions and is_valid_file should not be passed. + transform (callable, optional): A function/transform that takes in + a sample and returns a transformed version. + is_valid_file (callable, optional): A function that takes path of a file + and check if the file is a valid file (used to check of corrupt files) + both extensions and is_valid_file should not be passed. + + Attributes: + samples (list): List of sample path + """ + + def __init__(self, + root, + loader=None, + extensions=None, + transform=None, + is_valid_file=None): + self.root = root + if extensions is None: + extensions = IMG_EXTENSIONS + + samples = [] + path = os.path.expanduser(root) + if not ((extensions is None) ^ (is_valid_file is None)): + raise ValueError( + "Both extensions and is_valid_file cannot be None or not None at the same time" + ) + if extensions is not None: + + def is_valid_file(x): + return has_valid_extension(x, extensions) + + for root, _, fnames in sorted(os.walk(path, followlinks=True)): + for fname in sorted(fnames): + f = os.path.join(root, fname) + if is_valid_file(f): + samples.append(f) + + if len(samples) == 0: + raise (RuntimeError( + "Found 0 files in subfolders of: " + self.root + "\n" + "Supported extensions are: " + ",".join(extensions))) + + self.loader = cv2_loader if loader is None else loader + self.extensions = extensions + self.samples = samples + self.transform = transform + + def __getitem__(self, index): + """ + Args: + index (int): Index + + Returns: + tuple: (sample, target) where target is class_index of the target class. + """ + path = self.samples[index] + sample = self.loader(path) + if self.transform is not None: + sample = self.transform(sample) + return [sample] + + def __len__(self): + return len(self.samples) diff --git a/hapi/datasets/mnist.py b/hapi/datasets/mnist.py new file mode 100644 index 0000000000000000000000000000000000000000..18c62901edb95fd573334a4f3fe2201be7447711 --- /dev/null +++ b/hapi/datasets/mnist.py @@ -0,0 +1,156 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import os +import gzip +import struct +import numpy as np + +import paddle.dataset.common +from paddle.io import Dataset +from .utils import _check_exists_and_download + +__all__ = ["MNIST"] + +URL_PREFIX = 'https://dataset.bj.bcebos.com/mnist/' +TEST_IMAGE_URL = URL_PREFIX + 't10k-images-idx3-ubyte.gz' +TEST_IMAGE_MD5 = '9fb629c4189551a2d022fa330f9573f3' +TEST_LABEL_URL = URL_PREFIX + 't10k-labels-idx1-ubyte.gz' +TEST_LABEL_MD5 = 'ec29112dd5afa0611ce80d1b7f02629c' +TRAIN_IMAGE_URL = URL_PREFIX + 'train-images-idx3-ubyte.gz' +TRAIN_IMAGE_MD5 = 'f68b3c2dcbeaaa9fbdd348bbdeb94873' +TRAIN_LABEL_URL = URL_PREFIX + 'train-labels-idx1-ubyte.gz' +TRAIN_LABEL_MD5 = 'd53e105ee54ea40749a09fcbcd1e9432' + + +class MNIST(Dataset): + """ + Implement of MNIST dataset + + Args: + image_path(str): path to image file, can be set None if + :attr:`download` is True. Default None + label_path(str): path to label file, can be set None if + :attr:`download` is True. Default None + mode(str): 'train' or 'test' mode. Default 'train'. + download(bool): whether auto download mnist dataset if + :attr:`image_path`/:attr:`label_path` unset. Default + True + + Returns: + Dataset: MNIST Dataset. + + Examples: + + .. code-block:: python + + from hapi.vision.datasets import MNIST + + mnist = MNIST(mode='test') + + for i in range(len(mnist)): + sample = mnist[i] + print(sample[0].shape, sample[1]) + + """ + + def __init__(self, + image_path=None, + label_path=None, + mode='train', + transform=None, + download=True): + assert mode.lower() in ['train', 'test'], \ + "mode should be 'train' or 'test', but got {}".format(mode) + self.mode = mode.lower() + + self.image_path = image_path + if self.image_path is None: + assert download, "image_path not set and auto download disabled" + image_url = TRAIN_IMAGE_URL if mode == 'train' else TEST_IMAGE_URL + image_md5 = TRAIN_IMAGE_MD5 if mode == 'train' else TEST_IMAGE_MD5 + self.image_path = _check_exists_and_download( + image_path, image_url, image_md5, 'mnist', download) + + self.label_path = label_path + if self.label_path is None: + assert download, "label_path not set and auto download disabled" + label_url = TRAIN_LABEL_URL if mode == 'train' else TEST_LABEL_URL + label_md5 = TRAIN_LABEL_MD5 if mode == 'train' else TEST_LABEL_MD5 + self.label_path = _check_exists_and_download( + label_path, label_url, label_md5, 'mnist', download) + + self.transform = transform + + # read dataset into memory + self._parse_dataset() + + def _parse_dataset(self, buffer_size=100): + self.images = [] + self.labels = [] + with gzip.GzipFile(self.image_path, 'rb') as image_file: + img_buf = image_file.read() + with gzip.GzipFile(self.label_path, 'rb') as label_file: + lab_buf = label_file.read() + + step_label = 0 + offset_img = 0 + # read from Big-endian + # get file info from magic byte + # image file : 16B + magic_byte_img = '>IIII' + magic_img, image_num, rows, cols = struct.unpack_from( + magic_byte_img, img_buf, offset_img) + offset_img += struct.calcsize(magic_byte_img) + + offset_lab = 0 + # label file : 8B + magic_byte_lab = '>II' + magic_lab, label_num = struct.unpack_from(magic_byte_lab, + lab_buf, offset_lab) + offset_lab += struct.calcsize(magic_byte_lab) + + while True: + if step_label >= label_num: + break + fmt_label = '>' + str(buffer_size) + 'B' + labels = struct.unpack_from(fmt_label, lab_buf, offset_lab) + offset_lab += struct.calcsize(fmt_label) + step_label += buffer_size + + fmt_images = '>' + str(buffer_size * rows * cols) + 'B' + images_temp = struct.unpack_from(fmt_images, img_buf, + offset_img) + images = np.reshape(images_temp, (buffer_size, rows * + cols)).astype('float32') + offset_img += struct.calcsize(fmt_images) + + images = images / 255.0 + images = images * 2.0 + images = images - 1.0 + + for i in range(buffer_size): + self.images.append(images[i, :]) + self.labels.append(np.array([labels[i]])) + + def __getitem__(self, idx): + image, label = self.images[idx], self.labels[idx] + if self.transform is not None: + image, label = self.transform(image, label) + return image, label + + def __len__(self): + return len(self.labels) diff --git a/hapi/datasets/utils.py b/hapi/datasets/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..b580dd235739fe2d096a38fed16c8ef4af427ca1 --- /dev/null +++ b/hapi/datasets/utils.py @@ -0,0 +1,29 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import os +import paddle.dataset.common + + +def _check_exists_and_download(path, url, md5, module_name, download=True): + if path and os.path.exists(path): + return path + + if download: + return paddle.dataset.common.download(url, module_name, md5) + else: + raise FileNotFoundError( + '{} not exists and auto download disabled'.format(path)) diff --git a/distributed.py b/hapi/distributed.py similarity index 98% rename from distributed.py rename to hapi/distributed.py index 87818545671c45cf4faba234406e87762e897784..39bf9a35e79792a1f0c9dd23d296730fdc31daf5 100644 --- a/distributed.py +++ b/hapi/distributed.py @@ -23,7 +23,7 @@ import numpy as np from paddle import fluid from paddle.fluid.layers import collective from paddle.fluid.dygraph.parallel import ParallelEnv, ParallelStrategy -from paddle.fluid.io import BatchSampler +from paddle.io import BatchSampler _parallel_context_initialized = False @@ -39,7 +39,7 @@ class DistributedBatchSampler(BatchSampler): Dataset is assumed to be of constant size. Args: - data_source: this could be a `fluid.io.Dataset` implement + data_source: this could be a `paddle.io.Dataset` implement or other python object which implemented `__len__` for BatchSampler to get sample number of data source. diff --git a/models/download.py b/hapi/download.py similarity index 94% rename from models/download.py rename to hapi/download.py index 10d3fba390647c494448b83295901a8973d2aba8..e9a89ba53bc3bc74f03977659156121bce6db577 100644 --- a/models/download.py +++ b/hapi/download.py @@ -29,13 +29,22 @@ from paddle.fluid.dygraph.parallel import ParallelEnv import logging logger = logging.getLogger(__name__) -__all__ = ['get_weights_path'] +__all__ = ['get_weights_path', 'is_url'] WEIGHTS_HOME = osp.expanduser("~/.cache/paddle/hapi/weights") DOWNLOAD_RETRY_LIMIT = 3 +def is_url(path): + """ + Whether path is URL. + Args: + path (string): URL string or not. + """ + return path.startswith('http://') or path.startswith('https://') + + def get_weights_path(url, md5sum=None): """Get weights path from WEIGHT_HOME, if not exists, download it from url. @@ -62,6 +71,7 @@ def get_path(url, root_dir, md5sum=None, check_exist=True): WEIGHTS_HOME or DATASET_HOME md5sum (str): md5 sum of download package """ + assert is_url(url), "downloading from {} not a url".format(url) # parse path after download to decompress under root_dir fullpath = map_path(url, root_dir) diff --git a/metrics.py b/hapi/metrics.py similarity index 69% rename from metrics.py rename to hapi/metrics.py index 3350853677b62275bb0107addff3f3b3780ea81c..1d24c4ada2e77bba0df59cad75dd0fac3842f80c 100644 --- a/metrics.py +++ b/hapi/metrics.py @@ -48,9 +48,16 @@ class Metric(object): format(self.__class__.__name__)) @abc.abstractmethod - def update(self, *args, **kwargs): + def update(self, *args): """ Update states for metric + + Inputs of :code:`update` is the outputs of :code:`Metric.add_metric_op`, + if :code:`add_metric_op` is not defined, the inputs of :code:`update` + will be flatten arguments of **output** of mode and **label** from data: + :code:`update(output1, output2, ..., label1, label2,...)` + + see :code:`Metric.add_metric_op` """ raise NotImplementedError("function 'update' not implemented in {}.". format(self.__class__.__name__)) @@ -72,11 +79,26 @@ class Metric(object): raise NotImplementedError("function 'name' not implemented in {}.". format(self.__class__.__name__)) - def add_metric_op(self, pred, label): + def add_metric_op(self, *args): """ - Add process op for metric in program + This API is advanced usage to accelerate metric calculating, calulations + from outputs of model to the states which should be updated by Metric can + be defined here, where Paddle OPs is also supported. Outputs of this API + will be the inputs of "Metric.update". + + If :code:`add_metric_op` is defined, it will be called with **outputs** + of model and **labels** from data as arguments, all outputs and labels + will be concatenated and flatten and each filed as a separate argument + as follows: + :code:`add_metric_op(output1, output2, ..., label1, label2,...)` + + If :code:`add_metric_op` is not defined, default behaviour is to pass + input to output, so output format will be: + :code:`return output1, output2, ..., label1, label2,...` + + see :code:`Metric.update` """ - return pred, label + return args class Accuracy(Metric): @@ -91,12 +113,12 @@ class Accuracy(Metric): self._init_name(name) self.reset() - def add_metric_op(self, pred, label, *args, **kwargs): - pred = fluid.layers.argsort(pred[0], descending=True)[1][:, :self.maxk] - correct = pred == label[0] + def add_metric_op(self, pred, label, *args): + pred = fluid.layers.argsort(pred, descending=True)[1][:, :self.maxk] + correct = pred == label return correct - def update(self, correct, *args, **kwargs): + def update(self, correct, *args): accs = [] for i, k in enumerate(self.topk): num_corrects = correct[:, :k].sum() diff --git a/model.py b/hapi/model.py similarity index 91% rename from model.py rename to hapi/model.py index 6fecbf1d29fa3c37ad3073fae0fcdcd819b52937..3593f00acaa9f2763e01cf139e1ccdb06d339d55 100644 --- a/model.py +++ b/hapi/model.py @@ -32,13 +32,16 @@ from paddle.fluid.dygraph.parallel import ParallelEnv from paddle.fluid.layers.utils import flatten from paddle.fluid.incubate.fleet.collective import fleet, DistributedStrategy from paddle.fluid.incubate.fleet.base import role_maker -from paddle.fluid.io import DataLoader, Dataset +from paddle.io import DataLoader, Dataset -from distributed import DistributedBatchSampler, _all_gather, prepare_distributed_context, _parallel_context_initialized -from metrics import Metric -from callbacks import config_callbacks +from hapi.distributed import DistributedBatchSampler, _all_gather, prepare_distributed_context, _parallel_context_initialized +from hapi.metrics import Metric +from hapi.callbacks import config_callbacks -__all__ = ['Model', 'Loss', 'CrossEntropy', 'Input', 'set_device'] +__all__ = [ + 'Model', 'Loss', 'CrossEntropy', 'Input', 'set_device', + 'SoftmaxWithCrossEntropy' +] def set_device(device): @@ -64,7 +67,7 @@ def to_list(value): if value is None: return value if isinstance(value, (list, tuple)): - return value + return list(value) return [value] @@ -144,6 +147,17 @@ class CrossEntropy(Loss): ] +class SoftmaxWithCrossEntropy(Loss): + def __init__(self, average=True): + super(SoftmaxWithCrossEntropy, self).__init__() + + def forward(self, outputs, labels): + return [ + fluid.layers.softmax_with_cross_entropy( + o, l, return_softmax=False) for o, l in zip(outputs, labels) + ] + + class StaticGraphAdapter(object): def __init__(self, model): super(StaticGraphAdapter, self).__init__() @@ -179,17 +193,17 @@ class StaticGraphAdapter(object): def mode(self, value): self.model.mode = value - def train(self, inputs, labels=None): + def train_batch(self, inputs, labels=None): assert self.model._optimizer, \ "model not ready, please call `model.prepare()` first" self.mode = 'train' return self._run(inputs, labels) - def eval(self, inputs, labels=None): + def eval_batch(self, inputs, labels=None): self.mode = 'eval' return self._run(inputs, labels) - def test(self, inputs): + def test_batch(self, inputs): self.mode = 'test' return self._run(inputs, None) @@ -360,10 +374,27 @@ class StaticGraphAdapter(object): metric_list, metric_splits = flatten_list(endpoints['metric']) fetch_list = endpoints['loss'] + metric_list num_loss = len(endpoints['loss']) + + # if fetch Variable is same as input Variable, do not fetch + # from program, get it from input directly + pruned_fetch_list = [] + pruned_fetch_idx_name_map = [""] * len(fetch_list) + for i, fetch_var in enumerate(fetch_list): + if fetch_var.name in feed.keys(): + pruned_fetch_idx_name_map[i] = fetch_var.name + else: + pruned_fetch_list.append(fetch_var) + rets = self._executor.run(compiled_prog, feed=feed, - fetch_list=fetch_list, + fetch_list=pruned_fetch_list, return_numpy=False) + + # restore pruned fetch_list Variable from feeds + for i, name in enumerate(pruned_fetch_idx_name_map): + if len(name) > 0: + rets.insert(i, feed[name]) + # LoDTensor cannot be fetch as numpy directly rets = [np.array(v) for v in rets] if self.mode == 'test': @@ -442,7 +473,7 @@ class StaticGraphAdapter(object): if mode != 'test': for metric in self.model._metrics: metrics.append( - to_list(metric.add_metric_op(outputs, labels))) + to_list(metric.add_metric_op(*(outputs + labels)))) if mode == 'train' and self.model._optimizer: self._loss_endpoint = fluid.layers.sum(losses) @@ -536,7 +567,7 @@ class DynamicGraphAdapter(object): self.model.mode = value # TODO multi device in dygraph mode not implemented at present time - def train(self, inputs, labels=None): + def train_batch(self, inputs, labels=None): assert self.model._optimizer, \ "model not ready, please call `model.prepare()` first" super(Model, self.model).train() @@ -562,14 +593,14 @@ class DynamicGraphAdapter(object): metrics = [] for metric in self.model._metrics: metric_outs = metric.add_metric_op( - to_list(outputs), to_list(labels)) + *(to_list(outputs) + to_list(labels))) m = metric.update(*[to_numpy(m) for m in to_list(metric_outs)]) metrics.append(m) return ([to_numpy(l) for l in losses], metrics) \ if len(metrics) > 0 else [to_numpy(l) for l in losses] - def eval(self, inputs, labels=None): + def eval_batch(self, inputs, labels=None): super(Model, self.model).eval() self.mode = 'eval' inputs = to_list(inputs) @@ -601,7 +632,8 @@ class DynamicGraphAdapter(object): self._merge_count[self.mode + '_total'] += samples self._merge_count[self.mode + '_batch'] = samples - metric_outs = metric.add_metric_op(to_list(outputs), labels) + metric_outs = metric.add_metric_op( + *(to_list(outputs) + to_list(labels))) m = metric.update(*[to_numpy(m) for m in to_list(metric_outs)]) metrics.append(m) @@ -610,7 +642,7 @@ class DynamicGraphAdapter(object): return ([to_numpy(l) for l in losses], metrics) \ if len(metrics) > 0 else [to_numpy(l) for l in losses] - def test(self, inputs): + def test_batch(self, inputs): super(Model, self.model).eval() self.mode = 'test' inputs = [to_variable(x) for x in to_list(inputs)] @@ -709,14 +741,14 @@ class Model(fluid.dygraph.Layer): else: self._adapter = StaticGraphAdapter(self) - def train(self, *args, **kwargs): - return self._adapter.train(*args, **kwargs) + def train_batch(self, *args, **kwargs): + return self._adapter.train_batch(*args, **kwargs) - def eval(self, *args, **kwargs): - return self._adapter.eval(*args, **kwargs) + def eval_batch(self, *args, **kwargs): + return self._adapter.eval_batch(*args, **kwargs) - def test(self, *args, **kwargs): - return self._adapter.test(*args, **kwargs) + def test_batch(self, *args, **kwargs): + return self._adapter.test_batch(*args, **kwargs) def save(self, *args, **kwargs): if ParallelEnv().local_rank == 0: @@ -767,6 +799,13 @@ class Model(fluid.dygraph.Layer): format(key, list(state.shape), list(param.shape))) return param, state + def _strip_postfix(path): + path, ext = os.path.splitext(path) + assert ext in ['', '.pdparams', '.pdopt', '.pdmodel'], \ + "Unknown postfix {} from weights".format(ext) + return path + + path = _strip_postfix(path) param_state = _load_state_from_path(path + ".pdparams") assert param_state, "Failed to load parameters, please check path." @@ -777,7 +816,7 @@ class Model(fluid.dygraph.Layer): except ValueError as err: if skip_mismatch: warnings.warn( - ("Skip loading for {}. ".format(key) + err.message)) + ("Skip loading for {}. ".format(key) + str(err))) # reset optimizer when mismatch happens reset_optimizer = True else: @@ -896,36 +935,36 @@ class Model(fluid.dygraph.Layer): FIXME: add more comments and usage Args: train_data (Dataset|DataLoader): An iterable data loader is used for - train. An instance of paddle.fluid.io.Dataset or - paddle.fluid.io.Dataloader is recomended. + train. An instance of paddle paddle.io.Dataset or + paddle.io.Dataloader is recomended. Default: None. eval_data (Dataset|DataLoader): An iterable data loader is used for evaluation at the end of epoch. If None, will not do evaluation. - An instance of paddle.fluid.io.Dataset or paddle.fluid.io.Dataloader - is recomended. + An instance of paddle.io.Dataset or paddle.io.Dataloader + is recomended. Default: None. batch_size (int): Integer number. The batch size of train_data and eval_data. When train_data and eval_data are both the instance of Dataloader, this - parameter will be ignored. - epochs (int): Integer number. The number of epochs to train the model. + parameter will be ignored. Default: 1. + epochs (int): Integer number. The number of epochs to train the model. Default: 1. eval_freq (int): The frequency, in number of epochs, an evalutation - is performed. + is performed. Default: 1. log_freq (int): The frequency, in number of steps, the training logs - are printed. + are printed. Default: 10. save_dir(str|None): The directory to save checkpoint during training. - If None, will not save checkpoint. - save_freq (int): The frequency, in number of epochs, to save checkpoint. + If None, will not save checkpoint. Default: None. + save_freq (int): The frequency, in number of epochs, to save checkpoint. Default: 1. verbose (int): The verbosity mode, should be 0, 1, or 2. - 0 = silent, 1 = progress bar, 2 = one line per epoch. + 0 = silent, 1 = progress bar, 2 = one line per epoch. Default: 2. drop_last (bool): whether drop the last incomplete batch of train_data when dataset size is not divisible by the batch size. When train_data - is an instance of Dataloader, this parameter will be ignored. + is an instance of Dataloader, this parameter will be ignored. Default: False. shuffle (bool): whther to shuffle train_data. When train_data is an instance - of Dataloader, this parameter will be ignored. + of Dataloader, this parameter will be ignored. Default: True. num_workers (int): the number of subprocess to load data, 0 for no subprocess used and loading data in main process. When train_data and eval_data are - both the instance of Dataloader, this parameter will be ignored. + both the instance of Dataloader, this parameter will be ignored. Default: 0. callbacks (Callback|None): A list of `Callback` instances to apply during training. If None, `ProgBarLogger` and `ModelCheckpoint` - are automatically inserted. + are automatically inserted. Default: None. """ assert train_data is not None, \ @@ -1024,21 +1063,23 @@ class Model(fluid.dygraph.Layer): FIXME: add more comments and usage Args: eval_data (Dataset|DataLoader): An iterable data loader is used for - evaluation. An instance of paddle.fluid.io.Dataset or - paddle.fluid.io.Dataloader is recomended. + evaluation. An instance of paddle.io.Dataset or + paddle.io.Dataloader is recomended. batch_size (int): Integer number. The batch size of train_data and eval_data. - When train_data and eval_data are both the instance of Dataloader, this - parameter will be ignored. + When eval_data is the instance of Dataloader, this argument will be ignored. + Default: 1. log_freq (int): The frequency, in number of steps, the eval logs - are printed. + are printed. Default: 10. verbose (int): The verbosity mode, should be 0, 1, or 2. - 0 = silent, 1 = progress bar, 2 = one line per epoch. + 0 = silent, 1 = progress bar, 2 = one line per epoch. Default: 2. num_workers (int): The number of subprocess to load data, 0 for no subprocess used and loading data in main process. When train_data and eval_data are - both the instance of Dataloader, this parameter will be ignored. + both the instance of Dataloader, this parameter will be ignored. Default: 0. callbacks (Callback|None): A list of `Callback` instances to apply during training. If None, `ProgBarLogger` and `ModelCheckpoint` - are automatically inserted. + are automatically inserted. Default: None. + Returns: + dict: Result of metric. """ if fluid.in_dygraph_mode(): @@ -1099,26 +1140,28 @@ class Model(fluid.dygraph.Layer): FIXME: add more comments and usage Args: test_data (Dataset|DataLoader): An iterable data loader is used for - predict. An instance of paddle.fluid.io.Dataset or paddle.fluid.io.Dataloader + predict. An instance of paddle.io.Dataset or paddle.io.Dataloader is recomended. batch_size (int): Integer number. The batch size of train_data and eval_data. When train_data and eval_data are both the instance of Dataloader, this - parameter will be ignored. + argument will be ignored. Default: 1. num_workers (int): the number of subprocess to load data, 0 for no subprocess used and loading data in main process. When train_data and eval_data are - both the instance of Dataloader, this parameter will be ignored. + both the instance of Dataloader, this argument will be ignored. Default: 0. stack_output (bool): whether stack output field like a batch, as for an output filed of a sample is in shape [X, Y], test_data contains N samples, predict output field will be in shape [N, X, Y] if stack_output is True, and will be a length N list in shape [[X, Y], [X, Y], ....[X, Y]] if stack_outputs is False. stack_outputs as False is used for LoDTensor output situation, - it is recommended set as True if outputs contains no LoDTensor. Default False + it is recommended set as True if outputs contains no LoDTensor. Default: False. + Returns: + list: output of models. """ if fluid.in_dygraph_mode(): feed_list = None else: - feed_list = [x.forward() for x in self._inputs + self._labels] + feed_list = [x.forward() for x in self._inputs] if test_data is not None and isinstance(test_data, Dataset): test_sampler = DistributedBatchSampler( @@ -1142,7 +1185,7 @@ class Model(fluid.dygraph.Layer): outputs = [] for data in tqdm.tqdm(loader): data = flatten(data) - outputs.append(self.test(data[:len(self._inputs)])) + outputs.append(self.test_batch(data[:len(self._inputs)])) # NOTE: for lod tensor output, we should not stack outputs # for stacking may loss its detail info @@ -1156,18 +1199,6 @@ class Model(fluid.dygraph.Layer): outputs = [o[:len(test_loader.dataset)] for o in outputs] return outputs - def set_eval_data(self, eval_data): - """ - Args: - eval_data (Dataset|DataLoader|None): An iterable data loader is used for - eval. An instance of paddle.fluid.io.Dataset or - paddle.fluid.io.Dataloader is recomended. - """ - assert isinstance( - eval_data, - DataLoader), "eval_data must be a instance of Dataloader!" - self._test_dataloader = eval_data - def _run_one_epoch(self, data_loader, callbacks, @@ -1204,11 +1235,11 @@ class Model(fluid.dygraph.Layer): callbacks.on_batch_begin(mode, step, logs) if mode == 'train': - outs = self.train(data[:len(self._inputs)], - data[len(self._inputs):]) + outs = self.train_batch(data[:len(self._inputs)], + data[len(self._inputs):]) else: - outs = self.eval(data[:len(self._inputs)], - data[len(self._inputs):]) + outs = self.eval_batch(data[:len(self._inputs)], + data[len(self._inputs):]) # losses loss = outs[0] if self._metrics else outs @@ -1236,7 +1267,7 @@ class Model(fluid.dygraph.Layer): if mode == 'train': assert epoch is not None, 'when mode is train, epoch must be given' - callbacks.on_epoch_end(epoch) + callbacks.on_epoch_end(epoch, logs) return logs diff --git a/progressbar.py b/hapi/progressbar.py similarity index 100% rename from progressbar.py rename to hapi/progressbar.py diff --git a/hapi/text/__init__.py b/hapi/text/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2177ada5c0c7135e3feea0772b609d0ab29a7ba2 --- /dev/null +++ b/hapi/text/__init__.py @@ -0,0 +1,33 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hapi.text.text import RNNCell as RNNCell +from hapi.text.text import BasicLSTMCell as BasicLSTMCell +from hapi.text.text import BasicGRUCell as BasicGRUCell +from hapi.text.text import RNN as RNN +from hapi.text.text import DynamicDecode as DynamicDecode +from hapi.text.text import BeamSearchDecoder as BeamSearchDecoder +from hapi.text.text import MultiHeadAttention as MultiHeadAttention +from hapi.text.text import FFN as FFN +from hapi.text.text import TransformerEncoderLayer as TransformerEncoderLayer +from hapi.text.text import TransformerDecoderLayer as TransformerDecoderLayer +from hapi.text.text import TransformerEncoder as TransformerEncoder +from hapi.text.text import TransformerDecoder as TransformerDecoder +from hapi.text.text import TransformerBeamSearchDecoder as TransformerBeamSearchDecoder +from hapi.text.text import GRUCell as GRUCell +from hapi.text.text import GRUEncoderCell as GRUEncoderCell +from hapi.text.text import BiGRU as BiGRU +from hapi.text.text import Linear_chain_crf as Linear_chain_crf +from hapi.text.text import Crf_decoding as Crf_decoding +from hapi.text.text import SequenceTagging as SequenceTagging diff --git a/hapi/text/bert/__init__.py b/hapi/text/bert/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cd1332dfe97a5636ef6a0a855fa4931ba5903688 --- /dev/null +++ b/hapi/text/bert/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hapi.text.bert.bert import BertConfig as BertConfig +from hapi.text.bert.optimization import Optimizer as Optimizer +from hapi.text.bert.dataloader import BertDataLoader as BertDataLoader +from hapi.text.bert.dataloader import BertInputExample as BertInputExample +from hapi.text.tokenizer import tokenization as tokenization +from hapi.text.bert.bert import BertEncoder as BertEncoder diff --git a/hapi/text/bert/batching.py b/hapi/text/bert/batching.py new file mode 100644 index 0000000000000000000000000000000000000000..f9e3106856a51e0b49d61a7e01835a46cc9a4db2 --- /dev/null +++ b/hapi/text/bert/batching.py @@ -0,0 +1,189 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Mask, padding and batching.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + + +def mask(batch_tokens, total_token_num, vocab_size, CLS=1, SEP=2, MASK=3): + """ + Add mask for batch_tokens, return out, mask_label, mask_pos; + Note: mask_pos responding the batch_tokens after padded; + """ + max_len = max([len(sent) for sent in batch_tokens]) + mask_label = [] + mask_pos = [] + prob_mask = np.random.rand(total_token_num) + # Note: the first token is [CLS], so [low=1] + replace_ids = np.random.randint(1, high=vocab_size, size=total_token_num) + pre_sent_len = 0 + prob_index = 0 + for sent_index, sent in enumerate(batch_tokens): + mask_flag = False + prob_index += pre_sent_len + for token_index, token in enumerate(sent): + prob = prob_mask[prob_index + token_index] + if prob > 0.15: + continue + elif 0.03 < prob <= 0.15: + # mask + if token != SEP and token != CLS: + mask_label.append(sent[token_index]) + sent[token_index] = MASK + mask_flag = True + mask_pos.append(sent_index * max_len + token_index) + elif 0.015 < prob <= 0.03: + # random replace + if token != SEP and token != CLS: + mask_label.append(sent[token_index]) + sent[token_index] = replace_ids[prob_index + token_index] + mask_flag = True + mask_pos.append(sent_index * max_len + token_index) + else: + # keep the original token + if token != SEP and token != CLS: + mask_label.append(sent[token_index]) + mask_pos.append(sent_index * max_len + token_index) + pre_sent_len = len(sent) + + # ensure at least mask one word in a sentence + while not mask_flag: + token_index = int(np.random.randint(1, high=len(sent) - 1, size=1)) + if sent[token_index] != SEP and sent[token_index] != CLS: + mask_label.append(sent[token_index]) + sent[token_index] = MASK + mask_flag = True + mask_pos.append(sent_index * max_len + token_index) + mask_label = np.array(mask_label).astype("int64").reshape([-1, 1]) + mask_pos = np.array(mask_pos).astype("int64").reshape([-1, 1]) + return batch_tokens, mask_label, mask_pos + + +def prepare_batch_data(insts, + total_token_num, + voc_size=0, + pad_id=None, + cls_id=None, + sep_id=None, + mask_id=None, + return_input_mask=True, + return_max_len=True, + return_num_token=False): + """ + 1. generate Tensor of data + 2. generate Tensor of position + 3. generate self attention mask, [shape: batch_size * max_len * max_len] + """ + + batch_src_ids = [inst[0] for inst in insts] + batch_pos_ids = [inst[1] for inst in insts] + batch_sent_ids = [inst[2] for inst in insts] + labels_list = [] + # compatible with squad, whose example includes start/end positions, + # or unique id + + for i in range(3, len(insts[0]), 1): + labels = [inst[i] for inst in insts] + labels = np.array(labels).astype("int64").reshape([-1, 1]) + labels_list.append(labels) + + # First step: do mask without padding + if mask_id >= 0: + out, mask_label, mask_pos = mask( + batch_src_ids, + total_token_num, + vocab_size=voc_size, + CLS=cls_id, + SEP=sep_id, + MASK=mask_id) + else: + out = batch_src_ids + # Second step: padding + src_id, self_input_mask = pad_batch_data( + out, pad_idx=pad_id, return_input_mask=True) + pos_id = pad_batch_data( + batch_pos_ids, + pad_idx=pad_id, + return_pos=False, + return_input_mask=False) + sent_id = pad_batch_data( + batch_sent_ids, + pad_idx=pad_id, + return_pos=False, + return_input_mask=False) + + if mask_id >= 0: + return_list = [ + src_id, pos_id, sent_id, self_input_mask, mask_label, mask_pos + ] + labels_list + else: + return_list = [src_id, pos_id, sent_id, self_input_mask] + labels_list + + return return_list if len(return_list) > 1 else return_list[0] + + +def pad_batch_data(insts, + pad_idx=0, + return_pos=False, + return_input_mask=False, + return_max_len=False, + return_num_token=False): + """ + Pad the instances to the max sequence length in batch, and generate the + corresponding position data and input mask. + """ + return_list = [] + max_len = max(len(inst) for inst in insts) + # Any token included in dict can be used to pad, since the paddings' loss + # will be masked out by weights and make no effect on parameter gradients. + + inst_data = np.array([ + list(inst) + list([pad_idx] * (max_len - len(inst))) for inst in insts + ]) + return_list += [inst_data.astype("int64").reshape([-1, max_len])] + + # position data + if return_pos: + inst_pos = np.array([ + list(range(0, len(inst))) + [pad_idx] * (max_len - len(inst)) + for inst in insts + ]) + + return_list += [inst_pos.astype("int64").reshape([-1, max_len])] + + if return_input_mask: + # This is used to avoid attention on paddings. + input_mask_data = np.array([[1] * len(inst) + [0] * + (max_len - len(inst)) for inst in insts]) + input_mask_data = np.expand_dims(input_mask_data, axis=-1) + return_list += [input_mask_data.astype("float32")] + + if return_max_len: + return_list += [max_len] + + if return_num_token: + num_token = 0 + for inst in insts: + num_token += len(inst) + return_list += [num_token] + + return return_list if len(return_list) > 1 else return_list[0] + + +if __name__ == "__main__": + pass diff --git a/hapi/text/bert/bert.py b/hapi/text/bert/bert.py new file mode 100644 index 0000000000000000000000000000000000000000..fdf17ac100b7d9b3b4cecee2f9d3af9fc3d1ca84 --- /dev/null +++ b/hapi/text/bert/bert.py @@ -0,0 +1,160 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"bert" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import six +import json +import numpy as np + +import paddle +import paddle.fluid as fluid +from paddle.fluid.dygraph import Embedding, LayerNorm, Linear, to_variable, Layer, guard + +from hapi.text.text import PrePostProcessLayer, TransformerEncoder +from hapi.text.bert.utils.init import init_from_static_model + + +class BertConfig(object): + def __init__(self, config_path): + self._config_dict = self._parse(config_path) + + def _parse(self, config_path): + try: + with open(config_path) as json_file: + config_dict = json.load(json_file) + except Exception: + raise IOError("Error in parsing bert model config file '%s'" % + config_path) + else: + return config_dict + + def __getitem__(self, key): + return self._config_dict[key] + + def print_config(self): + for arg, value in sorted(six.iteritems(self._config_dict)): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + + +class BertEncoder(Layer): + """ + bert + """ + + def __init__(self, config, return_pooled_out=True, use_fp16=False): + super(BertEncoder, self).__init__() + + self.config = config + self._emb_size = config['hidden_size'] + self._n_layer = config['num_hidden_layers'] + self._n_head = config['num_attention_heads'] + self._voc_size = config['vocab_size'] + self._max_position_seq_len = config['max_position_embeddings'] + self._sent_types = config['type_vocab_size'] + self._hidden_act = config['hidden_act'] + self._prepostprocess_dropout = config['hidden_dropout_prob'] + self._attention_dropout = config['attention_probs_dropout_prob'] + self.return_pooled_out = return_pooled_out + + self._word_emb_name = "word_embedding" + self._pos_emb_name = "pos_embedding" + self._sent_emb_name = "sent_embedding" + self._dtype = "float16" if use_fp16 else "float32" + + self._param_initializer = fluid.initializer.TruncatedNormal( + scale=config['initializer_range']) + + self._src_emb = Embedding( + size=[self._voc_size, self._emb_size], + param_attr=fluid.ParamAttr( + name=self._word_emb_name, initializer=self._param_initializer), + dtype=self._dtype) + + self._pos_emb = Embedding( + size=[self._max_position_seq_len, self._emb_size], + param_attr=fluid.ParamAttr( + name=self._pos_emb_name, initializer=self._param_initializer), + dtype=self._dtype) + + self._sent_emb = Embedding( + size=[self._sent_types, self._emb_size], + param_attr=fluid.ParamAttr( + name=self._sent_emb_name, initializer=self._param_initializer), + dtype=self._dtype) + + self.pooled_fc = Linear( + input_dim=self._emb_size, + output_dim=self._emb_size, + param_attr=fluid.ParamAttr( + name="pooled_fc.w_0", initializer=self._param_initializer), + bias_attr="pooled_fc.b_0", + act="tanh") + + self.pre_process_layer = PrePostProcessLayer( + "nd", self._emb_size, self._prepostprocess_dropout, None) + + self._encoder = TransformerEncoder( + n_layer=self._n_layer, + n_head=self._n_head, + d_key=self._emb_size // self._n_head, + d_value=self._emb_size // self._n_head, + d_model=self._emb_size, + d_inner_hid=self._emb_size * 4, + prepostprocess_dropout=self._prepostprocess_dropout, + attention_dropout=self._attention_dropout, + relu_dropout=0, + preprocess_cmd="", + postprocess_cmd="dan", + ffn_fc1_act=self._hidden_act) + + def init_parameters(self, param_path="", verbose=False): + init_from_static_model(param_path, self, self.config, verbose) + + def forward(self, src_ids, position_ids, sentence_ids, input_mask): + """ + forward + """ + src_emb = self._src_emb(src_ids) + pos_emb = self._pos_emb(position_ids) + sent_emb = self._sent_emb(sentence_ids) + + emb_out = src_emb + pos_emb + emb_out = emb_out + sent_emb + + emb_out = self.pre_process_layer(emb_out) + + self_attn_mask = fluid.layers.matmul( + x=input_mask, y=input_mask, transpose_y=True) + self_attn_mask = fluid.layers.scale( + x=self_attn_mask, scale=10000.0, bias=-1.0, bias_after_scale=False) + n_head_self_attn_mask = fluid.layers.stack( + x=[self_attn_mask] * self._n_head, axis=1) + n_head_self_attn_mask.stop_gradient = True + + enc_output = self._encoder(emb_out, n_head_self_attn_mask) + + if not self.return_pooled_out: + return enc_output + next_sent_feat = fluid.layers.slice( + input=enc_output, axes=[1], starts=[0], ends=[1]) + next_sent_feat = self.pooled_fc(next_sent_feat) + next_sent_feat = fluid.layers.reshape( + next_sent_feat, shape=[-1, self._emb_size]) + + return enc_output, next_sent_feat diff --git a/hapi/text/bert/data_processor.py b/hapi/text/bert/data_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..7429fcb66d12eef7c4b3664d6fc14b3db817476c --- /dev/null +++ b/hapi/text/bert/data_processor.py @@ -0,0 +1,676 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +import os +import types +import csv +import numpy as np + +import hapi.text.tokenizer.tokenization as tokenization +from hapi.text.bert.batching import prepare_batch_data + + +class DataProcessor(object): + """Base class for data converters for sequence classification data sets.""" + + def __init__(self, tokenizer, max_seq_len, in_tokens, random_seed=None): + + self.max_seq_len = max_seq_len + self.tokenizer = tokenizer + self.vocab = self.tokenizer.vocab + + self.in_tokens = in_tokens + np.random.seed(random_seed) + + self.current_train_example = -1 + self.num_examples = {'train': -1, 'dev': -1, 'test': -1} + self.current_train_epoch = -1 + + def get_train_iter(self, + data_dir, + epoch_num=1, + shuffle=True, + shuffle_seed=None): + """Gets a collection of `InputExample`s for the train set.""" + raise NotImplementedError() + + def get_dev_iter(self, data_dir): + """Gets a collection of `InputExample`s for the dev set.""" + raise NotImplementedError() + + def get_test_iter(self, data_dir): + """Gets a collection of `InputExample`s for prediction.""" + raise NotImplementedError() + + def get_labels(self): + """Gets the list of labels for this data set.""" + raise NotImplementedError() + + def convert_example(self, index, example, labels, max_seq_len, tokenizer): + """Converts a single `InputExample` into a single `InputFeatures`.""" + feature = convert_single_example(index, example, labels, max_seq_len, + tokenizer) + return feature + + def _read_tsv(cls, input_file, quotechar=None): + """Reads a tab separated value file.""" + with io.open(input_file, "r", encoding="utf8") as f: + reader = csv.reader(f, delimiter="\t", quotechar=quotechar) + lines = [] + for line in reader: + lines.append(line) + return lines + + def generate_instance(self, feature): + """ + generate instance with given feature + + Args: + feature: InputFeatures(object). A single set of features of data. + """ + input_pos = list(range(len(feature.input_ids))) + return [ + feature.input_ids, feature.segment_ids, input_pos, feature.label_id + ] + + def generate_batch_data(self, + batch_data, + total_token_num, + voc_size=-1, + mask_id=-1, + return_input_mask=True, + return_max_len=False, + return_num_token=False): + return prepare_batch_data( + batch_data, + total_token_num, + voc_size=-1, + pad_id=self.vocab["[PAD]"], + cls_id=self.vocab["[CLS]"], + sep_id=self.vocab["[SEP]"], + mask_id=-1, + return_input_mask=True, + return_max_len=False, + return_num_token=False) + + def get_num_examples(self, phase): + """Get number of examples for train, dev or test.""" + if phase not in ['train', 'dev', 'test']: + raise ValueError( + "Unknown phase, which should be in ['train', 'dev', 'test'].") + if phase == 'train': + return len(self.train_examples) + elif phase == 'dev': + return len(self.dev_examples) + elif phase == 'test': + return len(self.test_examples) + else: + raise ValueError( + "Unknown phase, which should be in ['train', 'dev', 'test'].") + + def get_train_progress(self): + """Gets progress for training phase.""" + return self.current_train_example, self.current_train_epoch + + def data_generator(self, data_iter, batch_size, phase='train', + dev_count=1): + """ + Generate data for train, dev or test. + + Args: + batch_size: int. The batch size of generated data. + phase: string. The phase for which to generate data. + """ + assert phase in ['train', 'dev', 'test'] + if phase == 'train': + sample_num = len(self.train_examples) + elif phase == 'dev': + sample_num = len(self.dev_examples) + elif phase == 'test': + sample_num = len(self.test_examples) + else: + sample_num = -1 + self.num_examples[phase] = sample_num + + def instance_reader(): + for epoch_idx, example_idx, example in data_iter(): + if phase == 'train': + self.current_train_epoch = epoch_idx + self.current_train_example = example_idx + feature = self.convert_example( + example_idx, example, + self.get_labels(), self.max_seq_len, self.tokenizer) + + instance = self.generate_instance(feature) + yield instance + + def batch_reader(reader, batch_size, in_tokens): + batch, total_token_num, max_len = [], 0, 0 + for instance in reader(): + token_ids, sent_ids, pos_ids, label = instance[:4] + max_len = max(max_len, len(token_ids)) + if in_tokens: + to_append = (len(batch) + 1) * max_len <= batch_size + else: + to_append = len(batch) < batch_size + if to_append: + batch.append(instance) + total_token_num += len(token_ids) + else: + yield batch, total_token_num + batch, total_token_num, max_len = [instance], len( + token_ids), len(token_ids) + + if len(batch) > 0: + yield batch, total_token_num + + def wrapper(): + all_dev_batches = [] + for batch_data, total_token_num in batch_reader( + instance_reader, batch_size, self.in_tokens): + batch_data = self.generate_batch_data( + batch_data, + total_token_num, + voc_size=-1, + mask_id=-1, + return_input_mask=True, + return_max_len=False, + return_num_token=False) + if len(all_dev_batches) < dev_count: + all_dev_batches.append(batch_data) + + if len(all_dev_batches) == dev_count: + for batch in all_dev_batches: + yield batch + all_dev_batches = [] + + return wrapper + + +class InputExample(object): + """A single training/test example for simple sequence classification.""" + + def __init__(self, guid, text_a, text_b=None, label=None): + """Constructs a InputExample. + + Args: + guid: Unique id for the example. + text_a: string. The untokenized text of the first sequence. For single + sequence tasks, only this sequence must be specified. + text_b: (Optional) string. The untokenized text of the second sequence. + Only must be specified for sequence pair tasks. + label: (Optional) string. The label of the example. This should be + specified for train and dev examples, but not for test examples. + """ + self.guid = guid + self.text_a = text_a + self.text_b = text_b + self.label = label + + +def _truncate_seq_pair(tokens_a, tokens_b, max_length): + """Truncates a sequence pair in place to the maximum length.""" + + # This is a simple heuristic which will always truncate the longer sequence + # one token at a time. This makes more sense than truncating an equal percent + # of tokens from each, since if one sequence is very short then each token + # that's truncated likely contains more information than a longer sequence. + while True: + total_length = len(tokens_a) + len(tokens_b) + if total_length <= max_length: + break + if len(tokens_a) > len(tokens_b): + tokens_a.pop() + else: + tokens_b.pop() + + +class InputFeatures(object): + """A single set of features of data.""" + + def __init__(self, input_ids, input_mask, segment_ids, label_id): + self.input_ids = input_ids + self.input_mask = input_mask + self.segment_ids = segment_ids + self.label_id = label_id + + +class XnliProcessor(DataProcessor): + """Processor for the XNLI data set.""" + + def get_train_iter(self, + data_dir, + epoch_num=1, + shuffle=True, + shuffle_seed=None): + """See base class.""" + self.language = "zh" + lines = self._read_tsv( + os.path.join(data_dir, "multinli", "multinli.train.%s.tsv" % + self.language)) + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "train-%d" % (i) + text_a = tokenization.convert_to_unicode(line[0]) + text_b = tokenization.convert_to_unicode(line[1]) + label = tokenization.convert_to_unicode(line[2]) + if label == tokenization.convert_to_unicode("contradictory"): + label = tokenization.convert_to_unicode("contradiction") + examples.append( + InputExample( + guid=guid, text_a=text_a, text_b=text_b, label=label)) + + self.train_examples = examples + + def wrapper(): + if shuffle: + if shuffle_seed is not None: + np.random.seed(shuffle_seed) + for epoch_idx in range(epoch_num): + if shuffle: + np.random.shuffle(examples) + for (example_idx, example) in enumerate(examples): + yield epoch_idx, example_idx, example + + return wrapper + + def get_dev_iter(self, data_dir): + """See base class.""" + self.language = "zh" + lines = self._read_tsv(os.path.join(data_dir, "xnli.dev.tsv")) + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "dev-%d" % (i) + language = tokenization.convert_to_unicode(line[0]) + if language != tokenization.convert_to_unicode(self.language): + continue + text_a = tokenization.convert_to_unicode(line[6]) + text_b = tokenization.convert_to_unicode(line[7]) + label = tokenization.convert_to_unicode(line[1]) + examples.append( + InputExample( + guid=guid, text_a=text_a, text_b=text_b, label=label)) + + self.dev_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_test_iter(self, data_dir): + """See base class.""" + self.language = "zh" + lines = self._read_tsv(os.path.join(data_dir, "xnli.test.tsv")) + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "test-%d" % (i) + language = tokenization.convert_to_unicode(line[0]) + if language != tokenization.convert_to_unicode(self.language): + continue + text_a = tokenization.convert_to_unicode(line[6]) + text_b = tokenization.convert_to_unicode(line[7]) + label = tokenization.convert_to_unicode(line[1]) + examples.append( + InputExample( + guid=guid, text_a=text_a, text_b=text_b, label=label)) + + self.test_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + +class MnliProcessor(DataProcessor): + """Processor for the MultiNLI data set (GLUE version).""" + + def get_train_iter(self, + data_dir, + epoch_num=1, + shuffle=True, + shuffle_seed=None): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "train.tsv")), "train") + + self.train_examples = examples + + def wrapper(): + if shuffle: + if shuffle_seed is not None: + np.random.seed(shuffle_seed) + for epoch_idx in range(epoch_num): + if shuffle: + np.random.shuffle(examples) + for (example_idx, example) in enumerate(examples): + yield epoch_idx, example_idx, example + + return wrapper + + def get_dev_iter(self, data_dir): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev_matched.tsv")), + "dev_matched") + + self.dev_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_test_iter(self, data_dir): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "test_matched.tsv")), "test") + + self.test_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_labels(self): + """See base class.""" + return ["contradiction", "entailment", "neutral"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training and dev sets.""" + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "%s-%s" % (set_type, + tokenization.convert_to_unicode(line[0])) + text_a = tokenization.convert_to_unicode(line[8]) + text_b = tokenization.convert_to_unicode(line[9]) + if set_type == "test": + label = "contradiction" + else: + label = tokenization.convert_to_unicode(line[-1]) + examples.append( + InputExample( + guid=guid, text_a=text_a, text_b=text_b, label=label)) + return examples + + +class MrpcProcessor(DataProcessor): + """Processor for the MRPC data set (GLUE version).""" + + def get_train_iter(self, + data_dir, + epoch_num=1, + shuffle=True, + shuffle_seed=None): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "train.tsv")), "train") + + self.train_examples = examples + + def wrapper(): + if shuffle: + if shuffle_seed is not None: + np.random.seed(shuffle_seed) + for epoch_idx in range(epoch_num): + if shuffle: + np.random.shuffle(examples) + for (example_idx, example) in enumerate(examples): + yield epoch_idx, example_idx, example + + return wrapper + + def get_dev_examples(self, data_dir): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev") + + self.dev_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_test_examples(self, data_dir): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "test.tsv")), "test") + + self.test_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_labels(self): + """See base class.""" + return ["0", "1"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training and dev sets.""" + examples = [] + for (i, line) in enumerate(lines): + if i == 0: + continue + guid = "%s-%s" % (set_type, i) + text_a = tokenization.convert_to_unicode(line[3]) + text_b = tokenization.convert_to_unicode(line[4]) + if set_type == "test": + label = "0" + else: + label = tokenization.convert_to_unicode(line[0]) + examples.append( + InputExample( + guid=guid, text_a=text_a, text_b=text_b, label=label)) + return examples + + +class ColaProcessor(DataProcessor): + """Processor for the CoLA data set (GLUE version).""" + + def get_train_iter(self, + data_dir, + epoch_num=1, + shuffle=True, + shuffle_seed=None): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "train.tsv")), "train") + + self.train_examples = examples + + def wrapper(): + if shuffle: + if shuffle_seed is not None: + np.random.seed(shuffle_seed) + for epoch_idx in range(epoch_num): + if shuffle: + np.random.shuffle(examples) + for (example_idx, example) in enumerate(examples): + yield epoch_idx, example_idx, example + + return wrapper + + def get_dev_iter(self, data_dir): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev") + + self.dev_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_test_iter(self, data_dir): + """See base class.""" + examples = self._create_examples( + self._read_tsv(os.path.join(data_dir, "test.tsv")), "test") + + self.test_examples = examples + + def wrapper(): + for (example_idx, example) in enumerate(examples): + yield 0, example_idx, example + + return wrapper + + def get_labels(self): + """See base class.""" + return ["0", "1"] + + def _create_examples(self, lines, set_type): + """Creates examples for the training and dev sets.""" + examples = [] + for (i, line) in enumerate(lines): + # Only the test set has a header + if set_type == "test" and i == 0: + continue + guid = "%s-%s" % (set_type, i) + if set_type == "test": + text_a = tokenization.convert_to_unicode(line[1]) + label = "0" + else: + text_a = tokenization.convert_to_unicode(line[3]) + label = tokenization.convert_to_unicode(line[1]) + examples.append( + InputExample( + guid=guid, text_a=text_a, text_b=None, label=label)) + return examples + + +def convert_single_example_to_unicode(guid, single_example): + text_a = tokenization.convert_to_unicode(single_example[0]) + text_b = tokenization.convert_to_unicode(single_example[1]) + label = tokenization.convert_to_unicode(single_example[2]) + return InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label) + + +def convert_single_example(ex_index, example, label_list, max_seq_length, + tokenizer): + """Converts a single `InputExample` into a single `InputFeatures`.""" + label_map = {} + for (i, label) in enumerate(label_list): + label_map[label] = i + + tokens_a = tokenizer.tokenize(example.text_a) + tokens_b = None + if example.text_b: + tokens_b = tokenizer.tokenize(example.text_b) + + if tokens_b: + # Modifies `tokens_a` and `tokens_b` in place so that the total + # length is less than the specified length. + # Account for [CLS], [SEP], [SEP] with "- 3" + _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3) + else: + # Account for [CLS] and [SEP] with "- 2" + if len(tokens_a) > max_seq_length - 2: + tokens_a = tokens_a[0:(max_seq_length - 2)] + + # The convention in BERT is: + # (a) For sequence pairs: + # tokens: [CLS] is this jack ##son ##ville ? [SEP] no it is not . [SEP] + # type_ids: 0 0 0 0 0 0 0 0 1 1 1 1 1 1 + # (b) For single sequences: + # tokens: [CLS] the dog is hairy . [SEP] + # type_ids: 0 0 0 0 0 0 0 + # + # Where "type_ids" are used to indicate whether this is the first + # sequence or the second sequence. The embedding vectors for `type=0` and + # `type=1` were learned during pre-training and are added to the wordpiece + # embedding vector (and position vector). This is not *strictly* necessary + # since the [SEP] token unambiguously separates the sequences, but it makes + # it easier for the model to learn the concept of sequences. + # + # For classification tasks, the first vector (corresponding to [CLS]) is + # used as as the "sentence vector". Note that this only makes sense because + # the entire model is fine-tuned. + tokens = [] + segment_ids = [] + tokens.append("[CLS]") + segment_ids.append(0) + for token in tokens_a: + tokens.append(token) + segment_ids.append(0) + tokens.append("[SEP]") + segment_ids.append(0) + + if tokens_b: + for token in tokens_b: + tokens.append(token) + segment_ids.append(1) + tokens.append("[SEP]") + segment_ids.append(1) + + input_ids = tokenizer.convert_tokens_to_ids(tokens) + + # The mask has 1 for real tokens and 0 for padding tokens. Only real + # tokens are attended to. + input_mask = [1] * len(input_ids) + + label_id = label_map[example.label] + + feature = InputFeatures( + input_ids=input_ids, + input_mask=input_mask, + segment_ids=segment_ids, + label_id=label_id) + return feature + + +def convert_examples_to_features(examples, label_list, max_seq_length, + tokenizer): + """Convert a set of `InputExample`s to a list of `InputFeatures`.""" + + features = [] + for (ex_index, example) in enumerate(examples): + if ex_index % 10000 == 0: + print("Writing example %d of %d" % (ex_index, len(examples))) + + feature = convert_single_example(ex_index, example, label_list, + max_seq_length, tokenizer) + + features.append(feature) + return features + + +if __name__ == '__main__': + print("hello world") + pass diff --git a/hapi/text/bert/dataloader.py b/hapi/text/bert/dataloader.py new file mode 100644 index 0000000000000000000000000000000000000000..838ad50b370c7ff4c9247880adc994be3aedf501 --- /dev/null +++ b/hapi/text/bert/dataloader.py @@ -0,0 +1,441 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +import os +import six +import csv +import glob +import tarfile +import itertools +import leveldb +from functools import partial + +import numpy as np +import paddle.fluid as fluid +from paddle.fluid.dygraph.parallel import ParallelEnv +from paddle.io import BatchSampler, DataLoader, Dataset +from hapi.distributed import DistributedBatchSampler +from hapi.text.bert.data_processor import DataProcessor, XnliProcessor, ColaProcessor, MrpcProcessor, MnliProcessor +from hapi.text.bert.batching import prepare_batch_data +import hapi.text.tokenizer.tokenization as tokenization +from paddle.fluid.dygraph.parallel import ParallelEnv, ParallelStrategy + +__all__ = [ + 'BertInputExample', 'BertInputFeatures', 'SingleSentenceDataset', + 'SentencePairDataset', 'BertDataLoader' +] + + +class BertInputExample(object): + def __init__(self, uid, text_a, text_b=None, label=None): + self.uid = uid + self.text_a = text_a + self.text_b = text_b + self.label = label + + +class BertInputFeatures(object): + def __init__(self, input_ids, input_mask, segment_ids, label_id): + self.input_ids = input_ids + self.pos_ids = list(range(len(self.input_ids))) + self.input_mask = input_mask + self.segment_ids = segment_ids + self.label_id = label_id + + +def _truncate_seq_pair(tokens_a, tokens_b, max_length): + """Truncates a sequence pair in place to the maximum length.""" + # This is a simple heuristic which will always truncate the longer sequence + # one token at a time. This makes more sense than truncating an equal percent + # of tokens from each, since if one sequence is very short then each token + # that's truncated likely contains more information than a longer sequence. + + while True: + total_length = len(tokens_a) + len(tokens_b) + if total_length <= max_length: + break + if len(tokens_a) > len(tokens_b): + tokens_a.pop() + else: + tokens_b.pop() + + +def convert_single_example_to_unicode(guid, single_example): + text_a = tokenization.convert_to_unicode(single_example[0]) + text_b = tokenization.convert_to_unicode(single_example[1]) + label = tokenization.convert_to_unicode(single_example[2]) + return BertInputExample(uid=uid, text_a=text_a, text_b=text_b, label=label) + + +def convert_single_example(ex_index, example, label_list, max_seq_length, + tokenizer): + """Converts a single `BertInputExample` into a single `BertInputFeatures`.""" + label_map = {} + for (i, label) in enumerate(label_list): + label_map[label] = i + + tokens_a = tokenizer.tokenize(example.text_a) + tokens_b = None + if example.text_b: + tokens_b = tokenizer.tokenize(example.text_b) + + if tokens_b: + # Modifies `tokens_a` and `tokens_b` in place so that the total + # length is less than the specified length. + # Account for [CLS], [SEP], [SEP] with "- 3" + _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3) + else: + # Account for [CLS] and [SEP] with "- 2" + if len(tokens_a) > max_seq_length - 2: + tokens_a = tokens_a[0:(max_seq_length - 2)] + + tokens = [] + segment_ids = [] + tokens.append("[CLS]") + segment_ids.append(0) + for token in tokens_a: + tokens.append(token) + segment_ids.append(0) + tokens.append("[SEP]") + segment_ids.append(0) + + if tokens_b: + for token in tokens_b: + tokens.append(token) + segment_ids.append(1) + tokens.append("[SEP]") + segment_ids.append(1) + + input_ids = tokenizer.convert_tokens_to_ids(tokens) + + input_mask = [1] * len(input_ids) + label_id = label_map[example.label] + + feature = BertInputFeatures( + input_ids=input_ids, + input_mask=input_mask, + segment_ids=segment_ids, + label_id=label_id) + + return feature + + +def convert_examples_to_features(examples, label_list, max_seq_length, + tokenizer): + """Convert a set of `InputExample`s to a list of `InputFeatures`.""" + + features = [] + for (ex_index, example) in enumerate(examples): + if ex_index % 10000 == 0: + print("Writing example %d of %d" % (ex_index, len(examples))) + + feature = convert_single_example(ex_index, example, label_list, + max_seq_length, tokenizer) + + features.append(feature) + + return features + + +def _read_tsv(input_file, delimiter="\t", quotechar=None): + """Reads a tab separated value file.""" + with io.open(input_file, "r", encoding="utf8") as f: + reader = csv.reader(f, delimiter=delimiter, quotechar=quotechar) + lines = [] + for line in reader: + lines.append(line) + return lines + + +class SingleSentenceDataset(Dataset): + def __init__(self, + tokenizer, + label_list, + max_seq_length, + mode="all_in_memory"): + + assert isinstance(mode, + str), "mode of SingleSentenceDataset should be str" + assert mode in [ + "all_in_memory", "leveldb", "streaming" + ], "mode of SingleSentenceDataset should be in [all_in_memory, leveldb, streaming], but get" % mode + + self.delimiter = None + self.mode = mode + self.examples = [] + self._db = None + self._line_processor = None + + def load_all_data_in_memory(self, + input_file, + label_list, + max_seq_length, + tokenizer, + line_processor=None, + delimiter="\t", + quotechar=None): + lines = _read_tsv(input_file, delimiter=delimiter, quotechar=quotechar) + + def default_line_processor(line_id, line): + assert len(line) == 2 + text_a = line[0] + label = line[1] + + return BertInputExample( + str(line_id), text_a=text_a, text_b=None, label=label) + + if line_processor is None: + line_processor = default_line_processor + + for (line_id, line) in enumerate(lines): + input_example = line_processor(line_id, line) + if not input_example: + continue + input_feature = convert_single_example( + str(line_id), input_example, label_list, max_seq_length, + tokenizer) + self.examples.append(input_feature) + + def prepare_leveldb(self, + input_file, + leveldb_file, + label_list, + max_seq_length, + tokenizer, + line_processor=None, + delimiter="\t", + quotechar=None): + def default_line_processor(line_id, line): + assert len(line) == 2 + text_a = line[0] + label = line[1] + + return BertInputExample( + str(line_id), text_a=text_a, text_b=None, label=label) + + if line_processor is None: + line_processor = default_line_processor + + if ParallelEnv().nranks > 1: + leveldb_file = leveldb_file + "_" + str(ParallelEnv().local_rank) + + if not os.path.exists(leveldb_file): + print("putting data %s into leveldb %s" % + (input_file, leveldb_file)) + _example_num = 0 + _db = leveldb.LevelDB(leveldb_file, create_if_missing=True) + with io.open(input_file, "r", encoding="utf8") as f: + reader = csv.reader( + f, delimiter=delimiter, quotechar=quotechar) + line_id = 0 + for (_line_id, line) in enumerate(reader): + if line_processor(str(_line_id), line) is None: + continue + + line_str = delimiter.join(line) + _db.Put( + str(line_id).encode("utf8"), line_str.encode("utf8")) + line_id += 1 + _example_num += 1 + _db.Put("_example_num_".encode("utf8"), + str(_example_num).encode("utf8")) + else: + _db = leveldb.LevelDB(leveldb_file, create_if_missing=False) + + self.label_list = label_list + self.max_seq_length = max_seq_length + self.tokenizer = tokenizer + self.delimiter = delimiter + self._db = _db + self._line_processor = line_processor + + def __getitem__(self, idx): + + if self.mode == "all_in_memory": + return self.examples[idx].input_ids, self.examples[ + idx].pos_ids, self.examples[idx].segment_ids, self.examples[ + idx].label_id + + if self.mode == "leveldb": + assert self._db is not None, "you shold call prepare_leveldb before you run dataloader" + line_str = self._db.Get(str(idx).encode("utf8")) + line_str = line_str.decode("utf8") + + line = line_str.split(self.delimiter) + input_example = self._line_processor(str(idx + 1), line) + + input_example = convert_single_example( + str(idx + 1), input_example, self.label_list, + self.max_seq_length, self.tokenizer) + + return input_example.input_ids, input_example.pos_ids, input_example.segment_ids, input_example.label_id + + def __len__(self): + if self.mode == "all_in_memory": + return len(self.examples) + + if self.mode == "leveldb": + assert self._db is not None, "you shold call prepare_leveldb before you run dataloader" + + exmaple_num = self._db.Get("_example_num_".encode("utf8")) + exmaple_num = exmaple_num.decode("utf8") + return int(exmaple_num) + + +class SentencePairDataset(Dataset): + def __init__(self, + tokenizer, + label_ist, + max_seq_length, + mode="all_in_memory"): + + assert isinstance(mode, + str), "mode of SentencePairDataset should be str" + assert mode in [ + "all_in_memory", "leveldb" + ], "mode of SentencePairDataset should be in [all_in_memory, leveldb], but get" % mode + + self.examples = [] + + def load_all_data_in_memory(self, + input_file, + label_list, + max_seq_length, + tokenizer, + line_processor=None, + delimiter="\t", + quotechar=None): + lines = _read_tsv(input_file, delimiter=delimiter, quotechar=quotechar) + + def default_line_processor(line_id, line): + assert len(line) == 3 + text_a = line[0] + text_b = line[1] + label = line[2] + + return BertInputExample( + str(line_id), text_a=text_a, text_b=text_b, label=label) + + if line_processor is None: + line_processor = default_line_processor + + for (line_id, line) in enumerate(lines): + input_example = line_processor(line_id, line) + if not input_example: + continue + input_feature = convert_single_example( + str(line_id), input_example, label_list, max_seq_length, + tokenizer) + self.examples.append(input_feature) + + def __getitem__(self, idx): + return self.examples[idx].input_ids, self.examples[ + idx].pos_ids, self.examples[idx].segment_ids, self.examples[ + idx].label_id + + def __len__(self): + return len(self.examples) + + +def _prepare_train_batch(insts, + vocab_size=0, + pad_id=None, + cls_id=None, + sep_id=None, + mask_id=-1, + return_input_mask=True, + return_max_len=True, + return_num_token=False): + + return prepare_batch_data( + insts, + 0, + voc_size=vocab_size, + pad_id=pad_id, + cls_id=cls_id, + sep_id=sep_id, + mask_id=mask_id, + return_input_mask=return_input_mask, + return_max_len=return_max_len, + return_num_token=return_num_token) + + +class BertDataLoader(object): + def __init__(self, + input_file, + tokenizer, + label_list, + max_seq_length, + batch_size, + shuffle=False, + drop_last=False, + mode="all_in_memory", + leveldb_file="./leveldb", + line_processor=None, + delimiter="\t", + quotechar=None, + device=fluid.CPUPlace(), + num_workers=0, + return_list=True, + phase="train"): + + assert phase in [ + "train", "predict", "test" + ], "phase of BertDataLoader should be in [train, predict, test], but get %s" % phase + + self.dataset = SingleSentenceDataset(tokenizer, label_list, + max_seq_length, mode) + + if mode == "all_in_memory": + self.dataset.load_all_data_in_memory( + input_file, label_list, max_seq_length, tokenizer, + line_processor, delimiter, quotechar) + elif mode == "leveldb": + self.dataset.prepare_leveldb(input_file, leveldb_file, label_list, + max_seq_length, tokenizer, + line_processor, delimiter, quotechar) + else: + raise ValueError("mode should be in [all_in_memory, leveldb]") + + if phase == "train": + self.sampler = DistributedBatchSampler( + self.dataset, batch_size, shuffle=shuffle, drop_last=drop_last) + elif phase == "test" or phase == "predict": + self.sampler = BatchSampler( + dataset=self.dataset, + batch_size=batch_size, + shuffle=shuffle, + drop_last=drop_last) + + self.dataloader = DataLoader( + dataset=self.dataset, + batch_sampler=self.sampler, + places=device, + collate_fn=partial( + _prepare_train_batch, + vocab_size=-1, + pad_id=tokenizer.vocab["[PAD]"], + cls_id=tokenizer.vocab["[CLS]"], + sep_id=tokenizer.vocab["[SEP]"], + mask_id=-1, + return_input_mask=True, + return_max_len=False, + return_num_token=False), + num_workers=num_workers, + return_list=return_list) + + +if __name__ == "__main__": + print("hello world.") diff --git a/hapi/text/bert/optimization.py b/hapi/text/bert/optimization.py new file mode 100755 index 0000000000000000000000000000000000000000..b2ba8f65a744754e8ff96ca66ccf818bc8b06c34 --- /dev/null +++ b/hapi/text/bert/optimization.py @@ -0,0 +1,182 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Optimization and learning rate scheduling.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle.fluid as fluid + +from paddle.fluid.dygraph.learning_rate_scheduler import LearningRateDecay + + +class ConstantLR(LearningRateDecay): + def __init__(self, learning_rate, begin=0, step=1, dtype='float32'): + super(ConstantLR, self).__init__(begin, step, dtype) + self.learning_rate = learning_rate + + def step(self): + return self.learning_rate + + +class LinearDecay(LearningRateDecay): + def __init__(self, + learning_rate, + warmup_steps, + decay_steps, + end_learning_rate=0.0001, + power=1.0, + cycle=False, + begin=0, + step=1, + dtype='float32'): + super(LinearDecay, self).__init__(begin, step, dtype) + self.learning_rate = learning_rate + self.warmup_steps = warmup_steps + self.decay_steps = decay_steps + self.end_learning_rate = end_learning_rate + self.power = power + self.cycle = cycle + + def step(self): + if self.step_num < self.warmup_steps: + decayed_lr = self.learning_rate * (self.step_num / + self.warmup_steps) + decayed_lr = self.create_lr_var(decayed_lr) + else: + tmp_step_num = self.step_num + tmp_decay_steps = self.decay_steps + if self.cycle: + div_res = fluid.layers.ceil( + self.create_lr_var(tmp_step_num / float(self.decay_steps))) + if tmp_step_num == 0: + div_res = self.create_lr_var(1.0) + tmp_decay_steps = self.decay_steps * div_res + else: + tmp_step_num = self.create_lr_var( + tmp_step_num + if tmp_step_num < self.decay_steps else self.decay_steps) + decayed_lr = (self.learning_rate - self.end_learning_rate) * \ + ((1 - tmp_step_num / tmp_decay_steps) ** self.power) + self.end_learning_rate + + return decayed_lr + + +class Optimizer(object): + def __init__(self, + warmup_steps, + num_train_steps, + learning_rate, + model_cls, + weight_decay, + scheduler='linear_warmup_decay', + loss_scaling=1.0, + parameter_list=None): + self.warmup_steps = warmup_steps + self.num_train_steps = num_train_steps + self.learning_rate = learning_rate + self.model_cls = model_cls + self.weight_decay = weight_decay + self.scheduler = scheduler + self.loss_scaling = loss_scaling + self.parameter_list = parameter_list + + self.scheduled_lr = 0.0 + self.optimizer = self.lr_schedule() + + def lr_schedule(self): + if self.warmup_steps > 0: + if self.scheduler == 'noam_decay': + self.scheduled_lr = fluid.dygraph.NoamDecay(1 / ( + self.warmup_steps * (self.learning_rate**2)), + self.warmup_steps) + elif self.scheduler == 'linear_warmup_decay': + self.scheduled_lr = LinearDecay(self.learning_rate, + self.warmup_steps, + self.num_train_steps, 0.0) + else: + raise ValueError("Unkown learning rate scheduler, should be " + "'noam_decay' or 'linear_warmup_decay'") + optimizer = fluid.optimizer.Adam( + learning_rate=self.scheduled_lr, + parameter_list=self.parameter_list) + else: + self.scheduled_lr = ConstantLR(self.learning_rate) + optimizer = fluid.optimizer.Adam( + learning_rate=self.scheduled_lr, + parameter_list=self.parameter_list) + + return optimizer + + def exclude_from_weight_decay(self, name): + if name.find("layer_norm") > -1: + return True + bias_suffix = ["_bias", "_b", ".b_0"] + for suffix in bias_suffix: + if name.endswith(suffix): + return True + return False + + def state_dict(self): + return self.optimizer.state_dict() + + def set_dict(self, state_dict): + return self.optimizer.set_dict(state_dict) + + def get_opti_var_name_list(self): + return self.optimizer.get_opti_var_name_list() + + def current_step_lr(self): + return self.optimizer.current_step_lr() + + def minimize(self, loss, use_data_parallel=False, model=None): + param_list = dict() + + clip_norm_thres = 1.0 + #grad_clip = fluid.clip.GradientClipByGlobalNorm(clip_norm_thres) + + if use_data_parallel: + loss = model.scale_loss(loss) + + loss.backward() + + if self.weight_decay > 0: + for param in self.model_cls.parameters(): + param_list[param.name] = param * 1.0 + param_list[param.name].stop_gradient = True + + if use_data_parallel: + assert model is not None + model.apply_collective_grads() + + #_, param_grads = self.optimizer.minimize(loss, grad_clip=grad_clip) + _, param_grads = self.optimizer.minimize(loss) + + if self.weight_decay > 0: + for param, grad in param_grads: + if self.exclude_from_weight_decay(param.name): + continue + if isinstance(self.scheduled_lr.step(), float): + updated_param = param.numpy() - param_list[ + param.name].numpy( + ) * self.weight_decay * self.scheduled_lr.step() + else: + updated_param = param.numpy( + ) - param_list[param.name].numpy( + ) * self.weight_decay * self.scheduled_lr.step().numpy() + updated_param_var = fluid.dygraph.to_variable(updated_param) + param = updated_param_var + #param = fluid.layers.reshape(x=updated_param_var, shape=list(updated_param_var.shape)) diff --git a/hapi/text/bert/static_optimization.py b/hapi/text/bert/static_optimization.py new file mode 100644 index 0000000000000000000000000000000000000000..a577d1bf05091d5101ff49d61ec3aed8fefcbb14 --- /dev/null +++ b/hapi/text/bert/static_optimization.py @@ -0,0 +1,178 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Optimization and learning rate scheduling.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle.fluid as fluid +from utils.fp16 import create_master_params_grads, master_param_to_train_param, apply_dynamic_loss_scaling + + +def linear_warmup_decay(learning_rate, warmup_steps, num_train_steps): + """ Applies linear warmup of learning rate from 0 and decay to 0.""" + with fluid.default_main_program()._lr_schedule_guard(): + lr = fluid.layers.tensor.create_global_var( + shape=[1], + value=0.0, + dtype='float32', + persistable=True, + name="scheduled_learning_rate") + + global_step = fluid.layers.learning_rate_scheduler._decay_step_counter( + ) + + with fluid.layers.control_flow.Switch() as switch: + with switch.case(global_step < warmup_steps): + warmup_lr = learning_rate * (global_step / warmup_steps) + fluid.layers.tensor.assign(warmup_lr, lr) + with switch.default(): + decayed_lr = fluid.layers.learning_rate_scheduler.polynomial_decay( + learning_rate=learning_rate, + decay_steps=num_train_steps, + end_learning_rate=0.0, + power=1.0, + cycle=False) + fluid.layers.tensor.assign(decayed_lr, lr) + + return lr + + +def optimization(loss, + warmup_steps, + num_train_steps, + learning_rate, + train_program, + startup_prog, + weight_decay, + scheduler='linear_warmup_decay', + use_fp16=False, + use_dynamic_loss_scaling=False, + init_loss_scaling=1.0, + incr_every_n_steps=1000, + decr_every_n_nan_or_inf=2, + incr_ratio=2.0, + decr_ratio=0.8): + + scheduled_lr, loss_scaling = None, None + if scheduler == 'noam_decay': + if warmup_steps > 0: + scheduled_lr = fluid.layers.learning_rate_scheduler\ + .noam_decay(1/(warmup_steps *(learning_rate ** 2)), + warmup_steps) + else: + print( + "WARNING: noam decay of learning rate should have postive warmup " + "steps but given {}, using constant learning rate instead!" + .format(warmup_steps)) + scheduled_lr = fluid.layers.create_global_var( + name=fluid.unique_name.generate("learning_rate"), + shape=[1], + value=learning_rate, + dtype='float32', + persistable=True) + elif scheduler == 'linear_warmup_decay': + if warmup_steps > 0: + scheduled_lr = linear_warmup_decay(learning_rate, warmup_steps, + num_train_steps) + else: + print( + "WARNING: linear warmup decay of learning rate should have " + "postive warmup steps but given {}, use constant learning rate " + "instead!".format(warmup_steps)) + scheduled_lr = fluid.layers.create_global_var( + name=fluid.unique_name.generate("learning_rate"), + shape=[1], + value=learning_rate, + dtype='float32', + persistable=True) + else: + raise ValueError("Unkown learning rate scheduler, should be " + "'noam_decay' or 'linear_warmup_decay'") + + optimizer = fluid.optimizer.Adam(learning_rate=scheduled_lr) + fluid.clip.set_gradient_clip( + clip=fluid.clip.GradientClipByGlobalNorm(clip_norm=1.0)) + + def exclude_from_weight_decay(param): + name = param.name.rstrip(".master") + if name.find("layer_norm") > -1: + return True + bias_suffix = ["_bias", "_b", ".b_0"] + for suffix in bias_suffix: + if name.endswith(suffix): + return True + return False + + param_list = dict() + + if use_fp16: + loss_scaling = fluid.layers.create_global_var( + name=fluid.unique_name.generate("loss_scaling"), + shape=[1], + value=init_loss_scaling, + dtype='float32', + persistable=True) + loss *= loss_scaling + + param_grads = optimizer.backward(loss) + master_param_grads = create_master_params_grads( + param_grads, train_program, startup_prog, loss_scaling) + + if weight_decay > 0: + for param, _ in master_param_grads: + param_list[param.name] = param * 1.0 + param_list[param.name].stop_gradient = True + + if use_dynamic_loss_scaling: + apply_dynamic_loss_scaling( + loss_scaling, master_param_grads, incr_every_n_steps, + decr_every_n_nan_or_inf, incr_ratio, decr_ratio) + + optimizer.apply_gradients(master_param_grads) + + if weight_decay > 0: + for param, grad in master_param_grads: + if exclude_from_weight_decay(param): + continue + with param.block.program._optimized_guard( + [param, grad]), fluid.framework.name_scope("weight_decay"): + updated_param = param - param_list[ + param.name] * weight_decay * scheduled_lr + fluid.layers.assign(output=param, input=updated_param) + + master_param_to_train_param(master_param_grads, param_grads, + train_program) + + else: + if weight_decay > 0: + for param in train_program.all_parameters(): + param_list[param.name] = param * 1.0 + param_list[param.name].stop_gradient = True + + _, param_grads = optimizer.minimize(loss) + + if weight_decay > 0: + for param, grad in param_grads: + if exclude_from_weight_decay(param): + continue + with param.block.program._optimized_guard( + [param, grad]), fluid.framework.name_scope("weight_decay"): + updated_param = param - param_list[ + param.name] * weight_decay * scheduled_lr + fluid.layers.assign(output=param, input=updated_param) + + return scheduled_lr, loss_scaling diff --git a/hapi/text/bert/utils/__init__.py b/hapi/text/bert/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..89b95c137b760b01623a2b78d66ecebb1b2a5e43 --- /dev/null +++ b/hapi/text/bert/utils/__init__.py @@ -0,0 +1,30 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from hapi.text.bert.utils.args import str2bool as str2bool +from hapi.text.bert.utils.args import ArgumentGroup as ArgumentGroup +from hapi.text.bert.utils.args import print_arguments as print_arguments +from hapi.text.bert.utils.args import check_cuda as check_cuda + +from hapi.text.bert.utils.cards import get_cards as get_cards + +from hapi.text.bert.utils.fp16 import cast_fp16_to_fp32 as cast_fp16_to_fp32 +from hapi.text.bert.utils.fp16 import cast_fp32_to_fp16 as cast_fp32_to_fp16 +from hapi.text.bert.utils.fp16 import copy_to_master_param as copy_to_master_param +from hapi.text.bert.utils.fp16 import create_master_params_grads as create_master_params_grads +from hapi.text.bert.utils.fp16 import master_param_to_train_param as master_param_to_train_param + +from hapi.text.bert.utils.init import init_checkpoint as init_checkpoint +from hapi.text.bert.utils.init import init_pretraining_params as init_pretraining_params +from hapi.text.bert.utils.init import init_from_static_model as init_from_static_model diff --git a/hapi/text/bert/utils/args.py b/hapi/text/bert/utils/args.py new file mode 100644 index 0000000000000000000000000000000000000000..66e9bb81a35bb4cc4c8c79cac4631841742bdeb8 --- /dev/null +++ b/hapi/text/bert/utils/args.py @@ -0,0 +1,61 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Arguments for configuration.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import six +import argparse + +import paddle.fluid as fluid + + +def str2bool(v): + # because argparse does not support to parse "true, False" as python + # boolean directly + return v.lower() in ("true", "t", "1") + + +class ArgumentGroup(object): + def __init__(self, parser, title, des): + self._group = parser.add_argument_group(title=title, description=des) + + def add_arg(self, name, type, default, help, **kwargs): + type = str2bool if type == bool else type + self._group.add_argument( + "--" + name, + default=default, + type=type, + help=help + ' Default: %(default)s.', + **kwargs) + + +def print_arguments(args): + print('----------- Configuration Arguments -----------') + for arg, value in sorted(six.iteritems(vars(args))): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + +def check_cuda(use_cuda, err = \ + "\nYou can not set use_cuda = True in the model because you are using paddlepaddle-cpu.\n \ + Please: 1. Install paddlepaddle-gpu to run your models on GPU or 2. Set use_cuda = False to run models on CPU.\n" + ): + try: + if use_cuda == True and fluid.is_compiled_with_cuda() == False: + print(err) + sys.exit(1) + except Exception as e: + pass diff --git a/hapi/text/bert/utils/cards.py b/hapi/text/bert/utils/cards.py new file mode 100644 index 0000000000000000000000000000000000000000..70c58ee30da7f68f00d12af0b5dc1025dad42630 --- /dev/null +++ b/hapi/text/bert/utils/cards.py @@ -0,0 +1,26 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + + +def get_cards(): + """ + get gpu cards number + """ + num = 0 + cards = os.environ.get('CUDA_VISIBLE_DEVICES', '') + if cards != '': + num = len(cards.split(",")) + return num diff --git a/hapi/text/bert/utils/convert_static_to_dygraph.py b/hapi/text/bert/utils/convert_static_to_dygraph.py new file mode 100755 index 0000000000000000000000000000000000000000..cbd4f7f74003cbcb1f7f800e7f72e69fbbb3a5f9 --- /dev/null +++ b/hapi/text/bert/utils/convert_static_to_dygraph.py @@ -0,0 +1,228 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import shutil +import sys +import os + + +def usage(): + """ + usage information + """ + print + print("please use command: ") + print( + "python convert_static_to_dygraph.py input_params_dir output_params_dir" + ) + print + + +def convert_static_to_dygraph(static_model_path, dygraph_model_path): + """ + convert paddle static bert model to dygraph model + """ + + def mkdir(path): + if not os.path.isdir(path): + if os.path.split(path)[0]: + mkdir(os.path.split(path)[0]) + else: + return + os.mkdir(path) + + if os.path.exists(dygraph_model_path): + shutil.rmtree(dygraph_model_path) + mkdir(dygraph_model_path) + + if not os.path.exists(static_model_path): + print("paddle static model path doesn't exist.....") + return -1 + + file_list = [] + for root, dirs, files in os.walk(static_model_path): + file_list.extend(files) + + os.makedirs(os.path.join(dygraph_model_path, "PretrainModelLayer_0")) + os.makedirs( + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0")) + os.makedirs( + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/PrePostProcessLayer_0")) + os.makedirs( + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/PrePostProcessLayer_0")) + + #os.chdir(static_model_path) + #convert embedding file + embedding_type = ["word", "pos", "sent"] + for i in range(3): + src_name = embedding_type[i] + "_embedding" + trg_name = "Embedding_" + str(i) + "." + src_name + shutil.copyfile( + os.path.join(static_model_path, src_name), + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/" + trg_name)) + + #convert pre_encoder file + shutil.copyfile( + os.path.join(static_model_path, "pre_encoder_layer_norm_scale"), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/PrePostProcessLayer_0/LayerNorm_0._layer_norm_scale" + )) + shutil.copyfile( + os.path.join(static_model_path, "pre_encoder_layer_norm_bias"), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/PrePostProcessLayer_0/LayerNorm_0._layer_norm_bias" + )) + + #convert mask lm params file + shutil.copyfile( + os.path.join(static_model_path, "mask_lm_out_fc.b_0"), + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/Layer_0.mask_lm_out_fc.b_0")) + shutil.copyfile( + os.path.join(static_model_path, "mask_lm_trans_fc.b_0"), + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/FC_0.mask_lm_trans_fc.b_0")) + shutil.copyfile( + os.path.join(static_model_path, "mask_lm_trans_fc.w_0"), + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/FC_0.mask_lm_trans_fc.w_0")) + shutil.copyfile( + os.path.join(static_model_path, "mask_lm_trans_layer_norm_bias"), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/PrePostProcessLayer_0/LayerNorm_0._layer_norm_bias" + )) + shutil.copyfile( + os.path.join(static_model_path, "mask_lm_trans_layer_norm_scale"), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/PrePostProcessLayer_0/LayerNorm_0._layer_norm_scale" + )) + shutil.copyfile( + os.path.join(static_model_path, "next_sent_fc.b_0"), + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/FC_1.next_sent_fc.b_0")) + shutil.copyfile( + os.path.join(static_model_path, "next_sent_fc.w_0"), + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/FC_1.next_sent_fc.w_0")) + shutil.copyfile( + os.path.join(static_model_path, "pooled_fc.b_0"), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/FC_0.pooled_fc.b_0")) + shutil.copyfile( + os.path.join(static_model_path, "pooled_fc.w_0"), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/FC_0.pooled_fc.w_0")) + + encoder_num = 0 + for f in file_list: + if not f.startswith("encoder_layer"): + continue + layer_num = f.split('_')[2] + if int(layer_num) > encoder_num: + encoder_num = int(layer_num) + + encoder_num += 1 + for i in range(encoder_num): + encoder_dir = "EncoderSubLayer_" + str(i) + os.makedirs( + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/" + + "EncoderLayer_0/", encoder_dir)) + os.makedirs( + os.path.join(dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/" + + "EncoderLayer_0/", encoder_dir + + "/PositionwiseFeedForwardLayer_0")) + os.makedirs( + os.path.join( + dygraph_model_path, "PretrainModelLayer_0/BertModelLayer_0/" + + "EncoderLayer_0/", encoder_dir + "/MultiHeadAttentionLayer_0")) + os.makedirs( + os.path.join( + dygraph_model_path, "PretrainModelLayer_0/BertModelLayer_0/" + + "EncoderLayer_0/", encoder_dir + "/PrePostProcessLayer_1")) + os.makedirs( + os.path.join( + dygraph_model_path, "PretrainModelLayer_0/BertModelLayer_0/" + + "EncoderLayer_0/", encoder_dir + "/PrePostProcessLayer_3")) + + encoder_map_dict = { + "ffn_fc_0.b_0": + ("PositionwiseFeedForwardLayer_0", "FC_0.ffn_fc_0.b_0"), + "ffn_fc_0.w_0": + ("PositionwiseFeedForwardLayer_0", "FC_0.ffn_fc_0.w_0"), + "ffn_fc_1.b_0": + ("PositionwiseFeedForwardLayer_0", "FC_1.ffn_fc_1.b_0"), + "ffn_fc_1.w_0": + ("PositionwiseFeedForwardLayer_0", "FC_1.ffn_fc_1.w_0"), + "multi_head_att_key_fc.b_0": + ("MultiHeadAttentionLayer_0", "FC_1.key_fc.b_0"), + "multi_head_att_key_fc.w_0": + ("MultiHeadAttentionLayer_0", "FC_1.key_fc.w_0"), + "multi_head_att_output_fc.b_0": + ("MultiHeadAttentionLayer_0", "FC_3.output_fc.b_0"), + "multi_head_att_output_fc.w_0": + ("MultiHeadAttentionLayer_0", "FC_3.output_fc.w_0"), + "multi_head_att_query_fc.b_0": + ("MultiHeadAttentionLayer_0", "FC_0.query_fc.b_0"), + "multi_head_att_query_fc.w_0": + ("MultiHeadAttentionLayer_0", "FC_0.query_fc.w_0"), + "multi_head_att_value_fc.b_0": + ("MultiHeadAttentionLayer_0", "FC_2.value_fc.b_0"), + "multi_head_att_value_fc.w_0": + ("MultiHeadAttentionLayer_0", "FC_2.value_fc.w_0"), + "post_att_layer_norm_bias": + ("PrePostProcessLayer_1", "LayerNorm_0.post_att_layer_norm_bias"), + "post_att_layer_norm_scale": + ("PrePostProcessLayer_1", "LayerNorm_0.post_att_layer_norm_scale"), + "post_ffn_layer_norm_bias": + ("PrePostProcessLayer_3", "LayerNorm_0.post_ffn_layer_norm_bias"), + "post_ffn_layer_norm_scale": + ("PrePostProcessLayer_3", "LayerNorm_0.post_ffn_layer_norm_scale") + } + + for f in file_list: + if not f.startswith("encoder_layer"): + continue + layer_num = f.split('_')[2] + suffix_name = "_".join(f.split('_')[3:]) + in_dir = encoder_map_dict[suffix_name][0] + rename = encoder_map_dict[suffix_name][1] + encoder_layer = "EncoderSubLayer_" + layer_num + shutil.copyfile( + os.path.join(static_model_path, f), + os.path.join( + dygraph_model_path, + "PretrainModelLayer_0/BertModelLayer_0/EncoderLayer_0/" + + encoder_layer + "/" + in_dir + "/" + rename)) + + +if __name__ == "__main__": + + if len(sys.argv) < 3: + usage() + exit(1) + static_model_path = sys.argv[1] + dygraph_model_path = sys.argv[2] + convert_static_to_dygraph(static_model_path, dygraph_model_path) diff --git a/hapi/text/bert/utils/fp16.py b/hapi/text/bert/utils/fp16.py new file mode 100644 index 0000000000000000000000000000000000000000..e153c2b9a1029897def264278c5dbe72e1f369f5 --- /dev/null +++ b/hapi/text/bert/utils/fp16.py @@ -0,0 +1,97 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function +import paddle +import paddle.fluid as fluid + + +def cast_fp16_to_fp32(i, o, prog): + prog.global_block().append_op( + type="cast", + inputs={"X": i}, + outputs={"Out": o}, + attrs={ + "in_dtype": fluid.core.VarDesc.VarType.FP16, + "out_dtype": fluid.core.VarDesc.VarType.FP32 + }) + + +def cast_fp32_to_fp16(i, o, prog): + prog.global_block().append_op( + type="cast", + inputs={"X": i}, + outputs={"Out": o}, + attrs={ + "in_dtype": fluid.core.VarDesc.VarType.FP32, + "out_dtype": fluid.core.VarDesc.VarType.FP16 + }) + + +def copy_to_master_param(p, block): + v = block.vars.get(p.name, None) + if v is None: + raise ValueError("no param name %s found!" % p.name) + new_p = fluid.framework.Parameter( + block=block, + shape=v.shape, + dtype=fluid.core.VarDesc.VarType.FP32, + type=v.type, + lod_level=v.lod_level, + stop_gradient=p.stop_gradient, + trainable=p.trainable, + optimize_attr=p.optimize_attr, + regularizer=p.regularizer, + gradient_clip_attr=p.gradient_clip_attr, + error_clip=p.error_clip, + name=v.name + ".master") + return new_p + + +def create_master_params_grads(params_grads, main_prog, startup_prog, + loss_scaling): + master_params_grads = [] + tmp_role = main_prog._current_role + OpRole = fluid.core.op_proto_and_checker_maker.OpRole + main_prog._current_role = OpRole.Backward + for p, g in params_grads: + # create master parameters + master_param = copy_to_master_param(p, main_prog.global_block()) + startup_master_param = startup_prog.global_block()._clone_variable( + master_param) + startup_p = startup_prog.global_block().var(p.name) + cast_fp16_to_fp32(startup_p, startup_master_param, startup_prog) + # cast fp16 gradients to fp32 before apply gradients + if g.name.find("layer_norm") > -1: + if loss_scaling > 1: + scaled_g = g / float(loss_scaling) + else: + scaled_g = g + master_params_grads.append([p, scaled_g]) + continue + master_grad = fluid.layers.cast(g, "float32") + if loss_scaling > 1: + master_grad = master_grad / float(loss_scaling) + master_params_grads.append([master_param, master_grad]) + main_prog._current_role = tmp_role + return master_params_grads + + +def master_param_to_train_param(master_params_grads, params_grads, main_prog): + for idx, m_p_g in enumerate(master_params_grads): + train_p, _ = params_grads[idx] + if train_p.name.find("layer_norm") > -1: + continue + with main_prog._optimized_guard([m_p_g[0], m_p_g[1]]): + cast_fp32_to_fp16(m_p_g[0], train_p, main_prog) diff --git a/hapi/text/bert/utils/init.py b/hapi/text/bert/utils/init.py new file mode 100644 index 0000000000000000000000000000000000000000..48087ee750e637ca3201f2b9115b8ca7df60d54b --- /dev/null +++ b/hapi/text/bert/utils/init.py @@ -0,0 +1,246 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import os +import six +import ast +import copy + +import numpy as np +import paddle.fluid as fluid + + +def cast_fp32_to_fp16(exe, main_program): + print("Cast parameters to float16 data format.") + for param in main_program.global_block().all_parameters(): + if not param.name.endswith(".master"): + param_t = fluid.global_scope().find_var(param.name).get_tensor() + data = np.array(param_t) + if param.name.find("layer_norm") == -1: + param_t.set(np.float16(data).view(np.uint16), exe.place) + master_param_var = fluid.global_scope().find_var(param.name + + ".master") + if master_param_var is not None: + master_param_var.get_tensor().set(data, exe.place) + + +def init_checkpoint(exe, init_checkpoint_path, main_program, use_fp16=False): + assert os.path.exists( + init_checkpoint_path), "[%s] cann't be found." % init_checkpoint_path + + def existed_persitables(var): + if not fluid.io.is_persistable(var): + return False + return os.path.exists(os.path.join(init_checkpoint_path, var.name)) + + fluid.io.load_vars( + exe, + init_checkpoint_path, + main_program=main_program, + predicate=existed_persitables) + print("Load model from {}".format(init_checkpoint_path)) + + if use_fp16: + cast_fp32_to_fp16(exe, main_program) + + +def init_pretraining_params(exe, + pretraining_params_path, + main_program, + use_fp16=False): + assert os.path.exists(pretraining_params_path + ), "[%s] cann't be found." % pretraining_params_path + + def existed_params(var): + if not isinstance(var, fluid.framework.Parameter): + return False + return os.path.exists(os.path.join(pretraining_params_path, var.name)) + + fluid.io.load_vars( + exe, + pretraining_params_path, + main_program=main_program, + predicate=existed_params) + print("Load pretraining parameters from {}.".format( + pretraining_params_path)) + + if use_fp16: + cast_fp32_to_fp16(exe, main_program) + + +def init_from_static_model(dir_path, + backbone_model, + bert_config, + verbose=False): + def load_numpy_weight(file_name): + if six.PY2: + res = np.load(os.path.join(dir_path, file_name), allow_pickle=True) + else: + res = np.load( + os.path.join(dir_path, file_name), + allow_pickle=True, + encoding='latin1') + assert res is not None + return res + + # load word embedding + _param = load_numpy_weight("word_embedding") + backbone_model._src_emb.set_dict({"weight": _param}) + if verbose: + print("INIT word embedding") + + _param = load_numpy_weight("pos_embedding") + backbone_model._pos_emb.set_dict({"weight": _param}) + if verbose: + print("INIT pos embedding") + + _param = load_numpy_weight("sent_embedding") + backbone_model._sent_emb.set_dict({"weight": _param}) + if verbose: + print("INIT sent embedding") + + _param0 = load_numpy_weight("pooled_fc.w_0") + _param1 = load_numpy_weight("pooled_fc.b_0") + backbone_model.pooled_fc.set_dict({"weight": _param0, "bias": _param1}) + if verbose: + print("INIT pooled_fc") + + _param0 = load_numpy_weight("pre_encoder_layer_norm_scale") + _param1 = load_numpy_weight("pre_encoder_layer_norm_bias") + backbone_model.pre_process_layer._sub_layers["layer_norm_0"].set_dict({ + "weight": _param0, + "bias": _param1 + }) + if verbose: + print("INIT pre_encoder layer norm") + + for _i in range(bert_config["num_hidden_layers"]): + _param_weight = "encoder_layer_%d_multi_head_att_query_fc.w_0" % _i + _param_bias = "encoder_layer_%d_multi_head_att_query_fc.b_0" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers["layer_%d" % + _i].self_attn.q_fc.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT multi_head_att_query_fc %d" % _i) + + _param_weight = "encoder_layer_%d_multi_head_att_key_fc.w_0" % _i + _param_bias = "encoder_layer_%d_multi_head_att_key_fc.b_0" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers["layer_%d" % + _i].self_attn.k_fc.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT multi_head_att_key_fc %d" % _i) + + _param_weight = "encoder_layer_%d_multi_head_att_value_fc.w_0" % _i + _param_bias = "encoder_layer_%d_multi_head_att_value_fc.b_0" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers["layer_%d" % + _i].self_attn.v_fc.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT multi_head_att_value_fc %d" % _i) + + # init output fc + _param_weight = "encoder_layer_%d_multi_head_att_output_fc.w_0" % _i + _param_bias = "encoder_layer_%d_multi_head_att_output_fc.b_0" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers["layer_%d" % + _i].self_attn.proj_fc.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT multi_head_att_output_fc %d" % _i) + + # init layer_norm 1 + _param_weight = "encoder_layer_%d_post_att_layer_norm_scale" % _i + _param_bias = "encoder_layer_%d_post_att_layer_norm_bias" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers[ + "layer_%d" % _i].postprocesser1.layer_norm_0.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT layer norm in attention at %d layer" % _i) + + # init layer_norm 2 + _param_weight = "encoder_layer_%d_post_ffn_layer_norm_scale" % _i + _param_bias = "encoder_layer_%d_post_ffn_layer_norm_bias" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers[ + "layer_%d" % _i].postprocesser2.layer_norm_0.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT layer norm in FFN at %d layer" % _i) + + # init FFN 1 + _param_weight = "encoder_layer_%d_ffn_fc_0.w_0" % _i + _param_bias = "encoder_layer_%d_ffn_fc_0.b_0" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers["layer_%d" % _i].ffn.fc1.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT FFN-1 at %d layer" % _i) + + # init FFN 2 + _param_weight = "encoder_layer_%d_ffn_fc_1.w_0" % _i + _param_bias = "encoder_layer_%d_ffn_fc_1.b_0" % _i + + _param_weight = load_numpy_weight(_param_weight) + _param_bias = load_numpy_weight(_param_bias) + + backbone_model._encoder._sub_layers["layer_%d" % _i].ffn.fc2.set_dict({ + "weight": _param_weight, + "bias": _param_bias + }) + if verbose: + print("INIT FFN-2 at %d layer" % _i) + + return True diff --git a/hapi/text/text.py b/hapi/text/text.py new file mode 100644 index 0000000000000000000000000000000000000000..e5be32bcb531b938c3cc8c21ec7caf2a4f40ee6e --- /dev/null +++ b/hapi/text/text.py @@ -0,0 +1,1855 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import six +import sys +if six.PY2: + reload(sys) + sys.setdefaultencoding('utf8') + +import ast +import time +import argparse as argparse +import numpy as np +import multiprocessing + +import collections +import copy +from functools import partial, reduce + +import paddle +import paddle.fluid as fluid +import paddle.fluid.layers.utils as utils +from paddle.fluid.layers.utils import map_structure, flatten, pack_sequence_as +from paddle.fluid.dygraph import to_variable, Embedding, Linear, LayerNorm, GRUUnit +from paddle.fluid.data_feeder import convert_dtype + +from paddle.fluid import layers +from paddle.fluid.dygraph import Layer +from paddle.fluid.layers import BeamSearchDecoder + +__all__ = [ + 'RNNCell', 'BasicLSTMCell', 'BasicGRUCell', 'RNN', 'DynamicDecode', + 'BeamSearchDecoder', 'MultiHeadAttention', 'FFN', + 'TransformerEncoderLayer', 'TransformerEncoder', 'TransformerDecoderLayer', + 'TransformerDecoder', 'TransformerBeamSearchDecoder', 'Linear_chain_crf', + 'Crf_decoding', 'SequenceTagging' +] + + +class RNNCell(Layer): + def get_initial_states(self, + batch_ref, + shape=None, + dtype=None, + init_value=0, + batch_dim_idx=0): + """ + Generate initialized states according to provided shape, data type and + value. + + Parameters: + batch_ref: A (possibly nested structure of) tensor variable[s]. + The first dimension of the tensor will be used as batch size to + initialize states. + shape: A (possiblely nested structure of) shape[s], where a shape is + represented as a list/tuple of integer). -1(for batch size) will + beautomatically inserted if shape is not started with it. If None, + property `state_shape` will be used. The default value is None. + dtype: A (possiblely nested structure of) data type[s]. The structure + must be same as that of `shape`, except when all tensors' in states + has the same data type, a single data type can be used. If None and + property `cell.state_shape` is not available, float32 will be used + as the data type. The default value is None. + init_value: A float value used to initialize states. + + Returns: + Variable: tensor variable[s] packed in the same structure provided \ + by shape, representing the initialized states. + """ + # TODO: use inputs and batch_size + batch_ref = flatten(batch_ref)[0] + + def _is_shape_sequence(seq): + if sys.version_info < (3, ): + integer_types = ( + int, + long, ) + else: + integer_types = (int, ) + """For shape, list/tuple of integer is the finest-grained objection""" + if (isinstance(seq, list) or isinstance(seq, tuple)): + if reduce( + lambda flag, x: isinstance(x, integer_types) and flag, + seq, True): + return False + # TODO: Add check for the illegal + if isinstance(seq, dict): + return True + return (isinstance(seq, collections.Sequence) and + not isinstance(seq, six.string_types)) + + class Shape(object): + def __init__(self, shape): + self.shape = shape if shape[0] == -1 else ([-1] + list(shape)) + + # nested structure of shapes + states_shapes = self.state_shape if shape is None else shape + is_sequence_ori = utils.is_sequence + utils.is_sequence = _is_shape_sequence + states_shapes = map_structure(lambda shape: Shape(shape), + states_shapes) + utils.is_sequence = is_sequence_ori + + # nested structure of dtypes + try: + states_dtypes = self.state_dtype if dtype is None else dtype + except NotImplementedError: # use fp32 as default + states_dtypes = "float32" + if len(flatten(states_dtypes)) == 1: + dtype = flatten(states_dtypes)[0] + states_dtypes = map_structure(lambda shape: dtype, states_shapes) + + init_states = map_structure( + lambda shape, dtype: fluid.layers.fill_constant_batch_size_like( + input=batch_ref, + shape=shape.shape, + dtype=dtype, + value=init_value, + input_dim_idx=batch_dim_idx), states_shapes, states_dtypes) + return init_states + + @property + def state_shape(self): + """ + Abstract method (property). + Used to initialize states. + A (possiblely nested structure of) shape[s], where a shape is represented + as a list/tuple of integers (-1 for batch size would be automatically + inserted into a shape if shape is not started with it). + Not necessary to be implemented if states are not initialized by + `get_initial_states` or the `shape` argument is provided when using + `get_initial_states`. + """ + raise NotImplementedError( + "Please add implementaion for `state_shape` in the used cell.") + + @property + def state_dtype(self): + """ + Abstract method (property). + Used to initialize states. + A (possiblely nested structure of) data types[s]. The structure must be + same as that of `shape`, except when all tensors' in states has the same + data type, a signle data type can be used. + Not necessary to be implemented if states are not initialized + by `get_initial_states` or the `dtype` argument is provided when using + `get_initial_states`. + """ + raise NotImplementedError( + "Please add implementaion for `state_dtype` in the used cell.") + + +class BasicLSTMCell(RNNCell): + """ + **** + BasicLSTMUnit class, Using basic operator to build LSTM + The algorithm can be described as the code below. + .. math:: + i_t &= \sigma(W_{ix}x_{t} + W_{ih}h_{t-1} + b_i) + f_t &= \sigma(W_{fx}x_{t} + W_{fh}h_{t-1} + b_f + forget_bias ) + o_t &= \sigma(W_{ox}x_{t} + W_{oh}h_{t-1} + b_o) + \\tilde{c_t} &= tanh(W_{cx}x_t + W_{ch}h_{t-1} + b_c) + c_t &= f_t \odot c_{t-1} + i_t \odot \\tilde{c_t} + h_t &= o_t \odot tanh(c_t) + - $W$ terms denote weight matrices (e.g. $W_{ix}$ is the matrix + of weights from the input gate to the input) + - The b terms denote bias vectors ($bx_i$ and $bh_i$ are the input gate bias vector). + - sigmoid is the logistic sigmoid function. + - $i, f, o$ and $c$ are the input gate, forget gate, output gate, + and cell activation vectors, respectively, all of which have the same size as + the cell output activation vector $h$. + - The :math:`\odot` is the element-wise product of the vectors. + - :math:`tanh` is the activation functions. + - :math:`\\tilde{c_t}` is also called candidate hidden state, + which is computed based on the current input and the previous hidden state. + Args: + name_scope(string) : The name scope used to identify parameter and bias name + hidden_size (integer): The hidden size used in the Unit. + param_attr(ParamAttr|None): The parameter attribute for the learnable + weight matrix. Note: + If it is set to None or one attribute of ParamAttr, lstm_unit will + create ParamAttr as param_attr. If the Initializer of the param_attr + is not set, the parameter is initialized with Xavier. Default: None. + bias_attr (ParamAttr|None): The parameter attribute for the bias + of LSTM unit. + If it is set to None or one attribute of ParamAttr, lstm_unit will + create ParamAttr as bias_attr. If the Initializer of the bias_attr + is not set, the bias is initialized as zero. Default: None. + gate_activation (function|None): The activation function for gates (actGate). + Default: 'fluid.layers.sigmoid' + activation (function|None): The activation function for cells (actNode). + Default: 'fluid.layers.tanh' + forget_bias(float|1.0): forget bias used when computing forget gate + dtype(string): data type used in this unit + """ + + def __init__(self, + input_size, + hidden_size, + param_attr=None, + bias_attr=None, + gate_activation=None, + activation=None, + forget_bias=1.0, + dtype='float32', + forget_gate_weights={"w": None, + "h": None, + "b": None}, + input_gate_weights={"w": None, + "h": None, + "b": None}, + output_gate_weights={"w": None, + "h": None, + "b": None}, + cell_weights={"w": None, + "h": None, + "b": None}): + super(BasicLSTMCell, self).__init__() + + self._hidden_size = hidden_size + self._param_attr = param_attr + self._bias_attr = bias_attr + self._gate_activation = gate_activation or layers.sigmoid + self._activation = activation or layers.tanh + self._forget_bias = layers.fill_constant( + [1], dtype=dtype, value=forget_bias) + self._forget_bias.stop_gradient = False + self._dtype = dtype + self._input_size = input_size + + self.use_customized_weight = False + for _weights in [ + forget_gate_weights, input_gate_weights, output_gate_weights, + cell_weights + ]: + for _key in _weights: + if _weights[_key] is not None: + self.use_customized_weight = True + break + if self.use_customized_weight: + break + + if not self.use_customized_weight: + + self._weight = self.create_parameter( + attr=self._param_attr, + shape=[ + self._input_size + self._hidden_size, 4 * self._hidden_size + ], + dtype=self._dtype) + + self._bias = self.create_parameter( + attr=self._bias_attr, + shape=[4 * self._hidden_size], + dtype=self._dtype, + is_bias=True) + else: + if "w" in forget_gate_weights and forget_gate_weights[ + "w"] is not None: + self.fg_w = forget_gate_weights["w"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_forget_gate_w" + else: + tmp_param_attr = self._param_attr + self.fg_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in forget_gate_weights and forget_gate_weights[ + "h"] is not None: + self.fg_h = forget_gate_weights["h"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_forget_gate_h" + else: + tmp_param_attr = self._param_attr + self.fg_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in forget_gate_weights and forget_gate_weights[ + "b"] is not None: + self.fg_b = forget_gate_weights["b"] + else: + if self._bias_attr is not None and self._bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._bias_attr) + tmp_param_attr.name += "_forget_gate_b" + else: + tmp_param_attr = self._bias_attr + self.fg_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + if "w" in input_gate_weights and input_gate_weights[ + "w"] is not None: + self.ig_w = input_gate_weights["w"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_input_gate_w" + else: + tmp_param_attr = self._param_attr + + self.ig_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in input_gate_weights and input_gate_weights[ + "h"] is not None: + self.ig_h = input_gate_weights["h"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_input_gate_h" + else: + tmp_param_attr = self._param_attr + + self.ig_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in input_gate_weights and input_gate_weights[ + "b"] is not None: + self.ig_b = input_gate_weights["b"] + else: + if self._bias_attr is not None and self._bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._bias_attr) + tmp_param_attr.name += "_input_gate_b" + else: + tmp_param_attr = self._bias_attr + self.ig_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + if "w" in output_gate_weights and output_gate_weights[ + "w"] is not None: + self.og_w = output_gate_weights["w"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_output_gate_w" + else: + tmp_param_attr = self._param_attr + self.og_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in output_gate_weights and output_gate_weights[ + "h"] is not None: + self.og_h = output_gate_weights["h"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_output_gate_h" + else: + tmp_param_attr = self._param_attr + + self.og_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in output_gate_weights and output_gate_weights[ + "b"] is not None: + self.og_b = output_gate_weights["b"] + else: + if self._bias_attr is not None and self._bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._bias_attr) + tmp_param_attr.name += "_output_gate_b" + else: + tmp_param_attr = self._bias_attr + self.og_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + if "w" in cell_weights and cell_weights["w"] is not None: + self.c_w = cell_weights["w"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_cell_w" + else: + tmp_param_attr = self._param_attr + + self.c_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in cell_weights and cell_weights["h"] is not None: + self.c_h = cell_weights["h"] + else: + if self._param_attr is not None and self._param_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._param_attr) + tmp_param_attr.name += "_cell_h" + else: + tmp_param_attr = self._param_attr + self.c_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in cell_weights and cell_weights["b"] is not None: + self.c_b = cell_weights["b"] + else: + if self._bias_attr is not None and self._bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(self._bias_attr) + tmp_param_attr.name += "_cell_b" + else: + tmp_param_attr = self._bias_attr + self.c_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + def forward(self, input, state): + + if self.use_customized_weight: + weight_w = fluid.layers.concat( + [self.ig_w, self.c_w, self.fg_w, self.og_w], axis=-1) + weight_h = fluid.layers.concat( + [self.ig_h, self.c_h, self.fg_h, self.og_h], axis=-1) + _weight = fluid.layers.concat([weight_w, weight_h], axis=0) + _bias = fluid.layers.concat( + [self.ig_b, self.c_b, self.fg_b, self.og_b]) + else: + _weight = self._weight + _bias = self._bias + + pre_hidden, pre_cell = state + concat_input_hidden = layers.concat([input, pre_hidden], 1) + gate_input = layers.matmul(x=concat_input_hidden, y=_weight) + + gate_input = layers.elementwise_add(gate_input, _bias) + i, j, f, o = layers.split(gate_input, num_or_sections=4, dim=-1) + new_cell = layers.elementwise_add( + layers.elementwise_mul( + pre_cell, + layers.sigmoid(layers.elementwise_add(f, self._forget_bias))), + layers.elementwise_mul(layers.sigmoid(i), layers.tanh(j))) + new_hidden = layers.tanh(new_cell) * layers.sigmoid(o) + + return new_hidden, [new_hidden, new_cell] + + @property + def state_shape(self): + return [[self._hidden_size], [self._hidden_size]] + + +class BasicGRUCell(RNNCell): + """ + **** + BasicGRUUnit class, using basic operators to build GRU + The algorithm can be described as the equations below. + + .. math:: + u_t & = actGate(W_ux xu_{t} + W_uh h_{t-1} + b_u) + + r_t & = actGate(W_rx xr_{t} + W_rh h_{t-1} + b_r) + + m_t & = actNode(W_cx xm_t + W_ch dot(r_t, h_{t-1}) + b_m) + + h_t & = dot(u_t, h_{t-1}) + dot((1-u_t), m_t) + + Args: + hidden_size (integer): The hidden size used in the Unit. + param_attr(ParamAttr|None): The parameter attribute for the learnable + weight matrix. Note: + If it is set to None or one attribute of ParamAttr, gru_unit will + create ParamAttr as param_attr. If the Initializer of the param_attr + is not set, the parameter is initialized with Xavier. Default: None. + bias_attr (ParamAttr|None): The parameter attribute for the bias + of GRU unit. + If it is set to None or one attribute of ParamAttr, gru_unit will + create ParamAttr as bias_attr. If the Initializer of the bias_attr + is not set, the bias is initialized zero. Default: None. + gate_activation (function|None): The activation function for gates (actGate). + Default: 'fluid.layers.sigmoid' + activation (function|None): The activation function for cell (actNode). + Default: 'fluid.layers.tanh' + dtype(string): data type used in this unit + """ + + def __init__(self, + input_size, + hidden_size, + param_attr=None, + bias_attr=None, + gate_activation=None, + activation=None, + dtype='float32', + update_gate_weights={"w": None, + "h": None, + "b": None}, + reset_gate_weights={"w": None, + "h": None, + "b": None}, + cell_weights={"w": None, + "h": None, + "b": None}): + super(BasicGRUCell, self).__init__() + self._input_size = input_size + self._hidden_size = hidden_size + self._param_attr = param_attr + self._bias_attr = bias_attr + self._gate_activation = gate_activation or layers.sigmoid + self._activation = activation or layers.tanh + self._dtype = dtype + + assert isinstance(update_gate_weights, dict) + assert isinstance(reset_gate_weights, dict) + assert isinstance(cell_weights, dict) + + self.use_customized_weight = False + for _weights in [ + update_gate_weights, reset_gate_weights, cell_weights + ]: + for _key in _weights: + if _weights[_key] is not None: + self.use_customized_weight = True + if self.use_customized_weight: + break + + if self._param_attr is not None and self._param_attr.name is not None: + gate_param_attr = copy.deepcopy(self._param_attr) + candidate_param_attr = copy.deepcopy(self._param_attr) + gate_param_attr.name += "_gate" + candidate_param_attr.name += "_candidate" + else: + gate_param_attr = self._param_attr + candidate_param_attr = self._param_attr + + if not self.use_customized_weight: + self._gate_weight = self.create_parameter( + attr=gate_param_attr, + shape=[ + self._input_size + self._hidden_size, 2 * self._hidden_size + ], + dtype=self._dtype) + + self._candidate_weight = self.create_parameter( + attr=candidate_param_attr, + shape=[ + self._input_size + self._hidden_size, self._hidden_size + ], + dtype=self._dtype) + + if self._bias_attr is not None and self._bias_attr.name is not None: + gate_bias_attr = copy.deepcopy(self._bias_attr) + candidate_bias_attr = copy.deepcopy(self._bias_attr) + gate_bias_attr.name += "_gate" + candidate_bias_attr.name += "_candidate" + else: + gate_bias_attr = self._bias_attr + candidate_bias_attr = self._bias_attr + + self._gate_bias = self.create_parameter( + attr=gate_bias_attr, + shape=[2 * self._hidden_size], + dtype=self._dtype, + is_bias=True) + self._candidate_bias = self.create_parameter( + attr=candidate_bias_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + else: + + # create the parameters of gates in gru + if "w" in update_gate_weights and update_gate_weights[ + "w"] is not None: + self.ug_w = update_gate_weights["w"] + else: + if gate_param_attr is not None and gate_param_attr.name is not None: + tmp_param_attr = copy.deepcopy(gate_param_attr) + tmp_param_attr.name += "_update_gate_w" + else: + tmp_param_attr = gate_param_attr + self.ug_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in update_gate_weights and update_gate_weights[ + "h"] is not None: + self.ug_h = update_gate_weights["h"] + else: + if gate_param_attr is not None and gate_param_attr.name is not None: + tmp_param_attr = copy.deepcopy(gate_param_attr) + tmp_param_attr.name += "_update_gate_h" + else: + tmp_param_attr = gate_param_attr + self.ug_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in update_gate_weights and update_gate_weights[ + "b"] is not None: + self.ug_b = update_gate_weights["b"] + else: + if gate_bias_attr is not None and gate_bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(gate_bias_attr) + tmp_param_attr.name += "_update_gate_b" + else: + tmp_param_attr = gate_bias_attr + self.ug_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + # reset gate parameters + if "w" in reset_gate_weights and reset_gate_weights[ + "w"] is not None: + self.rg_w = reset_gate_weights["w"] + else: + if gate_param_attr is not None and gate_param_attr.name is not None: + tmp_param_attr = copy.deepcopy(gate_param_attr) + tmp_param_attr.name += "_reset_gate_w" + else: + tmp_param_attr = gate_param_attr + self.rg_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in reset_gate_weights and reset_gate_weights[ + "h"] is not None: + self.rg_h = reset_gate_weights["h"] + else: + if gate_param_attr is not None and gate_param_attr.name is not None: + tmp_param_attr = copy.deepcopy(gate_param_attr) + tmp_param_attr.name += "_reset_gate_h" + else: + tmp_param_attr = gate_param_attr + self.rg_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in reset_gate_weights and reset_gate_weights[ + "b"] is not None: + self.rg_b = reused_params["b"] + else: + if gate_bias_attr is not None and gate_bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(gate_bias_attr) + tmp_param_attr.name += "_reset_gate_b" + else: + tmp_param_attr = gate_bias_attr + self.rg_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + # cell parameters + if "w" in cell_weights and cell_weights["w"] is not None: + self.c_w = cell_weights["w"] + else: + if candidate_param_attr is not None and candidate_param_attr.name is not None: + tmp_param_attr = copy.deepcopy(candidate_param_attr) + tmp_param_attr.name += "_cell_w" + else: + tmp_param_attr = gate_param_attr + + self.c_w = self.create_parameter( + attr=tmp_param_attr, + shape=[self._input_size, self._hidden_size], + dtype=self._dtype) + + if "h" in cell_weights and cell_weights["h"] is not None: + self.c_h = cell_weights["h"] + else: + if candidate_param_attr is not None and candidate_param_attr.name is not None: + tmp_param_attr = copy.deepcopy(candidate_param_attr) + tmp_param_attr.name += "_cell_h" + else: + tmp_param_attr = gate_param_attr + self.c_h = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size, self._hidden_size], + dtype=self._dtype) + + if "b" in cell_weights and cell_weights["b"] is not None: + self.c_b = cell_weights["b"] + else: + if candidate_bias_attr is not None and candidate_bias_attr.name is not None: + tmp_param_attr = copy.deepcopy(candidate_bias_attr) + tmp_param_attr.name += "_cell_b" + else: + tmp_param_attr = gate_bias_attr + self.c_b = self.create_parameter( + attr=tmp_param_attr, + shape=[self._hidden_size], + dtype=self._dtype, + is_bias=True) + + def forward(self, input, state): + + if self.use_customized_weight: + rg_weights = layers.concat([self.rg_w, self.rg_h], axis=0) + ug_weights = layers.concat([self.ug_w, self.ug_h], axis=0) + _gate_weight = layers.concat([rg_weights, ug_weights], axis=-1) + _candidate_weight = layers.concat([self.c_w, self.c_h], axis=0) + _gate_bias = layers.concat([self.rg_b, self.ug_b], axis=0) + _candidate_bias = self.c_b + else: + _gate_weight = self._gate_weight + _gate_bias = self._gate_bias + _candidate_weight = self._candidate_weight + _candidate_bias = self._candidate_bias + + pre_hidden = state + concat_input_hidden = layers.concat([input, pre_hidden], axis=1) + + gate_input = layers.matmul(x=concat_input_hidden, y=_gate_weight) + + gate_input = layers.elementwise_add(gate_input, _gate_bias) + + gate_input = self._gate_activation(gate_input) + r, u = layers.split(gate_input, num_or_sections=2, dim=1) + + r_hidden = r * pre_hidden + + candidate = layers.matmul( + layers.concat([input, r_hidden], 1), _candidate_weight) + candidate = layers.elementwise_add(candidate, _candidate_bias) + + c = self._activation(candidate) + new_hidden = u * pre_hidden + (1 - u) * c + + return new_hidden + + @property + def state_shape(self): + return [self._hidden_size] + + +class RNN(fluid.dygraph.Layer): + def __init__(self, cell, is_reverse=False, time_major=False): + super(RNN, self).__init__() + self.cell = cell + if not hasattr(self.cell, "call"): + self.cell.call = self.cell.forward + self.is_reverse = is_reverse + self.time_major = time_major + self.batch_index, self.time_step_index = (1, 0) if time_major else (0, + 1) + + def forward(self, + inputs, + initial_states=None, + sequence_length=None, + **kwargs): + if fluid.in_dygraph_mode(): + + class ArrayWrapper(object): + def __init__(self, x): + self.array = [x] + + def append(self, x): + self.array.append(x) + return self + + def _maybe_copy(state, new_state, step_mask): + # TODO: use where_op + new_state = fluid.layers.elementwise_mul( + new_state, step_mask, + axis=0) - fluid.layers.elementwise_mul( + state, (step_mask - 1), axis=0) + return new_state + + flat_inputs = flatten(inputs) + batch_size, time_steps = ( + flat_inputs[0].shape[self.batch_index], + flat_inputs[0].shape[self.time_step_index]) + + if initial_states is None: + initial_states = self.cell.get_initial_states( + batch_ref=inputs, batch_dim_idx=self.batch_index) + + if not self.time_major: + inputs = map_structure( + lambda x: fluid.layers.transpose(x, [1, 0] + list( + range(2, len(x.shape)))), inputs) + + if sequence_length: + mask = fluid.layers.sequence_mask( + sequence_length, + maxlen=time_steps, + dtype=flatten(initial_states)[0].dtype) + mask = fluid.layers.transpose(mask, [1, 0]) + + if self.is_reverse: + inputs = map_structure( + lambda x: fluid.layers.reverse(x, axis=[0]), inputs) + mask = fluid.layers.reverse( + mask, axis=[0]) if sequence_length else None + + states = initial_states + outputs = [] + for i in range(time_steps): + step_inputs = map_structure(lambda x: x[i], inputs) + step_outputs, new_states = self.cell(step_inputs, states, + **kwargs) + if sequence_length: + new_states = map_structure( + partial( + _maybe_copy, step_mask=mask[i]), + states, + new_states) + states = new_states + outputs = map_structure( + lambda x: ArrayWrapper(x), + step_outputs) if i == 0 else map_structure( + lambda x, x_array: x_array.append(x), step_outputs, + outputs) + + final_outputs = map_structure( + lambda x: fluid.layers.stack(x.array, + axis=self.time_step_index), + outputs) + + if self.is_reverse: + final_outputs = map_structure( + lambda x: fluid.layers.reverse(x, + axis=self.time_step_index), + final_outputs) + + final_states = new_states + else: + final_outputs, final_states = fluid.layers.rnn( + self.cell, + inputs, + initial_states=initial_states, + sequence_length=sequence_length, + time_major=self.time_major, + is_reverse=self.is_reverse, + **kwargs) + return final_outputs, final_states + + +class DynamicDecode(Layer): + def __init__(self, + decoder, + max_step_num=None, + output_time_major=False, + impute_finished=False, + is_test=False, + return_length=False): + super(DynamicDecode, self).__init__() + self.decoder = decoder + self.max_step_num = max_step_num + self.output_time_major = output_time_major + self.impute_finished = impute_finished + self.is_test = is_test + self.return_length = return_length + + def forward(self, inits=None, **kwargs): + if fluid.in_dygraph_mode(): + + class ArrayWrapper(object): + def __init__(self, x): + self.array = [x] + + def append(self, x): + self.array.append(x) + return self + + def __getitem__(self, item): + return self.array.__getitem__(item) + + def _maybe_copy(state, new_state, step_mask): + # TODO: use where_op + state_dtype = state.dtype + if convert_dtype(state_dtype) in ["bool"]: + state = layers.cast(state, dtype="float32") + new_state = layers.cast(new_state, dtype="float32") + if step_mask.dtype != state.dtype: + step_mask = layers.cast(step_mask, dtype=state.dtype) + # otherwise, renamed bool gradients of would be summed up leading + # to sum(bool) error. + step_mask.stop_gradient = True + new_state = layers.elementwise_mul( + state, step_mask, axis=0) - layers.elementwise_mul( + new_state, (step_mask - 1), axis=0) + if convert_dtype(state_dtype) in ["bool"]: + new_state = layers.cast(new_state, dtype=state_dtype) + return new_state + + initial_inputs, initial_states, initial_finished = self.decoder.initialize( + inits) + inputs, states, finished = (initial_inputs, initial_states, + initial_finished) + cond = layers.logical_not((layers.reduce_all(initial_finished))) + sequence_lengths = layers.cast( + layers.zeros_like(initial_finished), "int64") + outputs = None + + step_idx = 0 + step_idx_tensor = layers.fill_constant( + shape=[1], dtype="int64", value=step_idx) + while cond.numpy(): + (step_outputs, next_states, next_inputs, + next_finished) = self.decoder.step(step_idx_tensor, inputs, + states, **kwargs) + if not self.decoder.tracks_own_finished: + # BeamSearchDecoder would track it own finished, since + # beams would be reordered and the finished status of each + # entry might change. Otherwise, perform logical OR which + # would not change the already finished. + next_finished = layers.logical_or(next_finished, finished) + # To confirm states.finished/finished be consistent with + # next_finished. + layers.assign(next_finished, finished) + next_sequence_lengths = layers.elementwise_add( + sequence_lengths, + layers.cast( + layers.logical_not(finished), sequence_lengths.dtype)) + + if self.impute_finished: # rectify the states for the finished. + next_states = map_structure( + lambda x, y: _maybe_copy(x, y, finished), states, + next_states) + outputs = map_structure( + lambda x: ArrayWrapper(x), + step_outputs) if step_idx == 0 else map_structure( + lambda x, x_array: x_array.append(x), step_outputs, + outputs) + inputs, states, finished, sequence_lengths = ( + next_inputs, next_states, next_finished, + next_sequence_lengths) + + layers.increment(x=step_idx_tensor, value=1.0, in_place=True) + step_idx += 1 + + layers.logical_not(layers.reduce_all(finished), cond) + if self.max_step_num is not None and step_idx > self.max_step_num: + break + + final_outputs = map_structure( + lambda x: fluid.layers.stack(x.array, axis=0), outputs) + final_states = states + + try: + final_outputs, final_states = self.decoder.finalize( + final_outputs, final_states, sequence_lengths) + except NotImplementedError: + pass + + if not self.output_time_major: + final_outputs = map_structure( + lambda x: layers.transpose(x, [1, 0] + list( + range(2, len(x.shape)))), final_outputs) + + return (final_outputs, final_states, + sequence_lengths) if self.return_length else ( + final_outputs, final_states) + else: + return fluid.layers.dynamic_decode( + self.decoder, + inits, + max_step_num=self.max_step_num, + output_time_major=self.output_time_major, + impute_finished=self.impute_finished, + is_test=self.is_test, + return_length=self.return_length, + **kwargs) + + +class TransfomerCell(object): + """ + Let inputs=(trg_word, trg_pos), states=cache to make Transformer can be + used as RNNCell + """ + + def __init__(self, decoder): + self.decoder = decoder + + def __call__(self, inputs, states, trg_src_attn_bias, enc_output, + static_caches): + trg_word, trg_pos = inputs + for cache, static_cache in zip(states, static_caches): + cache.update(static_cache) + logits = self.decoder(trg_word, trg_pos, None, trg_src_attn_bias, + enc_output, states) + new_states = [{"k": cache["k"], "v": cache["v"]} for cache in states] + return logits, new_states + + +class TransformerBeamSearchDecoder(layers.BeamSearchDecoder): + def __init__(self, cell, start_token, end_token, beam_size, + var_dim_in_state): + super(TransformerBeamSearchDecoder, + self).__init__(cell, start_token, end_token, beam_size) + self.cell = cell + self.var_dim_in_state = var_dim_in_state + + def _merge_batch_beams_with_var_dim(self, x): + # init length of cache is 0, and it increases with decoding carrying on, + # thus need to reshape elaborately + var_dim_in_state = self.var_dim_in_state + 1 # count in beam dim + x = layers.transpose(x, + list(range(var_dim_in_state, len(x.shape))) + + list(range(0, var_dim_in_state))) + x = layers.reshape( + x, [0] * (len(x.shape) - var_dim_in_state + ) + [self.batch_size * self.beam_size] + + [int(size) for size in x.shape[-var_dim_in_state + 2:]]) + x = layers.transpose( + x, + list(range((len(x.shape) + 1 - var_dim_in_state), len(x.shape))) + + list(range(0, (len(x.shape) + 1 - var_dim_in_state)))) + return x + + def _split_batch_beams_with_var_dim(self, x): + var_dim_size = layers.shape(x)[self.var_dim_in_state] + x = layers.reshape( + x, [-1, self.beam_size] + + [int(size) + for size in x.shape[1:self.var_dim_in_state]] + [var_dim_size] + + [int(size) for size in x.shape[self.var_dim_in_state + 1:]]) + return x + + def step(self, time, inputs, states, **kwargs): + # compared to RNN, Transformer has 3D data at every decoding step + inputs = layers.reshape(inputs, [-1, 1]) # token + pos = layers.ones_like(inputs) * time # pos + cell_states = map_structure(self._merge_batch_beams_with_var_dim, + states.cell_states) + + cell_outputs, next_cell_states = self.cell((inputs, pos), cell_states, + **kwargs) + cell_outputs = map_structure(self._split_batch_beams, cell_outputs) + next_cell_states = map_structure(self._split_batch_beams_with_var_dim, + next_cell_states) + + beam_search_output, beam_search_state = self._beam_search_step( + time=time, + logits=cell_outputs, + next_cell_states=next_cell_states, + beam_state=states) + next_inputs, finished = (beam_search_output.predicted_ids, + beam_search_state.finished) + + return (beam_search_output, beam_search_state, next_inputs, finished) + + +### Transformer Modules ### +class PrePostProcessLayer(Layer): + """ + PrePostProcessLayer + """ + + def __init__(self, + process_cmd, + d_model, + dropout_rate, + reused_layer_norm=None): + super(PrePostProcessLayer, self).__init__() + self.process_cmd = process_cmd + self.functors = [] + for cmd in self.process_cmd: + if cmd == "a": # add residual connection + self.functors.append(lambda x, y: x + y if y else x) + elif cmd == "n": # add layer normalization + if reused_layer_norm is not None: + layer_norm = reused_layer_norm + else: + layer_norm = LayerNorm( + normalized_shape=d_model, + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.Constant(1.)), + bias_attr=fluid.ParamAttr( + initializer=fluid.initializer.Constant(0.))) + + self.functors.append( + self.add_sublayer( + "layer_norm_%d" % len( + self.sublayers(include_sublayers=False)), + layer_norm)) + elif cmd == "d": # add dropout + self.functors.append(lambda x: layers.dropout( + x, dropout_prob=dropout_rate, is_test=False) + if dropout_rate else x) + + def forward(self, x, residual=None): + for i, cmd in enumerate(self.process_cmd): + if cmd == "a": + x = self.functors[i](x, residual) + else: + x = self.functors[i](x) + return x + + +class MultiHeadAttention(Layer): + """ + Multi-Head Attention + """ + + def __init__(self, + d_key, + d_value, + d_model, + n_head=1, + dropout_rate=0.0, + reused_query_fc=None, + reused_key_fc=None, + reused_value_fc=None, + reused_proj_fc=None): + + super(MultiHeadAttention, self).__init__() + self.n_head = n_head + self.d_key = d_key + self.d_value = d_value + self.d_model = d_model + self.dropout_rate = dropout_rate + + if reused_query_fc is not None: + self.q_fc = reused_query_fc + else: + self.q_fc = Linear( + input_dim=d_model, output_dim=d_key * n_head, bias_attr=False) + if reused_key_fc is not None: + self.k_fc = reused_key_fc + else: + self.k_fc = Linear( + input_dim=d_model, output_dim=d_key * n_head, bias_attr=False) + if reused_value_fc is not None: + self.v_fc = reused_value_fc + else: + self.v_fc = Linear( + input_dim=d_model, + output_dim=d_value * n_head, + bias_attr=False) + if reused_proj_fc is not None: + self.proj_fc = reused_proj_fc + else: + self.proj_fc = Linear( + input_dim=d_value * n_head, + output_dim=d_model, + bias_attr=False) + + def _prepare_qkv(self, queries, keys, values, cache=None): + if keys is None: # self-attention + keys, values = queries, queries + static_kv = False + else: # cross-attention + static_kv = True + + q = self.q_fc(queries) + q = layers.reshape(x=q, shape=[0, 0, self.n_head, self.d_key]) + q = layers.transpose(x=q, perm=[0, 2, 1, 3]) + + if cache is not None and static_kv and "static_k" in cache: + # for encoder-decoder attention in inference and has cached + k = cache["static_k"] + v = cache["static_v"] + else: + k = self.k_fc(keys) + v = self.v_fc(values) + k = layers.reshape(x=k, shape=[0, 0, self.n_head, self.d_key]) + k = layers.transpose(x=k, perm=[0, 2, 1, 3]) + v = layers.reshape(x=v, shape=[0, 0, self.n_head, self.d_value]) + v = layers.transpose(x=v, perm=[0, 2, 1, 3]) + + if cache is not None: + if static_kv and not "static_k" in cache: + # for encoder-decoder attention in inference and has not cached + cache["static_k"], cache["static_v"] = k, v + elif not static_kv: + # for decoder self-attention in inference + cache_k, cache_v = cache["k"], cache["v"] + k = layers.concat([cache_k, k], axis=2) + v = layers.concat([cache_v, v], axis=2) + cache["k"], cache["v"] = k, v + + return q, k, v + + def forward(self, queries, keys, values, attn_bias, cache=None): + # compute q ,k ,v + q, k, v = self._prepare_qkv(queries, keys, values, cache) + + # scale dot product attention + product = layers.matmul( + x=q, y=k, transpose_y=True, alpha=self.d_model**-0.5) + if attn_bias: + product += attn_bias + weights = layers.softmax(product) + if self.dropout_rate: + weights = layers.dropout( + weights, dropout_prob=self.dropout_rate, is_test=False) + + out = layers.matmul(weights, v) + + # combine heads + out = layers.transpose(out, perm=[0, 2, 1, 3]) + out = layers.reshape(x=out, shape=[0, 0, out.shape[2] * out.shape[3]]) + + # project to output + out = self.proj_fc(out) + return out + + def cal_kv(self, keys, values): + k = self.k_fc(keys) + v = self.v_fc(values) + k = layers.reshape(x=k, shape=[0, 0, self.n_head, self.d_key]) + k = layers.transpose(x=k, perm=[0, 2, 1, 3]) + v = layers.reshape(x=v, shape=[0, 0, self.n_head, self.d_value]) + v = layers.transpose(x=v, perm=[0, 2, 1, 3]) + return k, v + + +class FFN(Layer): + """ + Feed-Forward Network + """ + + def __init__(self, + d_inner_hid, + d_model, + dropout_rate, + fc1_act="relu", + reused_fc1=None, + reused_fc2=None): + super(FFN, self).__init__() + self.dropout_rate = dropout_rate + if reused_fc1 is not None: + self.fc1 = reused_fc1 + else: + self.fc1 = Linear( + input_dim=d_model, output_dim=d_inner_hid, act=fc1_act) + if reused_fc2 is not None: + self.fc2 = reused_fc2 + else: + self.fc2 = Linear(input_dim=d_inner_hid, output_dim=d_model) + + def forward(self, x): + hidden = self.fc1(x) + if self.dropout_rate: + hidden = layers.dropout( + hidden, dropout_prob=self.dropout_rate, is_test=False) + out = self.fc2(hidden) + return out + + +class TransformerEncoderLayer(Layer): + """ + EncoderLayer + """ + + def __init__(self, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd="n", + postprocess_cmd="da", + ffn_fc1_act="relu", + reused_pre_selatt_layernorm=None, + reused_multihead_att_weights={ + "reused_query_fc": None, + "reused_key_fc": None, + "reused_value_fc": None, + "reused_proj_fc": None + }, + reused_post_selfatt_layernorm=None, + reused_pre_ffn_layernorm=None, + reused_ffn_weights={"reused_fc1": None, + "reused_fc2": None}, + reused_post_ffn_layernorm=None): + + super(TransformerEncoderLayer, self).__init__() + + self.preprocesser1 = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout, + reused_pre_selatt_layernorm) + self.self_attn = MultiHeadAttention( + d_key, + d_value, + d_model, + n_head, + attention_dropout, + reused_query_fc=reused_multihead_att_weights["reused_query_fc"], + reused_key_fc=reused_multihead_att_weights["reused_key_fc"], + reused_value_fc=reused_multihead_att_weights["reused_value_fc"], + reused_proj_fc=reused_multihead_att_weights["reused_proj_fc"]) + self.postprocesser1 = PrePostProcessLayer( + postprocess_cmd, d_model, prepostprocess_dropout, + reused_post_selfatt_layernorm) + + self.preprocesser2 = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout, + reused_pre_ffn_layernorm) + self.ffn = FFN(d_inner_hid, + d_model, + relu_dropout, + fc1_act=ffn_fc1_act, + reused_fc1=reused_ffn_weights["reused_fc1"], + reused_fc2=reused_ffn_weights["reused_fc2"]) + self.postprocesser2 = PrePostProcessLayer(postprocess_cmd, d_model, + prepostprocess_dropout, + reused_post_ffn_layernorm) + + def forward(self, enc_input, attn_bias): + attn_output = self.self_attn( + self.preprocesser1(enc_input), None, None, attn_bias) + attn_output = self.postprocesser1(attn_output, enc_input) + + ffn_output = self.ffn(self.preprocesser2(attn_output)) + ffn_output = self.postprocesser2(ffn_output, attn_output) + return ffn_output + + +class TransformerEncoder(Layer): + """ + encoder + """ + + def __init__(self, + n_layer, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd="n", + postprocess_cmd="da", + ffn_fc1_act="relu"): + + super(TransformerEncoder, self).__init__() + + self.encoder_layers = list() + for i in range(n_layer): + self.encoder_layers.append( + self.add_sublayer( + "layer_%d" % i, + TransformerEncoderLayer( + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd, + postprocess_cmd, + ffn_fc1_act=ffn_fc1_act))) + self.processer = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout) + + def forward(self, enc_input, attn_bias): + for encoder_layer in self.encoder_layers: + enc_output = encoder_layer(enc_input, attn_bias) + enc_input = enc_output + + return self.processer(enc_output) + + +class TransformerDecoderLayer(Layer): + """ + decoder + """ + + def __init__(self, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd="n", + postprocess_cmd="da", + reused_pre_selfatt_layernorm=None, + reused_self_multihead_att_weights={ + "reused_query_fc": None, + "reused_key_fc": None, + "reused_value_fc": None, + "reused_proj_fc": None + }, + reused_post_selfatt_layernorm=None, + reused_pre_crossatt_layernorm=None, + reused_cross_multihead_att_weights={ + "reused_query_fc": None, + "reused_key_fc": None, + "reused_value_fc": None, + "reused_proj_fc": None + }, + reused_post_crossatt_layernorm=None, + reused_pre_ffn_layernorm=None, + reused_ffn_weights={"reused_fc1": None, + "reused_fc2": None}, + reused_post_ffn_layernorm=None): + super(TransformerDecoderLayer, self).__init__() + + self.preprocesser1 = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout, + reused_pre_selfatt_layernorm) + self.self_attn = MultiHeadAttention( + d_key, + d_value, + d_model, + n_head, + attention_dropout, + reused_query_fc=reused_self_multihead_att_weights[ + "reused_query_fc"], + reused_key_fc=reused_self_multihead_att_weights["reused_key_fc"], + reused_value_fc=reused_self_multihead_att_weights[ + "reused_value_fc"], + reused_proj_fc=reused_self_multihead_att_weights["reused_proj_fc"]) + self.postprocesser1 = PrePostProcessLayer( + postprocess_cmd, d_model, prepostprocess_dropout, + reused_post_selfatt_layernorm) + + self.preprocesser2 = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout, + reused_pre_crossatt_layernorm) + self.cross_attn = MultiHeadAttention( + d_key, + d_value, + d_model, + n_head, + attention_dropout, + reused_query_fc=reused_cross_multihead_att_weights[ + "reused_query_fc"], + reused_key_fc=reused_cross_multihead_att_weights["reused_key_fc"], + reused_value_fc=reused_cross_multihead_att_weights[ + "reused_value_fc"], + reused_proj_fc=reused_cross_multihead_att_weights[ + "reused_proj_fc"]) + self.postprocesser2 = PrePostProcessLayer( + postprocess_cmd, d_model, prepostprocess_dropout, + reused_post_crossatt_layernorm) + + self.preprocesser3 = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout, + reused_pre_ffn_layernorm) + self.ffn = FFN(d_inner_hid, + d_model, + relu_dropout, + reused_fc1=reused_ffn_weights["reused_fc1"], + reused_fc2=reused_ffn_weights["reused_fc2"]) + self.postprocesser3 = PrePostProcessLayer(postprocess_cmd, d_model, + prepostprocess_dropout, + reused_post_ffn_layernorm) + + def forward(self, + dec_input, + enc_output, + self_attn_bias, + cross_attn_bias, + cache=None): + self_attn_output = self.self_attn( + self.preprocesser1(dec_input), None, None, self_attn_bias, cache) + self_attn_output = self.postprocesser1(self_attn_output, dec_input) + + cross_attn_output = self.cross_attn( + self.preprocesser2(self_attn_output), enc_output, enc_output, + cross_attn_bias, cache) + cross_attn_output = self.postprocesser2(cross_attn_output, + self_attn_output) + + ffn_output = self.ffn(self.preprocesser3(cross_attn_output)) + ffn_output = self.postprocesser3(ffn_output, cross_attn_output) + + return ffn_output + + +class TransformerDecoder(Layer): + """ + decoder + """ + + def __init__(self, n_layer, n_head, d_key, d_value, d_model, d_inner_hid, + prepostprocess_dropout, attention_dropout, relu_dropout, + preprocess_cmd, postprocess_cmd): + super(TransformerDecoder, self).__init__() + + self.decoder_layers = list() + for i in range(n_layer): + self.decoder_layers.append( + self.add_sublayer( + "layer_%d" % i, + TransformerDecoderLayer( + n_head, d_key, d_value, d_model, d_inner_hid, + prepostprocess_dropout, attention_dropout, + relu_dropout, preprocess_cmd, postprocess_cmd))) + self.processer = PrePostProcessLayer(preprocess_cmd, d_model, + prepostprocess_dropout) + + def forward(self, + dec_input, + enc_output, + self_attn_bias, + cross_attn_bias, + caches=None): + for i, decoder_layer in enumerate(self.decoder_layers): + dec_output = decoder_layer(dec_input, enc_output, self_attn_bias, + cross_attn_bias, None + if caches is None else caches[i]) + dec_input = dec_output + + return self.processer(dec_output) + + def prepare_static_cache(self, enc_output): + return [ + dict( + zip(("static_k", "static_v"), + decoder_layer.cross_attn.cal_kv(enc_output, enc_output))) + for decoder_layer in self.decoder_layers + ] + + +#TODO: we should merge GRUCell with BasicGRUCell +class GRUCell(RNNCell): + def __init__(self, + input_size, + hidden_size, + param_attr=None, + bias_attr=None, + gate_activation='sigmoid', + candidate_activation='tanh', + origin_mode=False): + super(GRUCell, self).__init__() + self.hidden_size = hidden_size + self.fc_layer = Linear( + input_size, hidden_size * 3, param_attr=param_attr) + + self.gru_unit = GRUUnit( + hidden_size * 3, + param_attr=param_attr, + bias_attr=bias_attr, + activation=candidate_activation, + gate_activation=gate_activation, + origin_mode=origin_mode) + + def forward(self, inputs, states): + # for GRUCell, `step_outputs` and `new_states` both are hidden + x = self.fc_layer(inputs) + hidden, _, _ = self.gru_unit(x, states) + return hidden, hidden + + @property + def state_shape(self): + return [self.hidden_size] + + +#TODO: we should merge GRUCell with BasicGRUCell +class GRUEncoderCell(RNNCell): + def __init__(self, + num_layers, + input_size, + hidden_size, + dropout_prob=0., + init_scale=0.1): + super(GRUEncoderCell, self).__init__() + self.dropout_prob = dropout_prob + # use add_sublayer to add multi-layers + self.gru_cells = [] + for i in range(num_layers): + self.gru_cells.append( + self.add_sublayer( + "gru_%d" % i, + #BasicGRUCell( + GRUCell( + input_size=input_size if i == 0 else hidden_size, + hidden_size=hidden_size, + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.UniformInitializer( + low=-init_scale, high=init_scale))))) + + def forward(self, step_input, states): + new_states = [] + for i, gru_cell in enumerate(self.gru_cells): + out, state = gru_cell(step_input, states[i]) + step_input = layers.dropout( + out, + self.dropout_prob, + dropout_implementation='upscale_in_train' + ) if self.dropout_prob > 0 else out + new_states.append(step_input) + return step_input, new_states + + @property + def state_shape(self): + return [cell.state_shape for cell in self.gru_cells] + + +class BiGRU(fluid.dygraph.Layer): + def __init__(self, input_dim, grnn_hidden_dim, init_bound, h_0=None): + super(BiGRU, self).__init__() + self.gru = RNN(GRUEncoderCell(1, input_dim, grnn_hidden_dim, 0.0, + init_bound), + is_reverse=False, + time_major=False) + + self.gru_r = RNN(GRUEncoderCell(1, input_dim, grnn_hidden_dim, 0.0, + init_bound), + is_reverse=True, + time_major=False) + + def forward(self, input_feature): + pre_gru, pre_state = self.gru(input_feature) + gru_r, r_state = self.gru_r(input_feature) + bi_merge = fluid.layers.concat(input=[pre_gru, gru_r], axis=-1) + return bi_merge + + +class Linear_chain_crf(fluid.dygraph.Layer): + def __init__(self, param_attr, size=None, is_test=False, dtype='float32'): + super(Linear_chain_crf, self).__init__() + + self._param_attr = param_attr + self._dtype = dtype + self._size = size + self._is_test = is_test + self._transition = self.create_parameter( + attr=self._param_attr, + shape=[self._size + 2, self._size], + dtype=self._dtype) + + @property + def weight(self): + return self._transition + + @weight.setter + def weight(self, value): + self._transition = value + + def forward(self, input, label, length=None): + + alpha = self._helper.create_variable_for_type_inference( + dtype=self._dtype) + emission_exps = self._helper.create_variable_for_type_inference( + dtype=self._dtype) + transition_exps = self._helper.create_variable_for_type_inference( + dtype=self._dtype) + log_likelihood = self._helper.create_variable_for_type_inference( + dtype=self._dtype) + this_inputs = { + "Emission": [input], + "Transition": self._transition, + "Label": [label] + } + if length is not None: + this_inputs['Length'] = [length] + self._helper.append_op( + type='linear_chain_crf', + inputs=this_inputs, + outputs={ + "Alpha": [alpha], + "EmissionExps": [emission_exps], + "TransitionExps": transition_exps, + "LogLikelihood": log_likelihood + }, + attrs={"is_test": self._is_test, }) + return log_likelihood + + +class Crf_decoding(fluid.dygraph.Layer): + def __init__(self, param_attr, size=None, is_test=False, dtype='float32'): + super(Crf_decoding, self).__init__() + + self._dtype = dtype + self._size = size + self._is_test = is_test + self._param_attr = param_attr + self._transition = self.create_parameter( + attr=self._param_attr, + shape=[self._size + 2, self._size], + dtype=self._dtype) + + @property + def weight(self): + return self._transition + + @weight.setter + def weight(self, value): + self._transition = value + + def forward(self, input, label=None, length=None): + + viterbi_path = self._helper.create_variable_for_type_inference( + dtype=self._dtype) + this_inputs = { + "Emission": [input], + "Transition": self._transition, + "Label": label + } + if length is not None: + this_inputs['Length'] = [length] + self._helper.append_op( + type='crf_decoding', + inputs=this_inputs, + outputs={"ViterbiPath": [viterbi_path]}, + attrs={"is_test": self._is_test, }) + return viterbi_path + + +class SequenceTagging(fluid.dygraph.Layer): + def __init__(self, + vocab_size, + num_labels, + batch_size, + word_emb_dim=128, + grnn_hidden_dim=128, + emb_learning_rate=0.1, + crf_learning_rate=0.1, + bigru_num=2, + init_bound=0.1, + length=None): + super(SequenceTagging, self).__init__() + """ + define the sequence tagging network structure + word: stores the input of the model + for_infer: a boolean value, indicating if the model to be created is for training or predicting. + + return: + for infer: return the prediction + otherwise: return the prediction + """ + self.word_emb_dim = word_emb_dim + self.vocab_size = vocab_size + self.num_labels = num_labels + self.grnn_hidden_dim = grnn_hidden_dim + self.emb_lr = emb_learning_rate + self.crf_lr = crf_learning_rate + self.bigru_num = bigru_num + self.batch_size = batch_size + self.init_bound = 0.1 + + self.word_embedding = Embedding( + size=[self.vocab_size, self.word_emb_dim], + dtype='float32', + param_attr=fluid.ParamAttr( + learning_rate=self.emb_lr, + name="word_emb", + initializer=fluid.initializer.Uniform( + low=-self.init_bound, high=self.init_bound))) + + h_0 = fluid.layers.create_global_var( + shape=[self.batch_size, self.grnn_hidden_dim], + value=0.0, + dtype='float32', + persistable=True, + force_cpu=True, + name='h_0') + + self.bigru_units = [] + for i in range(self.bigru_num): + if i == 0: + self.bigru_units.append( + self.add_sublayer( + "bigru_units%d" % i, + BiGRU( + self.grnn_hidden_dim, + self.grnn_hidden_dim, + self.init_bound, + h_0=h_0))) + else: + self.bigru_units.append( + self.add_sublayer( + "bigru_units%d" % i, + BiGRU( + self.grnn_hidden_dim * 2, + self.grnn_hidden_dim, + self.init_bound, + h_0=h_0))) + + self.fc = Linear( + input_dim=self.grnn_hidden_dim * 2, + output_dim=self.num_labels, + param_attr=fluid.ParamAttr( + initializer=fluid.initializer.Uniform( + low=-self.init_bound, high=self.init_bound), + regularizer=fluid.regularizer.L2DecayRegularizer( + regularization_coeff=1e-4))) + + self.linear_chain_crf = Linear_chain_crf( + param_attr=fluid.ParamAttr( + name='linear_chain_crfw', learning_rate=self.crf_lr), + size=self.num_labels) + + self.crf_decoding = Crf_decoding( + param_attr=fluid.ParamAttr( + name='crfw', learning_rate=self.crf_lr), + size=self.num_labels) + + def forward(self, word, lengths, target=None): + """ + Configure the network + """ + word_embed = self.word_embedding(word) + input_feature = word_embed + + for i in range(self.bigru_num): + bigru_output = self.bigru_units[i](input_feature) + input_feature = bigru_output + + emission = self.fc(bigru_output) + + if target is not None: + crf_cost = self.linear_chain_crf( + input=emission, label=target, length=lengths) + avg_cost = fluid.layers.mean(x=crf_cost) + self.crf_decoding.weight = self.linear_chain_crf.weight + crf_decode = self.crf_decoding(input=emission, length=lengths) + return crf_decode, avg_cost, lengths + else: + self.linear_chain_crf.weight = self.crf_decoding.weight + crf_decode = self.crf_decoding(input=emission, length=lengths) + return crf_decode, lengths diff --git a/hapi/text/tokenizer/__init__.py b/hapi/text/tokenizer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hapi/text/tokenizer/tokenization.py b/hapi/text/tokenizer/tokenization.py new file mode 100644 index 0000000000000000000000000000000000000000..08570f30fe9e6a8036a15095e67e6e8dd8686c14 --- /dev/null +++ b/hapi/text/tokenizer/tokenization.py @@ -0,0 +1,371 @@ +# coding=utf-8 +# Copyright 2018 The Google AI Language Team Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tokenization classes.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections +import unicodedata +import six +import io + + +def convert_to_unicode(text): + """Converts `text` to Unicode (if it's not already), assuming utf-8 input.""" + if six.PY3: + if isinstance(text, str): + return text + elif isinstance(text, bytes): + return text.decode("utf-8", "ignore") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + elif six.PY2: + if isinstance(text, str): + return text.decode("utf-8", "ignore") + elif isinstance(text, unicode): + return text + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + else: + raise ValueError("Not running on Python2 or Python 3?") + + +def printable_text(text): + """Returns text encoded in a way suitable for print or `tf.logging`.""" + + # These functions want `str` for both Python2 and Python3, but in one case + # it's a Unicode string and in the other it's a byte string. + if six.PY3: + if isinstance(text, str): + return text + elif isinstance(text, bytes): + return text.decode("utf-8", "ignore") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + elif six.PY2: + if isinstance(text, str): + return text + elif isinstance(text, unicode): + return text.encode("utf-8") + else: + raise ValueError("Unsupported string type: %s" % (type(text))) + else: + raise ValueError("Not running on Python2 or Python 3?") + + +def load_vocab(vocab_file): + """Loads a vocabulary file into a dictionary.""" + vocab = collections.OrderedDict() + fin = io.open(vocab_file, encoding="utf8") + for num, line in enumerate(fin): + items = convert_to_unicode(line.strip()).split("\t") + if len(items) > 2: + break + token = items[0] + index = items[1] if len(items) == 2 else num + token = token.strip() + vocab[token] = int(index) + return vocab + + +def convert_by_vocab(vocab, items): + """Converts a sequence of [tokens|ids] using the vocab.""" + output = [] + for item in items: + output.append(vocab[item]) + return output + + +def convert_tokens_to_ids(vocab, tokens): + return convert_by_vocab(vocab, tokens) + + +def convert_ids_to_tokens(inv_vocab, ids): + return convert_by_vocab(inv_vocab, ids) + + +def whitespace_tokenize(text): + """Runs basic whitespace cleaning and splitting on a peice of text.""" + text = text.strip() + if not text: + return [] + tokens = text.split() + return tokens + + +class FullTokenizer(object): + """Runs end-to-end tokenziation.""" + + def __init__(self, vocab_file, do_lower_case=True): + self.vocab = load_vocab(vocab_file) + self.inv_vocab = {v: k for k, v in self.vocab.items()} + self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case) + self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) + + def tokenize(self, text): + split_tokens = [] + for token in self.basic_tokenizer.tokenize(text): + for sub_token in self.wordpiece_tokenizer.tokenize(token): + split_tokens.append(sub_token) + + return split_tokens + + def convert_tokens_to_ids(self, tokens): + return convert_by_vocab(self.vocab, tokens) + + def convert_ids_to_tokens(self, ids): + return convert_by_vocab(self.inv_vocab, ids) + + +class CharTokenizer(object): + """Runs end-to-end tokenziation.""" + + def __init__(self, vocab_file, do_lower_case=True): + self.vocab = load_vocab(vocab_file) + self.inv_vocab = {v: k for k, v in self.vocab.items()} + self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) + + def tokenize(self, text): + split_tokens = [] + for token in text.lower().split(" "): + for sub_token in self.wordpiece_tokenizer.tokenize(token): + split_tokens.append(sub_token) + + return split_tokens + + def convert_tokens_to_ids(self, tokens): + return convert_by_vocab(self.vocab, tokens) + + def convert_ids_to_tokens(self, ids): + return convert_by_vocab(self.inv_vocab, ids) + + +class BasicTokenizer(object): + """Runs basic tokenization (punctuation splitting, lower casing, etc.).""" + + def __init__(self, do_lower_case=True): + """Constructs a BasicTokenizer. + + Args: + do_lower_case: Whether to lower case the input. + """ + self.do_lower_case = do_lower_case + + def tokenize(self, text): + """Tokenizes a piece of text.""" + text = convert_to_unicode(text) + text = self._clean_text(text) + + # This was added on November 1st, 2018 for the multilingual and Chinese + # models. This is also applied to the English models now, but it doesn't + # matter since the English models were not trained on any Chinese data + # and generally don't have any Chinese data in them (there are Chinese + # characters in the vocabulary because Wikipedia does have some Chinese + # words in the English Wikipedia.). + text = self._tokenize_chinese_chars(text) + + orig_tokens = whitespace_tokenize(text) + split_tokens = [] + for token in orig_tokens: + if self.do_lower_case: + token = token.lower() + token = self._run_strip_accents(token) + split_tokens.extend(self._run_split_on_punc(token)) + + output_tokens = whitespace_tokenize(" ".join(split_tokens)) + return output_tokens + + def _run_strip_accents(self, text): + """Strips accents from a piece of text.""" + text = unicodedata.normalize("NFD", text) + output = [] + for char in text: + cat = unicodedata.category(char) + if cat == "Mn": + continue + output.append(char) + return "".join(output) + + def _run_split_on_punc(self, text): + """Splits punctuation on a piece of text.""" + chars = list(text) + i = 0 + start_new_word = True + output = [] + while i < len(chars): + char = chars[i] + if _is_punctuation(char): + output.append([char]) + start_new_word = True + else: + if start_new_word: + output.append([]) + start_new_word = False + output[-1].append(char) + i += 1 + + return ["".join(x) for x in output] + + def _tokenize_chinese_chars(self, text): + """Adds whitespace around any CJK character.""" + output = [] + for char in text: + cp = ord(char) + if self._is_chinese_char(cp): + output.append(" ") + output.append(char) + output.append(" ") + else: + output.append(char) + return "".join(output) + + def _is_chinese_char(self, cp): + """Checks whether CP is the codepoint of a CJK character.""" + # This defines a "chinese character" as anything in the CJK Unicode block: + # https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block) + # + # Note that the CJK Unicode block is NOT all Japanese and Korean characters, + # despite its name. The modern Korean Hangul alphabet is a different block, + # as is Japanese Hiragana and Katakana. Those alphabets are used to write + # space-separated words, so they are not treated specially and handled + # like the all of the other languages. + if ((cp >= 0x4E00 and cp <= 0x9FFF) or # + (cp >= 0x3400 and cp <= 0x4DBF) or # + (cp >= 0x20000 and cp <= 0x2A6DF) or # + (cp >= 0x2A700 and cp <= 0x2B73F) or # + (cp >= 0x2B740 and cp <= 0x2B81F) or # + (cp >= 0x2B820 and cp <= 0x2CEAF) or + (cp >= 0xF900 and cp <= 0xFAFF) or # + (cp >= 0x2F800 and cp <= 0x2FA1F)): # + return True + + return False + + def _clean_text(self, text): + """Performs invalid character removal and whitespace cleanup on text.""" + output = [] + for char in text: + cp = ord(char) + if cp == 0 or cp == 0xfffd or _is_control(char): + continue + if _is_whitespace(char): + output.append(" ") + else: + output.append(char) + return "".join(output) + + +class WordpieceTokenizer(object): + """Runs WordPiece tokenziation.""" + + def __init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=100): + self.vocab = vocab + self.unk_token = unk_token + self.max_input_chars_per_word = max_input_chars_per_word + + def tokenize(self, text): + """Tokenizes a piece of text into its word pieces. + + This uses a greedy longest-match-first algorithm to perform tokenization + using the given vocabulary. + + For example: + input = "unaffable" + output = ["un", "##aff", "##able"] + + Args: + text: A single token or whitespace separated tokens. This should have + already been passed through `BasicTokenizer. + + Returns: + A list of wordpiece tokens. + """ + + text = convert_to_unicode(text) + + output_tokens = [] + for token in whitespace_tokenize(text): + chars = list(token) + if len(chars) > self.max_input_chars_per_word: + output_tokens.append(self.unk_token) + continue + + is_bad = False + start = 0 + sub_tokens = [] + while start < len(chars): + end = len(chars) + cur_substr = None + while start < end: + substr = "".join(chars[start:end]) + if start > 0: + substr = "##" + substr + if substr in self.vocab: + cur_substr = substr + break + end -= 1 + if cur_substr is None: + is_bad = True + break + sub_tokens.append(cur_substr) + start = end + + if is_bad: + output_tokens.append(self.unk_token) + else: + output_tokens.extend(sub_tokens) + return output_tokens + + +def _is_whitespace(char): + """Checks whether `chars` is a whitespace character.""" + # \t, \n, and \r are technically contorl characters but we treat them + # as whitespace since they are generally considered as such. + if char == " " or char == "\t" or char == "\n" or char == "\r": + return True + cat = unicodedata.category(char) + if cat == "Zs": + return True + return False + + +def _is_control(char): + """Checks whether `chars` is a control character.""" + # These are technically control characters but we count them as whitespace + # characters. + if char == "\t" or char == "\n" or char == "\r": + return False + cat = unicodedata.category(char) + if cat.startswith("C"): + return True + return False + + +def _is_punctuation(char): + """Checks whether `chars` is a punctuation character.""" + cp = ord(char) + # We treat all non-letter/number ASCII as punctuation. + # Characters such as "^", "$", and "`" are not in the Unicode + # Punctuation class but we treat them as punctuation anyways, for + # consistency. + if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or + (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)): + return True + cat = unicodedata.category(char) + if cat.startswith("P"): + return True + return False diff --git a/hapi/vision/__init__.py b/hapi/vision/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d2be76375599071e4b5016f1e4d1cb3f679050e8 --- /dev/null +++ b/hapi/vision/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import models +from . import transforms + +__all__ = ["models", "transforms"] diff --git a/models/__init__.py b/hapi/vision/models/__init__.py similarity index 83% rename from models/__init__.py rename to hapi/vision/models/__init__.py index 26ad506c20a7395108bb1999806d8667fbb074dd..d444cd6627e8228a796c29cd7396d459e10cc4c7 100644 --- a/models/__init__.py +++ b/hapi/vision/models/__init__.py @@ -17,21 +17,15 @@ from . import vgg from . import mobilenetv1 from . import mobilenetv2 from . import darknet -from . import yolov3 -from . import tsm from .resnet import * from .mobilenetv1 import * from .mobilenetv2 import * from .vgg import * from .darknet import * -from .yolov3 import * -from .tsm import * __all__ = resnet.__all__ \ + vgg.__all__ \ + mobilenetv1.__all__ \ + mobilenetv2.__all__ \ - + darknet.__all__ \ - + yolov3.__all__ \ - + tsm.__all__ + + darknet.__all__ diff --git a/models/darknet.py b/hapi/vision/models/darknet.py similarity index 89% rename from models/darknet.py rename to hapi/vision/models/darknet.py index 095cf7d63c628483b3b0842f4c54d81bba75ceb6..df0588846c075009f28a23596682fb7287579672 100755 --- a/models/darknet.py +++ b/hapi/vision/models/darknet.py @@ -18,10 +18,10 @@ from paddle.fluid.regularizer import L2Decay from paddle.fluid.dygraph.nn import Conv2D, BatchNorm -from model import Model -from .download import get_weights_path +from hapi.model import Model +from hapi.download import get_weights_path -__all__ = ['DarkNet53', 'ConvBNLayer', 'darknet53'] +__all__ = ['DarkNet', 'darknet53'] # {num_layers: (url, md5)} pretrain_infos = { @@ -136,9 +136,17 @@ class LayerWarp(fluid.dygraph.Layer): DarkNet_cfg = {53: ([1, 2, 8, 8, 4])} -class DarkNet53(Model): +class DarkNet(Model): + """DarkNet model from + `"YOLOv3: An Incremental Improvement" `_ + + Args: + num_layers (int): layer number of DarkNet, only 53 supported currently, default: 53. + ch_in (int): channel number of input data, default 3. + """ + def __init__(self, num_layers=53, ch_in=3): - super(DarkNet53, self).__init__() + super(DarkNet, self).__init__() assert num_layers in DarkNet_cfg.keys(), \ "only support num_layers in {} currently" \ .format(DarkNet_cfg.keys()) @@ -188,7 +196,7 @@ class DarkNet53(Model): def _darknet(num_layers=53, input_channels=3, pretrained=True): - model = DarkNet53(num_layers, input_channels) + model = DarkNet(num_layers, input_channels) if pretrained: assert num_layers in pretrain_infos.keys(), \ "DarkNet{} do not have pretrained weights now, " \ @@ -201,4 +209,11 @@ def _darknet(num_layers=53, input_channels=3, pretrained=True): def darknet53(input_channels=3, pretrained=True): + """DarkNet 53-layer model + + Args: + input_channels (bool): channel number of input data, default 3. + pretrained (bool): If True, returns a model pre-trained on ImageNet, + default True. + """ return _darknet(53, input_channels, pretrained) diff --git a/models/mobilenetv1.py b/hapi/vision/models/mobilenetv1.py similarity index 82% rename from models/mobilenetv1.py rename to hapi/vision/models/mobilenetv1.py index c2e7959b1b9bf78e30ac80f874262234f66ff22e..31c0acbee2fdc107b0d776605c296c2c9296bcfd 100644 --- a/models/mobilenetv1.py +++ b/hapi/vision/models/mobilenetv1.py @@ -19,8 +19,8 @@ from paddle.fluid.initializer import MSRA from paddle.fluid.param_attr import ParamAttr from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear -from model import Model -from .download import get_weights_path +from hapi.model import Model +from hapi.download import get_weights_path __all__ = ['MobileNetV1', 'mobilenet_v1'] @@ -111,13 +111,22 @@ class MobileNetV1(Model): Args: scale (float): scale of channels in each layer. Default: 1.0. - class_dim (int): output dim of last fc layer. Default: 1000. + num_classes (int): output dim of last fc layer. If num_classes <=0, last fc layer + will not be defined. Default: 1000. + with_pool (bool): use pool before the last fc layer or not. Default: True. + classifier_activation (str): activation for the last fc layer. Default: 'softmax'. """ - def __init__(self, scale=1.0, class_dim=1000): + def __init__(self, + scale=1.0, + num_classes=1000, + with_pool=True, + classifier_activation='softmax'): super(MobileNetV1, self).__init__() self.scale = scale self.dwsl = [] + self.num_classes = num_classes + self.with_pool = with_pool self.conv1 = ConvBNLayer( num_channels=3, @@ -227,23 +236,29 @@ class MobileNetV1(Model): name="conv6") self.dwsl.append(dws6) - self.pool2d_avg = Pool2D(pool_type='avg', global_pooling=True) + if with_pool: + self.pool2d_avg = Pool2D(pool_type='avg', global_pooling=True) - self.out = Linear( - int(1024 * scale), - class_dim, - act='softmax', - param_attr=ParamAttr( - initializer=MSRA(), name=self.full_name() + "fc7_weights"), - bias_attr=ParamAttr(name="fc7_offset")) + if num_classes > -1: + self.out = Linear( + int(1024 * scale), + num_classes, + act=classifier_activation, + param_attr=ParamAttr( + initializer=MSRA(), name=self.full_name() + "fc7_weights"), + bias_attr=ParamAttr(name="fc7_offset")) def forward(self, inputs): y = self.conv1(inputs) for dws in self.dwsl: y = dws(y) - y = self.pool2d_avg(y) - y = fluid.layers.reshape(y, shape=[-1, 1024]) - y = self.out(y) + + if self.with_pool: + y = self.pool2d_avg(y) + + if self.num_classes > 0: + y = fluid.layers.reshape(y, shape=[-1, 1024]) + y = self.out(y) return y @@ -261,6 +276,13 @@ def _mobilenet(arch, pretrained=False, **kwargs): return model -def mobilenet_v1(pretrained=False, scale=1.0): - model = _mobilenet('mobilenetv1_' + str(scale), pretrained, scale=scale) +def mobilenet_v1(pretrained=False, scale=1.0, **kwargs): + """MobileNetV1 + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet. Default: False. + scale: (float): scale of channels in each layer. Default: 1.0. + """ + model = _mobilenet( + 'mobilenetv1_' + str(scale), pretrained, scale=scale, **kwargs) return model diff --git a/models/mobilenetv2.py b/hapi/vision/models/mobilenetv2.py similarity index 82% rename from models/mobilenetv2.py rename to hapi/vision/models/mobilenetv2.py index 0079ee79d932a76dc75548b7641526bc80019011..d624625bcda1b763a0b3e511b6146776245e2fd5 100644 --- a/models/mobilenetv2.py +++ b/hapi/vision/models/mobilenetv2.py @@ -18,8 +18,8 @@ import paddle.fluid as fluid from paddle.fluid.param_attr import ParamAttr from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear -from model import Model -from .download import get_weights_path +from hapi.model import Model +from hapi.download import get_weights_path __all__ = ['MobileNetV2', 'mobilenet_v2'] @@ -156,13 +156,21 @@ class MobileNetV2(Model): Args: scale (float): scale of channels in each layer. Default: 1.0. - class_dim (int): output dim of last fc layer. Default: 1000. + num_classes (int): output dim of last fc layer. If num_classes <=0, last fc layer + will not be defined. Default: 1000. + with_pool (bool): use pool before the last fc layer or not. Default: True. + classifier_activation (str): activation for the last fc layer. Default: 'softmax'. """ - def __init__(self, scale=1.0, class_dim=1000): + def __init__(self, + scale=1.0, + num_classes=1000, + with_pool=True, + classifier_activation='softmax'): super(MobileNetV2, self).__init__() self.scale = scale - self.class_dim = class_dim + self.num_classes = num_classes + self.with_pool = with_pool bottleneck_params_list = [ (1, 16, 1, 1), @@ -174,7 +182,6 @@ class MobileNetV2(Model): (6, 320, 1, 1), ] - #1. conv1 self._conv1 = ConvBNLayer( num_channels=3, num_filters=int(32 * scale), @@ -182,7 +189,6 @@ class MobileNetV2(Model): stride=2, padding=1) - #2. bottleneck sequences self._invl = [] i = 1 in_c = int(32 * scale) @@ -196,7 +202,6 @@ class MobileNetV2(Model): self._invl.append(tmp) in_c = int(c * scale) - #3. last_conv self._out_c = int(1280 * scale) if scale > 1.0 else 1280 self._conv9 = ConvBNLayer( num_channels=in_c, @@ -205,26 +210,29 @@ class MobileNetV2(Model): stride=1, padding=0) - #4. pool - self._pool2d_avg = Pool2D(pool_type='avg', global_pooling=True) + if with_pool: + self._pool2d_avg = Pool2D(pool_type='avg', global_pooling=True) - #5. fc - tmp_param = ParamAttr(name=self.full_name() + "fc10_weights") - self._fc = Linear( - self._out_c, - class_dim, - act='softmax', - param_attr=tmp_param, - bias_attr=ParamAttr(name="fc10_offset")) + if num_classes > 0: + tmp_param = ParamAttr(name=self.full_name() + "fc10_weights") + self._fc = Linear( + self._out_c, + num_classes, + act=classifier_activation, + param_attr=tmp_param, + bias_attr=ParamAttr(name="fc10_offset")) def forward(self, inputs): y = self._conv1(inputs, if_act=True) for inv in self._invl: y = inv(y) y = self._conv9(y, if_act=True) - y = self._pool2d_avg(y) - y = fluid.layers.reshape(y, shape=[-1, self._out_c]) - y = self._fc(y) + + if self.with_pool: + y = self._pool2d_avg(y) + if self.num_classes > 0: + y = fluid.layers.reshape(y, shape=[-1, self._out_c]) + y = self._fc(y) return y @@ -242,11 +250,13 @@ def _mobilenet(arch, pretrained=False, **kwargs): return model -def mobilenet_v2(pretrained=False, scale=1.0): +def mobilenet_v2(pretrained=False, scale=1.0, **kwargs): """MobileNetV2 Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet + pretrained (bool): If True, returns a model pre-trained on ImageNet. Default: False. + scale: (float): scale of channels in each layer. Default: 1.0. """ - model = _mobilenet('mobilenetv2_' + str(scale), pretrained, scale=scale) + model = _mobilenet( + 'mobilenetv2_' + str(scale), pretrained, scale=scale, **kwargs) return model diff --git a/models/resnet.py b/hapi/vision/models/resnet.py similarity index 74% rename from models/resnet.py rename to hapi/vision/models/resnet.py index f2cf4b603e6890510e9fafb65bcb96ab52cd2771..ac0944ee651224b106db71d0c87e9e5c29fd14d9 100644 --- a/models/resnet.py +++ b/hapi/vision/models/resnet.py @@ -22,16 +22,26 @@ from paddle.fluid.layer_helper import LayerHelper from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear from paddle.fluid.dygraph.container import Sequential -from model import Model -from .download import get_weights_path +from hapi.model import Model +from hapi.download import get_weights_path __all__ = [ 'ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', 'resnet152' ] model_urls = { + 'resnet18': ('https://paddle-hapi.bj.bcebos.com/models/resnet18.pdparams', + '0ba53eea9bc970962d0ef96f7b94057e'), + 'resnet34': ('https://paddle-hapi.bj.bcebos.com/models/resnet34.pdparams', + '46bc9f7c3dd2e55b7866285bee91eff3'), 'resnet50': ('https://paddle-hapi.bj.bcebos.com/models/resnet50.pdparams', - '0884c9087266496c41c60d14a96f8530') + '0884c9087266496c41c60d14a96f8530'), + 'resnet101': + ('https://paddle-hapi.bj.bcebos.com/models/resnet101.pdparams', + 'fb07a451df331e4b0bb861ed97c3a9b9'), + 'resnet152': + ('https://paddle-hapi.bj.bcebos.com/models/resnet152.pdparams', + 'f9c700f26d3644bb76ad2226ed5f5713'), } @@ -163,12 +173,23 @@ class ResNet(Model): Args: Block (BasicBlock|BottleneckBlock): block module of model. depth (int): layers of resnet, default: 50. - num_classes (int): output dim of last fc layer, default: 1000. + num_classes (int): output dim of last fc layer. If num_classes <=0, last fc layer + will not be defined. Default: 1000. + with_pool (bool): use pool before the last fc layer or not. Default: True. + classifier_activation (str): activation for the last fc layer. Default: 'softmax'. """ - def __init__(self, Block, depth=50, num_classes=1000): + def __init__(self, + Block, + depth=50, + num_classes=1000, + with_pool=True, + classifier_activation='softmax'): super(ResNet, self).__init__() + self.num_classes = num_classes + self.with_pool = with_pool + layer_config = { 18: [2, 2, 2, 2], 34: [3, 4, 6, 3], @@ -212,31 +233,37 @@ class ResNet(Model): Sequential(*blocks)) self.layers.append(layer) - self.global_pool = Pool2D( - pool_size=7, pool_type='avg', global_pooling=True) + if with_pool: + self.global_pool = Pool2D( + pool_size=7, pool_type='avg', global_pooling=True) - stdv = 1.0 / math.sqrt(out_channels[-1] * Block.expansion * 1.0) - self.fc_input_dim = out_channels[-1] * Block.expansion * 1 * 1 - self.fc = Linear( - self.fc_input_dim, - num_classes, - act='softmax', - param_attr=fluid.param_attr.ParamAttr( - initializer=fluid.initializer.Uniform(-stdv, stdv))) + if num_classes > 0: + stdv = 1.0 / math.sqrt(out_channels[-1] * Block.expansion * 1.0) + self.fc_input_dim = out_channels[-1] * Block.expansion * 1 * 1 + self.fc = Linear( + self.fc_input_dim, + num_classes, + act=classifier_activation, + param_attr=fluid.param_attr.ParamAttr( + initializer=fluid.initializer.Uniform(-stdv, stdv))) def forward(self, inputs): x = self.conv(inputs) x = self.pool(x) for layer in self.layers: x = layer(x) - x = self.global_pool(x) - x = fluid.layers.reshape(x, shape=[-1, self.fc_input_dim]) - x = self.fc(x) + + if self.with_pool: + x = self.global_pool(x) + + if self.num_classes > -1: + x = fluid.layers.reshape(x, shape=[-1, self.fc_input_dim]) + x = self.fc(x) return x -def _resnet(arch, Block, depth, pretrained): - model = ResNet(Block, depth) +def _resnet(arch, Block, depth, pretrained, **kwargs): + model = ResNet(Block, depth, **kwargs) if pretrained: assert arch in model_urls, "{} model do not have a pretrained model now, you should set pretrained=False".format( arch) @@ -248,46 +275,46 @@ def _resnet(arch, Block, depth, pretrained): return model -def resnet18(pretrained=False): +def resnet18(pretrained=False, **kwargs): """ResNet 18-layer model Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ - return _resnet('resnet18', BasicBlock, 18, pretrained) + return _resnet('resnet18', BasicBlock, 18, pretrained, **kwargs) -def resnet34(pretrained=False): +def resnet34(pretrained=False, **kwargs): """ResNet 34-layer model Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ - return _resnet('resnet34', BasicBlock, 34, pretrained) + return _resnet('resnet34', BasicBlock, 34, pretrained, **kwargs) -def resnet50(pretrained=False): +def resnet50(pretrained=False, **kwargs): """ResNet 50-layer model Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ - return _resnet('resnet50', BottleneckBlock, 50, pretrained) + return _resnet('resnet50', BottleneckBlock, 50, pretrained, **kwargs) -def resnet101(pretrained=False): +def resnet101(pretrained=False, **kwargs): """ResNet 101-layer model Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ - return _resnet('resnet101', BottleneckBlock, 101, pretrained) + return _resnet('resnet101', BottleneckBlock, 101, pretrained, **kwargs) -def resnet152(pretrained=False): +def resnet152(pretrained=False, **kwargs): """ResNet 152-layer model Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ - return _resnet('resnet152', BottleneckBlock, 152, pretrained) + return _resnet('resnet152', BottleneckBlock, 152, pretrained, **kwargs) diff --git a/models/vgg.py b/hapi/vision/models/vgg.py similarity index 61% rename from models/vgg.py rename to hapi/vision/models/vgg.py index b8ca21f0c370c1963b1c7c61aca101abe63d179b..41cf34eddf7d4d379f9ea3a6bc5490f9763919dc 100644 --- a/models/vgg.py +++ b/hapi/vision/models/vgg.py @@ -17,18 +17,14 @@ import paddle.fluid as fluid from paddle.fluid.dygraph.nn import Conv2D, Pool2D, BatchNorm, Linear from paddle.fluid.dygraph.container import Sequential -from model import Model -from .download import get_weights_path +from hapi.model import Model +from hapi.download import get_weights_path __all__ = [ 'VGG', 'vgg11', - 'vgg11_bn', 'vgg13', - 'vgg13_bn', 'vgg16', - 'vgg16_bn', - 'vgg19_bn', 'vgg19', ] @@ -39,11 +35,11 @@ model_urls = { class Classifier(fluid.dygraph.Layer): - def __init__(self, num_classes): + def __init__(self, num_classes, classifier_activation='softmax'): super(Classifier, self).__init__() self.linear1 = Linear(512 * 7 * 7, 4096) self.linear2 = Linear(4096, 4096) - self.linear3 = Linear(4096, num_classes, act='softmax') + self.linear3 = Linear(4096, num_classes, act=classifier_activation) def forward(self, x): x = self.linear1(x) @@ -62,20 +58,30 @@ class VGG(Model): Args: features (fluid.dygraph.Layer): vgg features create by function make_layers. - num_classes (int): output dim of last fc layer. Default: 1000. + num_classes (int): output dim of last fc layer. If num_classes <=0, last fc layer + will not be defined. Default: 1000. + classifier_activation (str): activation for the last fc layer. Default: 'softmax'. """ - def __init__(self, features, num_classes=1000): + def __init__(self, + features, + num_classes=1000, + classifier_activation='softmax'): super(VGG, self).__init__() self.features = features - classifier = Classifier(num_classes) - self.classifier = self.add_sublayer("classifier", - Sequential(classifier)) + self.num_classes = num_classes + + if num_classes > 0: + classifier = Classifier(num_classes, classifier_activation) + self.classifier = self.add_sublayer("classifier", + Sequential(classifier)) def forward(self, x): x = self.features(x) - x = fluid.layers.flatten(x, 1) - x = self.classifier(x) + + if self.num_classes > 0: + x = fluid.layers.flatten(x, 1) + x = self.classifier(x) return x @@ -114,7 +120,10 @@ cfgs = { def _vgg(arch, cfg, batch_norm, pretrained, **kwargs): - model = VGG(make_layers(cfgs[cfg], batch_norm=batch_norm), **kwargs) + model = VGG(make_layers( + cfgs[cfg], batch_norm=batch_norm), + num_classes=1000, + **kwargs) if pretrained: assert arch in model_urls, "{} model do not have a pretrained model now, you should set pretrained=False".format( @@ -128,73 +137,53 @@ def _vgg(arch, cfg, batch_norm, pretrained, **kwargs): return model -def vgg11(pretrained=False, **kwargs): +def vgg11(pretrained=False, batch_norm=False, **kwargs): """VGG 11-layer model Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet + pretrained (bool): If True, returns a model pre-trained on ImageNet. Default: False. + batch_norm (bool): If True, returns a model with batch_norm layer. Default: False. """ - return _vgg('vgg11', 'A', False, pretrained, **kwargs) - + model_name = 'vgg11' + if batch_norm: + model_name += ('_bn') + return _vgg(model_name, 'A', batch_norm, pretrained, **kwargs) -def vgg11_bn(pretrained=False, **kwargs): - """VGG 11-layer model with batch normalization - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - """ - return _vgg('vgg11_bn', 'A', True, pretrained, **kwargs) - -def vgg13(pretrained=False, **kwargs): +def vgg13(pretrained=False, batch_norm=False, **kwargs): """VGG 13-layer model Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - """ - return _vgg('vgg13', 'B', False, pretrained, **kwargs) - - -def vgg13_bn(pretrained=False, **kwargs): - """VGG 13-layer model with batch normalization - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet + pretrained (bool): If True, returns a model pre-trained on ImageNet. Default: False. + batch_norm (bool): If True, returns a model with batch_norm layer. Default: False. """ - return _vgg('vgg13_bn', 'B', True, pretrained, **kwargs) + model_name = 'vgg13' + if batch_norm: + model_name += ('_bn') + return _vgg(model_name, 'B', batch_norm, pretrained, **kwargs) -def vgg16(pretrained=False, **kwargs): +def vgg16(pretrained=False, batch_norm=False, **kwargs): """VGG 16-layer model Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - """ - return _vgg('vgg16', 'D', False, pretrained, **kwargs) - - -def vgg16_bn(pretrained=False, **kwargs): - """VGG 16-layer with batch normalization - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet + pretrained (bool): If True, returns a model pre-trained on ImageNet. Default: False. + batch_norm (bool): If True, returns a model with batch_norm layer. Default: False. """ - return _vgg('vgg16_bn', 'D', True, pretrained, **kwargs) + model_name = 'vgg16' + if batch_norm: + model_name += ('_bn') + return _vgg(model_name, 'D', batch_norm, pretrained, **kwargs) -def vgg19(pretrained=False, **kwargs): +def vgg19(pretrained=False, batch_norm=False, **kwargs): """VGG 19-layer model Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet - """ - return _vgg('vgg19', 'E', False, pretrained, **kwargs) - - -def vgg19_bn(pretrained=False, **kwargs): - """VGG 19-layer model with batch normalization - - Args: - pretrained (bool): If True, returns a model pre-trained on ImageNet + pretrained (bool): If True, returns a model pre-trained on ImageNet. Default: False. + batch_norm (bool): If True, returns a model with batch_norm layer. Default: False. """ - return _vgg('vgg19_bn', 'E', True, pretrained, **kwargs) + model_name = 'vgg19' + if batch_norm: + model_name += ('_bn') + return _vgg(model_name, 'E', batch_norm, pretrained, **kwargs) diff --git a/hapi/vision/transforms/__init__.py b/hapi/vision/transforms/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f7c5b63b19ed081ee6887850c1aa3ef918715222 --- /dev/null +++ b/hapi/vision/transforms/__init__.py @@ -0,0 +1,22 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import transforms +from . import functional + +from .transforms import * +from .functional import * + +__all__ = transforms.__all__ \ + + functional.__all__ diff --git a/hapi/vision/transforms/functional.py b/hapi/vision/transforms/functional.py new file mode 100644 index 0000000000000000000000000000000000000000..a4ca466c12ca5bf1e4db6fa4e47f58f95f73aea9 --- /dev/null +++ b/hapi/vision/transforms/functional.py @@ -0,0 +1,72 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import collections +import random + +import cv2 +import numpy as np + +if sys.version_info < (3, 3): + Sequence = collections.Sequence + Iterable = collections.Iterable +else: + Sequence = collections.abc.Sequence + Iterable = collections.abc.Iterable + +__all__ = ['flip', 'resize'] + + +def flip(image, code): + """ + Accordding to the code (the type of flip), flip the input image + + Args: + image: Input image, with (H, W, C) shape + code: code that indicates the type of flip. + -1 : Flip horizontally and vertically + 0 : Flip vertically + 1 : Flip horizontally + """ + return cv2.flip(image, flipCode=code) + + +def resize(img, size, interpolation=cv2.INTER_LINEAR): + """ + resize the input data to given size + + Args: + input: Input data, could be image or masks, with (H, W, C) shape + size: Target size of input data, with (height, width) shape. + interpolation: Interpolation method. + """ + + if isinstance(interpolation, Sequence): + interpolation = random.choice(interpolation) + + if isinstance(size, int): + h, w = img.shape[:2] + if (w <= h and w == size) or (h <= w and h == size): + return img + if w < h: + ow = size + oh = int(size * h / w) + return cv2.resize(img, (ow, oh), interpolation=interpolation) + else: + oh = size + ow = int(size * w / h) + return cv2.resize(img, (ow, oh), interpolation=interpolation) + else: + return cv2.resize(img, size[::-1], interpolation=interpolation) diff --git a/hapi/vision/transforms/transforms.py b/hapi/vision/transforms/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..3d974171ce0d6f5a80f2af6a272a4250d771fb4d --- /dev/null +++ b/hapi/vision/transforms/transforms.py @@ -0,0 +1,503 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import division + +import math +import sys +import random +import cv2 + +import numpy as np +import numbers +import types +import collections +import warnings +import traceback + +from . import functional as F + +if sys.version_info < (3, 3): + Iterable = collections.Iterable +else: + Iterable = collections.abc.Iterable + +__all__ = [ + "Compose", + "BatchCompose", + "Resize", + "RandomResizedCrop", + "CenterCropResize", + "CenterCrop", + "RandomHorizontalFlip", + "RandomVerticalFlip", + "Permute", + "Normalize", + "GaussianNoise", + "BrightnessTransform", + "SaturationTransform", + "ContrastTransform", + "HueTransform", + "ColorJitter", +] + + +class Compose(object): + """Composes several transforms together. + + Args: + transforms (list of ``Transform`` objects): list of transforms to compose. + + """ + + def __init__(self, transforms): + self.transforms = transforms + + def __call__(self, *data): + for f in self.transforms: + try: + data = f(*data) + except Exception as e: + stack_info = traceback.format_exc() + print("fail to perform transform [{}] with error: " + "{} and stack:\n{}".format(f, e, str(stack_info))) + raise e + return data + + def __repr__(self): + format_string = self.__class__.__name__ + '(' + for t in self.transforms: + format_string += '\n' + format_string += ' {0}'.format(t) + format_string += '\n)' + return format_string + + +class BatchCompose(object): + """Composes several batch transforms together + + Args: + transforms (list of ``Transform`` objects): list of transforms to compose. + these transforms perform on batch data. + + """ + + def __init__(self, transforms=[]): + self.transforms = transforms + + def __call__(self, data): + for f in self.transforms: + try: + data = f(data) + except Exception as e: + stack_info = traceback.format_exc() + print("fail to perform batch transform [{}] with error: " + "{} and stack:\n{}".format(f, e, str(stack_info))) + raise e + + # sample list to batch data + batch = list(zip(*data)) + + return batch + + +class Resize(object): + """Resize the input Image to the given size. + + Args: + size (int|list|tuple): Desired output size. If size is a sequence like + (h, w), output size will be matched to this. If size is an int, + smaller edge of the image will be matched to this number. + i.e, if height > width, then image will be rescaled to + (size * height / width, size) + interpolation (int): interpolation mode of resize. Default: cv2.INTER_LINEAR. + """ + + def __init__(self, size, interpolation=cv2.INTER_LINEAR): + assert isinstance(size, int) or (isinstance(size, Iterable) and + len(size) == 2) + self.size = size + self.interpolation = interpolation + + def __call__(self, img, lbl): + return F.resize(img, self.size, self.interpolation), lbl + + +class RandomResizedCrop(object): + """Crop the input data to random size and aspect ratio. + A crop of random size (default: of 0.08 to 1.0) of the original size and a random + aspect ratio (default: of 3/4 to 1.33) of the original aspect ratio is made. + After applying crop transfrom, the input data will be resized to given size. + + Args: + output_size (int|list|tuple): Target size of output image, with (height, width) shape. + scale (list|tuple): Range of size of the origin size cropped. Default: (0.08, 1.0) + ratio (list|tuple): Range of aspect ratio of the origin aspect ratio cropped. Default: (0.75, 1.33) + """ + + def __init__(self, + output_size, + scale=(0.08, 1.0), + ratio=(3. / 4, 4. / 3), + interpolation=cv2.INTER_LINEAR): + if isinstance(output_size, int): + self.output_size = (output_size, output_size) + else: + self.output_size = output_size + assert (scale[0] <= scale[1]), "scale should be of kind (min, max)" + assert (ratio[0] <= ratio[1]), "ratio should be of kind (min, max)" + self.scale = scale + self.ratio = ratio + self.interpolation = interpolation + + def _get_params(self, image, attempts=10): + height, width, _ = image.shape + area = height * width + + for _ in range(attempts): + target_area = np.random.uniform(*self.scale) * area + log_ratio = tuple(math.log(x) for x in self.ratio) + aspect_ratio = math.exp(np.random.uniform(*log_ratio)) + + w = int(round(math.sqrt(target_area * aspect_ratio))) + h = int(round(math.sqrt(target_area / aspect_ratio))) + + if 0 < w <= width and 0 < h <= height: + x = np.random.randint(0, width - w + 1) + y = np.random.randint(0, height - h + 1) + return x, y, w, h + + # Fallback to central crop + in_ratio = float(width) / float(height) + if in_ratio < min(self.ratio): + w = width + h = int(round(w / min(self.ratio))) + elif in_ratio > max(self.ratio): + h = height + w = int(round(h * max(self.ratio))) + else: # whole image + w = width + h = height + x = (width - w) // 2 + y = (height - h) // 2 + return x, y, w, h + + def __call__(self, img, lbl): + x, y, w, h = self._get_params(img) + cropped_img = img[y:y + h, x:x + w] + return F.resize(cropped_img, self.output_size, self.interpolation), lbl + + +class CenterCropResize(object): + """Crops to center of image with padding then scales size. + + Args: + size (int|list|tuple): Target size of output image, with (height, width) shape. + crop_padding (int): center crop with the padding. Default: 32. + interpolation (int): interpolation mode of resize. Default: cv2.INTER_LINEAR. + """ + + def __init__(self, size, crop_padding=32, interpolation=cv2.INTER_LINEAR): + if isinstance(size, int): + self.size = (size, size) + else: + self.size = size + self.crop_padding = crop_padding + self.interpolation = interpolation + + def _get_params(self, img): + h, w = img.shape[:2] + size = min(self.size) + c = int(size / (size + self.crop_padding) * min((h, w))) + x = (h + 1 - c) // 2 + y = (w + 1 - c) // 2 + return c, x, y + + def __call__(self, img, lbl): + c, x, y = self._get_params(img) + cropped_img = img[x:x + c, y:y + c, :] + return F.resize(cropped_img, self.size, self.interpolation), lbl + + +class CenterCrop(object): + """Crops the given the input data at the center. + + Args: + output_size: Target size of output image, with (height, width) shape. + """ + + def __init__(self, output_size): + if isinstance(output_size, int): + self.output_size = (output_size, output_size) + else: + self.output_size = output_size + + def _get_params(self, img): + th, tw = self.output_size + h, w, _ = img.shape + assert th <= h and tw <= w, "output size is bigger than image size" + x = int(round((w - tw) / 2.0)) + y = int(round((h - th) / 2.0)) + return x, y + + def __call__(self, img, lbl): + x, y = self._get_params(img) + th, tw = self.output_size + return img[y:y + th, x:x + tw], lbl + + +class RandomHorizontalFlip(object): + """Horizontally flip the input data randomly with a given probability. + + Args: + prob (float): probability of the input data being flipped. Default: 0.5 + """ + + def __init__(self, prob=0.5): + self.prob = prob + + def __call__(self, img, lbl): + if np.random.random() < self.prob: + return F.flip(img, code=1), lbl + return img, lbl + + +class RandomVerticalFlip(object): + """Vertically flip the input data randomly with a given probability. + + Args: + prob (float): probability of the input data being flipped. Default: 0.5 + """ + + def __init__(self, prob=0.5): + self.prob = prob + + def __call__(self, img, lbl): + if np.random.random() < self.prob: + return F.flip(img, code=0), lbl + return img, lbl + + +class Normalize(object): + """Normalize the input data with mean and standard deviation. + Given mean: ``(M1,...,Mn)`` and std: ``(S1,..,Sn)`` for ``n`` channels, + this transform will normalize each channel of the input data. + ``output[channel] = (input[channel] - mean[channel]) / std[channel]`` + + Args: + mean (int|float|list): Sequence of means for each channel. + std (int|float|list): Sequence of standard deviations for each channel. + + """ + + def __init__(self, mean=0.0, std=1.0): + if isinstance(mean, numbers.Number): + mean = [mean, mean, mean] + + if isinstance(std, numbers.Number): + mean = [std, std, std] + + self.mean = np.array(mean, dtype=np.float32).reshape(len(mean), 1, 1) + self.std = np.array(std, dtype=np.float32).reshape(len(std), 1, 1) + + def __call__(self, img, lbl): + return (img - self.mean) / self.std, lbl + + +class Permute(object): + """Change input data to a target mode. + For example, most transforms use HWC mode image, + while the Neural Network might use CHW mode input tensor. + Input image should be HWC mode and an instance of numpy.ndarray. + + Args: + mode: Output mode of input. Default: "CHW". + to_rgb: convert 'bgr' image to 'rgb'. Default: True. + """ + + def __init__(self, mode="CHW", to_rgb=True): + assert mode in [ + "CHW" + ], "Only support 'CHW' mode, but received mode: {}".format(mode) + self.mode = mode + self.to_rgb = to_rgb + + def __call__(self, img, lbl): + if self.to_rgb: + img = img[..., ::-1] + if self.mode == "CHW": + return img.transpose((2, 0, 1)), lbl + return img, lbl + + +class GaussianNoise(object): + """Add random gaussian noise to the input data. + Gaussian noise is generated with given mean and std. + + Args: + mean: Gaussian mean used to generate noise. + std: Gaussian standard deviation used to generate noise. + """ + + def __init__(self, mean=0.0, std=1.0): + self.mean = np.array(mean, dtype=np.float32) + self.std = np.array(std, dtype=np.float32) + + def __call__(self, img, lbl): + dtype = img.dtype + noise = np.random.normal(self.mean, self.std, img.shape) * 255 + img = img + noise.astype(np.float32) + return np.clip(img, 0, 255).astype(dtype), lbl + + +class BrightnessTransform(object): + """Adjust brightness of the image. + + Args: + value: How much to adjust the brightness. Can be any + non negative number. 0 gives the original image + """ + + def __init__(self, value): + if value < 0: + raise ValueError("brightness value should be non-negative") + self.value = value + + def __call__(self, img, lbl): + if self.value == 0: + return img, lbl + + dtype = img.dtype + img = img.astype(np.float32) + alpha = np.random.uniform(max(0, 1 - self.value), 1 + self.value) + img = img * alpha + return img.clip(0, 255).astype(dtype), lbl + + +class ContrastTransform(object): + """Adjust contrast of the image. + + Args: + value: How much to adjust the contrast. Can be any + non negative number. 0 gives the original image + """ + + def __init__(self, value): + if value < 0: + raise ValueError("contrast value should be non-negative") + self.value = value + + def __call__(self, img, lbl): + if self.value == 0: + return img, lbl + + dtype = img.dtype + img = img.astype(np.float32) + alpha = np.random.uniform(max(0, 1 - self.value), 1 + self.value) + img = img * alpha + cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).mean() * ( + 1 - alpha) + return img.clip(0, 255).astype(dtype), lbl + + +class SaturationTransform(object): + """Adjust saturation of the image. + + Args: + value: How much to adjust the saturation. Can be any + non negative number. 0 gives the original image + """ + + def __init__(self, value): + if value < 0: + raise ValueError("saturation value should be non-negative") + self.value = value + + def __call__(self, img, lbl): + if self.value == 0: + return img, lbl + + dtype = img.dtype + img = img.astype(np.float32) + alpha = np.random.uniform(max(0, 1 - self.value), 1 + self.value) + gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + gray_img = gray_img[..., np.newaxis] + img = img * alpha + gray_img * (1 - alpha) + return img.clip(0, 255).astype(dtype), lbl + + +class HueTransform(object): + """Adjust hue of the image. + + Args: + value: How much to adjust the hue. Can be any number + between 0 and 0.5, 0 gives the original image + """ + + def __init__(self, value): + if value < 0 or value > 0.5: + raise ValueError("hue value should be in [0.0, 0.5]") + self.value = value + + def __call__(self, img, lbl): + if self.value == 0: + return img, lbl + + dtype = img.dtype + img = img.astype(np.uint8) + hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV_FULL) + h, s, v = cv2.split(hsv_img) + + alpha = np.random.uniform(-self.value, self.value) + h = h.astype(np.uint8) + # uint8 addition take cares of rotation across boundaries + with np.errstate(over="ignore"): + h += np.uint8(alpha * 255) + hsv_img = cv2.merge([h, s, v]) + return cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR_FULL).astype(dtype), lbl + + +class ColorJitter(object): + """Randomly change the brightness, contrast, saturation and hue of an image. + + Args: + brightness: How much to jitter brightness. + Chosen uniformly from [max(0, 1 - brightness), 1 + brightness] + or the given [min, max]. Should be non negative numbers. + contrast: How much to jitter contrast. + Chosen uniformly from [max(0, 1 - contrast), 1 + contrast] + or the given [min, max]. Should be non negative numbers. + saturation: How much to jitter saturation. + Chosen uniformly from [max(0, 1 - saturation), 1 + saturation] + or the given [min, max]. Should be non negative numbers. + hue: How much to jitter hue. + Chosen uniformly from [-hue, hue] or the given [min, max]. + Should have 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5. + """ + + def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): + transforms = [] + if brightness != 0: + transforms.append(BrightnessTransform(brightness)) + if contrast != 0: + transforms.append(ContrastTransform(contrast)) + if saturation != 0: + transforms.append(SaturationTransform(saturation)) + if hue != 0: + transforms.append(HueTransform(hue)) + + random.shuffle(transforms) + self.transforms = Compose(transforms) + + def __call__(self, img, lbl): + return self.transforms(img, lbl) diff --git a/image_classification/imagenet_dataset.py b/image_classification/imagenet_dataset.py deleted file mode 100644 index 948ac5b8bb4c360bc2ea52d819c2958da52ef68f..0000000000000000000000000000000000000000 --- a/image_classification/imagenet_dataset.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import cv2 -import math -import random -import numpy as np - -from datasets.folder import DatasetFolder - - -def center_crop_resize(img): - h, w = img.shape[:2] - c = int(224 / 256 * min((h, w))) - i = (h + 1 - c) // 2 - j = (w + 1 - c) // 2 - img = img[i:i + c, j:j + c, :] - return cv2.resize(img, (224, 224), 0, 0, cv2.INTER_LINEAR) - - -def random_crop_resize(img): - height, width = img.shape[:2] - area = height * width - - for attempt in range(10): - target_area = random.uniform(0.08, 1.) * area - log_ratio = (math.log(3 / 4), math.log(4 / 3)) - aspect_ratio = math.exp(random.uniform(*log_ratio)) - - w = int(round(math.sqrt(target_area * aspect_ratio))) - h = int(round(math.sqrt(target_area / aspect_ratio))) - - if w <= width and h <= height: - i = random.randint(0, height - h) - j = random.randint(0, width - w) - img = img[i:i + h, j:j + w, :] - return cv2.resize(img, (224, 224), 0, 0, cv2.INTER_LINEAR) - - return center_crop_resize(img) - - -def random_flip(img): - if np.random.randint(0, 2) == 1: - img = img[:, ::-1, :] - return img - - -def normalize_permute(img): - # transpose and convert to RGB from BGR - img = img.astype(np.float32).transpose((2, 0, 1))[::-1, ...] - mean = np.array([123.675, 116.28, 103.53], dtype=np.float32) - std = np.array([58.395, 57.120, 57.375], dtype=np.float32) - invstd = 1. / std - for v, m, s in zip(img, mean, invstd): - v.__isub__(m).__imul__(s) - return img - - -def compose(functions): - def process(sample): - img, label = sample - for fn in functions: - img = fn(img) - return img, label - - return process - - -class ImageNetDataset(DatasetFolder): - def __init__(self, path, mode='train'): - super(ImageNetDataset, self).__init__(path) - self.mode = mode - if self.mode == 'train': - self.transform = compose([ - cv2.imread, random_crop_resize, random_flip, normalize_permute - ]) - else: - self.transform = compose( - [cv2.imread, center_crop_resize, normalize_permute]) - - def __getitem__(self, idx): - img, label = self.samples[idx] - return self.transform((img, [label])) - - def __len__(self): - return len(self.samples) diff --git a/lac.py b/lac.py deleted file mode 100644 index cdd380686256b2039f6aa7f2289639559969a6a8..0000000000000000000000000000000000000000 --- a/lac.py +++ /dev/null @@ -1,728 +0,0 @@ -# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -lexical analysis network structure -""" - -from __future__ import division -from __future__ import print_function - -import io -import os -import sys -import math -import argparse -import numpy as np - -from metrics import Metric -from model import Model, Input, Loss, set_device - -import paddle.fluid as fluid -from paddle.fluid.optimizer import AdamOptimizer -from paddle.fluid.initializer import NormalInitializer -from paddle.fluid.dygraph.nn import Embedding, Linear, GRUUnit - - -class DynamicGRU(fluid.dygraph.Layer): - def __init__(self, - size, - h_0=None, - param_attr=None, - bias_attr=None, - is_reverse=False, - gate_activation='sigmoid', - candidate_activation='tanh', - origin_mode=False, - init_size=None): - super(DynamicGRU, self).__init__() - - self.gru_unit = GRUUnit( - size * 3, - param_attr=param_attr, - bias_attr=bias_attr, - activation=candidate_activation, - gate_activation=gate_activation, - origin_mode=origin_mode) - - self.size = size - self.h_0 = h_0 - self.is_reverse = is_reverse - - def forward(self, inputs): - hidden = self.h_0 - res = [] - - for i in range(inputs.shape[1]): - if self.is_reverse: - i = inputs.shape[1] - 1 - i - input_ = inputs[:, i:i + 1, :] - input_ = fluid.layers.reshape( - input_, [-1, input_.shape[2]], inplace=False) - hidden, reset, gate = self.gru_unit(input_, hidden) - hidden_ = fluid.layers.reshape( - hidden, [-1, 1, hidden.shape[1]], inplace=False) - res.append(hidden_) - if self.is_reverse: - res = res[::-1] - res = fluid.layers.concat(res, axis=1) - return res - - -class BiGRU(fluid.dygraph.Layer): - def __init__(self, input_dim, grnn_hidden_dim, init_bound, h_0=None): - super(BiGRU, self).__init__() - - self.pre_gru = Linear( - input_dim=input_dim, - output_dim=grnn_hidden_dim * 3, - param_attr=fluid.ParamAttr( - initializer=fluid.initializer.Uniform( - low=-init_bound, high=init_bound), - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=1e-4))) - - self.gru = DynamicGRU( - size=grnn_hidden_dim, - h_0=h_0, - param_attr=fluid.ParamAttr( - initializer=fluid.initializer.Uniform( - low=-init_bound, high=init_bound), - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=1e-4))) - - self.pre_gru_r = Linear( - input_dim=input_dim, - output_dim=grnn_hidden_dim * 3, - param_attr=fluid.ParamAttr( - initializer=fluid.initializer.Uniform( - low=-init_bound, high=init_bound), - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=1e-4))) - - self.gru_r = DynamicGRU( - size=grnn_hidden_dim, - is_reverse=True, - h_0=h_0, - param_attr=fluid.ParamAttr( - initializer=fluid.initializer.Uniform( - low=-init_bound, high=init_bound), - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=1e-4))) - - def forward(self, input_feature): - res_pre_gru = self.pre_gru(input_feature) - res_gru = self.gru(res_pre_gru) - res_pre_gru_r = self.pre_gru_r(input_feature) - res_gru_r = self.gru_r(res_pre_gru_r) - bi_merge = fluid.layers.concat(input=[res_gru, res_gru_r], axis=-1) - return bi_merge - - -class Linear_chain_crf(fluid.dygraph.Layer): - def __init__(self, param_attr, size=None, is_test=False, dtype='float32'): - super(Linear_chain_crf, self).__init__() - - self._param_attr = param_attr - self._dtype = dtype - self._size = size - self._is_test = is_test - self._transition = self.create_parameter( - attr=self._param_attr, - shape=[self._size + 2, self._size], - dtype=self._dtype) - - @property - def weight(self): - return self._transition - - @weight.setter - def weight(self, value): - self._transition = value - - def forward(self, input, label, length=None): - - alpha = self._helper.create_variable_for_type_inference( - dtype=self._dtype) - emission_exps = self._helper.create_variable_for_type_inference( - dtype=self._dtype) - transition_exps = self._helper.create_variable_for_type_inference( - dtype=self._dtype) - log_likelihood = self._helper.create_variable_for_type_inference( - dtype=self._dtype) - this_inputs = { - "Emission": [input], - "Transition": self._transition, - "Label": [label] - } - if length: - this_inputs['Length'] = [length] - self._helper.append_op( - type='linear_chain_crf', - inputs=this_inputs, - outputs={ - "Alpha": [alpha], - "EmissionExps": [emission_exps], - "TransitionExps": transition_exps, - "LogLikelihood": log_likelihood - }, - attrs={"is_test": self._is_test, }) - return log_likelihood - - -class Crf_decoding(fluid.dygraph.Layer): - def __init__(self, param_attr, size=None, is_test=False, dtype='float32'): - super(Crf_decoding, self).__init__() - - self._dtype = dtype - self._size = size - self._is_test = is_test - self._param_attr = param_attr - self._transition = self.create_parameter( - attr=self._param_attr, - shape=[self._size + 2, self._size], - dtype=self._dtype) - - @property - def weight(self): - return self._transition - - @weight.setter - def weight(self, value): - self._transition = value - - def forward(self, input, label=None, length=None): - - viterbi_path = self._helper.create_variable_for_type_inference( - dtype=self._dtype) - this_inputs = { - "Emission": [input], - "Transition": self._transition, - "Label": label - } - if length: - this_inputs['Length'] = [length] - self._helper.append_op( - type='crf_decoding', - inputs=this_inputs, - outputs={"ViterbiPath": [viterbi_path]}, - attrs={"is_test": self._is_test, }) - return viterbi_path - - -class Chunk_eval(fluid.dygraph.Layer): - def __init__(self, - num_chunk_types, - chunk_scheme, - excluded_chunk_types=None): - super(Chunk_eval, self).__init__() - self.num_chunk_types = num_chunk_types - self.chunk_scheme = chunk_scheme - self.excluded_chunk_types = excluded_chunk_types - - def forward(self, input, label, seq_length=None): - precision = self._helper.create_variable_for_type_inference( - dtype="float32") - recall = self._helper.create_variable_for_type_inference( - dtype="float32") - f1_score = self._helper.create_variable_for_type_inference( - dtype="float32") - num_infer_chunks = self._helper.create_variable_for_type_inference( - dtype="int64") - num_label_chunks = self._helper.create_variable_for_type_inference( - dtype="int64") - num_correct_chunks = self._helper.create_variable_for_type_inference( - dtype="int64") - - this_input = {"Inference": input, "Label": label[0]} - if seq_length: - this_input["SeqLength"] = seq_length[0] - self._helper.append_op( - type='chunk_eval', - inputs=this_input, - outputs={ - "Precision": [precision], - "Recall": [recall], - "F1-Score": [f1_score], - "NumInferChunks": [num_infer_chunks], - "NumLabelChunks": [num_label_chunks], - "NumCorrectChunks": [num_correct_chunks] - }, - attrs={ - "num_chunk_types": self.num_chunk_types, - "chunk_scheme": self.chunk_scheme, - "excluded_chunk_types": self.excluded_chunk_types or [] - }) - return (num_infer_chunks, num_label_chunks, num_correct_chunks) - - -class LAC(Model): - def __init__(self, args, vocab_size, num_labels, length=None): - super(LAC, self).__init__() - """ - define the lexical analysis network structure - word: stores the input of the model - for_infer: a boolean value, indicating if the model to be created is for training or predicting. - - return: - for infer: return the prediction - otherwise: return the prediction - """ - self.word_emb_dim = args.word_emb_dim - self.vocab_size = vocab_size - self.num_labels = num_labels - self.grnn_hidden_dim = args.grnn_hidden_dim - self.emb_lr = args.emb_learning_rate if 'emb_learning_rate' in dir( - args) else 1.0 - self.crf_lr = args.emb_learning_rate if 'crf_learning_rate' in dir( - args) else 1.0 - self.bigru_num = args.bigru_num - self.init_bound = 0.1 - - self.word_embedding = Embedding( - size=[self.vocab_size, self.word_emb_dim], - dtype='float32', - param_attr=fluid.ParamAttr( - learning_rate=self.emb_lr, - name="word_emb", - initializer=fluid.initializer.Uniform( - low=-self.init_bound, high=self.init_bound))) - - h_0 = fluid.layers.create_global_var( - shape=[args.batch_size, self.grnn_hidden_dim], - value=0.0, - dtype='float32', - persistable=True, - force_cpu=True, - name='h_0') - - self.bigru_units = [] - for i in range(self.bigru_num): - if i == 0: - self.bigru_units.append( - self.add_sublayer( - "bigru_units%d" % i, - BiGRU( - self.grnn_hidden_dim, - self.grnn_hidden_dim, - self.init_bound, - h_0=h_0))) - else: - self.bigru_units.append( - self.add_sublayer( - "bigru_units%d" % i, - BiGRU( - self.grnn_hidden_dim * 2, - self.grnn_hidden_dim, - self.init_bound, - h_0=h_0))) - - self.fc = Linear( - input_dim=self.grnn_hidden_dim * 2, - output_dim=self.num_labels, - param_attr=fluid.ParamAttr( - initializer=fluid.initializer.Uniform( - low=-self.init_bound, high=self.init_bound), - regularizer=fluid.regularizer.L2DecayRegularizer( - regularization_coeff=1e-4))) - - self.linear_chain_crf = Linear_chain_crf( - param_attr=fluid.ParamAttr( - name='linear_chain_crfw', learning_rate=self.crf_lr), - size=self.num_labels) - - self.crf_decoding = Crf_decoding( - param_attr=fluid.ParamAttr( - name='crfw', learning_rate=self.crf_lr), - size=self.num_labels) - - def forward(self, word, target, lengths): - """ - Configure the network - """ - word_embed = self.word_embedding(word) - input_feature = word_embed - - for i in range(self.bigru_num): - bigru_output = self.bigru_units[i](input_feature) - input_feature = bigru_output - - emission = self.fc(bigru_output) - - crf_cost = self.linear_chain_crf( - input=emission, label=target, length=lengths) - avg_cost = fluid.layers.mean(x=crf_cost) - self.crf_decoding.weight = self.linear_chain_crf.weight - crf_decode = self.crf_decoding(input=emission, length=lengths) - return crf_decode, avg_cost, lengths - - -class LacLoss(Loss): - def __init__(self): - super(LacLoss, self).__init__() - pass - - def forward(self, outputs, labels): - avg_cost = outputs[1] - return avg_cost - - -class ChunkEval(Metric): - def __init__(self, num_labels, name=None, *args, **kwargs): - super(ChunkEval, self).__init__(*args, **kwargs) - self._init_name(name) - self.chunk_eval = Chunk_eval( - int(math.ceil((num_labels - 1) / 2.0)), "IOB") - self.reset() - - def add_metric_op(self, pred, label, *args, **kwargs): - crf_decode = pred[0] - lengths = pred[2] - (num_infer_chunks, num_label_chunks, - num_correct_chunks) = self.chunk_eval( - input=crf_decode, label=label, seq_length=lengths) - return [num_infer_chunks, num_label_chunks, num_correct_chunks] - - def update(self, num_infer_chunks, num_label_chunks, num_correct_chunks, - *args, **kwargs): - self.infer_chunks_total += num_infer_chunks - self.label_chunks_total += num_label_chunks - self.correct_chunks_total += num_correct_chunks - precision = float( - num_correct_chunks) / num_infer_chunks if num_infer_chunks else 0 - recall = float( - num_correct_chunks) / num_label_chunks if num_label_chunks else 0 - f1_score = float(2 * precision * recall) / ( - precision + recall) if num_correct_chunks else 0 - return [precision, recall, f1_score] - - def reset(self): - self.infer_chunks_total = 0 - self.label_chunks_total = 0 - self.correct_chunks_total = 0 - - def accumulate(self): - precision = float( - self.correct_chunks_total - ) / self.infer_chunks_total if self.infer_chunks_total else 0 - recall = float( - self.correct_chunks_total - ) / self.label_chunks_total if self.label_chunks_total else 0 - f1_score = float(2 * precision * recall) / ( - precision + recall) if self.correct_chunks_total else 0 - res = [precision, recall, f1_score] - return res - - def _init_name(self, name): - name = name or 'chunk eval' - self._name = ['precision', 'recall', 'F1'] - - def name(self): - return self._name - - -class LacDataset(object): - """ - Load lexical analysis dataset - """ - - def __init__(self, args): - self.word_dict_path = args.word_dict_path - self.label_dict_path = args.label_dict_path - self.word_rep_dict_path = args.word_rep_dict_path - self._load_dict() - - def _load_dict(self): - self.word2id_dict = self.load_kv_dict( - self.word_dict_path, reverse=True, value_func=np.int64) - self.id2word_dict = self.load_kv_dict(self.word_dict_path) - self.label2id_dict = self.load_kv_dict( - self.label_dict_path, reverse=True, value_func=np.int64) - self.id2label_dict = self.load_kv_dict(self.label_dict_path) - if self.word_rep_dict_path is None: - self.word_replace_dict = dict() - else: - self.word_replace_dict = self.load_kv_dict(self.word_rep_dict_path) - - def load_kv_dict(self, - dict_path, - reverse=False, - delimiter="\t", - key_func=None, - value_func=None): - """ - Load key-value dict from file - """ - result_dict = {} - for line in io.open(dict_path, "r", encoding='utf8'): - terms = line.strip("\n").split(delimiter) - if len(terms) != 2: - continue - if reverse: - value, key = terms - else: - key, value = terms - if key in result_dict: - raise KeyError("key duplicated with [%s]" % (key)) - if key_func: - key = key_func(key) - if value_func: - value = value_func(value) - result_dict[key] = value - return result_dict - - @property - def vocab_size(self): - return len(self.word2id_dict.values()) - - @property - def num_labels(self): - return len(self.label2id_dict.values()) - - def get_num_examples(self, filename): - """num of line of file""" - return sum(1 for line in io.open(filename, "r", encoding='utf8')) - - def word_to_ids(self, words): - """convert word to word index""" - word_ids = [] - for word in words: - word = self.word_replace_dict.get(word, word) - if word not in self.word2id_dict: - word = "OOV" - word_id = self.word2id_dict[word] - word_ids.append(word_id) - - return word_ids - - def label_to_ids(self, labels): - """convert label to label index""" - label_ids = [] - for label in labels: - if label not in self.label2id_dict: - label = "O" - label_id = self.label2id_dict[label] - label_ids.append(label_id) - return label_ids - - def file_reader(self, - filename, - mode="train", - batch_size=32, - max_seq_len=126): - """ - yield (word_idx, target_idx) one by one from file, - or yield (word_idx, ) in `infer` mode - """ - - def wrapper(): - fread = io.open(filename, "r", encoding="utf-8") - headline = next(fread) - headline = headline.strip().split('\t') - assert len(headline) == 2 and headline[0] == "text_a" and headline[ - 1] == "label" - buf = [] - for line in fread: - words, labels = line.strip("\n").split("\t") - if len(words) < 1: - continue - word_ids = self.word_to_ids(words.split("\002")) - label_ids = self.label_to_ids(labels.split("\002")) - assert len(word_ids) == len(label_ids) - word_ids = word_ids[0:max_seq_len] - words_len = np.int64(len(word_ids)) - word_ids += [0 for _ in range(max_seq_len - words_len)] - label_ids = label_ids[0:max_seq_len] - label_ids += [0 for _ in range(max_seq_len - words_len)] - assert len(word_ids) == len(label_ids) - yield word_ids, label_ids, words_len - fread.close() - - return wrapper - - -def create_lexnet_data_generator(args, reader, file_name, place, mode="train"): - def wrapper(): - batch_words, batch_labels, seq_lens = [], [], [] - for epoch in xrange(args.epoch): - for instance in reader.file_reader( - file_name, mode, max_seq_len=args.max_seq_len)(): - words, labels, words_len = instance - if len(seq_lens) < args.batch_size: - batch_words.append(words) - batch_labels.append(labels) - seq_lens.append(words_len) - if len(seq_lens) == args.batch_size: - yield batch_words, batch_labels, seq_lens, batch_labels - batch_words, batch_labels, seq_lens = [], [], [] - - if len(seq_lens) > 0: - yield batch_words, batch_labels, seq_lens, batch_labels - batch_words, batch_labels, seq_lens = [], [], [] - - return wrapper - - -def create_dataloader(generator, place, feed_list=None): - if not feed_list: - data_loader = fluid.io.DataLoader.from_generator( - capacity=50, - use_double_buffer=True, - iterable=True, - return_list=True) - else: - data_loader = fluid.io.DataLoader.from_generator( - feed_list=feed_list, - capacity=50, - use_double_buffer=True, - iterable=True, - return_list=True) - data_loader.set_batch_generator(generator, places=place) - return data_loader - - -def main(args): - place = set_device(args.device) - fluid.enable_dygraph(place) if args.dynamic else None - - inputs = [ - Input( - [None, args.max_seq_len], 'int64', name='words'), Input( - [None, args.max_seq_len], 'int64', name='target'), Input( - [None], 'int64', name='length') - ] - labels = [Input([None, args.max_seq_len], 'int64', name='labels')] - - feed = [x.forward() for x in inputs + labels] - dataset = LacDataset(args) - train_path = os.path.join(args.data, "train.tsv") - test_path = os.path.join(args.data, "test.tsv") - - if args.dynamic: - feed_list = None - else: - feed_list = feed - train_generator = create_lexnet_data_generator( - args, reader=dataset, file_name=train_path, place=place, mode="train") - test_generator = create_lexnet_data_generator( - args, reader=dataset, file_name=test_path, place=place, mode="test") - - train_dataset = create_dataloader( - train_generator, place, feed_list=feed_list) - test_dataset = create_dataloader( - test_generator, place, feed_list=feed_list) - - vocab_size = dataset.vocab_size - num_labels = dataset.num_labels - model = LAC(args, vocab_size, num_labels) - - optim = AdamOptimizer( - learning_rate=args.base_learning_rate, - parameter_list=model.parameters()) - - model.prepare( - optim, - LacLoss(), - ChunkEval(num_labels), - inputs=inputs, - labels=labels, - device=args.device) - - if args.resume is not None: - model.load(args.resume) - - model.fit(train_dataset, - test_dataset, - epochs=args.epoch, - batch_size=args.batch_size, - eval_freq=args.eval_freq, - save_freq=args.save_freq, - save_dir=args.save_dir) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser("LAC training") - parser.add_argument( - "-dir", "--data", default=None, type=str, help='path to LAC dataset') - parser.add_argument( - "-wd", - "--word_dict_path", - default=None, - type=str, - help='word dict path') - parser.add_argument( - "-ld", - "--label_dict_path", - default=None, - type=str, - help='label dict path') - parser.add_argument( - "-wrd", - "--word_rep_dict_path", - default=None, - type=str, - help='The path of the word replacement Dictionary.') - parser.add_argument( - "-dev", - "--device", - type=str, - default='gpu', - help="device to use, gpu or cpu") - parser.add_argument( - "-d", "--dynamic", action='store_true', help="enable dygraph mode") - parser.add_argument( - "-e", "--epoch", default=10, type=int, help="number of epoch") - parser.add_argument( - '-lr', - '--base_learning_rate', - default=1e-3, - type=float, - metavar='LR', - help='initial learning rate') - parser.add_argument( - "--word_emb_dim", - default=128, - type=int, - help='word embedding dimension') - parser.add_argument( - "--grnn_hidden_dim", default=128, type=int, help="hidden dimension") - parser.add_argument( - "--bigru_num", default=2, type=int, help='the number of bi-rnn') - parser.add_argument("-elr", "--emb_learning_rate", default=1.0, type=float) - parser.add_argument("-clr", "--crf_learning_rate", default=1.0, type=float) - parser.add_argument( - "-b", "--batch_size", default=300, type=int, help="batch size") - parser.add_argument( - "--max_seq_len", default=126, type=int, help="max sequence length") - parser.add_argument( - "-n", "--num_devices", default=1, type=int, help="number of devices") - parser.add_argument( - "-r", - "--resume", - default=None, - type=str, - help="checkpoint path to resume") - parser.add_argument( - "-o", - "--save_dir", - default="./model", - type=str, - help="save model path") - parser.add_argument( - "-sf", "--save_freq", default=1, type=int, help="save frequency") - parser.add_argument( - "-ef", "--eval_freq", default=1, type=int, help="eval frequency") - - args = parser.parse_args() - print(args) - main(args) diff --git a/mnist.py b/mnist.py index 745dc2f06e54136756ce5ae4f3b077c24468dd1d..39f323ac6454ed7dd06359017703401321428611 100644 --- a/mnist.py +++ b/mnist.py @@ -24,7 +24,7 @@ import numpy as np from paddle import fluid from paddle.fluid.optimizer import Momentum from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear -from paddle.fluid.io import MNIST as MnistDataset +from vision.datasets import MNIST as MnistDataset from model import Model, CrossEntropy, Input, set_device from metrics import Accuracy diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..0b6f6058e6d048df673e242c452b196bd87ac1f6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,52 @@ +[metadata] + +name = hapi + +author = zhouxiangyang +author_email = zhouxiangyang@baidu.com + +version = 0.0.1 + +description = HAPI +long_description = file: README.md +long_description_content_type = text/markdown + +home_page = https://github.com/PaddlePaddle/hapi +license = Apache 2.0 + +classifier = + Private :: Do Not Upload + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + +keywords = + paddlepaddle + paddle + high-level-api + +[options] + +packages = find: + +#install_requires = +# paddlepaddle-gpu >= 1.5.2 + +include_package_data = True +zip_safe = False + +[sdist] +dist_dir = output/dist + +[bdist_wheel] +dist_dir = output/dist + +[easy_install] +index_url = http://pip.baidu.com/root/baidu/+simple/ + + + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..4a8c246d75e43a4182190326f5996dd3afb8ba02 --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +# -*- coding: UTF-8 -*- +################################################################################ +# +# Copyright (c) 2020 Baidu.com, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ +""" +Setup script. +Authors: zhouxiangyang(zhouxiangyang@baidu.com) +Date: 2020/2/4 00:00:01 +""" +import setuptools +with open("README.md", "r") as fh: + long_description = fh.read() +setuptools.setup( + name="hapi", + version="0.0.1", + author="PaddlePaddle", + author_email="zhouxiangyang@baidu.com", + description="A Paddle High-level API that supports both static and dynamic execution modes (still under development)", + url="https://github.com/PaddlePaddle/hapi", + packages=[ + 'hapi', + 'hapi.datasets', + 'hapi.text', + 'hapi.text.tokenizer', + 'hapi.text.bert', + 'hapi.text.bert.utils', + 'hapi.vision', + 'hapi.vision.models', + 'hapi.vision.transforms', + ], + package_dir={ + 'hapi': './hapi', + 'hapi.datasets': './hapi/datasets', + 'hapi.text': './hapi/text', + 'hapi.text.tokenizer': './hapi/text/tokenizer', + 'hapi.text.bert': './hapi/text/bert', + 'hapi.text.bert.utils': './hapi/text/bert/utils', + 'hapi.vision': './hapi/vision', + 'hapi.vision.models': './hapi/vision/models', + 'hapi.vision.transforms': './hapi/vision/transforms', + }, + platforms="any", + license='Apache 2.0', + classifiers=[ + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], ) diff --git a/tests/test_bert_dataloader.py b/tests/test_bert_dataloader.py new file mode 100644 index 0000000000000000000000000000000000000000..f4a303b57f9443e5bbf7bb8106861989aa8120d9 --- /dev/null +++ b/tests/test_bert_dataloader.py @@ -0,0 +1,20 @@ +import paddle +from hapi.model import set_device +from hapi.text.bert.dataloader import SingleSentenceDataLoader +import hapi.text.tokenizer.tokenization as tokenization + +device = set_device("cpu") +paddle.fluid.enable_dygraph(device) + +tokenizer = tokenization.FullTokenizer( + vocab_file="./tmp/hapi/data/pretrained_models/uncased_L-12_H-768_A-12/vocab.txt", + do_lower_case=True) + +bert_dataloader = SingleSentenceDataLoader( + "./tmp/hapi/aaa.txt", + tokenizer, ["1", "2"], + max_seq_length=32, + batch_size=1) + +for data in bert_dataloader.dataloader(): + print(data) diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 3528a78a48f4ff784e495a954053aaa0a749af20..b9f42d977a681ce4bf0ed4fa1e28dbf80a859103 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +# when test, you should add hapi root path to the PYTHONPATH, +# export PYTHONPATH=PATH_TO_HAPI:$PYTHONPATH import unittest import time import random -from callbacks import config_callbacks +from hapi.callbacks import config_callbacks class TestCallbacks(unittest.TestCase): diff --git a/tests/test_data/class_a/ILSVRC2012_val_00000293.JPEG b/tests/test_data/class_a/ILSVRC2012_val_00000293.JPEG new file mode 100644 index 0000000000000000000000000000000000000000..1b332471a78cbb3e362a0871d8e2dfad14320910 Binary files /dev/null and b/tests/test_data/class_a/ILSVRC2012_val_00000293.JPEG differ diff --git a/tests/test_data/class_a/ILSVRC2012_val_00002138.JPEG b/tests/test_data/class_a/ILSVRC2012_val_00002138.JPEG new file mode 100644 index 0000000000000000000000000000000000000000..251f84450c8734d9683ad2bfba59dcf0ff2c9109 Binary files /dev/null and b/tests/test_data/class_a/ILSVRC2012_val_00002138.JPEG differ diff --git a/tests/test_data/class_b/ILSVRC2012_val_00000236.JPEG b/tests/test_data/class_b/ILSVRC2012_val_00000236.JPEG new file mode 100644 index 0000000000000000000000000000000000000000..a62f618980125faa60af6649d1b88799bde25228 Binary files /dev/null and b/tests/test_data/class_b/ILSVRC2012_val_00000236.JPEG differ diff --git a/tests/test_datasets.py b/tests/test_datasets.py new file mode 100644 index 0000000000000000000000000000000000000000..6adc9b667ac12c95fce0632ce2647db15e9fd470 --- /dev/null +++ b/tests/test_datasets.py @@ -0,0 +1,102 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# when test, you should add hapi root path to the PYTHONPATH, +# export PYTHONPATH=PATH_TO_HAPI:$PYTHONPATH + +import unittest +import numpy as np + +from hapi.datasets import * + + +class TestFolderDatasets(unittest.TestCase): + def test_dataset(self): + dataset_folder = DatasetFolder('tests/test_data') + + for _ in dataset_folder: + pass + + assert len(dataset_folder) == 3 + assert len(dataset_folder.classes) == 2 + + +class TestMNISTTest(unittest.TestCase): + def test_main(self): + mnist = MNIST(mode='test') + self.assertTrue(len(mnist) == 10000) + + for i in range(len(mnist)): + image, label = mnist[i] + self.assertTrue(image.shape[0] == 784) + self.assertTrue(label.shape[0] == 1) + self.assertTrue(0 <= int(label) <= 9) + + +class TestMNISTTrain(unittest.TestCase): + def test_main(self): + mnist = MNIST(mode='train') + self.assertTrue(len(mnist) == 60000) + + for i in range(len(mnist)): + image, label = mnist[i] + self.assertTrue(image.shape[0] == 784) + self.assertTrue(label.shape[0] == 1) + self.assertTrue(0 <= int(label) <= 9) + + +class TestFlowersTrain(unittest.TestCase): + def test_main(self): + flowers = Flowers(mode='train') + self.assertTrue(len(flowers) == 6149) + + # traversal whole dataset may cost a + # long time, randomly check 1 sample + idx = np.random.randint(0, 6149) + image, label = flowers[idx] + self.assertTrue(len(image.shape) == 3) + self.assertTrue(image.shape[2] == 3) + self.assertTrue(label.shape[0] == 1) + + +class TestFlowersValid(unittest.TestCase): + def test_main(self): + flowers = Flowers(mode='valid') + self.assertTrue(len(flowers) == 1020) + + # traversal whole dataset may cost a + # long time, randomly check 1 sample + idx = np.random.randint(0, 1020) + image, label = flowers[idx] + self.assertTrue(len(image.shape) == 3) + self.assertTrue(image.shape[2] == 3) + self.assertTrue(label.shape[0] == 1) + + +class TestFlowersTest(unittest.TestCase): + def test_main(self): + flowers = Flowers(mode='test') + self.assertTrue(len(flowers) == 1020) + + # traversal whole dataset may cost a + # long time, randomly check 1 sample + idx = np.random.randint(0, 1020) + image, label = flowers[idx] + self.assertTrue(len(image.shape) == 3) + self.assertTrue(image.shape[2] == 3) + self.assertTrue(label.shape[0] == 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_model.py b/tests/test_model.py index 9e8c880e461c684bc46e392c362ace3d00e67f53..7fe414c0c914b561cc78083f1fe89b0c79e77da2 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -15,24 +15,25 @@ from __future__ import division from __future__ import print_function +# when test, you should add hapi root path to the PYTHONPATH, +# export PYTHONPATH=PATH_TO_HAPI:$PYTHONPATH + import unittest import os -import sys -sys.path.append('../') - import numpy as np import contextlib import paddle from paddle import fluid from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear -from model import Model, CrossEntropy, Input, Loss, set_device -from metrics import Accuracy -from callbacks import ProgBarLogger -from paddle.fluid.io import BatchSampler, DataLoader -from paddle.fluid.io import MNIST as MnistDataset +from paddle.io import BatchSampler, DataLoader + +from hapi.model import Model, CrossEntropy, Input, Loss, set_device +from hapi.metrics import Accuracy +from hapi.callbacks import ProgBarLogger +from hapi.datasets import MNIST as MnistDataset class SimpleImgConvPool(fluid.dygraph.Layer): diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index b1f46e26341c264f9371617fadbf1819f93b0a27..797b94a1f0cae8ee37c50803bc4b2f6f4f4afe25 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +# when test, you should add hapi root path to the PYTHONPATH, +# export PYTHONPATH=PATH_TO_HAPI:$PYTHONPATH import unittest import random import time -from progressbar import ProgressBar +from hapi.progressbar import ProgressBar class TestProgressBar(unittest.TestCase): diff --git a/tests/test_transforms.py b/tests/test_transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..4471470d62ee1ba88ed6bb1bcebce6252908dc03 --- /dev/null +++ b/tests/test_transforms.py @@ -0,0 +1,56 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# when test, you should add hapi root path to the PYTHONPATH, +# export PYTHONPATH=PATH_TO_HAPI:$PYTHONPATH +import unittest + +from hapi.datasets import DatasetFolder +import hapi.vision.transforms as transforms + + +class TestTransforms(unittest.TestCase): + def do_transform(self, trans): + dataset_folder = DatasetFolder('tests/test_data', transform=trans) + + for _ in dataset_folder: + pass + + def test_trans0(self): + normalize = transforms.Normalize( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.120, 57.375]) + trans = transforms.Compose([ + transforms.RandomResizedCrop(224), transforms.GaussianNoise(), + transforms.ColorJitter( + brightness=0.4, contrast=0.4, saturation=0.4, + hue=0.4), transforms.RandomHorizontalFlip(), + transforms.Permute(mode='CHW'), normalize + ]) + + self.do_transform(trans) + + def test_trans1(self): + trans = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224), + ]) + self.do_transform(trans) + + def test_trans2(self): + trans = transforms.Compose([transforms.CenterCropResize(224)]) + self.do_transform(trans) + + +if __name__ == '__main__': + unittest.main() diff --git a/text.py b/text.py deleted file mode 100644 index 3b6cac2fea9539b5836d0ada1184ab50e2424e1e..0000000000000000000000000000000000000000 --- a/text.py +++ /dev/null @@ -1,1000 +0,0 @@ -import collections -import copy -import six -import sys -from functools import partial, reduce - -import paddle -import paddle.fluid as fluid -import paddle.fluid.layers.utils as utils -from paddle.fluid.layers.utils import map_structure, flatten, pack_sequence_as -from paddle.fluid.dygraph import to_variable, Embedding, Linear, LayerNorm -from paddle.fluid.data_feeder import convert_dtype - -from paddle.fluid import layers -from paddle.fluid.dygraph import Layer -from paddle.fluid.layers import BeamSearchDecoder - -__all__ = [ - 'RNNCell', 'BasicLSTMCell', 'BasicGRUCell', 'RNN', 'DynamicDecode', - 'BeamSearchDecoder', 'MultiHeadAttention', 'FFN', - 'TransformerEncoderLayer', 'TransformerEncoder', 'TransformerDecoderLayer', - 'TransformerDecoder', 'TransformerBeamSearchDecoder' -] - - -class RNNCell(Layer): - def get_initial_states(self, - batch_ref, - shape=None, - dtype=None, - init_value=0, - batch_dim_idx=0): - """ - Generate initialized states according to provided shape, data type and - value. - - Parameters: - batch_ref: A (possibly nested structure of) tensor variable[s]. - The first dimension of the tensor will be used as batch size to - initialize states. - shape: A (possiblely nested structure of) shape[s], where a shape is - represented as a list/tuple of integer). -1(for batch size) will - beautomatically inserted if shape is not started with it. If None, - property `state_shape` will be used. The default value is None. - dtype: A (possiblely nested structure of) data type[s]. The structure - must be same as that of `shape`, except when all tensors' in states - has the same data type, a single data type can be used. If None and - property `cell.state_shape` is not available, float32 will be used - as the data type. The default value is None. - init_value: A float value used to initialize states. - - Returns: - Variable: tensor variable[s] packed in the same structure provided \ - by shape, representing the initialized states. - """ - # TODO: use inputs and batch_size - batch_ref = flatten(batch_ref)[0] - - def _is_shape_sequence(seq): - if sys.version_info < (3, ): - integer_types = ( - int, - long, ) - else: - integer_types = (int, ) - """For shape, list/tuple of integer is the finest-grained objection""" - if (isinstance(seq, list) or isinstance(seq, tuple)): - if reduce( - lambda flag, x: isinstance(x, integer_types) and flag, - seq, True): - return False - # TODO: Add check for the illegal - if isinstance(seq, dict): - return True - return (isinstance(seq, collections.Sequence) and - not isinstance(seq, six.string_types)) - - class Shape(object): - def __init__(self, shape): - self.shape = shape if shape[0] == -1 else ([-1] + list(shape)) - - # nested structure of shapes - states_shapes = self.state_shape if shape is None else shape - is_sequence_ori = utils.is_sequence - utils.is_sequence = _is_shape_sequence - states_shapes = map_structure(lambda shape: Shape(shape), - states_shapes) - utils.is_sequence = is_sequence_ori - - # nested structure of dtypes - try: - states_dtypes = self.state_dtype if dtype is None else dtype - except NotImplementedError: # use fp32 as default - states_dtypes = "float32" - if len(flatten(states_dtypes)) == 1: - dtype = flatten(states_dtypes)[0] - states_dtypes = map_structure(lambda shape: dtype, states_shapes) - - init_states = map_structure( - lambda shape, dtype: fluid.layers.fill_constant_batch_size_like( - input=batch_ref, - shape=shape.shape, - dtype=dtype, - value=init_value, - input_dim_idx=batch_dim_idx), states_shapes, states_dtypes) - return init_states - - @property - def state_shape(self): - """ - Abstract method (property). - Used to initialize states. - A (possiblely nested structure of) shape[s], where a shape is represented - as a list/tuple of integers (-1 for batch size would be automatically - inserted into a shape if shape is not started with it). - Not necessary to be implemented if states are not initialized by - `get_initial_states` or the `shape` argument is provided when using - `get_initial_states`. - """ - raise NotImplementedError( - "Please add implementaion for `state_shape` in the used cell.") - - @property - def state_dtype(self): - """ - Abstract method (property). - Used to initialize states. - A (possiblely nested structure of) data types[s]. The structure must be - same as that of `shape`, except when all tensors' in states has the same - data type, a signle data type can be used. - Not necessary to be implemented if states are not initialized - by `get_initial_states` or the `dtype` argument is provided when using - `get_initial_states`. - """ - raise NotImplementedError( - "Please add implementaion for `state_dtype` in the used cell.") - - -class BasicLSTMCell(RNNCell): - """ - **** - BasicLSTMUnit class, Using basic operator to build LSTM - The algorithm can be described as the code below. - .. math:: - i_t &= \sigma(W_{ix}x_{t} + W_{ih}h_{t-1} + b_i) - f_t &= \sigma(W_{fx}x_{t} + W_{fh}h_{t-1} + b_f + forget_bias ) - o_t &= \sigma(W_{ox}x_{t} + W_{oh}h_{t-1} + b_o) - \\tilde{c_t} &= tanh(W_{cx}x_t + W_{ch}h_{t-1} + b_c) - c_t &= f_t \odot c_{t-1} + i_t \odot \\tilde{c_t} - h_t &= o_t \odot tanh(c_t) - - $W$ terms denote weight matrices (e.g. $W_{ix}$ is the matrix - of weights from the input gate to the input) - - The b terms denote bias vectors ($bx_i$ and $bh_i$ are the input gate bias vector). - - sigmoid is the logistic sigmoid function. - - $i, f, o$ and $c$ are the input gate, forget gate, output gate, - and cell activation vectors, respectively, all of which have the same size as - the cell output activation vector $h$. - - The :math:`\odot` is the element-wise product of the vectors. - - :math:`tanh` is the activation functions. - - :math:`\\tilde{c_t}` is also called candidate hidden state, - which is computed based on the current input and the previous hidden state. - Args: - name_scope(string) : The name scope used to identify parameter and bias name - hidden_size (integer): The hidden size used in the Unit. - param_attr(ParamAttr|None): The parameter attribute for the learnable - weight matrix. Note: - If it is set to None or one attribute of ParamAttr, lstm_unit will - create ParamAttr as param_attr. If the Initializer of the param_attr - is not set, the parameter is initialized with Xavier. Default: None. - bias_attr (ParamAttr|None): The parameter attribute for the bias - of LSTM unit. - If it is set to None or one attribute of ParamAttr, lstm_unit will - create ParamAttr as bias_attr. If the Initializer of the bias_attr - is not set, the bias is initialized as zero. Default: None. - gate_activation (function|None): The activation function for gates (actGate). - Default: 'fluid.layers.sigmoid' - activation (function|None): The activation function for cells (actNode). - Default: 'fluid.layers.tanh' - forget_bias(float|1.0): forget bias used when computing forget gate - dtype(string): data type used in this unit - """ - - def __init__(self, - input_size, - hidden_size, - param_attr=None, - bias_attr=None, - gate_activation=None, - activation=None, - forget_bias=1.0, - dtype='float32'): - super(BasicLSTMCell, self).__init__() - - self._hidden_size = hidden_size - self._param_attr = param_attr - self._bias_attr = bias_attr - self._gate_activation = gate_activation or layers.sigmoid - self._activation = activation or layers.tanh - self._forget_bias = layers.fill_constant( - [1], dtype=dtype, value=forget_bias) - self._forget_bias.stop_gradient = False - self._dtype = dtype - self._input_size = input_size - - self._weight = self.create_parameter( - attr=self._param_attr, - shape=[ - self._input_size + self._hidden_size, 4 * self._hidden_size - ], - dtype=self._dtype) - - self._bias = self.create_parameter( - attr=self._bias_attr, - shape=[4 * self._hidden_size], - dtype=self._dtype, - is_bias=True) - - def forward(self, input, state): - pre_hidden, pre_cell = state - concat_input_hidden = layers.concat([input, pre_hidden], 1) - gate_input = layers.matmul(x=concat_input_hidden, y=self._weight) - - gate_input = layers.elementwise_add(gate_input, self._bias) - i, j, f, o = layers.split(gate_input, num_or_sections=4, dim=-1) - new_cell = layers.elementwise_add( - layers.elementwise_mul( - pre_cell, - layers.sigmoid(layers.elementwise_add(f, self._forget_bias))), - layers.elementwise_mul(layers.sigmoid(i), layers.tanh(j))) - new_hidden = layers.tanh(new_cell) * layers.sigmoid(o) - - return new_hidden, [new_hidden, new_cell] - - @property - def state_shape(self): - return [[self._hidden_size], [self._hidden_size]] - - -class BasicGRUCell(RNNCell): - """ - **** - BasicGRUUnit class, using basic operators to build GRU - The algorithm can be described as the equations below. - - .. math:: - u_t & = actGate(W_ux xu_{t} + W_uh h_{t-1} + b_u) - - r_t & = actGate(W_rx xr_{t} + W_rh h_{t-1} + b_r) - - m_t & = actNode(W_cx xm_t + W_ch dot(r_t, h_{t-1}) + b_m) - - h_t & = dot(u_t, h_{t-1}) + dot((1-u_t), m_t) - - Args: - hidden_size (integer): The hidden size used in the Unit. - param_attr(ParamAttr|None): The parameter attribute for the learnable - weight matrix. Note: - If it is set to None or one attribute of ParamAttr, gru_unit will - create ParamAttr as param_attr. If the Initializer of the param_attr - is not set, the parameter is initialized with Xavier. Default: None. - bias_attr (ParamAttr|None): The parameter attribute for the bias - of GRU unit. - If it is set to None or one attribute of ParamAttr, gru_unit will - create ParamAttr as bias_attr. If the Initializer of the bias_attr - is not set, the bias is initialized zero. Default: None. - gate_activation (function|None): The activation function for gates (actGate). - Default: 'fluid.layers.sigmoid' - activation (function|None): The activation function for cell (actNode). - Default: 'fluid.layers.tanh' - dtype(string): data type used in this unit - """ - - def __init__(self, - input_size, - hidden_size, - param_attr=None, - bias_attr=None, - gate_activation=None, - activation=None, - dtype='float32'): - super(BasicGRUCell, self).__init__() - self._input_size = input_size - self._hiden_size = hidden_size - self._param_attr = param_attr - self._bias_attr = bias_attr - self._gate_activation = gate_activation or layers.sigmoid - self._activation = activation or layers.tanh - self._dtype = dtype - - if self._param_attr is not None and self._param_attr.name is not None: - gate_param_attr = copy.deepcopy(self._param_attr) - candidate_param_attr = copy.deepcopy(self._param_attr) - gate_param_attr.name += "_gate" - candidate_param_attr.name += "_candidate" - else: - gate_param_attr = self._param_attr - candidate_param_attr = self._param_attr - - self._gate_weight = self.create_parameter( - attr=gate_param_attr, - shape=[self._input_size + self._hiden_size, 2 * self._hiden_size], - dtype=self._dtype) - - self._candidate_weight = self.create_parameter( - attr=candidate_param_attr, - shape=[self._input_size + self._hiden_size, self._hiden_size], - dtype=self._dtype) - - if self._bias_attr is not None and self._bias_attr.name is not None: - gate_bias_attr = copy.deepcopy(self._bias_attr) - candidate_bias_attr = copy.deepcopy(self._bias_attr) - gate_bias_attr.name += "_gate" - candidate_bias_attr.name += "_candidate" - else: - gate_bias_attr = self._bias_attr - candidate_bias_attr = self._bias_attr - - self._gate_bias = self.create_parameter( - attr=gate_bias_attr, - shape=[2 * self._hiden_size], - dtype=self._dtype, - is_bias=True) - self._candidate_bias = self.create_parameter( - attr=candidate_bias_attr, - shape=[self._hiden_size], - dtype=self._dtype, - is_bias=True) - - def forward(self, input, state): - pre_hidden = state - concat_input_hidden = layers.concat([input, pre_hidden], axis=1) - - gate_input = layers.matmul(x=concat_input_hidden, y=self._gate_weight) - - gate_input = layers.elementwise_add(gate_input, self._gate_bias) - - gate_input = self._gate_activation(gate_input) - r, u = layers.split(gate_input, num_or_sections=2, dim=1) - - r_hidden = r * pre_hidden - - candidate = layers.matmul( - layers.concat([input, r_hidden], 1), self._candidate_weight) - candidate = layers.elementwise_add(candidate, self._candidate_bias) - - c = self._activation(candidate) - new_hidden = u * pre_hidden + (1 - u) * c - - return new_hidden - - @property - def state_shape(self): - return [self._hidden_size] - - -class RNN(fluid.dygraph.Layer): - def __init__(self, cell, is_reverse=False, time_major=False): - super(RNN, self).__init__() - self.cell = cell - if not hasattr(self.cell, "call"): - self.cell.call = self.cell.forward - self.is_reverse = is_reverse - self.time_major = time_major - self.batch_index, self.time_step_index = (1, 0) if time_major else (0, - 1) - - def forward(self, - inputs, - initial_states=None, - sequence_length=None, - **kwargs): - if fluid.in_dygraph_mode(): - - class ArrayWrapper(object): - def __init__(self, x): - self.array = [x] - - def append(self, x): - self.array.append(x) - return self - - def _maybe_copy(state, new_state, step_mask): - # TODO: use where_op - new_state = fluid.layers.elementwise_mul( - new_state, step_mask, - axis=0) - fluid.layers.elementwise_mul( - state, (step_mask - 1), axis=0) - return new_state - - flat_inputs = flatten(inputs) - batch_size, time_steps = ( - flat_inputs[0].shape[self.batch_index], - flat_inputs[0].shape[self.time_step_index]) - - if initial_states is None: - initial_states = self.cell.get_initial_states( - batch_ref=inputs, batch_dim_idx=self.batch_index) - - if not self.time_major: - inputs = map_structure( - lambda x: fluid.layers.transpose(x, [1, 0] + list( - range(2, len(x.shape)))), inputs) - - if sequence_length: - mask = fluid.layers.sequence_mask( - sequence_length, - maxlen=time_steps, - dtype=flatten(initial_states)[0].dtype) - mask = fluid.layers.transpose(mask, [1, 0]) - - if self.is_reverse: - inputs = map_structure( - lambda x: fluid.layers.reverse(x, axis=[0]), inputs) - mask = fluid.layers.reverse( - mask, axis=[0]) if sequence_length else None - - states = initial_states - outputs = [] - for i in range(time_steps): - step_inputs = map_structure(lambda x: x[i], inputs) - step_outputs, new_states = self.cell(step_inputs, states, - **kwargs) - if sequence_length: - new_states = map_structure( - partial( - _maybe_copy, step_mask=mask[i]), - states, - new_states) - states = new_states - outputs = map_structure( - lambda x: ArrayWrapper(x), - step_outputs) if i == 0 else map_structure( - lambda x, x_array: x_array.append(x), step_outputs, - outputs) - - final_outputs = map_structure( - lambda x: fluid.layers.stack(x.array, - axis=self.time_step_index), - outputs) - - if self.is_reverse: - final_outputs = map_structure( - lambda x: fluid.layers.reverse(x, - axis=self.time_step_index), - final_outputs) - - final_states = new_states - else: - final_outputs, final_states = fluid.layers.rnn( - self.cell, - inputs, - initial_states=initial_states, - sequence_length=sequence_length, - time_major=self.time_major, - is_reverse=self.is_reverse, - **kwargs) - return final_outputs, final_states - - -class DynamicDecode(Layer): - def __init__(self, - decoder, - max_step_num=None, - output_time_major=False, - impute_finished=False, - is_test=False, - return_length=False): - super(DynamicDecode, self).__init__() - self.decoder = decoder - self.max_step_num = max_step_num - self.output_time_major = output_time_major - self.impute_finished = impute_finished - self.is_test = is_test - self.return_length = return_length - - def forward(self, inits=None, **kwargs): - if fluid.in_dygraph_mode(): - - class ArrayWrapper(object): - def __init__(self, x): - self.array = [x] - - def append(self, x): - self.array.append(x) - return self - - def __getitem__(self, item): - return self.array.__getitem__(item) - - def _maybe_copy(state, new_state, step_mask): - # TODO: use where_op - state_dtype = state.dtype - if convert_dtype(state_dtype) in ["bool"]: - state = layers.cast(state, dtype="float32") - new_state = layers.cast(new_state, dtype="float32") - if step_mask.dtype != state.dtype: - step_mask = layers.cast(step_mask, dtype=state.dtype) - # otherwise, renamed bool gradients of would be summed up leading - # to sum(bool) error. - step_mask.stop_gradient = True - new_state = layers.elementwise_mul( - state, step_mask, axis=0) - layers.elementwise_mul( - new_state, (step_mask - 1), axis=0) - if convert_dtype(state_dtype) in ["bool"]: - new_state = layers.cast(new_state, dtype=state_dtype) - return new_state - - initial_inputs, initial_states, initial_finished = self.decoder.initialize( - inits) - inputs, states, finished = (initial_inputs, initial_states, - initial_finished) - cond = layers.logical_not((layers.reduce_all(initial_finished))) - sequence_lengths = layers.cast( - layers.zeros_like(initial_finished), "int64") - outputs = None - - step_idx = 0 - step_idx_tensor = layers.fill_constant( - shape=[1], dtype="int64", value=step_idx) - while cond.numpy(): - (step_outputs, next_states, next_inputs, - next_finished) = self.decoder.step(step_idx_tensor, inputs, - states, **kwargs) - if not self.decoder.tracks_own_finished: - # BeamSearchDecoder would track it own finished, since - # beams would be reordered and the finished status of each - # entry might change. Otherwise, perform logical OR which - # would not change the already finished. - next_finished = layers.logical_or(next_finished, finished) - # To confirm states.finished/finished be consistent with - # next_finished. - layers.assign(next_finished, finished) - next_sequence_lengths = layers.elementwise_add( - sequence_lengths, - layers.cast( - layers.logical_not(finished), sequence_lengths.dtype)) - - if self.impute_finished: # rectify the states for the finished. - next_states = map_structure( - lambda x, y: _maybe_copy(x, y, finished), states, - next_states) - outputs = map_structure( - lambda x: ArrayWrapper(x), - step_outputs) if step_idx == 0 else map_structure( - lambda x, x_array: x_array.append(x), step_outputs, - outputs) - inputs, states, finished, sequence_lengths = ( - next_inputs, next_states, next_finished, - next_sequence_lengths) - - layers.increment(x=step_idx_tensor, value=1.0, in_place=True) - step_idx += 1 - - layers.logical_not(layers.reduce_all(finished), cond) - if self.max_step_num is not None and step_idx > self.max_step_num: - break - - final_outputs = map_structure( - lambda x: fluid.layers.stack(x.array, axis=0), outputs) - final_states = states - - try: - final_outputs, final_states = self.decoder.finalize( - final_outputs, final_states, sequence_lengths) - except NotImplementedError: - pass - - if not self.output_time_major: - final_outputs = map_structure( - lambda x: layers.transpose(x, [1, 0] + list( - range(2, len(x.shape)))), final_outputs) - - return (final_outputs, final_states, - sequence_lengths) if self.return_length else ( - final_outputs, final_states) - else: - return fluid.layers.dynamic_decode( - self.decoder, - inits, - max_step_num=self.max_step_num, - output_time_major=self.output_time_major, - impute_finished=self.impute_finished, - is_test=self.is_test, - return_length=self.return_length, - **kwargs) - - -class TransfomerCell(object): - """ - Let inputs=(trg_word, trg_pos), states=cache to make Transformer can be - used as RNNCell - """ - - def __init__(self, decoder): - self.decoder = decoder - - def __call__(self, inputs, states, trg_src_attn_bias, enc_output, - static_caches): - trg_word, trg_pos = inputs - for cache, static_cache in zip(states, static_caches): - cache.update(static_cache) - logits = self.decoder(trg_word, trg_pos, None, trg_src_attn_bias, - enc_output, states) - new_states = [{"k": cache["k"], "v": cache["v"]} for cache in states] - return logits, new_states - - -class TransformerBeamSearchDecoder(layers.BeamSearchDecoder): - def __init__(self, cell, start_token, end_token, beam_size, - var_dim_in_state): - super(TransformerBeamSearchDecoder, - self).__init__(cell, start_token, end_token, beam_size) - self.cell = cell - self.var_dim_in_state = var_dim_in_state - - def _merge_batch_beams_with_var_dim(self, x): - # init length of cache is 0, and it increases with decoding carrying on, - # thus need to reshape elaborately - var_dim_in_state = self.var_dim_in_state + 1 # count in beam dim - x = layers.transpose(x, - list(range(var_dim_in_state, len(x.shape))) + - list(range(0, var_dim_in_state))) - x = layers.reshape( - x, [0] * (len(x.shape) - var_dim_in_state - ) + [self.batch_size * self.beam_size] + - [int(size) for size in x.shape[-var_dim_in_state + 2:]]) - x = layers.transpose( - x, - list(range((len(x.shape) + 1 - var_dim_in_state), len(x.shape))) + - list(range(0, (len(x.shape) + 1 - var_dim_in_state)))) - return x - - def _split_batch_beams_with_var_dim(self, x): - var_dim_size = layers.shape(x)[self.var_dim_in_state] - x = layers.reshape( - x, [-1, self.beam_size] + - [int(size) - for size in x.shape[1:self.var_dim_in_state]] + [var_dim_size] + - [int(size) for size in x.shape[self.var_dim_in_state + 1:]]) - return x - - def step(self, time, inputs, states, **kwargs): - # compared to RNN, Transformer has 3D data at every decoding step - inputs = layers.reshape(inputs, [-1, 1]) # token - pos = layers.ones_like(inputs) * time # pos - cell_states = map_structure(self._merge_batch_beams_with_var_dim, - states.cell_states) - - cell_outputs, next_cell_states = self.cell((inputs, pos), cell_states, - **kwargs) - cell_outputs = map_structure(self._split_batch_beams, cell_outputs) - next_cell_states = map_structure(self._split_batch_beams_with_var_dim, - next_cell_states) - - beam_search_output, beam_search_state = self._beam_search_step( - time=time, - logits=cell_outputs, - next_cell_states=next_cell_states, - beam_state=states) - next_inputs, finished = (beam_search_output.predicted_ids, - beam_search_state.finished) - - return (beam_search_output, beam_search_state, next_inputs, finished) - - -### Transformer Modules ### -class PrePostProcessLayer(Layer): - """ - PrePostProcessLayer - """ - - def __init__(self, process_cmd, d_model, dropout_rate): - super(PrePostProcessLayer, self).__init__() - self.process_cmd = process_cmd - self.functors = [] - for cmd in self.process_cmd: - if cmd == "a": # add residual connection - self.functors.append(lambda x, y: x + y if y else x) - elif cmd == "n": # add layer normalization - self.functors.append( - self.add_sublayer( - "layer_norm_%d" % len( - self.sublayers(include_sublayers=False)), - LayerNorm( - normalized_shape=d_model, - param_attr=fluid.ParamAttr( - initializer=fluid.initializer.Constant(1.)), - bias_attr=fluid.ParamAttr( - initializer=fluid.initializer.Constant(0.))))) - elif cmd == "d": # add dropout - self.functors.append(lambda x: layers.dropout( - x, dropout_prob=dropout_rate, is_test=False) - if dropout_rate else x) - - def forward(self, x, residual=None): - for i, cmd in enumerate(self.process_cmd): - if cmd == "a": - x = self.functors[i](x, residual) - else: - x = self.functors[i](x) - return x - - -class MultiHeadAttention(Layer): - """ - Multi-Head Attention - """ - - def __init__(self, d_key, d_value, d_model, n_head=1, dropout_rate=0.): - super(MultiHeadAttention, self).__init__() - self.n_head = n_head - self.d_key = d_key - self.d_value = d_value - self.d_model = d_model - self.dropout_rate = dropout_rate - self.q_fc = Linear( - input_dim=d_model, output_dim=d_key * n_head, bias_attr=False) - self.k_fc = Linear( - input_dim=d_model, output_dim=d_key * n_head, bias_attr=False) - self.v_fc = Linear( - input_dim=d_model, output_dim=d_value * n_head, bias_attr=False) - self.proj_fc = Linear( - input_dim=d_value * n_head, output_dim=d_model, bias_attr=False) - - def _prepare_qkv(self, queries, keys, values, cache=None): - if keys is None: # self-attention - keys, values = queries, queries - static_kv = False - else: # cross-attention - static_kv = True - - q = self.q_fc(queries) - q = layers.reshape(x=q, shape=[0, 0, self.n_head, self.d_key]) - q = layers.transpose(x=q, perm=[0, 2, 1, 3]) - - if cache is not None and static_kv and "static_k" in cache: - # for encoder-decoder attention in inference and has cached - k = cache["static_k"] - v = cache["static_v"] - else: - k = self.k_fc(keys) - v = self.v_fc(values) - k = layers.reshape(x=k, shape=[0, 0, self.n_head, self.d_key]) - k = layers.transpose(x=k, perm=[0, 2, 1, 3]) - v = layers.reshape(x=v, shape=[0, 0, self.n_head, self.d_value]) - v = layers.transpose(x=v, perm=[0, 2, 1, 3]) - - if cache is not None: - if static_kv and not "static_k" in cache: - # for encoder-decoder attention in inference and has not cached - cache["static_k"], cache["static_v"] = k, v - elif not static_kv: - # for decoder self-attention in inference - cache_k, cache_v = cache["k"], cache["v"] - k = layers.concat([cache_k, k], axis=2) - v = layers.concat([cache_v, v], axis=2) - cache["k"], cache["v"] = k, v - - return q, k, v - - def forward(self, queries, keys, values, attn_bias, cache=None): - # compute q ,k ,v - q, k, v = self._prepare_qkv(queries, keys, values, cache) - - # scale dot product attention - product = layers.matmul( - x=q, y=k, transpose_y=True, alpha=self.d_model**-0.5) - if attn_bias: - product += attn_bias - weights = layers.softmax(product) - if self.dropout_rate: - weights = layers.dropout( - weights, dropout_prob=self.dropout_rate, is_test=False) - - out = layers.matmul(weights, v) - - # combine heads - out = layers.transpose(out, perm=[0, 2, 1, 3]) - out = layers.reshape(x=out, shape=[0, 0, out.shape[2] * out.shape[3]]) - - # project to output - out = self.proj_fc(out) - return out - - def cal_kv(self, keys, values): - k = self.k_fc(keys) - v = self.v_fc(values) - k = layers.reshape(x=k, shape=[0, 0, self.n_head, self.d_key]) - k = layers.transpose(x=k, perm=[0, 2, 1, 3]) - v = layers.reshape(x=v, shape=[0, 0, self.n_head, self.d_value]) - v = layers.transpose(x=v, perm=[0, 2, 1, 3]) - return k, v - - -class FFN(Layer): - """ - Feed-Forward Network - """ - - def __init__(self, d_inner_hid, d_model, dropout_rate): - super(FFN, self).__init__() - self.dropout_rate = dropout_rate - self.fc1 = Linear( - input_dim=d_model, output_dim=d_inner_hid, act="relu") - self.fc2 = Linear(input_dim=d_inner_hid, output_dim=d_model) - - def forward(self, x): - hidden = self.fc1(x) - if self.dropout_rate: - hidden = layers.dropout( - hidden, dropout_prob=self.dropout_rate, is_test=False) - out = self.fc2(hidden) - return out - - -class TransformerEncoderLayer(Layer): - """ - EncoderLayer - """ - - def __init__(self, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd="n", - postprocess_cmd="da"): - - super(TransformerEncoderLayer, self).__init__() - - self.preprocesser1 = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - self.self_attn = MultiHeadAttention(d_key, d_value, d_model, n_head, - attention_dropout) - self.postprocesser1 = PrePostProcessLayer(postprocess_cmd, d_model, - prepostprocess_dropout) - - self.preprocesser2 = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - self.ffn = FFN(d_inner_hid, d_model, relu_dropout) - self.postprocesser2 = PrePostProcessLayer(postprocess_cmd, d_model, - prepostprocess_dropout) - - def forward(self, enc_input, attn_bias): - attn_output = self.self_attn( - self.preprocesser1(enc_input), None, None, attn_bias) - attn_output = self.postprocesser1(attn_output, enc_input) - - ffn_output = self.ffn(self.preprocesser2(attn_output)) - ffn_output = self.postprocesser2(ffn_output, attn_output) - return ffn_output - - -class TransformerEncoder(Layer): - """ - encoder - """ - - def __init__(self, - n_layer, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd="n", - postprocess_cmd="da"): - - super(TransformerEncoder, self).__init__() - - self.encoder_layers = list() - for i in range(n_layer): - self.encoder_layers.append( - self.add_sublayer( - "layer_%d" % i, - TransformerEncoderLayer( - n_head, d_key, d_value, d_model, d_inner_hid, - prepostprocess_dropout, attention_dropout, - relu_dropout, preprocess_cmd, postprocess_cmd))) - self.processer = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - - def forward(self, enc_input, attn_bias): - for encoder_layer in self.encoder_layers: - enc_output = encoder_layer(enc_input, attn_bias) - enc_input = enc_output - - return self.processer(enc_output) - - -class TransformerDecoderLayer(Layer): - """ - decoder - """ - - def __init__(self, - n_head, - d_key, - d_value, - d_model, - d_inner_hid, - prepostprocess_dropout, - attention_dropout, - relu_dropout, - preprocess_cmd="n", - postprocess_cmd="da"): - super(TransformerDecoderLayer, self).__init__() - - self.preprocesser1 = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - self.self_attn = MultiHeadAttention(d_key, d_value, d_model, n_head, - attention_dropout) - self.postprocesser1 = PrePostProcessLayer(postprocess_cmd, d_model, - prepostprocess_dropout) - - self.preprocesser2 = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - self.cross_attn = MultiHeadAttention(d_key, d_value, d_model, n_head, - attention_dropout) - self.postprocesser2 = PrePostProcessLayer(postprocess_cmd, d_model, - prepostprocess_dropout) - - self.preprocesser3 = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - self.ffn = FFN(d_inner_hid, d_model, relu_dropout) - self.postprocesser3 = PrePostProcessLayer(postprocess_cmd, d_model, - prepostprocess_dropout) - - def forward(self, - dec_input, - enc_output, - self_attn_bias, - cross_attn_bias, - cache=None): - self_attn_output = self.self_attn( - self.preprocesser1(dec_input), None, None, self_attn_bias, cache) - self_attn_output = self.postprocesser1(self_attn_output, dec_input) - - cross_attn_output = self.cross_attn( - self.preprocesser2(self_attn_output), enc_output, enc_output, - cross_attn_bias, cache) - cross_attn_output = self.postprocesser2(cross_attn_output, - self_attn_output) - - ffn_output = self.ffn(self.preprocesser3(cross_attn_output)) - ffn_output = self.postprocesser3(ffn_output, cross_attn_output) - - return ffn_output - - -class TransformerDecoder(Layer): - """ - decoder - """ - - def __init__(self, n_layer, n_head, d_key, d_value, d_model, d_inner_hid, - prepostprocess_dropout, attention_dropout, relu_dropout, - preprocess_cmd, postprocess_cmd): - super(TransformerDecoder, self).__init__() - - self.decoder_layers = list() - for i in range(n_layer): - self.decoder_layers.append( - self.add_sublayer( - "layer_%d" % i, - TransformerDecoderLayer( - n_head, d_key, d_value, d_model, d_inner_hid, - prepostprocess_dropout, attention_dropout, - relu_dropout, preprocess_cmd, postprocess_cmd))) - self.processer = PrePostProcessLayer(preprocess_cmd, d_model, - prepostprocess_dropout) - - def forward(self, - dec_input, - enc_output, - self_attn_bias, - cross_attn_bias, - caches=None): - for i, decoder_layer in enumerate(self.decoder_layers): - dec_output = decoder_layer(dec_input, enc_output, self_attn_bias, - cross_attn_bias, None - if caches is None else caches[i]) - dec_input = dec_output - - return self.processer(dec_output) - - def prepare_static_cache(self, enc_output): - return [ - dict( - zip(("static_k", "static_v"), - decoder_layer.cross_attn.cal_kv(enc_output, enc_output))) - for decoder_layer in self.decoder_layers - ] diff --git a/transformer/predict.py b/transformer/predict.py index 7a47ccdaef7426505ab69ee93ec20bfb2f765513..b83d5403486c1e661a939663bad154735b29b37e 100644 --- a/transformer/predict.py +++ b/transformer/predict.py @@ -22,7 +22,7 @@ from functools import partial import numpy as np import paddle import paddle.fluid as fluid -from paddle.fluid.io import DataLoader +from paddle.io import DataLoader from paddle.fluid.layers.utils import flatten from utils.configure import PDConfig diff --git a/transformer/reader.py b/transformer/reader.py index 8b2d8fa028aff2170a3d3f8bb43ff7c3a93abf8e..2e3fc59e0d3a85e8a674e6b1f6bb4b611ee45057 100644 --- a/transformer/reader.py +++ b/transformer/reader.py @@ -22,7 +22,7 @@ from functools import partial import numpy as np import paddle.fluid as fluid from paddle.fluid.dygraph.parallel import ParallelEnv -from paddle.fluid.io import BatchSampler, DataLoader, Dataset +from paddle.io import BatchSampler, DataLoader, Dataset def create_data_loader(args, device): diff --git a/transformer/train.py b/transformer/train.py index 58df6afb2cadc3b471aaffd4ed4caebbbc0bbc3d..04a61f83a0191a944d9b2611b3bca61f0bcf2a0a 100644 --- a/transformer/train.py +++ b/transformer/train.py @@ -21,7 +21,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import numpy as np import paddle import paddle.fluid as fluid -from paddle.fluid.io import DataLoader +from paddle.io import DataLoader from utils.configure import PDConfig from utils.check import check_gpu, check_version