From 881ce01762f179b3d7c6e837e0413fed6883d1ff Mon Sep 17 00:00:00 2001 From: ChenXin Date: Wed, 22 May 2019 18:43:56 +0800 Subject: [PATCH 1/7] Dev0.4.0 (#149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1. CRF增加支持bmeso类型的tag 2. vocabulary中增加注释 * BucketSampler增加一条错误检测 * 1.修改ClipGradientCallback的bug;删除LRSchedulerCallback中的print,之后应该传入pbar进行打印;2.增加MLP注释 * update MLP module * 增加metric注释;修改trainer save过程中的bug * Update README.md fix tutorial link * Add ENAS (Efficient Neural Architecture Search) * add ignore_type in DataSet.add_field * * AutoPadder will not pad when dtype is None * add ignore_type in DataSet.apply * 修复fieldarray中padder潜在bug * 修复crf中typo; 以及可能导致数值不稳定的地方 * 修复CRF中可能存在的bug * change two default init arguments of Trainer into None * Changes to Callbacks: * 给callback添加给定几个只读属性 * 通过manager设置这些属性 * 代码优化,减轻@transfer的负担 * * 将enas相关代码放到automl目录下 * 修复fast_param_mapping的一个bug * Trainer添加自动创建save目录 * Vocabulary的打印,显示内容 * * 给vocabulary添加遍历方法 * 修复CRF为负数的bug * add SQuAD metric * add sigmoid activate function in MLP * - add star transformer model - add ConllLoader, for all kinds of conll-format files - add JsonLoader, for json-format files - add SSTLoader, for SST-2 & SST-5 - change Callback interface - fix batch multi-process when killed - add README to list models and their performance * - fix test * - fix callback & tests * - update README * 修改部分bug;调整callback * 准备发布0.4.0版本“ * update readme * support parallel loss * 防止多卡的情况导致无法正确计算loss“ * update advance_tutorial jupyter notebook * 1. 在embedding_loader中增加新的读取函数load_with_vocab(), load_without_vocab, 比之前的函数改变主要在(1)不再需要传入embed_dim(2)自动判断当前是word2vec还是glove. 2. vocabulary增加from_dataset(), index_dataset()函数。避免需要多行写index dataset的问题。 3. 在utils中新增一个cache_result()修饰器,用于cache函数的返回值。 4. callback中新增update_every属性 * 1.DataSet.apply()报错时提供错误的index 2.Vocabulary.from_dataset(), index_dataset()提供报错时的vocab顺序 3.embedloader在embed读取时遇到不规则的数据跳过这一行. * update attention * doc tools * fix some doc errors * 修改为中文注释,增加viterbi解码方法 * 样例版本 * - add pad sequence for lstm - add csv, conll, json filereader - update dataloader - remove useless dataloader - fix trainer loss print - fix tests * - fix test_tutorial * 注释增加 * 测试文档 * 本地暂存 * 本地暂存 * 修改文档的顺序 * - add document * 本地暂存 * update pooling * update bert * update documents in MLP * update documents in snli * combine self attention module to attention.py * update documents on losses.py * 对DataSet的文档进行更新 * update documents on metrics * 1. 删除了LSTM中print的内容; 2. 将Trainer和Tester的use_cuda修改为了device; 3.补充Trainer的文档 * 增加对Trainer的注释 * 完善了trainer,callback等的文档; 修改了部分代码的命名以使得代码从文档中隐藏 * update char level encoder * update documents on embedding.py * - update doc * 补充注释,并修改部分代码 * - update doc - add get_embeddings * 修改了文档配置项 * 修改embedding为init_embed初始化 * 1.增加对Trainer和Tester的多卡支持; * - add test - fix jsonloader * 删除了注释教程 * 给 dataset 增加了get_field_names * 修复bug * - add Const - fix bugs * 修改部分注释 * - add model runner for easier test models - add model tests * 修改了 docs 的配置和架构 * 修改了核心部分的一大部分文档,TODO: 1. 完善 trainer 和 tester 部分的文档 2. 研究注释样例与测试 * core部分的注释基本检查完成 * 修改了 io 部分的注释 * 全部改为相对路径引用 * 全部改为相对路径引用 * small change * 1. 从安装文件中删除api/automl的安装 2. metric中存在seq_len的bug 3. sampler中存在命名错误,已修改 * 修复 bug :兼容 cpu 版本的 PyTorch TODO:其它地方可能也存在类似的 bug * 修改文档中的引用部分 * 把 tqdm.autonotebook 换成tqdm.auto * - fix batch & vocab * 上传了文档文件 *.rst * 上传了文档文件和若干 TODO * 讨论并整合了若干模块 * core部分的测试和一些小修改 * 删除了一些冗余文档 * update init files * update const files * update const files * 增加cnn的测试 * fix a little bug * - update attention - fix tests * 完善测试 * 完成快速入门教程 * 修改了sequence_modeling 命名为 sequence_labeling 的文档 * 重新 apidoc 解决改名的遗留问题 * 修改文档格式 * 统一不同位置的seq_len_to_mask, 现统一到core.utils.seq_len_to_mask * 增加了一行提示 * 在文档中展示 dataset_loader * 提示 Dataset.read_csv 会被 CSVLoader 替换 * 完成 Callback 和 Trainer 之间的文档 * index更新了部分 * 删除冗余的print * 删除用于分词的metric,因为有可能引起错误 * 修改文档中的中文名称 * 完成了详细介绍文档 * tutorial 的 ipynb 文件 * 修改了一些介绍文档 * 修改了 models 和 modules 的主页介绍 * 加上了 titlesonly 这个设置 * 修改了模块文档展示的标题 * 修改了 core 和 io 的开篇介绍 * 修改了 modules 和 models 开篇介绍 * 使用 .. todo:: 隐藏了可能被抽到文档中的 TODO 注释 * 修改了一些注释 * delete an old metric in test * 修改 tutorials 的测试文件 * 把暂不发布的功能移到 legacy 文件夹 * 删除了不能运行的测试 * 修改 callback 的测试文件 * 删除了过时的教程和测试文件 * cache_results 参数的修改 * 修改 io 的测试文件; 删除了一些过时的测试 * 修复bug * 修复无法通过test_utils.py的测试 * 修复与pytorch1.1中的padsequence的兼容问题; 修改Trainer的pbar * 1. 修复metric中的bug; 2.增加metric测试 * add model summary * 增加别名 * 删除encoder中的嵌套层 * 修改了 core 部分 import 的顺序,__all__ 暴露的内容 * 修改了 models 部分 import 的顺序,__all__ 暴露的内容 * 修改了文件名 * 修改了 modules 模块的__all__ 和 import * fix var runn * 增加vocab的clear方法 * 一些符合 PEP8 的微调 * 更新了cache_results的例子 * 1. 对callback中indices潜在None作出提示;2.DataSet支持通过List进行index * 修改了一个typo * 修改了 README.md * update documents on bert * update documents on encoder/bert * 增加一个fitlog callback,实现与fitlog实验记录 * typo * - update dataset_loader * 增加了到 fitlog 文档的链接。 * 增加了 DataSet Loader 的文档 * - add star-transformer reproduction --- MANIFEST.in | 7 + README.md | 101 +- docs/Makefile | 7 + docs/source/conf.py | 45 +- docs/source/fastNLP.api.rst | 36 - docs/source/fastNLP.core.batch.rst | 7 + docs/source/fastNLP.core.callback.rst | 7 + docs/source/fastNLP.core.const.rst | 7 + docs/source/fastNLP.core.dataset.rst | 7 + docs/source/fastNLP.core.field.rst | 7 + docs/source/fastNLP.core.instance.rst | 7 + docs/source/fastNLP.core.losses.rst | 7 + docs/source/fastNLP.core.metrics.rst | 7 + docs/source/fastNLP.core.optimizer.rst | 7 + docs/source/fastNLP.core.rst | 107 +- docs/source/fastNLP.core.sampler.rst | 7 + docs/source/fastNLP.core.tester.rst | 7 + docs/source/fastNLP.core.trainer.rst | 7 + docs/source/fastNLP.core.utils.rst | 7 + docs/source/fastNLP.core.vocabulary.rst | 7 + docs/source/fastNLP.io.base_loader.rst | 7 + docs/source/fastNLP.io.dataset_loader.rst | 7 + docs/source/fastNLP.io.embed_loader.rst | 7 + docs/source/fastNLP.io.model_io.rst | 7 + docs/source/fastNLP.io.rst | 49 +- .../source/fastNLP.models.biaffine_parser.rst | 7 + ...fastNLP.models.cnn_text_classification.rst | 7 + docs/source/fastNLP.models.rst | 50 +- .../fastNLP.models.sequence_labeling.rst | 7 + docs/source/fastNLP.models.snli.rst | 7 + .../fastNLP.models.star_transformer.rst | 7 + .../fastNLP.modules.aggregator.attention.rst | 7 + .../fastNLP.modules.aggregator.pooling.rst | 7 + docs/source/fastNLP.modules.aggregator.rst | 41 +- docs/source/fastNLP.modules.decoder.crf.rst | 7 + docs/source/fastNLP.modules.decoder.mlp.rst | 7 + docs/source/fastNLP.modules.decoder.rst | 24 +- docs/source/fastNLP.modules.decoder.utils.rst | 7 + docs/source/fastNLP.modules.encoder.bert.rst | 7 + .../fastNLP.modules.encoder.char_encoder.rst | 7 + .../fastNLP.modules.encoder.conv_maxpool.rst | 7 + .../fastNLP.modules.encoder.embedding.rst | 7 + docs/source/fastNLP.modules.encoder.lstm.rst | 7 + docs/source/fastNLP.modules.encoder.rst | 71 +- ...stNLP.modules.encoder.star_transformer.rst | 7 + .../fastNLP.modules.encoder.transformer.rst | 7 + ...astNLP.modules.encoder.variational_rnn.rst | 7 + docs/source/fastNLP.modules.rst | 37 +- docs/source/fastNLP.rst | 19 +- docs/source/index.rst | 74 +- docs/source/modules.rst | 8 + .../tutorials/fastnlp_10tmin_tutorial.rst | 376 - .../tutorials/fastnlp_1_minute_tutorial.rst | 113 - .../tutorials/fastnlp_advanced_tutorial.rst | 5 - .../tutorials/fastnlp_developer_guide.rst | 5 - docs/source/user/installation.rst | 21 +- docs/source/user/quickstart.rst | 129 +- docs/source/user/tutorial_one.rst | 371 + docs/source/user/with_fitlog.rst | 5 + fastNLP/__init__.py | 56 + fastNLP/api/__init__.py | 1 - fastNLP/core/__init__.py | 29 +- fastNLP/core/batch.py | 185 +- fastNLP/core/callback.py | 712 +- fastNLP/core/const.py | 59 + fastNLP/core/dataset.py | 823 +- fastNLP/core/{fieldarray.py => field.py} | 503 +- fastNLP/core/instance.py | 52 +- fastNLP/core/losses.py | 226 +- fastNLP/core/metrics.py | 874 +- fastNLP/core/optimizer.py | 57 +- fastNLP/core/predictor.py | 15 +- fastNLP/core/sampler.py | 102 +- fastNLP/core/tester.py | 144 +- fastNLP/core/trainer.py | 699 +- fastNLP/core/utils.py | 497 +- fastNLP/core/vocabulary.py | 282 +- fastNLP/io/__init__.py | 31 + fastNLP/io/base_loader.py | 37 +- fastNLP/io/config_io.py | 110 +- fastNLP/io/dataset_loader.py | 1030 +- fastNLP/io/embed_loader.py | 251 +- fastNLP/io/file_reader.py | 118 + fastNLP/io/logger.py | 35 - fastNLP/io/model_io.py | 59 +- fastNLP/models/__init__.py | 32 +- fastNLP/models/base_model.py | 12 +- fastNLP/models/bert.py | 639 +- fastNLP/models/biaffine_parser.py | 399 +- fastNLP/models/char_language_model.py | 131 - fastNLP/models/cnn_text_classification.py | 63 +- fastNLP/models/enas_controller.py | 6 +- fastNLP/models/enas_model.py | 144 +- fastNLP/models/enas_trainer.py | 195 +- fastNLP/models/enas_utils.py | 14 +- fastNLP/models/sequence_labeling.py | 233 + fastNLP/models/sequence_modeling.py | 225 - fastNLP/models/snli.py | 178 +- fastNLP/models/star_transformer.py | 307 + fastNLP/modules/__init__.py | 55 +- fastNLP/modules/aggregator/__init__.py | 20 +- fastNLP/modules/aggregator/attention.py | 217 +- fastNLP/modules/aggregator/avg_pool.py | 36 - fastNLP/modules/aggregator/kmax_pool.py | 20 - fastNLP/modules/aggregator/max_pool.py | 38 - fastNLP/modules/aggregator/pooling.py | 146 + fastNLP/modules/aggregator/self_attention.py | 68 - fastNLP/modules/decoder/MLP.py | 58 - fastNLP/modules/decoder/__init__.py | 13 +- fastNLP/modules/decoder/{CRF.py => crf.py} | 251 +- fastNLP/modules/decoder/mlp.py | 96 + fastNLP/modules/decoder/utils.py | 68 + fastNLP/modules/dropout.py | 9 +- fastNLP/modules/encoder/__init__.py | 33 +- fastNLP/modules/encoder/bert.py | 377 + fastNLP/modules/encoder/char_embedding.py | 82 - fastNLP/modules/encoder/char_encoder.py | 96 + fastNLP/modules/encoder/conv.py | 58 - fastNLP/modules/encoder/conv_maxpool.py | 77 +- fastNLP/modules/encoder/embedding.py | 50 +- fastNLP/modules/encoder/linear.py | 21 - fastNLP/modules/encoder/lstm.py | 77 +- fastNLP/modules/encoder/masked_rnn.py | 424 - fastNLP/modules/encoder/star_transformer.py | 123 +- fastNLP/modules/encoder/transformer.py | 43 +- fastNLP/modules/encoder/variational_rnn.py | 233 +- fastNLP/modules/other_modules.py | 186 - fastNLP/modules/utils.py | 81 +- {fastNLP => legacy}/api/README.md | 0 legacy/api/__init__.py | 2 + {fastNLP => legacy}/api/api.py | 228 +- {fastNLP => legacy}/api/converter.py | 0 {fastNLP => legacy}/api/examples.py | 2 +- {fastNLP => legacy}/api/pipeline.py | 2 +- {fastNLP => legacy}/api/processor.py | 5 +- {fastNLP => legacy}/api/utils.py | 8 +- legacy/automl/__init__.py | 0 legacy/automl/enas_controller.py | 223 + legacy/automl/enas_model.py | 388 + legacy/automl/enas_trainer.py | 383 + legacy/automl/enas_utils.py | 53 + legacy/component/__init__.py | 1 + legacy/component/bert_tokenizer.py | 378 + reproduction/Biaffine_parser/run.py | 2 +- .../models/cws_model.py | 10 +- .../models/cws_transformer.py | 104 +- .../main.py | 4 +- reproduction/POS_tagging/train_pos_tag.py | 2 +- reproduction/README.md | 44 + reproduction/Star_transformer/datasets.py | 157 + reproduction/Star_transformer/modules.py | 56 + reproduction/Star_transformer/run.sh | 5 + reproduction/Star_transformer/train.py | 214 + reproduction/Star_transformer/util.py | 112 + requirements.txt | 6 +- setup.py | 4 +- test/api/test_pipeline.py | 6 - test/api/test_processor.py | 101 - test/core/test_batch.py | 58 +- test/core/test_callbacks.py | 77 +- test/core/test_dataset.py | 50 +- .../{test_fieldarray.py => test_field.py} | 25 +- test/core/test_instance.py | 12 +- test/core/test_loss.py | 24 +- test/core/test_metrics.py | 432 +- test/core/test_optimizer.py | 18 +- test/core/test_predictor.py | 3 +- test/core/test_sampler.py | 14 +- test/core/test_tester.py | 30 +- test/core/test_trainer.py | 71 +- test/core/test_utils.py | 252 + test/core/test_vocabulary.py | 94 +- test/data_for_tests/sample_snli.jsonl | 3 + test/data_for_tests/word2vec_test.txt | 7 + test/io/config | 62 - test/io/test_config_saver.py | 112 - test/io/test_dataset_loader.py | 29 +- test/io/test_embed_loader.py | 50 +- test/models/__init__.py | 0 test/models/model_runner.py | 156 + test/models/test_biaffine_parser.py | 123 +- test/models/test_cnn_text_classification.py | 18 + test/models/test_enas.py | 112 - test/models/test_sequence_labeling.py | 36 + test/models/test_star_trans.py | 16 + test/modules/decoder/test_CRF.py | 36 +- ...char_embedding.py => test_char_encoder.py} | 6 +- test/modules/test_masked_rnn.py | 27 - test/modules/test_other_modules.py | 46 - test/modules/test_utils.py | 9 - test/test_tutorials.py | 300 +- tutorials/README.md | 11 +- tutorials/fastNLP_padding_tutorial.ipynb | 370 - tutorials/fastnlp_10min_tutorial.ipynb | 762 - tutorials/fastnlp_1min_tutorial.ipynb | 248 - .../advance_tutorial.ipynb | 1169 - .../fastnlp_advanced_tutorial/data/config | 8 - .../fastnlp_advanced_tutorial/hypothesis | 100 - tutorials/fastnlp_advanced_tutorial/label | 100 - tutorials/fastnlp_advanced_tutorial/premise | 100 - .../tutorial_sample_dataset.csv | 77 - tutorials/fastnlp_advanced_tutorial/vocab.txt | 30522 ---------------- tutorials/fastnlp_test_tutorial.ipynb | 97 - tutorials/quickstart.ipynb | 280 + tutorials/tutorial_1.ipynb | 831 + tutorials/tutorial_for_developer.md | 283 - 206 files changed, 13065 insertions(+), 41275 deletions(-) create mode 100644 MANIFEST.in delete mode 100644 docs/source/fastNLP.api.rst create mode 100644 docs/source/fastNLP.core.batch.rst create mode 100644 docs/source/fastNLP.core.callback.rst create mode 100644 docs/source/fastNLP.core.const.rst create mode 100644 docs/source/fastNLP.core.dataset.rst create mode 100644 docs/source/fastNLP.core.field.rst create mode 100644 docs/source/fastNLP.core.instance.rst create mode 100644 docs/source/fastNLP.core.losses.rst create mode 100644 docs/source/fastNLP.core.metrics.rst create mode 100644 docs/source/fastNLP.core.optimizer.rst create mode 100644 docs/source/fastNLP.core.sampler.rst create mode 100644 docs/source/fastNLP.core.tester.rst create mode 100644 docs/source/fastNLP.core.trainer.rst create mode 100644 docs/source/fastNLP.core.utils.rst create mode 100644 docs/source/fastNLP.core.vocabulary.rst create mode 100644 docs/source/fastNLP.io.base_loader.rst create mode 100644 docs/source/fastNLP.io.dataset_loader.rst create mode 100644 docs/source/fastNLP.io.embed_loader.rst create mode 100644 docs/source/fastNLP.io.model_io.rst create mode 100644 docs/source/fastNLP.models.biaffine_parser.rst create mode 100644 docs/source/fastNLP.models.cnn_text_classification.rst create mode 100644 docs/source/fastNLP.models.sequence_labeling.rst create mode 100644 docs/source/fastNLP.models.snli.rst create mode 100644 docs/source/fastNLP.models.star_transformer.rst create mode 100644 docs/source/fastNLP.modules.aggregator.attention.rst create mode 100644 docs/source/fastNLP.modules.aggregator.pooling.rst create mode 100644 docs/source/fastNLP.modules.decoder.crf.rst create mode 100644 docs/source/fastNLP.modules.decoder.mlp.rst create mode 100644 docs/source/fastNLP.modules.decoder.utils.rst create mode 100644 docs/source/fastNLP.modules.encoder.bert.rst create mode 100644 docs/source/fastNLP.modules.encoder.char_encoder.rst create mode 100644 docs/source/fastNLP.modules.encoder.conv_maxpool.rst create mode 100644 docs/source/fastNLP.modules.encoder.embedding.rst create mode 100644 docs/source/fastNLP.modules.encoder.lstm.rst create mode 100644 docs/source/fastNLP.modules.encoder.star_transformer.rst create mode 100644 docs/source/fastNLP.modules.encoder.transformer.rst create mode 100644 docs/source/fastNLP.modules.encoder.variational_rnn.rst create mode 100644 docs/source/modules.rst delete mode 100644 docs/source/tutorials/fastnlp_10tmin_tutorial.rst delete mode 100644 docs/source/tutorials/fastnlp_1_minute_tutorial.rst delete mode 100644 docs/source/tutorials/fastnlp_advanced_tutorial.rst delete mode 100644 docs/source/tutorials/fastnlp_developer_guide.rst create mode 100644 docs/source/user/tutorial_one.rst create mode 100644 docs/source/user/with_fitlog.rst delete mode 100644 fastNLP/api/__init__.py create mode 100644 fastNLP/core/const.py rename fastNLP/core/{fieldarray.py => field.py} (54%) create mode 100644 fastNLP/io/file_reader.py delete mode 100644 fastNLP/io/logger.py delete mode 100644 fastNLP/models/char_language_model.py create mode 100644 fastNLP/models/sequence_labeling.py delete mode 100644 fastNLP/models/sequence_modeling.py create mode 100644 fastNLP/models/star_transformer.py delete mode 100644 fastNLP/modules/aggregator/avg_pool.py delete mode 100644 fastNLP/modules/aggregator/kmax_pool.py delete mode 100644 fastNLP/modules/aggregator/max_pool.py create mode 100644 fastNLP/modules/aggregator/pooling.py delete mode 100644 fastNLP/modules/aggregator/self_attention.py delete mode 100644 fastNLP/modules/decoder/MLP.py rename fastNLP/modules/decoder/{CRF.py => crf.py} (51%) create mode 100644 fastNLP/modules/decoder/mlp.py create mode 100644 fastNLP/modules/decoder/utils.py create mode 100644 fastNLP/modules/encoder/bert.py delete mode 100644 fastNLP/modules/encoder/char_embedding.py create mode 100644 fastNLP/modules/encoder/char_encoder.py delete mode 100644 fastNLP/modules/encoder/conv.py delete mode 100644 fastNLP/modules/encoder/linear.py delete mode 100644 fastNLP/modules/encoder/masked_rnn.py delete mode 100644 fastNLP/modules/other_modules.py rename {fastNLP => legacy}/api/README.md (100%) create mode 100644 legacy/api/__init__.py rename {fastNLP => legacy}/api/api.py (75%) rename {fastNLP => legacy}/api/converter.py (100%) rename {fastNLP => legacy}/api/examples.py (98%) rename {fastNLP => legacy}/api/pipeline.py (95%) rename {fastNLP => legacy}/api/processor.py (99%) rename {fastNLP => legacy}/api/utils.py (97%) create mode 100644 legacy/automl/__init__.py create mode 100644 legacy/automl/enas_controller.py create mode 100644 legacy/automl/enas_model.py create mode 100644 legacy/automl/enas_trainer.py create mode 100644 legacy/automl/enas_utils.py create mode 100644 legacy/component/__init__.py create mode 100644 legacy/component/bert_tokenizer.py create mode 100644 reproduction/README.md create mode 100644 reproduction/Star_transformer/datasets.py create mode 100644 reproduction/Star_transformer/modules.py create mode 100644 reproduction/Star_transformer/run.sh create mode 100644 reproduction/Star_transformer/train.py create mode 100644 reproduction/Star_transformer/util.py delete mode 100644 test/api/test_pipeline.py delete mode 100644 test/api/test_processor.py rename test/core/{test_fieldarray.py => test_field.py} (90%) create mode 100644 test/core/test_utils.py create mode 100644 test/data_for_tests/sample_snli.jsonl create mode 100644 test/data_for_tests/word2vec_test.txt delete mode 100644 test/io/config delete mode 100644 test/io/test_config_saver.py create mode 100644 test/models/__init__.py create mode 100644 test/models/model_runner.py create mode 100644 test/models/test_cnn_text_classification.py delete mode 100644 test/models/test_enas.py create mode 100644 test/models/test_sequence_labeling.py create mode 100644 test/models/test_star_trans.py rename test/modules/{test_char_embedding.py => test_char_encoder.py} (81%) delete mode 100644 test/modules/test_masked_rnn.py delete mode 100644 test/modules/test_utils.py delete mode 100644 tutorials/fastNLP_padding_tutorial.ipynb delete mode 100644 tutorials/fastnlp_10min_tutorial.ipynb delete mode 100644 tutorials/fastnlp_1min_tutorial.ipynb delete mode 100644 tutorials/fastnlp_advanced_tutorial/advance_tutorial.ipynb delete mode 100644 tutorials/fastnlp_advanced_tutorial/data/config delete mode 100644 tutorials/fastnlp_advanced_tutorial/hypothesis delete mode 100644 tutorials/fastnlp_advanced_tutorial/label delete mode 100644 tutorials/fastnlp_advanced_tutorial/premise delete mode 100644 tutorials/fastnlp_advanced_tutorial/tutorial_sample_dataset.csv delete mode 100644 tutorials/fastnlp_advanced_tutorial/vocab.txt delete mode 100644 tutorials/fastnlp_test_tutorial.ipynb create mode 100644 tutorials/quickstart.ipynb create mode 100644 tutorials/tutorial_1.ipynb delete mode 100644 tutorials/tutorial_for_developer.md diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..d893b45a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include requirements.txt +include LICENSE +include README.md +prune test/ +prune reproduction/ +prune fastNLP/api +prune fastNLP/automl \ No newline at end of file diff --git a/README.md b/README.md index f1cdf128..f4a8f2a6 100644 --- a/README.md +++ b/README.md @@ -6,87 +6,108 @@ ![Hex.pm](https://img.shields.io/hexpm/l/plug.svg) [![Documentation Status](https://readthedocs.org/projects/fastnlp/badge/?version=latest)](http://fastnlp.readthedocs.io/?badge=latest) -FastNLP is a modular Natural Language Processing system based on PyTorch, built for fast development of NLP models. +fastNLP 是一款轻量级的 NLP 处理套件。你既可以使用它快速地完成一个命名实体识别(NER)、中文分词或文本分类任务; 也可以使用他构建许多复杂的网络模型,进行科研。它具有如下的特性: + +- 统一的Tabular式数据容器,让数据预处理过程简洁明了。内置多种数据集的DataSet Loader,省去预处理代码。 +- 各种方便的NLP工具,例如预处理embedding加载; 中间数据cache等; +- 详尽的中文文档以供查阅; +- 提供诸多高级模块,例如Variational LSTM, Transformer, CRF等; +- 封装CNNText,Biaffine等模型可供直接使用; +- 便捷且具有扩展性的训练器; 提供多种内置callback函数,方便实验记录、异常捕获等。 + + +## 安装指南 + +fastNLP 依赖如下包: + ++ numpy ++ torch>=0.4.0 ++ tqdm ++ nltk + +其中torch的安装可能与操作系统及 CUDA 的版本相关,请参见 PyTorch 官网 。 +在依赖包安装完成的情况,您可以在命令行执行如下指令完成安装 + +```shell +pip install fastNLP +``` + + +## 内置组件 + +大部分用于的 NLP 任务神经网络都可以看做由编码(encoder)、聚合(aggregator)、解码(decoder)三种模块组成。 + + +![](./docs/source/figures/text_classification.png) + +fastNLP 在 modules 模块中内置了三种模块的诸多组件,可以帮助用户快速搭建自己所需的网络。 三种模块的功能和常见组件如下: -A deep learning NLP model is the composition of three types of modules: - - - + + + - + - + - +
module type functionality example 类型 功能 例子
encoder encode the input into some abstract representation 将输入编码为具有具 有表示能力的向量 embedding, RNN, CNN, transformer
aggregator aggregate and reduce information 从多个向量中聚合信息 self-attention, max-pooling
decoder decode the representation into the output 将具有某种表示意义的 向量解码为需要的输出 形式 MLP, CRF
-For example: - -![](docs/source/figures/text_classification.png) - -## Requirements - -- Python>=3.6 -- numpy>=1.14.2 -- torch>=0.4.0 -- tensorboardX -- tqdm>=4.28.1 +## 完整模型 +fastNLP 为不同的 NLP 任务实现了许多完整的模型,它们都经过了训练和测试。 -## Resources +你可以在以下两个地方查看相关信息 +- [介绍](reproduction/) +- [源码](fastNLP/models/) -- [Tutorials](https://github.com/fastnlp/fastNLP/tree/master/tutorials) -- [Documentation](https://fastnlp.readthedocs.io/en/latest/) -- [Source Code](https://github.com/fastnlp/fastNLP) - - -## Installation -Run the following commands to install fastNLP package. -```shell -pip install fastNLP -``` +## 项目结构 +![](./docs/source/figures/workflow.png) -## Project Structure +fastNLP的大致工作流程如上图所示,而项目结构如下: - - - - - + - + - + - + - +
fastNLP an open-source NLP library
fastNLP.api APIs for end-to-end prediction 开源的自然语言处理库
fastNLP.core data representation & train/test procedure 实现了核心功能,包括数据处理组件、训练器、测速器等
fastNLP.models a collection of NLP models 实现了一些完整的神经网络模型
fastNLP.modules a collection of PyTorch sub-models/components/wheels 实现了用于搭建神经网络模型的诸多组件
fastNLP.io readers & savers 实现了读写功能,包括数据读入,模型读写等
+## 参考资源 + +- [教程](https://github.com/fastnlp/fastNLP/tree/master/tutorials) +- [文档](https://fastnlp.readthedocs.io/en/latest/) +- [源码](https://github.com/fastnlp/fastNLP) + + *In memory of @FengZiYjun. May his soul rest in peace. We will miss you very very much!* \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index e978dfe6..6ba2fa54 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,6 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = +SPHINXAPIDOC = sphinx-apidoc SPHINXBUILD = sphinx-build SPHINXPROJ = fastNLP SOURCEDIR = source @@ -12,6 +13,12 @@ BUILDDIR = build help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +apidoc: + $(SPHINXAPIDOC) -efM -o source ../$(SPHINXPROJ) + +server: + cd build/html && python -m http.server + .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new diff --git a/docs/source/conf.py b/docs/source/conf.py index e449a9f8..3e9753af 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,6 +14,7 @@ # import os import sys + sys.path.insert(0, os.path.abspath('../../')) # -- Project information ----------------------------------------------------- @@ -23,10 +24,9 @@ copyright = '2018, xpqiu' author = 'xpqiu' # The short X.Y version -version = '0.2' +version = '0.4' # The full version, including alpha/beta/rc tags -release = '0.2' - +release = '0.4' # -- General configuration --------------------------------------------------- @@ -42,9 +42,15 @@ extensions = [ 'sphinx.ext.viewcode', 'sphinx.ext.autosummary', 'sphinx.ext.mathjax', - + 'sphinx.ext.todo' ] +autodoc_default_options = { + 'member-order': 'bysource', + 'special-members': '__init__', + 'undoc-members': True, +} + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -62,17 +68,16 @@ master_doc = 'index' # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "zh_CN" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . -exclude_patterns = [] +exclude_patterns = ['modules.rst'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -84,7 +89,10 @@ html_theme = 'sphinx_rtd_theme' # further. For a list of options available for each theme, see the # documentation. # -# html_theme_options = {} +html_theme_options = { + 'collapse_navigation': False, + 'titles_only': True +} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -107,22 +115,21 @@ html_static_path = ['_static'] # Output file base name for HTML help builder. htmlhelp_basename = 'fastNLPdoc' - # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - + # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - + # Additional stuff for the LaTeX preamble. # # 'preamble': '', - + # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -136,7 +143,6 @@ latex_documents = [ 'xpqiu', 'manual'), ] - # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples @@ -146,7 +152,6 @@ man_pages = [ [author], 1) ] - # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples @@ -159,4 +164,14 @@ texinfo_documents = [ ] -# -- Extension configuration ------------------------------------------------- \ No newline at end of file +# -- Extension configuration ------------------------------------------------- +def maybe_skip_member(app, what, name, obj, skip, options): + if name.startswith("_"): + return True + if obj.__doc__ is None: + return True + return False + + +def setup(app): + app.connect('autodoc-skip-member', maybe_skip_member) diff --git a/docs/source/fastNLP.api.rst b/docs/source/fastNLP.api.rst deleted file mode 100644 index eb9192da..00000000 --- a/docs/source/fastNLP.api.rst +++ /dev/null @@ -1,36 +0,0 @@ -fastNLP.api -============ - -fastNLP.api.api ----------------- - -.. automodule:: fastNLP.api.api - :members: - -fastNLP.api.converter ----------------------- - -.. automodule:: fastNLP.api.converter - :members: - -fastNLP.api.model\_zoo ------------------------ - -.. automodule:: fastNLP.api.model_zoo - :members: - -fastNLP.api.pipeline ---------------------- - -.. automodule:: fastNLP.api.pipeline - :members: - -fastNLP.api.processor ----------------------- - -.. automodule:: fastNLP.api.processor - :members: - - -.. automodule:: fastNLP.api - :members: diff --git a/docs/source/fastNLP.core.batch.rst b/docs/source/fastNLP.core.batch.rst new file mode 100644 index 00000000..33a5b730 --- /dev/null +++ b/docs/source/fastNLP.core.batch.rst @@ -0,0 +1,7 @@ +fastNLP.core.batch +================== + +.. automodule:: fastNLP.core.batch + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.callback.rst b/docs/source/fastNLP.core.callback.rst new file mode 100644 index 00000000..31ec627b --- /dev/null +++ b/docs/source/fastNLP.core.callback.rst @@ -0,0 +1,7 @@ +fastNLP.core.callback +===================== + +.. automodule:: fastNLP.core.callback + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.const.rst b/docs/source/fastNLP.core.const.rst new file mode 100644 index 00000000..c9e3bd97 --- /dev/null +++ b/docs/source/fastNLP.core.const.rst @@ -0,0 +1,7 @@ +fastNLP.core.const +================== + +.. automodule:: fastNLP.core.const + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.dataset.rst b/docs/source/fastNLP.core.dataset.rst new file mode 100644 index 00000000..b377cb0f --- /dev/null +++ b/docs/source/fastNLP.core.dataset.rst @@ -0,0 +1,7 @@ +fastNLP.core.dataset +==================== + +.. automodule:: fastNLP.core.dataset + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.field.rst b/docs/source/fastNLP.core.field.rst new file mode 100644 index 00000000..7686e79a --- /dev/null +++ b/docs/source/fastNLP.core.field.rst @@ -0,0 +1,7 @@ +fastNLP.core.field +================== + +.. automodule:: fastNLP.core.field + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.instance.rst b/docs/source/fastNLP.core.instance.rst new file mode 100644 index 00000000..14393a91 --- /dev/null +++ b/docs/source/fastNLP.core.instance.rst @@ -0,0 +1,7 @@ +fastNLP.core.instance +===================== + +.. automodule:: fastNLP.core.instance + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.losses.rst b/docs/source/fastNLP.core.losses.rst new file mode 100644 index 00000000..d2dd492b --- /dev/null +++ b/docs/source/fastNLP.core.losses.rst @@ -0,0 +1,7 @@ +fastNLP.core.losses +=================== + +.. automodule:: fastNLP.core.losses + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.metrics.rst b/docs/source/fastNLP.core.metrics.rst new file mode 100644 index 00000000..69afff36 --- /dev/null +++ b/docs/source/fastNLP.core.metrics.rst @@ -0,0 +1,7 @@ +fastNLP.core.metrics +==================== + +.. automodule:: fastNLP.core.metrics + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.optimizer.rst b/docs/source/fastNLP.core.optimizer.rst new file mode 100644 index 00000000..e2100d2e --- /dev/null +++ b/docs/source/fastNLP.core.optimizer.rst @@ -0,0 +1,7 @@ +fastNLP.core.optimizer +====================== + +.. automodule:: fastNLP.core.optimizer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.rst b/docs/source/fastNLP.core.rst index b9f6c89f..82c13e46 100644 --- a/docs/source/fastNLP.core.rst +++ b/docs/source/fastNLP.core.rst @@ -1,84 +1,29 @@ -fastNLP.core -============= - -fastNLP.core.batch -------------------- - -.. automodule:: fastNLP.core.batch - :members: - -fastNLP.core.dataset ---------------------- - -.. automodule:: fastNLP.core.dataset - :members: - -fastNLP.core.fieldarray ------------------------- - -.. automodule:: fastNLP.core.fieldarray - :members: - -fastNLP.core.instance ----------------------- - -.. automodule:: fastNLP.core.instance - :members: - -fastNLP.core.losses --------------------- - -.. automodule:: fastNLP.core.losses - :members: - -fastNLP.core.metrics ---------------------- - -.. automodule:: fastNLP.core.metrics - :members: - -fastNLP.core.optimizer ------------------------ - -.. automodule:: fastNLP.core.optimizer - :members: - -fastNLP.core.predictor ------------------------ - -.. automodule:: fastNLP.core.predictor - :members: - -fastNLP.core.sampler ---------------------- - -.. automodule:: fastNLP.core.sampler - :members: - -fastNLP.core.tester --------------------- - -.. automodule:: fastNLP.core.tester - :members: - -fastNLP.core.trainer ---------------------- - -.. automodule:: fastNLP.core.trainer - :members: - -fastNLP.core.utils -------------------- - -.. automodule:: fastNLP.core.utils - :members: - -fastNLP.core.vocabulary ------------------------- - -.. automodule:: fastNLP.core.vocabulary - :members: - +fastNLP.core +============ .. automodule:: fastNLP.core :members: + :undoc-members: + :show-inheritance: + +子模块 +---------- + +.. toctree:: + :titlesonly: + + fastNLP.core.batch + fastNLP.core.callback + fastNLP.core.const + fastNLP.core.dataset + fastNLP.core.field + fastNLP.core.instance + fastNLP.core.losses + fastNLP.core.metrics + fastNLP.core.optimizer + fastNLP.core.sampler + fastNLP.core.tester + fastNLP.core.trainer + fastNLP.core.utils + fastNLP.core.vocabulary + diff --git a/docs/source/fastNLP.core.sampler.rst b/docs/source/fastNLP.core.sampler.rst new file mode 100644 index 00000000..1810d59c --- /dev/null +++ b/docs/source/fastNLP.core.sampler.rst @@ -0,0 +1,7 @@ +fastNLP.core.sampler +==================== + +.. automodule:: fastNLP.core.sampler + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.tester.rst b/docs/source/fastNLP.core.tester.rst new file mode 100644 index 00000000..a9e7e09f --- /dev/null +++ b/docs/source/fastNLP.core.tester.rst @@ -0,0 +1,7 @@ +fastNLP.core.tester +=================== + +.. automodule:: fastNLP.core.tester + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.trainer.rst b/docs/source/fastNLP.core.trainer.rst new file mode 100644 index 00000000..9e518d4b --- /dev/null +++ b/docs/source/fastNLP.core.trainer.rst @@ -0,0 +1,7 @@ +fastNLP.core.trainer +==================== + +.. automodule:: fastNLP.core.trainer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.utils.rst b/docs/source/fastNLP.core.utils.rst new file mode 100644 index 00000000..fcd3f50c --- /dev/null +++ b/docs/source/fastNLP.core.utils.rst @@ -0,0 +1,7 @@ +fastNLP.core.utils +================== + +.. automodule:: fastNLP.core.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.core.vocabulary.rst b/docs/source/fastNLP.core.vocabulary.rst new file mode 100644 index 00000000..b3bf4bac --- /dev/null +++ b/docs/source/fastNLP.core.vocabulary.rst @@ -0,0 +1,7 @@ +fastNLP.core.vocabulary +======================= + +.. automodule:: fastNLP.core.vocabulary + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.io.base_loader.rst b/docs/source/fastNLP.io.base_loader.rst new file mode 100644 index 00000000..c1f9ac14 --- /dev/null +++ b/docs/source/fastNLP.io.base_loader.rst @@ -0,0 +1,7 @@ +fastNLP.io.base\_loader +======================= + +.. automodule:: fastNLP.io.base_loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.io.dataset_loader.rst b/docs/source/fastNLP.io.dataset_loader.rst new file mode 100644 index 00000000..d6663e59 --- /dev/null +++ b/docs/source/fastNLP.io.dataset_loader.rst @@ -0,0 +1,7 @@ +fastNLP.io.dataset\_loader +========================== + +.. automodule:: fastNLP.io.dataset_loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.io.embed_loader.rst b/docs/source/fastNLP.io.embed_loader.rst new file mode 100644 index 00000000..7a8e730c --- /dev/null +++ b/docs/source/fastNLP.io.embed_loader.rst @@ -0,0 +1,7 @@ +fastNLP.io.embed\_loader +======================== + +.. automodule:: fastNLP.io.embed_loader + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.io.model_io.rst b/docs/source/fastNLP.io.model_io.rst new file mode 100644 index 00000000..50d4c25a --- /dev/null +++ b/docs/source/fastNLP.io.model_io.rst @@ -0,0 +1,7 @@ +fastNLP.io.model\_io +==================== + +.. automodule:: fastNLP.io.model_io + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.io.rst b/docs/source/fastNLP.io.rst index d91e0d1c..fad05a21 100644 --- a/docs/source/fastNLP.io.rst +++ b/docs/source/fastNLP.io.rst @@ -1,42 +1,19 @@ -fastNLP.io -=========== +fastNLP.io +========== -fastNLP.io.base\_loader ------------------------- - -.. automodule:: fastNLP.io.base_loader - :members: - -fastNLP.io.config\_io ----------------------- - -.. automodule:: fastNLP.io.config_io - :members: - -fastNLP.io.dataset\_loader ---------------------------- - -.. automodule:: fastNLP.io.dataset_loader - :members: - -fastNLP.io.embed\_loader -------------------------- - -.. automodule:: fastNLP.io.embed_loader - :members: - -fastNLP.io.logger ------------------- - -.. automodule:: fastNLP.io.logger +.. automodule:: fastNLP.io :members: + :undoc-members: + :show-inheritance: -fastNLP.io.model\_io ---------------------- +子模块 +---------- -.. automodule:: fastNLP.io.model_io - :members: +.. toctree:: + :titlesonly: + fastNLP.io.base_loader + fastNLP.io.dataset_loader + fastNLP.io.embed_loader + fastNLP.io.model_io -.. automodule:: fastNLP.io - :members: diff --git a/docs/source/fastNLP.models.biaffine_parser.rst b/docs/source/fastNLP.models.biaffine_parser.rst new file mode 100644 index 00000000..a3dd1836 --- /dev/null +++ b/docs/source/fastNLP.models.biaffine_parser.rst @@ -0,0 +1,7 @@ +fastNLP.models.biaffine\_parser +=============================== + +.. automodule:: fastNLP.models.biaffine_parser + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.models.cnn_text_classification.rst b/docs/source/fastNLP.models.cnn_text_classification.rst new file mode 100644 index 00000000..a935d0bf --- /dev/null +++ b/docs/source/fastNLP.models.cnn_text_classification.rst @@ -0,0 +1,7 @@ +fastNLP.models.cnn\_text\_classification +======================================== + +.. automodule:: fastNLP.models.cnn_text_classification + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.models.rst b/docs/source/fastNLP.models.rst index 7452fdf6..5858ebcd 100644 --- a/docs/source/fastNLP.models.rst +++ b/docs/source/fastNLP.models.rst @@ -1,42 +1,20 @@ -fastNLP.models -=============== +fastNLP.models +============== -fastNLP.models.base\_model ---------------------------- - -.. automodule:: fastNLP.models.base_model - :members: - -fastNLP.models.biaffine\_parser --------------------------------- - -.. automodule:: fastNLP.models.biaffine_parser - :members: - -fastNLP.models.char\_language\_model -------------------------------------- - -.. automodule:: fastNLP.models.char_language_model - :members: - -fastNLP.models.cnn\_text\_classification ------------------------------------------ - -.. automodule:: fastNLP.models.cnn_text_classification - :members: - -fastNLP.models.sequence\_modeling ----------------------------------- - -.. automodule:: fastNLP.models.sequence_modeling +.. automodule:: fastNLP.models :members: + :undoc-members: + :show-inheritance: -fastNLP.models.snli --------------------- +子模块 +---------- -.. automodule:: fastNLP.models.snli - :members: +.. toctree:: + :titlesonly: + fastNLP.models.biaffine_parser + fastNLP.models.cnn_text_classification + fastNLP.models.sequence_labeling + fastNLP.models.snli + fastNLP.models.star_transformer -.. automodule:: fastNLP.models - :members: diff --git a/docs/source/fastNLP.models.sequence_labeling.rst b/docs/source/fastNLP.models.sequence_labeling.rst new file mode 100644 index 00000000..6d569fe1 --- /dev/null +++ b/docs/source/fastNLP.models.sequence_labeling.rst @@ -0,0 +1,7 @@ +fastNLP.models.sequence\_labeling +================================= + +.. automodule:: fastNLP.models.sequence_labeling + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.models.snli.rst b/docs/source/fastNLP.models.snli.rst new file mode 100644 index 00000000..24c2cc53 --- /dev/null +++ b/docs/source/fastNLP.models.snli.rst @@ -0,0 +1,7 @@ +fastNLP.models.snli +=================== + +.. automodule:: fastNLP.models.snli + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.models.star_transformer.rst b/docs/source/fastNLP.models.star_transformer.rst new file mode 100644 index 00000000..c93fb8cd --- /dev/null +++ b/docs/source/fastNLP.models.star_transformer.rst @@ -0,0 +1,7 @@ +fastNLP.models.star\_transformer +================================ + +.. automodule:: fastNLP.models.star_transformer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.aggregator.attention.rst b/docs/source/fastNLP.modules.aggregator.attention.rst new file mode 100644 index 00000000..dc9c2b53 --- /dev/null +++ b/docs/source/fastNLP.modules.aggregator.attention.rst @@ -0,0 +1,7 @@ +fastNLP.modules.aggregator.attention +==================================== + +.. automodule:: fastNLP.modules.aggregator.attention + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.aggregator.pooling.rst b/docs/source/fastNLP.modules.aggregator.pooling.rst new file mode 100644 index 00000000..162f889d --- /dev/null +++ b/docs/source/fastNLP.modules.aggregator.pooling.rst @@ -0,0 +1,7 @@ +fastNLP.modules.aggregator.pooling +================================== + +.. automodule:: fastNLP.modules.aggregator.pooling + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.aggregator.rst b/docs/source/fastNLP.modules.aggregator.rst index 073da4a5..44398325 100644 --- a/docs/source/fastNLP.modules.aggregator.rst +++ b/docs/source/fastNLP.modules.aggregator.rst @@ -1,36 +1,17 @@ -fastNLP.modules.aggregator -=========================== +fastNLP.modules.aggregator +========================== -fastNLP.modules.aggregator.attention -------------------------------------- - -.. automodule:: fastNLP.modules.aggregator.attention - :members: - -fastNLP.modules.aggregator.avg\_pool -------------------------------------- - -.. automodule:: fastNLP.modules.aggregator.avg_pool - :members: - -fastNLP.modules.aggregator.kmax\_pool --------------------------------------- - -.. automodule:: fastNLP.modules.aggregator.kmax_pool +.. automodule:: fastNLP.modules.aggregator :members: + :undoc-members: + :show-inheritance: -fastNLP.modules.aggregator.max\_pool -------------------------------------- - -.. automodule:: fastNLP.modules.aggregator.max_pool - :members: +子模块 +---------- -fastNLP.modules.aggregator.self\_attention -------------------------------------------- +.. toctree:: + :titlesonly: -.. automodule:: fastNLP.modules.aggregator.self_attention - :members: + fastNLP.modules.aggregator.attention + fastNLP.modules.aggregator.pooling - -.. automodule:: fastNLP.modules.aggregator - :members: diff --git a/docs/source/fastNLP.modules.decoder.crf.rst b/docs/source/fastNLP.modules.decoder.crf.rst new file mode 100644 index 00000000..6d5b0d5b --- /dev/null +++ b/docs/source/fastNLP.modules.decoder.crf.rst @@ -0,0 +1,7 @@ +fastNLP.modules.decoder.CRF +=========================== + +.. automodule:: fastNLP.modules.decoder.crf + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.decoder.mlp.rst b/docs/source/fastNLP.modules.decoder.mlp.rst new file mode 100644 index 00000000..7d661ebf --- /dev/null +++ b/docs/source/fastNLP.modules.decoder.mlp.rst @@ -0,0 +1,7 @@ +fastNLP.modules.decoder.MLP +=========================== + +.. automodule:: fastNLP.modules.decoder.mlp + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.decoder.rst b/docs/source/fastNLP.modules.decoder.rst index 6844543a..e42a9f39 100644 --- a/docs/source/fastNLP.modules.decoder.rst +++ b/docs/source/fastNLP.modules.decoder.rst @@ -1,18 +1,18 @@ -fastNLP.modules.decoder -======================== +fastNLP.modules.decoder +======================= -fastNLP.modules.decoder.CRF ----------------------------- - -.. automodule:: fastNLP.modules.decoder.CRF +.. automodule:: fastNLP.modules.decoder :members: + :undoc-members: + :show-inheritance: -fastNLP.modules.decoder.MLP ----------------------------- +子模块 +---------- -.. automodule:: fastNLP.modules.decoder.MLP - :members: +.. toctree:: + :titlesonly: + fastNLP.modules.decoder.crf + fastNLP.modules.decoder.mlp + fastNLP.modules.decoder.utils -.. automodule:: fastNLP.modules.decoder - :members: diff --git a/docs/source/fastNLP.modules.decoder.utils.rst b/docs/source/fastNLP.modules.decoder.utils.rst new file mode 100644 index 00000000..da979d99 --- /dev/null +++ b/docs/source/fastNLP.modules.decoder.utils.rst @@ -0,0 +1,7 @@ +fastNLP.modules.decoder.utils +============================= + +.. automodule:: fastNLP.modules.decoder.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.bert.rst b/docs/source/fastNLP.modules.encoder.bert.rst new file mode 100644 index 00000000..66bd0bbd --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.bert.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.bert +============================ + +.. automodule:: fastNLP.modules.encoder.bert + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.char_encoder.rst b/docs/source/fastNLP.modules.encoder.char_encoder.rst new file mode 100644 index 00000000..61ea3340 --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.char_encoder.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.char\_encoder +===================================== + +.. automodule:: fastNLP.modules.encoder.char_encoder + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.conv_maxpool.rst b/docs/source/fastNLP.modules.encoder.conv_maxpool.rst new file mode 100644 index 00000000..7058a723 --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.conv_maxpool.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.conv\_maxpool +===================================== + +.. automodule:: fastNLP.modules.encoder.conv_maxpool + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.embedding.rst b/docs/source/fastNLP.modules.encoder.embedding.rst new file mode 100644 index 00000000..4427b3bf --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.embedding.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.embedding +================================= + +.. automodule:: fastNLP.modules.encoder.embedding + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.lstm.rst b/docs/source/fastNLP.modules.encoder.lstm.rst new file mode 100644 index 00000000..f9cbea88 --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.lstm.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.lstm +============================ + +.. automodule:: fastNLP.modules.encoder.lstm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.rst b/docs/source/fastNLP.modules.encoder.rst index ea8fc699..b15232fa 100644 --- a/docs/source/fastNLP.modules.encoder.rst +++ b/docs/source/fastNLP.modules.encoder.rst @@ -1,60 +1,23 @@ -fastNLP.modules.encoder -======================== +fastNLP.modules.encoder +======================= -fastNLP.modules.encoder.char\_embedding ----------------------------------------- - -.. automodule:: fastNLP.modules.encoder.char_embedding - :members: - -fastNLP.modules.encoder.conv ------------------------------ - -.. automodule:: fastNLP.modules.encoder.conv - :members: - -fastNLP.modules.encoder.conv\_maxpool --------------------------------------- - -.. automodule:: fastNLP.modules.encoder.conv_maxpool - :members: - -fastNLP.modules.encoder.embedding ----------------------------------- - -.. automodule:: fastNLP.modules.encoder.embedding - :members: - -fastNLP.modules.encoder.linear -------------------------------- - -.. automodule:: fastNLP.modules.encoder.linear - :members: - -fastNLP.modules.encoder.lstm ------------------------------ - -.. automodule:: fastNLP.modules.encoder.lstm - :members: - -fastNLP.modules.encoder.masked\_rnn ------------------------------------- - -.. automodule:: fastNLP.modules.encoder.masked_rnn - :members: - -fastNLP.modules.encoder.transformer ------------------------------------- - -.. automodule:: fastNLP.modules.encoder.transformer +.. automodule:: fastNLP.modules.encoder :members: + :undoc-members: + :show-inheritance: -fastNLP.modules.encoder.variational\_rnn ------------------------------------------ +子模块 +---------- -.. automodule:: fastNLP.modules.encoder.variational_rnn - :members: +.. toctree:: + :titlesonly: + fastNLP.modules.encoder.bert + fastNLP.modules.encoder.char_encoder + fastNLP.modules.encoder.conv_maxpool + fastNLP.modules.encoder.embedding + fastNLP.modules.encoder.lstm + fastNLP.modules.encoder.star_transformer + fastNLP.modules.encoder.transformer + fastNLP.modules.encoder.variational_rnn -.. automodule:: fastNLP.modules.encoder - :members: diff --git a/docs/source/fastNLP.modules.encoder.star_transformer.rst b/docs/source/fastNLP.modules.encoder.star_transformer.rst new file mode 100644 index 00000000..0c406782 --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.star_transformer.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.star\_transformer +========================================= + +.. automodule:: fastNLP.modules.encoder.star_transformer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.transformer.rst b/docs/source/fastNLP.modules.encoder.transformer.rst new file mode 100644 index 00000000..6a40c597 --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.transformer.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.transformer +=================================== + +.. automodule:: fastNLP.modules.encoder.transformer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.encoder.variational_rnn.rst b/docs/source/fastNLP.modules.encoder.variational_rnn.rst new file mode 100644 index 00000000..348fb3d8 --- /dev/null +++ b/docs/source/fastNLP.modules.encoder.variational_rnn.rst @@ -0,0 +1,7 @@ +fastNLP.modules.encoder.variational\_rnn +======================================== + +.. automodule:: fastNLP.modules.encoder.variational_rnn + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/fastNLP.modules.rst b/docs/source/fastNLP.modules.rst index 965fb27d..d04ccdcf 100644 --- a/docs/source/fastNLP.modules.rst +++ b/docs/source/fastNLP.modules.rst @@ -1,30 +1,17 @@ -fastNLP.modules -================ +fastNLP.modules +=============== -.. toctree:: - - fastNLP.modules.aggregator - fastNLP.modules.decoder - fastNLP.modules.encoder - -fastNLP.modules.dropout ------------------------- - -.. automodule:: fastNLP.modules.dropout - :members: - -fastNLP.modules.other\_modules -------------------------------- - -.. automodule:: fastNLP.modules.other_modules +.. automodule:: fastNLP.modules :members: + :undoc-members: + :show-inheritance: -fastNLP.modules.utils ----------------------- - -.. automodule:: fastNLP.modules.utils - :members: +子模块 +----------- +.. toctree:: + :titlesonly: -.. automodule:: fastNLP.modules - :members: + fastNLP.modules.aggregator + fastNLP.modules.decoder + fastNLP.modules.encoder \ No newline at end of file diff --git a/docs/source/fastNLP.rst b/docs/source/fastNLP.rst index 61882359..f0c3d41c 100644 --- a/docs/source/fastNLP.rst +++ b/docs/source/fastNLP.rst @@ -1,13 +1,20 @@ -fastNLP -======== +API 文档 +=============== + +.. automodule:: fastNLP + :members: + :undoc-members: + :show-inheritance: + +内部模块 +----------- .. toctree:: + :titlesonly: + :maxdepth: 3 - fastNLP.api fastNLP.core fastNLP.io - fastNLP.models fastNLP.modules + fastNLP.models -.. automodule:: fastNLP - :members: diff --git a/docs/source/index.rst b/docs/source/index.rst index 9f410f41..219e32f9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,63 +1,79 @@ -fastNLP documentation +fastNLP 中文文档 ===================== -A Modularized and Extensible Toolkit for Natural Language Processing. Currently still in incubation. +fastNLP 是一款轻量级的 NLP 处理套件。你既可以使用它快速地完成一个命名实体识别(NER)、中文分词或文本分类任务; +也可以使用他构建许多复杂的网络模型,进行科研。它具有如下的特性: -Introduction +- 统一的Tabular式数据容器,让数据预处理过程简洁明了。内置多种数据集的DataSet Loader,省去预处理代码。 +- 各种方便的NLP工具,例如预处理embedding加载; 中间数据cache等; +- 详尽的中文文档以供查阅; +- 提供诸多高级模块,例如Variational LSTM, Transformer, CRF等; +- 封装CNNText,Biaffine等模型可供直接使用; +- 便捷且具有扩展性的训练器; 提供多种内置callback函数,方便实验记录、异常捕获等。 + + +内置组件 ------------ -FastNLP is a modular Natural Language Processing system based on -PyTorch, built for fast development of NLP models. +大部分用于的 NLP 任务神经网络都可以看做由编码(encoder)、聚合(aggregator)、解码(decoder)三种模块组成。 -A deep learning NLP model is the composition of three types of modules: +.. image:: figures/text_classification.png + +fastNLP 在 :mod:`~fastNLP.modules` 模块中内置了三种模块的诸多组件,可以帮助用户快速搭建自己所需的网络。 +三种模块的功能和常见组件如下: +-----------------------+-----------------------+-----------------------+ | module type | functionality | example | +=======================+=======================+=======================+ -| encoder | encode the input into | embedding, RNN, CNN, | -| | some abstract | transformer | -| | representation | | +| encoder | 将输入编码为具有具 | embedding, RNN, CNN, | +| | 有表示能力的向量 | transformer | +-----------------------+-----------------------+-----------------------+ -| aggregator | aggregate and reduce | self-attention, | -| | information | max-pooling | +| aggregator | 从多个向量中聚合信息 | self-attention, | +| | | max-pooling | +-----------------------+-----------------------+-----------------------+ -| decoder | decode the | MLP, CRF | -| | representation into | | -| | the output | | +| decoder | 将具有某种表示意义的 | MLP, CRF | +| | 向量解码为需要的输出 | | +| | 形式 | | +-----------------------+-----------------------+-----------------------+ -For example: - -.. image:: figures/text_classification.png +内置模型 +---------------- +fastNLP 在 :mod:`~fastNLP.models` 模块中内置了如 :class:`~fastNLP.models.CNNText` 、 +:class:`~fastNLP.models.SeqLabeling` 等完整的模型,以供用户直接使用。 +.. todo:: + 这些模型的介绍如下表所示:(模型名称 + 介绍 + 任务上的结果) +用户手册 +---------------- -User's Guide ------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - user/installation - user/quickstart + 安装指南 + 快速入门 + 详细指南 - -API Reference +API 文档 ------------- -If you are looking for information on a specific function, class or -method, this part of the documentation is for you. +除了用户手册之外,你还可以通过查阅 API 文档来找到你所需要的工具。 .. toctree:: + :titlesonly: :maxdepth: 2 - fastNLP API - + fastNLP +fitlog +------ +用户可以 `点此 `_ 查看fitlog的文档。 +fitlog 是由我们团队开发,用于帮助用户记录日志并管理代码的工具 -Indices and tables +索引与搜索 ================== * :ref:`genindex` diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 00000000..9ca3c7f3 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,8 @@ +fastNLP +======= + +.. toctree:: + :titlesonly: + :maxdepth: 4 + + fastNLP diff --git a/docs/source/tutorials/fastnlp_10tmin_tutorial.rst b/docs/source/tutorials/fastnlp_10tmin_tutorial.rst deleted file mode 100644 index 4c5fc65e..00000000 --- a/docs/source/tutorials/fastnlp_10tmin_tutorial.rst +++ /dev/null @@ -1,376 +0,0 @@ -fastNLP 10分钟上手教程 -=============== - -教程原文见 https://github.com/fastnlp/fastNLP/blob/master/tutorials/fastnlp_10min_tutorial.ipynb - -fastNLP提供方便的数据预处理,训练和测试模型的功能 - -DataSet & Instance ------------------- - -fastNLP用DataSet和Instance保存和处理数据。每个DataSet表示一个数据集,每个Instance表示一个数据样本。一个DataSet存有多个Instance,每个Instance可以自定义存哪些内容。 - -有一些read\_\*方法,可以轻松从文件读取数据,存成DataSet。 - -.. code:: ipython3 - - from fastNLP import DataSet - from fastNLP import Instance - - # 从csv读取数据到DataSet - win_path = "C:\\Users\zyfeng\Desktop\FudanNLP\\fastNLP\\test\\data_for_tests\\tutorial_sample_dataset.csv" - dataset = DataSet.read_csv(win_path, headers=('raw_sentence', 'label'), sep='\t') - print(dataset[0]) - - -.. parsed-literal:: - - {'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story ., - 'label': 1} - - -.. code:: ipython3 - - # DataSet.append(Instance)加入新数据 - - dataset.append(Instance(raw_sentence='fake data', label='0')) - dataset[-1] - - - - -.. parsed-literal:: - - {'raw_sentence': fake data, - 'label': 0} - - - -.. code:: ipython3 - - # DataSet.apply(func, new_field_name)对数据预处理 - - # 将所有数字转为小写 - dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='raw_sentence') - # label转int - dataset.apply(lambda x: int(x['label']), new_field_name='label_seq', is_target=True) - # 使用空格分割句子 - dataset.drop(lambda x: len(x['raw_sentence'].split()) == 0) - def split_sent(ins): - return ins['raw_sentence'].split() - dataset.apply(split_sent, new_field_name='words', is_input=True) - -.. code:: ipython3 - - # DataSet.drop(func)筛除数据 - # 删除低于某个长度的词语 - dataset.drop(lambda x: len(x['words']) <= 3) - -.. code:: ipython3 - - # 分出测试集、训练集 - - test_data, train_data = dataset.split(0.3) - print("Train size: ", len(test_data)) - print("Test size: ", len(train_data)) - - -.. parsed-literal:: - - Train size: 54 - Test size: - -Vocabulary ----------- - -fastNLP中的Vocabulary轻松构建词表,将词转成数字 - -.. code:: ipython3 - - from fastNLP import Vocabulary - - # 构建词表, Vocabulary.add(word) - vocab = Vocabulary(min_freq=2) - train_data.apply(lambda x: [vocab.add(word) for word in x['words']]) - vocab.build_vocab() - - # index句子, Vocabulary.to_index(word) - train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', is_input=True) - test_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', is_input=True) - - - print(test_data[0]) - - -.. parsed-literal:: - - {'raw_sentence': the plot is romantic comedy boilerplate from start to finish ., - 'label': 2, - 'label_seq': 2, - 'words': ['the', 'plot', 'is', 'romantic', 'comedy', 'boilerplate', 'from', 'start', 'to', 'finish', '.'], - 'word_seq': [2, 13, 9, 24, 25, 26, 15, 27, 11, 28, 3]} - - -.. code:: ipython3 - - # 假设你们需要做强化学习或者gan之类的项目,也许你们可以使用这里的dataset - from fastNLP.core.batch import Batch - from fastNLP.core.sampler import RandomSampler - - batch_iterator = Batch(dataset=train_data, batch_size=2, sampler=RandomSampler()) - for batch_x, batch_y in batch_iterator: - print("batch_x has: ", batch_x) - print("batch_y has: ", batch_y) - break - - -.. parsed-literal:: - - batch_x has: {'words': array([list(['this', 'kind', 'of', 'hands-on', 'storytelling', 'is', 'ultimately', 'what', 'makes', 'shanghai', 'ghetto', 'move', 'beyond', 'a', 'good', ',', 'dry', ',', 'reliable', 'textbook', 'and', 'what', 'allows', 'it', 'to', 'rank', 'with', 'its', 'worthy', 'predecessors', '.']), - list(['the', 'entire', 'movie', 'is', 'filled', 'with', 'deja', 'vu', 'moments', '.'])], - dtype=object), 'word_seq': tensor([[ 19, 184, 6, 1, 481, 9, 206, 50, 91, 1210, 1609, 1330, - 495, 5, 63, 4, 1269, 4, 1, 1184, 7, 50, 1050, 10, - 8, 1611, 16, 21, 1039, 1, 2], - [ 3, 711, 22, 9, 1282, 16, 2482, 2483, 200, 2, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0]])} - batch_y has: {'label_seq': tensor([3, 2])} - - -Model ------ - -.. code:: ipython3 - - # 定义一个简单的Pytorch模型 - - from fastNLP.models import CNNText - model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1) - model - - - - -.. parsed-literal:: - - CNNText( - (embed): Embedding( - (embed): Embedding(77, 50, padding_idx=0) - (dropout): Dropout(p=0.0) - ) - (conv_pool): ConvMaxpool( - (convs): ModuleList( - (0): Conv1d(50, 3, kernel_size=(3,), stride=(1,), padding=(2,)) - (1): Conv1d(50, 4, kernel_size=(4,), stride=(1,), padding=(2,)) - (2): Conv1d(50, 5, kernel_size=(5,), stride=(1,), padding=(2,)) - ) - ) - (dropout): Dropout(p=0.1) - (fc): Linear( - (linear): Linear(in_features=12, out_features=5, bias=True) - ) - ) - - - -Trainer & Tester ----------------- - -使用fastNLP的Trainer训练模型 - -.. code:: ipython3 - - from fastNLP import Trainer - from copy import deepcopy - from fastNLP import CrossEntropyLoss - from fastNLP import AccuracyMetric - -.. code:: ipython3 - - # 进行overfitting测试 - copy_model = deepcopy(model) - overfit_trainer = Trainer(model=copy_model, - train_data=test_data, - dev_data=test_data, - loss=CrossEntropyLoss(pred="output", target="label_seq"), - metrics=AccuracyMetric(), - n_epochs=10, - save_path=None) - overfit_trainer.train() - - -.. parsed-literal:: - - training epochs started 2018-12-07 14:07:20 - - - - -.. parsed-literal:: - - HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=20), HTML(value='')), layout=Layout(display='… - - - -.. parsed-literal:: - - Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.037037 - Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.296296 - Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.333333 - Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.555556 - Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.611111 - Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.481481 - Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.62963 - Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.685185 - Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.722222 - Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.777778 - - -.. code:: ipython3 - - # 实例化Trainer,传入模型和数据,进行训练 - trainer = Trainer(model=model, - train_data=train_data, - dev_data=test_data, - loss=CrossEntropyLoss(pred="output", target="label_seq"), - metrics=AccuracyMetric(), - n_epochs=5) - trainer.train() - print('Train finished!') - - -.. parsed-literal:: - - training epochs started 2018-12-07 14:08:10 - - - - -.. parsed-literal:: - - HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=5), HTML(value='')), layout=Layout(display='i… - - - -.. parsed-literal:: - - Epoch 1/5. Step:1/5. AccuracyMetric: acc=0.037037 - Epoch 2/5. Step:2/5. AccuracyMetric: acc=0.037037 - Epoch 3/5. Step:3/5. AccuracyMetric: acc=0.037037 - Epoch 4/5. Step:4/5. AccuracyMetric: acc=0.185185 - Epoch 5/5. Step:5/5. AccuracyMetric: acc=0.240741 - Train finished! - - -.. code:: ipython3 - - from fastNLP import Tester - - tester = Tester(data=test_data, model=model, metrics=AccuracyMetric()) - acc = tester.test() - - -.. parsed-literal:: - - [tester] - AccuracyMetric: acc=0.240741 - - -In summary ----------- - -fastNLP Trainer的伪代码逻辑 ---------------------------- - -1. 准备DataSet,假设DataSet中共有如下的fields -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - ['raw_sentence', 'word_seq1', 'word_seq2', 'raw_label','label'] - 通过 - DataSet.set_input('word_seq1', word_seq2', flag=True)将'word_seq1', 'word_seq2'设置为input - 通过 - DataSet.set_target('label', flag=True)将'label'设置为target - -2. 初始化模型 -~~~~~~~~~~~~~ - -:: - - class Model(nn.Module): - def __init__(self): - xxx - def forward(self, word_seq1, word_seq2): - # (1) 这里使用的形参名必须和DataSet中的input field的名称对应。因为我们是通过形参名, 进行赋值的 - # (2) input field的数量可以多于这里的形参数量。但是不能少于。 - xxxx - # 输出必须是一个dict - -3. Trainer的训练过程 -~~~~~~~~~~~~~~~~~~~~ - -:: - - (1) 从DataSet中按照batch_size取出一个batch,调用Model.forward - (2) 将 Model.forward的结果 与 标记为target的field 传入Losser当中。 - 由于每个人写的Model.forward的output的dict可能key并不一样,比如有人是{'pred':xxx}, {'output': xxx}; - 另外每个人将target可能也会设置为不同的名称, 比如有人是label, 有人设置为target; - 为了解决以上的问题,我们的loss提供映射机制 - 比如CrossEntropyLosser的需要的输入是(prediction, target)。但是forward的output是{'output': xxx}; 'label'是target - 那么初始化losser的时候写为CrossEntropyLosser(prediction='output', target='label')即可 - (3) 对于Metric是同理的 - Metric计算也是从 forward的结果中取值 与 设置target的field中取值。 也是可以通过映射找到对应的值 - -一些问题. ---------- - -1. DataSet中为什么需要设置input和target -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - 只有被设置为input或者target的数据才会在train的过程中被取出来 - (1.1) 我们只会在设置为input的field中寻找传递给Model.forward的参数。 - (1.2) 我们在传递值给losser或者metric的时候会使用来自: - (a)Model.forward的output - (b)被设置为target的field - - -2. 我们是通过forwad中的形参名将DataSet中的field赋值给对应的参数 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - (1.1) 构建模型过程中, - 例如: - DataSet中x,seq_lens是input,那么forward就应该是 - def forward(self, x, seq_lens): - pass - 我们是通过形参名称进行匹配的field的 - - -1. 加载数据到DataSet -~~~~~~~~~~~~~~~~~~~~ - -2. 使用apply操作对DataSet进行预处理 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - (2.1) 处理过程中将某些field设置为input,某些field设置为target - -3. 构建模型 -~~~~~~~~~~~ - -:: - - (3.1) 构建模型过程中,需要注意forward函数的形参名需要和DataSet中设置为input的field名称是一致的。 - 例如: - DataSet中x,seq_lens是input,那么forward就应该是 - def forward(self, x, seq_lens): - pass - 我们是通过形参名称进行匹配的field的 - (3.2) 模型的forward的output需要是dict类型的。 - 建议将输出设置为{"pred": xx}. - diff --git a/docs/source/tutorials/fastnlp_1_minute_tutorial.rst b/docs/source/tutorials/fastnlp_1_minute_tutorial.rst deleted file mode 100644 index b4c6c8c4..00000000 --- a/docs/source/tutorials/fastnlp_1_minute_tutorial.rst +++ /dev/null @@ -1,113 +0,0 @@ - -FastNLP 1分钟上手教程 -===================== - -教程原文见 https://github.com/fastnlp/fastNLP/blob/master/tutorials/fastnlp_1min_tutorial.ipynb - -step 1 ------- - -读取数据集 - -.. code:: ipython3 - - from fastNLP import DataSet - # linux_path = "../test/data_for_tests/tutorial_sample_dataset.csv" - win_path = "C:\\Users\zyfeng\Desktop\FudanNLP\\fastNLP\\test\\data_for_tests\\tutorial_sample_dataset.csv" - ds = DataSet.read_csv(win_path, headers=('raw_sentence', 'label'), sep='\t') - -step 2 ------- - -数据预处理 1. 类型转换 2. 切分验证集 3. 构建词典 - -.. code:: ipython3 - - # 将所有数字转为小写 - ds.apply(lambda x: x['raw_sentence'].lower(), new_field_name='raw_sentence') - # label转int - ds.apply(lambda x: int(x['label']), new_field_name='label_seq', is_target=True) - - def split_sent(ins): - return ins['raw_sentence'].split() - ds.apply(split_sent, new_field_name='words', is_input=True) - - -.. code:: ipython3 - - # 分割训练集/验证集 - train_data, dev_data = ds.split(0.3) - print("Train size: ", len(train_data)) - print("Test size: ", len(dev_data)) - - -.. parsed-literal:: - - Train size: 54 - Test size: 23 - - -.. code:: ipython3 - - from fastNLP import Vocabulary - vocab = Vocabulary(min_freq=2) - train_data.apply(lambda x: [vocab.add(word) for word in x['words']]) - - # index句子, Vocabulary.to_index(word) - train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', is_input=True) - dev_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', is_input=True) - - -step 3 ------- - -定义模型 - -.. code:: ipython3 - - from fastNLP.models import CNNText - model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1) - - -step 4 ------- - -开始训练 - -.. code:: ipython3 - - from fastNLP import Trainer, CrossEntropyLoss, AccuracyMetric - trainer = Trainer(model=model, - train_data=train_data, - dev_data=dev_data, - loss=CrossEntropyLoss(), - metrics=AccuracyMetric() - ) - trainer.train() - print('Train finished!') - - - -.. parsed-literal:: - - training epochs started 2018-12-07 14:03:41 - - - - -.. parsed-literal:: - - HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=6), HTML(value='')), layout=Layout(display='i… - - - -.. parsed-literal:: - - Epoch 1/3. Step:2/6. AccuracyMetric: acc=0.26087 - Epoch 2/3. Step:4/6. AccuracyMetric: acc=0.347826 - Epoch 3/3. Step:6/6. AccuracyMetric: acc=0.608696 - Train finished! - - -本教程结束。更多操作请参考进阶教程。 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/source/tutorials/fastnlp_advanced_tutorial.rst b/docs/source/tutorials/fastnlp_advanced_tutorial.rst deleted file mode 100644 index d788e9d6..00000000 --- a/docs/source/tutorials/fastnlp_advanced_tutorial.rst +++ /dev/null @@ -1,5 +0,0 @@ -fastNLP 进阶教程 -=============== - -教程原文见 https://github.com/fastnlp/fastNLP/blob/master/tutorials/fastnlp_advanced_tutorial/advance_tutorial.ipynb - diff --git a/docs/source/tutorials/fastnlp_developer_guide.rst b/docs/source/tutorials/fastnlp_developer_guide.rst deleted file mode 100644 index 73b75f02..00000000 --- a/docs/source/tutorials/fastnlp_developer_guide.rst +++ /dev/null @@ -1,5 +0,0 @@ -fastNLP 开发者指南 -=============== - -原文见 https://github.com/fastnlp/fastNLP/blob/master/tutorials/tutorial_for_developer.md - diff --git a/docs/source/user/installation.rst b/docs/source/user/installation.rst index 5dfe4a11..c218b3e1 100644 --- a/docs/source/user/installation.rst +++ b/docs/source/user/installation.rst @@ -1,17 +1,20 @@ -============ -Installation -============ +=============== +安装指南 +=============== .. contents:: :local: -Make sure your environment satisfies https://github.com/fastnlp/fastNLP/blob/master/requirements.txt . +fastNLP 依赖如下包:: -Run the following commands to install fastNLP package: + torch>=0.4.0 + numpy + tqdm + nltk -.. code:: shell - - pip install fastNLP - +其中torch的安装可能与操作系统及 CUDA 的版本相关,请参见 `PyTorch 官网 `_ 。 +在依赖包安装完成的情况,您可以在命令行执行如下指令完成安装 +.. code:: shell + >>> pip install fastNLP diff --git a/docs/source/user/quickstart.rst b/docs/source/user/quickstart.rst index a5eb9402..43056a26 100644 --- a/docs/source/user/quickstart.rst +++ b/docs/source/user/quickstart.rst @@ -1,11 +1,124 @@ -Quickstart -========== +=============== +快速入门 +=============== -.. toctree:: - :maxdepth: 1 +这是一个简单的分类任务 (数据来源 `kaggle `_ )。 +给出一段文字,预测它的标签是0~4中的哪一个。 - ../tutorials/fastnlp_1_minute_tutorial - ../tutorials/fastnlp_10tmin_tutorial - ../tutorials/fastnlp_advanced_tutorial - ../tutorials/fastnlp_developer_guide +我们可以使用 fastNLP 中 io 模块中的 :class:`~fastNLP.io.CSVLoader` 类,轻松地从 csv 文件读取我们的数据。 +.. code-block:: python + + from fastNLP.io import CSVLoader + + loader = CSVLoader(headers=('raw_sentence', 'label'), sep='\t') + dataset = loader.load("./sample_data/tutorial_sample_dataset.csv") + +此时的 `dataset[0]` 的值如下,可以看到,数据集中的每个数据包含 ``raw_sentence`` 和 ``label`` 两个字段,他们的类型都是 ``str``:: + + {'raw_sentence': A series of escapades demonstrating the adage that what is good for the + goose is also good for the gander , some of which occasionally amuses but none of which + amounts to much of a story . type=str, + 'label': 1 type=str} + + +我们使用 :class:`~fastNLP.DataSet` 类的 :meth:`~fastNLP.DataSet.apply` 方法将 ``raw_sentence`` 中字母变成小写,并将句子分词。 + +.. code-block:: python + + dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='sentence') + dataset.apply(lambda x: x['sentence'].split(), new_field_name='words', is_input=True) + +然后我们再用 :class:`~fastNLP.Vocabulary` 类来统计数据中出现的单词,并将单词序列转化为训练可用的数字序列。 + +.. code-block:: python + + from fastNLP import Vocabulary + vocab = Vocabulary(min_freq=2).from_dataset(dataset, field_name='words') + vocab.index_dataset(dataset, field_name='words',new_field_name='words') + +同时,我们也将原来 str 类型的标签转化为数字,并设置为训练中的标准答案 ``target`` + +.. code-block:: python + + dataset.apply(lambda x: int(x['label']), new_field_name='target', is_target=True) + +现在我们可以导入 fastNLP 内置的文本分类模型 :class:`~fastNLP.models.CNNText` , + + +.. code-block:: python + + from fastNLP.models import CNNText + model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) + +:class:`~fastNLP.models.CNNText` 的网络结构如下:: + + CNNText( + (embed): Embedding( + 177, 50 + (dropout): Dropout(p=0.0) + ) + (conv_pool): ConvMaxpool( + (convs): ModuleList( + (0): Conv1d(50, 3, kernel_size=(3,), stride=(1,), padding=(2,)) + (1): Conv1d(50, 4, kernel_size=(4,), stride=(1,), padding=(2,)) + (2): Conv1d(50, 5, kernel_size=(5,), stride=(1,), padding=(2,)) + ) + ) + (dropout): Dropout(p=0.1) + (fc): Linear(in_features=12, out_features=5, bias=True) + ) + +下面我们用 :class:`~fastNLP.DataSet` 类的 :meth:`~fastNLP.DataSet.split` 方法将数据集划分为 ``train_data`` 和 ``dev_data`` +两个部分,分别用于训练和验证 + +.. code-block:: python + + train_data, dev_data = dataset.split(0.2) + +最后我们用 fastNLP 的 :class:`~fastNLP.Trainer` 进行训练,训练的过程中需要传入模型 ``model`` ,训练数据集 ``train_data`` , +验证数据集 ``dev_data`` ,损失函数 ``loss`` 和衡量标准 ``metrics`` 。 +其中损失函数使用的是 fastNLP 提供的 :class:`~fastNLP.CrossEntropyLoss` 损失函数; +衡量标准使用的是 fastNLP 提供的 :class:`~fastNLP.AccuracyMetric` 正确率指标。 + +.. code-block:: python + + from fastNLP import Trainer, CrossEntropyLoss, AccuracyMetric + + trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, + loss=CrossEntropyLoss(), metrics=AccuracyMetric()) + trainer.train() + +训练过程的输出如下:: + + input fields after batch(if batch size is 2): + words: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26]) + target fields after batch(if batch size is 2): + target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + + training epochs started 2019-05-09-10-59-39 + Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.333333 + + Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.533333 + + Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.533333 + + Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.533333 + + Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.6 + + Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.8 + + Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.8 + + Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.733333 + + Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.733333 + + Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.733333 + + + In Epoch:6/Step:12, got best dev performance:AccuracyMetric: acc=0.8 + Reloaded the best model. + +这份教程只是简单地介绍了使用 fastNLP 工作的流程,具体的细节分析见 :doc:`/user/tutorial_one` \ No newline at end of file diff --git a/docs/source/user/tutorial_one.rst b/docs/source/user/tutorial_one.rst new file mode 100644 index 00000000..0c7be77d --- /dev/null +++ b/docs/source/user/tutorial_one.rst @@ -0,0 +1,371 @@ +=============== +详细指南 +=============== + +我们使用和 :doc:`/user/quickstart` 中一样的任务来进行详细的介绍。给出一段文字,预测它的标签是0~4中的哪一个 +(数据来源 `kaggle `_ )。 + +-------------- +数据处理 +-------------- + +数据读入 + 我们可以使用 fastNLP :mod:`fastNLP.io` 模块中的 :class:`~fastNLP.io.CSVLoader` 类,轻松地从 csv 文件读取我们的数据。 + 这里的 dataset 是 fastNLP 中 :class:`~fastNLP.DataSet` 类的对象 + + .. code-block:: python + + from fastNLP.io import CSVLoader + + loader = CSVLoader(headers=('raw_sentence', 'label'), sep='\t') + dataset = loader.load("./sample_data/tutorial_sample_dataset.csv") + + 除了读取数据外,fastNLP 还提供了读取其它文件类型的 Loader 类、读取 Embedding的 Loader 等。详见 :doc:`/fastNLP.io` 。 + +Instance 和 DataSet + fastNLP 中的 :class:`~fastNLP.DataSet` 类对象类似于二维表格,它的每一列是一个 :mod:`~fastNLP.core.field` + 每一行是一个 :mod:`~fastNLP.core.instance` 。我们可以手动向数据集中添加 :class:`~fastNLP.Instance` 类的对象 + + .. code-block:: python + + from fastNLP import Instance + + dataset.append(Instance(raw_sentence='fake data', label='0')) + + 此时的 ``dataset[-1]`` 的值如下,可以看到,数据集中的每个数据包含 ``raw_sentence`` 和 ``label`` 两个 + :mod:`~fastNLP.core.field` ,他们的类型都是 ``str`` :: + + {'raw_sentence': fake data type=str, 'label': 0 type=str} + +field 的修改 + 我们使用 :class:`~fastNLP.DataSet` 类的 :meth:`~fastNLP.DataSet.apply` 方法将 ``raw_sentence`` 中字母变成小写,并将句子分词。 + 同时也将 ``label`` :mod:`~fastNLP.core.field` 转化为整数并改名为 ``target`` + + .. code-block:: python + + dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='sentence') + dataset.apply_field(lambda x: x.split(), field_name='sentence', new_field_name='words') + dataset.apply(lambda x: int(x['label']), new_field_name='target') + + ``words`` 和 ``target`` 已经足够用于 :class:`~fastNLP.models.CNNText` 的训练了,但我们从其文档 + :class:`~fastNLP.models.CNNText` 中看到,在 :meth:`~fastNLP.models.CNNText.forward` 的时候,还可以传入可选参数 ``seq_len`` 。 + 所以,我们再使用 :meth:`~fastNLP.DataSet.apply_field` 方法增加一个名为 ``seq_len`` 的 :mod:`~fastNLP.core.field` 。 + + .. code-block:: python + + dataset.apply_field(lambda x: len(x), field_name='words', new_field_name='seq_len') + + 观察可知: :meth:`~fastNLP.DataSet.apply_field` 与 :meth:`~fastNLP.DataSet.apply` 类似, + 但所传入的 `lambda` 函数是针对一个 :class:`~fastNLP.Instance` 中的一个 :mod:`~fastNLP.core.field` 的; + 而 :meth:`~fastNLP.DataSet.apply` 所传入的 `lambda` 函数是针对整个 :class:`~fastNLP.Instance` 的。 + + .. note:: + `lambda` 函数即匿名函数,是 Python 的重要特性。 ``lambda x: len(x)`` 和下面的这个函数的作用相同:: + + def func_lambda(x): + return len(x) + + 你也可以编写复杂的函数做为 :meth:`~fastNLP.DataSet.apply_field` 与 :meth:`~fastNLP.DataSet.apply` 的参数 + +Vocabulary 的使用 + 我们再用 :class:`~fastNLP.Vocabulary` 类来统计数据中出现的单词,并使用 :meth:`~fastNLP.Vocabularyindex_dataset` + 将单词序列转化为训练可用的数字序列。 + + .. code-block:: python + + from fastNLP import Vocabulary + + vocab = Vocabulary(min_freq=2).from_dataset(dataset, field_name='words') + vocab.index_dataset(dataset, field_name='words',new_field_name='words') + +数据集分割 + 除了修改 :mod:`~fastNLP.core.field` 之外,我们还可以对 :class:`~fastNLP.DataSet` 进行分割,以供训练、开发和测试使用。 + 下面这段代码展示了 :meth:`~fastNLP.DataSet.split` 的使用方法(但实际应该放在后面两段改名和设置输入的代码之后) + + .. code-block:: python + + train_dev_data, test_data = dataset.split(0.1) + train_data, dev_data = train_dev_data.split(0.1) + len(train_data), len(dev_data), len(test_data) + +--------------------- +使用内置模型训练 +--------------------- + +内置模型的输入输出命名 + fastNLP内置了一些完整的神经网络模型,详见 :doc:`/fastNLP.models` , 我们使用其中的 :class:`~fastNLP.models.CNNText` 模型进行训练。 + 为了使用内置的 :class:`~fastNLP.models.CNNText`,我们必须修改 :class:`~fastNLP.DataSet` 中 :mod:`~fastNLP.core.field` 的名称。 + 在这个例子中模型输入 (forward方法的参数) 为 ``words`` 和 ``seq_len`` ; 预测输出为 ``pred`` ;标准答案为 ``target`` 。 + 具体的命名规范可以参考 :doc:`/fastNLP.core.const` 。 + + 如果不想查看文档,您也可以使用 :class:`~fastNLP.Const` 类进行命名。下面的代码展示了给 :class:`~fastNLP.DataSet` 中 + :mod:`~fastNLP.core.field` 改名的 :meth:`~fastNLP.DataSet.rename_field` 方法,以及 :class:`~fastNLP.Const` 类的使用方法。 + + .. code-block:: python + + from fastNLP import Const + + dataset.rename_field('words', Const.INPUT) + dataset.rename_field('seq_len', Const.INPUT_LEN) + dataset.rename_field('target', Const.TARGET) + + 在给 :class:`~fastNLP.DataSet` 中 :mod:`~fastNLP.core.field` 改名后,我们还需要设置训练所需的输入和目标,这里使用的是 + :meth:`~fastNLP.DataSet.set_input` 和 :meth:`~fastNLP.DataSet.set_target` 两个函数。 + + .. code-block:: python + + dataset.set_input(Const.INPUT, Const.INPUT_LEN) + dataset.set_target(Const.TARGET) + +快速训练 + 现在我们可以导入 fastNLP 内置的文本分类模型 :class:`~fastNLP.models.CNNText` ,并使用 :class:`~fastNLP.Trainer` 进行训练了 + (其中 ``loss`` 和 ``metrics`` 的定义,我们将在后续两段代码中给出)。 + + .. code-block:: python + + from fastNLP.models import CNNText + from fastNLP import Trainer + + model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) + + trainer = Trainer(model=model_cnn, train_data=train_data, dev_data=dev_data, + loss=loss, metrics=metrics) + trainer.train() + + 训练过程的输出如下:: + + input fields after batch(if batch size is 2): + words: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26]) + target fields after batch(if batch size is 2): + target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + + training epochs started 2019-05-09-10-59-39 + Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.333333 + + Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.533333 + + Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.533333 + + Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.533333 + + Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.6 + + Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.8 + + Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.8 + + Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.733333 + + Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.733333 + + Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.733333 + + + In Epoch:6/Step:12, got best dev performance:AccuracyMetric: acc=0.8 + Reloaded the best model. + +损失函数 + 训练模型需要提供一个损失函数, 下面提供了一个在分类问题中常用的交叉熵损失。注意它的 **初始化参数** 。 + ``pred`` 参数对应的是模型的 forward 方法返回的 dict 中的一个 key 的名字。 + ``target`` 参数对应的是 :class:`~fastNLP.DataSet` 中作为标签的 :mod:`~fastNLP.core.field` 的名字。 + 这里我们用 :class:`~fastNLP.Const` 来辅助命名,如果你自己编写模型中 forward 方法的返回值或 + 数据集中 :mod:`~fastNLP.core.field` 的名字与本例不同, 你可以把 ``pred`` 参数和 ``target`` 参数设定符合自己代码的值。 + + .. code-block:: python + + from fastNLP import CrossEntropyLoss + + # loss = CrossEntropyLoss() 在本例中与下面这行代码等价 + loss = CrossEntropyLoss(pred=Const.OUTPUT, target=Const.TARGET) + +评价指标 + 训练模型需要提供一个评价指标。这里使用准确率做为评价指标。参数的 `命名规则` 跟上面类似。 + ``pred`` 参数对应的是模型的 forward 方法返回的 dict 中的一个 key 的名字。 + ``target`` 参数对应的是 :class:`~fastNLP.DataSet` 中作为标签的 :mod:`~fastNLP.core.field` 的名字。 + + .. code-block:: python + + from fastNLP import AccuracyMetric + + # metrics=AccuracyMetric() 在本例中与下面这行代码等价 + metrics=AccuracyMetric(pred=Const.OUTPUT, target=Const.TARGET) + +快速测试 + 与 :class:`~fastNLP.Trainer` 对应,fastNLP 也提供了 :class:`~fastNLP.Tester` 用于快速测试,用法如下 + + .. code-block:: python + + from fastNLP import Tester + + tester = Tester(test_data, model_cnn, metrics=AccuracyMetric()) + tester.test() + +--------------------- +编写自己的模型 +--------------------- + +因为 fastNLP 是基于 `PyTorch `_ 开发的框架,所以我们可以基于 PyTorch 模型编写自己的神经网络模型。 +与标准的 PyTorch 模型不同,fastNLP 模型中 forward 方法返回的是一个字典,字典中至少需要包含 "pred" 这个字段。 +而 forward 方法的参数名称必须与 :class:`~fastNLP.DataSet` 中用 :meth:`~fastNLP.DataSet.set_input` 设定的名称一致。 +模型定义的代码如下: + +.. code-block:: python + + import torch + import torch.nn as nn + + class LSTMText(nn.Module): + def __init__(self, vocab_size, embedding_dim, output_dim, hidden_dim=64, num_layers=2, dropout=0.5): + super().__init__() + + self.embedding = nn.Embedding(vocab_size, embedding_dim) + self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=num_layers, bidirectional=True, dropout=dropout) + self.fc = nn.Linear(hidden_dim * 2, output_dim) + self.dropout = nn.Dropout(dropout) + + def forward(self, words): + # (input) words : (batch_size, seq_len) + words = words.permute(1,0) + # words : (seq_len, batch_size) + + embedded = self.dropout(self.embedding(words)) + # embedded : (seq_len, batch_size, embedding_dim) + output, (hidden, cell) = self.lstm(embedded) + # output: (seq_len, batch_size, hidden_dim * 2) + # hidden: (num_layers * 2, batch_size, hidden_dim) + # cell: (num_layers * 2, batch_size, hidden_dim) + + hidden = torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1) + hidden = self.dropout(hidden) + # hidden: (batch_size, hidden_dim * 2) + + pred = self.fc(hidden.squeeze(0)) + # result: (batch_size, output_dim) + return {"pred":pred} + +模型的使用方法与内置模型 :class:`~fastNLP.models.CNNText` 一致 + +.. code-block:: python + + model_lstm = LSTMText(len(vocab),50,5) + + trainer = Trainer(model=model_lstm, train_data=train_data, dev_data=dev_data, + loss=loss, metrics=metrics) + trainer.train() + + tester = Tester(test_data, model_lstm, metrics=AccuracyMetric()) + tester.test() + +.. todo:: + 使用 :doc:`/fastNLP.modules` 编写模型 + +-------------------------- +自己编写训练过程 +-------------------------- + +如果你想用类似 PyTorch 的使用方法,自己编写训练过程,你可以参考下面这段代码。其中使用了 fastNLP 提供的 :class:`~fastNLP.Batch` +来获得小批量训练的小批量数据,使用 :class:`~fastNLP.BucketSampler` 做为 :class:`~fastNLP.Batch` 的参数来选择采样的方式。 +这段代码中使用了 PyTorch 的 `torch.optim.Adam` 优化器 和 `torch.nn.CrossEntropyLoss` 损失函数,并自己计算了正确率 + +.. code-block:: python + + from fastNLP import BucketSampler + from fastNLP import Batch + import torch + import time + + model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) + + def train(epoch, data): + optim = torch.optim.Adam(model.parameters(), lr=0.001) + lossfunc = torch.nn.CrossEntropyLoss() + batch_size = 32 + + train_sampler = BucketSampler(batch_size=batch_size, seq_len_field_name='seq_len') + train_batch = Batch(batch_size=batch_size, dataset=data, sampler=train_sampler) + + start_time = time.time() + for i in range(epoch): + loss_list = [] + for batch_x, batch_y in train_batch: + optim.zero_grad() + output = model(batch_x['words']) + loss = lossfunc(output['pred'], batch_y['target']) + loss.backward() + optim.step() + loss_list.append(loss.item()) + print('Epoch {:d} Avg Loss: {:.2f}'.format(i, sum(loss_list) / len(loss_list)),end=" ") + print('{:d}ms'.format(round((time.time()-start_time)*1000))) + loss_list.clear() + + train(10, train_data) + + tester = Tester(test_data, model, metrics=AccuracyMetric()) + tester.test() + +这段代码的输出如下:: + + Epoch 0 Avg Loss: 2.76 17ms + Epoch 1 Avg Loss: 2.55 29ms + Epoch 2 Avg Loss: 2.37 41ms + Epoch 3 Avg Loss: 2.30 53ms + Epoch 4 Avg Loss: 2.12 65ms + Epoch 5 Avg Loss: 2.16 76ms + Epoch 6 Avg Loss: 1.88 88ms + Epoch 7 Avg Loss: 1.84 99ms + Epoch 8 Avg Loss: 1.71 111ms + Epoch 9 Avg Loss: 1.62 122ms + [tester] + AccuracyMetric: acc=0.142857 + +---------------------------------- +使用 Callback 增强 Trainer +---------------------------------- + +如果你不想自己实现繁琐的训练过程,只希望在训练过程中实现一些自己的功能(比如:输出从训练开始到当前 batch 结束的总时间), +你可以使用 fastNLP 提供的 :class:`~fastNLP.Callback` 类。下面的例子中,我们继承 :class:`~fastNLP.Callback` 类实现了这个功能。 + +.. code-block:: python + + from fastNLP import Callback + + start_time = time.time() + + class MyCallback(Callback): + def on_epoch_end(self): + print('Sum Time: {:d}ms\n\n'.format(round((time.time()-start_time)*1000))) + + + model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) + trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, + loss=CrossEntropyLoss(), metrics=AccuracyMetric(), callbacks=[MyCallback()]) + trainer.train() + +训练输出如下:: + + input fields after batch(if batch size is 2): + words: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 16]) + seq_len: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + target fields after batch(if batch size is 2): + target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + + training epochs started 2019-05-12-21-38-40 + Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.285714 + + Sum Time: 51ms + + + ………………………… + + + Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.857143 + + Sum Time: 212ms + + + + In Epoch:10/Step:20, got best dev performance:AccuracyMetric: acc=0.857143 + Reloaded the best model. + +这个例子只是介绍了 :class:`~fastNLP.Callback` 类的使用方法。实际应用(比如:负采样、Learning Rate Decay、Early Stop 等)中 +很多功能已经被 fastNLP 实现了。你可以直接 import 它们使用,详细请查看文档 :doc:`/fastNLP.core.callback` 。 \ No newline at end of file diff --git a/docs/source/user/with_fitlog.rst b/docs/source/user/with_fitlog.rst new file mode 100644 index 00000000..97c3ea71 --- /dev/null +++ b/docs/source/user/with_fitlog.rst @@ -0,0 +1,5 @@ +================= +科研向导 +================= + +本文介绍使用 fastNLP 和 fitlog 进行科学研究的方法 \ No newline at end of file diff --git a/fastNLP/__init__.py b/fastNLP/__init__.py index 0f6da45f..c67e5919 100644 --- a/fastNLP/__init__.py +++ b/fastNLP/__init__.py @@ -1,3 +1,59 @@ +""" +fastNLP 由 :mod:`~fastNLP.core` 、 :mod:`~fastNLP.io` 、:mod:`~fastNLP.modules`、:mod:`~fastNLP.models` +等子模块组成,你可以点进去查看每个模块的文档。 + +- :mod:`~fastNLP.core` 是fastNLP 的核心模块,包括 DataSet、 Trainer、 Tester 等组件。详见文档 :doc:`/fastNLP.core` +- :mod:`~fastNLP.io` 是实现输入输出的模块,包括了数据集的读取,模型的存取等功能。详见文档 :doc:`/fastNLP.io` +- :mod:`~fastNLP.modules` 包含了用于搭建神经网络模型的诸多组件,可以帮助用户快速搭建自己所需的网络。详见文档 :doc:`/fastNLP.modules` +- :mod:`~fastNLP.models` 包含了一些使用 fastNLP 实现的完整网络模型,包括CNNText、SeqLabeling等常见模型。详见文档 :doc:`/fastNLP.models` + +fastNLP 中最常用的组件可以直接从 fastNLP 包中 import ,他们的文档如下: +""" +__all__ = [ + "Instance", + "FieldArray", + "Batch", + "Vocabulary", + "DataSet", + "Const", + + "Trainer", + "Tester", + + "Callback", + "GradientClipCallback", + "EarlyStopCallback", + "TensorboardCallback", + "LRScheduler", + "ControlC", + + "Padder", + "AutoPadder", + "EngChar2DPadder", + + "AccuracyMetric", + "SpanFPreRecMetric", + "SQuADMetric", + + "Optimizer", + "SGD", + "Adam", + + "Sampler", + "SequentialSampler", + "BucketSampler", + "RandomSampler", + + "LossFunc", + "CrossEntropyLoss", + "L1Loss", "BCELoss", + "NLLLoss", + "LossInForward", + + "cache_results" +] +__version__ = '0.4.0' + from .core import * from . import models from . import modules diff --git a/fastNLP/api/__init__.py b/fastNLP/api/__init__.py deleted file mode 100644 index a21a4c42..00000000 --- a/fastNLP/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .api import CWS, POS, Parser diff --git a/fastNLP/core/__init__.py b/fastNLP/core/__init__.py index 038ca12f..d6ab8983 100644 --- a/fastNLP/core/__init__.py +++ b/fastNLP/core/__init__.py @@ -1,13 +1,30 @@ +""" +core 模块里实现了 fastNLP 的核心框架,常用的功能都可以从 fastNLP 包中直接 import。当然你也同样可以从 core 模块的子模块中 import, +例如 Batch 组件有两种 import 的方式:: + + # 直接从 fastNLP 中 import + from fastNLP import Batch + + # 从 core 模块的子模块 batch 中 import + from fastNLP.core.batch import Batch + +对于常用的功能,你只需要在 :doc:`fastNLP` 中查看即可。如果想了解各个子模块的具体作用,您可以在下面找到每个子模块的具体文档。 + +.. todo:: + 介绍core 的子模块的分工,好像必要性不大 + +""" from .batch import Batch -# from .dataset import DataSet -from .fieldarray import FieldArray +from .callback import Callback, GradientClipCallback, EarlyStopCallback, TensorboardCallback, LRScheduler, ControlC +from .const import Const +from .dataset import DataSet +from .field import FieldArray, Padder, AutoPadder, EngChar2DPadder from .instance import Instance from .losses import LossFunc, CrossEntropyLoss, L1Loss, BCELoss, NLLLoss, LossInForward -from .metrics import AccuracyMetric +from .metrics import AccuracyMetric, SpanFPreRecMetric, SQuADMetric from .optimizer import Optimizer, SGD, Adam -from .sampler import SequentialSampler, BucketSampler, RandomSampler, BaseSampler +from .sampler import SequentialSampler, BucketSampler, RandomSampler, Sampler from .tester import Tester from .trainer import Trainer +from .utils import cache_results, seq_len_to_mask from .vocabulary import Vocabulary -from ..io.dataset_loader import DataSet - diff --git a/fastNLP/core/batch.py b/fastNLP/core/batch.py index 88d9185d..c1289adf 100644 --- a/fastNLP/core/batch.py +++ b/fastNLP/core/batch.py @@ -1,28 +1,64 @@ +""" +batch 模块实现了 fastNLP 所需的 Batch 类。 + +""" +__all__ = [ + "Batch" +] + +import atexit +from queue import Empty, Full + import numpy as np import torch - -from fastNLP.core.sampler import RandomSampler import torch.multiprocessing as mp -class Batch(object): - """Batch is an iterable object which iterates over mini-batches. +from .sampler import RandomSampler - Example:: +_python_is_exit = False - for batch_x, batch_y in Batch(data_set, batch_size=16, sampler=SequentialSampler()): - # ... - :param DataSet dataset: a DataSet object - :param int batch_size: the size of the batch - :param Sampler sampler: a Sampler object - :param bool as_numpy: If True, return Numpy array. Otherwise, return torch tensors. - :param bool prefetch: If True, use multiprocessing to fetch next batch when training. - :param str or torch.device device: the batch's device, if as_numpy is True, device is ignored. - """ +def _set_python_is_exit(): + global _python_is_exit + _python_is_exit = True - def __init__(self, dataset, batch_size, sampler=RandomSampler(), as_numpy=False, prefetch=False): + +atexit.register(_set_python_is_exit) + + +class Batch(object): + """ + 别名::class:`fastNLP.Batch` :class:`fastNLP.core.batch.Batch` + + Batch 用于从 `DataSet` 中按一定的顺序, 依次按 ``batch_size`` 的大小将数据取出. + 组成 `x` 和 `y` + + + Example:: + + batch = Batch(data_set, batch_size=16, sampler=SequentialSampler()) + num_batch = len(batch) + for batch_x, batch_y in batch: + # do stuff ... + + :param dataset: :class:`~fastNLP.DataSet` 对象, 数据集 + :param int batch_size: 取出的batch大小 + :param sampler: 规定使用的 :class:`~fastNLP.Sampler` 方式. 若为 ``None`` , 使用 :class:`~fastNLP.RandomSampler`. + + Default: ``None`` + :param bool as_numpy: 若为 ``True`` , 输出batch为 numpy.array. 否则为 :class:`torch.Tensor`. + + Default: ``False`` + :param bool prefetch: 若为 ``True`` 使用多进程预先取出下一batch. + + Default: ``False`` + """ + + def __init__(self, dataset, batch_size, sampler=None, as_numpy=False, prefetch=False): self.dataset = dataset self.batch_size = batch_size + if sampler is None: + sampler = RandomSampler() self.sampler = sampler self.as_numpy = as_numpy self.idx_list = None @@ -31,37 +67,38 @@ class Batch(object): self.cur_batch_indices = None self.prefetch = prefetch self.lengths = 0 - + def fetch_one(self): if self.curidx >= len(self.idx_list): return None else: endidx = min(self.curidx + self.batch_size, len(self.idx_list)) batch_x, batch_y = {}, {} - + indices = self.idx_list[self.curidx:endidx] self.cur_batch_indices = indices - + for field_name, field in self.dataset.get_all_fields().items(): if field.is_target or field.is_input: batch = field.get(indices) if not self.as_numpy and field.padder is not None: - batch = to_tensor(batch, field.dtype) + batch = _to_tensor(batch, field.dtype) if field.is_target: batch_y[field_name] = batch if field.is_input: batch_x[field_name] = batch - + self.curidx = endidx return batch_x, batch_y - + def __iter__(self): """ Iterate on dataset, fetch batch data. Fetch process don't block the iterate process :return: """ if self.prefetch: - return run_batch_iter(self) + return self._run_batch_iter(self) + def batch_iter(): self.init_iter() while 1: @@ -69,21 +106,78 @@ class Batch(object): if res is None: break yield res + return batch_iter() - + def init_iter(self): self.idx_list = self.sampler(self.dataset) self.curidx = 0 self.lengths = self.dataset.get_length() - + def __len__(self): return self.num_batches - + def get_batch_indices(self): + """ + 取得当前batch在DataSet中所在的index下标序列 + + :return list(int) indexes: 下标序列 + """ return self.cur_batch_indices + + @staticmethod + def _run_fetch(batch, q): + try: + global _python_is_exit + batch.init_iter() + # print('start fetch') + while 1: + res = batch.fetch_one() + # print('fetch one') + while 1: + try: + q.put(res, timeout=3) + break + except Full: + if _python_is_exit: + return + if res is None: + # print('fetch done, waiting processing') + break + # print('fetch exit') + except Exception as e: + q.put(e) + finally: + q.join() + + @staticmethod + def _run_batch_iter(batch): + q = mp.JoinableQueue(maxsize=10) + fetch_p = mp.Process(target=Batch._run_fetch, args=(batch, q)) + fetch_p.daemon = True + fetch_p.start() + # print('fork fetch process') + while 1: + try: + res = q.get(timeout=1) + q.task_done() + # print('get fetched') + if res is None: + break + elif isinstance(res, Exception): + raise res + yield res + except Empty as e: + if fetch_p.is_alive(): + continue + else: + break + fetch_p.terminate() + fetch_p.join() + # print('iter done') -def to_tensor(batch, dtype): +def _to_tensor(batch, dtype): try: if dtype in (int, np.int8, np.int16, np.int32, np.int64): batch = torch.LongTensor(batch) @@ -92,42 +186,3 @@ def to_tensor(batch, dtype): except: pass return batch - - -def run_fetch(batch, q): - batch.init_iter() - # print('start fetch') - while 1: - res = batch.fetch_one() - # print('fetch one') - q.put(res) - if res is None: - # print('fetch done, waiting processing') - q.join() - break - # print('fetch exit') - - -def run_batch_iter(batch): - q = mp.JoinableQueue(maxsize=10) - fetch_p = mp.Process(target=run_fetch, args=(batch, q)) - fetch_p.daemon = True - fetch_p.start() - # print('fork fetch process') - while 1: - try: - res = q.get(timeout=1) - q.task_done() - # print('get fetched') - if res is None: - break - yield res - except Exception as e: - if fetch_p.is_alive(): - continue - else: - break - fetch_p.terminate() - fetch_p.join() - # print('iter done') - diff --git a/fastNLP/core/callback.py b/fastNLP/core/callback.py index b1a480cc..7fad2d0b 100644 --- a/fastNLP/core/callback.py +++ b/fastNLP/core/callback.py @@ -1,130 +1,298 @@ +r""" +callback模块实现了 fastNLP 中的许多 callback 类,用于增强 :class:`~fastNLP.Trainer` 类。 + +虽然Trainer本身已经集成了一些功能,但仍然不足以囊括训练过程中可能需要到的功能, +比如负采样,learning rate decay, Early Stop等。 +为了解决这个问题fastNLP引入了callback的机制,Callback 是一种在Trainer训练过程中特定阶段会运行的函数集合。 +关于Trainer的详细文档,请参见 :doc:`trainer 模块` + +我们将 :meth:`~fastNLP.Train.train` 这个函数内部分为以下的阶段,在对应阶段会触发相应的调用:: + + callback.on_train_begin() # 开始进行训练 + for i in range(1, n_epochs+1): + callback.on_epoch_begin() # 开始新的epoch + for batch_x, batch_y in Batch: + callback.on_batch_begin(batch_x, batch_y, indices) # batch_x是设置为input的field,batch_y是设置为target的field + 获取模型输出 + callback.on_loss_begin() + 计算loss + callback.on_backward_begin() # 可以进行一些检查,比如loss是否为None + 反向梯度回传 + callback.on_backward_end() # 进行梯度截断等 + 进行参数更新 + callback.on_step_end() + callback.on_batch_end() + # 根据设置进行evaluation,比如这是本epoch最后一个batch或者达到一定step + if do evaluation: + callback.on_valid_begin() + 进行dev data上的验证 + callback.on_valid_end() # 可以进行在其它数据集上进行验证 + callback.on_epoch_end() # epoch结束调用 + callback.on_train_end() # 训练结束 + callback.on_exception() # 这是一个特殊的步骤,在训练过程中遭遇exception会跳转到这里。 + +如下面的例子所示,我们可以使用内置的 callback 类,或者继承 :class:`~fastNLP.core.callback.Callback` +定义自己的 callback 类:: + + from fastNLP import Callback, EarlyStopCallback, Trainer, CrossEntropyLoss, AccuracyMetric + from fastNLP.models import CNNText + + start_time = time.time() + + class MyCallback(Callback): + def on_epoch_end(self): + print('{:d}ms\n\n'.format(round((time.time()-start_time)*1000))) + + model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) + trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, loss=CrossEntropyLoss(), + metrics=AccuracyMetric(), callbacks=[MyCallback(),EarlyStopCallback(10)]) + trainer.train() + +""" +__all__ = [ + "Callback", + "GradientClipCallback", + "EarlyStopCallback", + "TensorboardCallback", + "LRScheduler", + "ControlC", + + "CallbackException", + "EarlyStopError" +] + import os import torch -from tensorboardX import SummaryWriter - -from fastNLP.io.model_io import ModelSaver, ModelLoader - +from copy import deepcopy +try: + from tensorboardX import SummaryWriter + + tensorboardX_flag = True +except: + tensorboardX_flag = False + +from ..io.model_io import ModelSaver, ModelLoader +from .dataset import DataSet +from .tester import Tester + +try: + import fitlog +except: + pass class Callback(object): - """An Interface for all callbacks. + """ + 别名::class:`fastNLP.Callback` :class:`fastNLP.core.callback.Callback` - Any customized callback should implement at least one of the following methods. + Callback是fastNLP中被设计用于增强 :class:`~fastNLP.Trainer` 的类。 + 如果Callback被传递给了 Trainer , 则 Trainer 会在对应的阶段调用Callback的函数, + 具体调用时机可以通过 :doc:`trainer 模块` 查看。 + 这是Callback的基类,所有的callback必须继承自这个类 """ - + def __init__(self): super(Callback, self).__init__() - self.trainer = None # 在Trainer内部被重新赋值 - + self._trainer = None # 在Trainer内部被重新赋值 + + @property + def trainer(self): + """ + 该属性可以通过self.trainer获取到,一般情况下不需要使用这个属性。 + """ + return self._trainer + + @property + def step(self): + """当前运行到的step, 范围为[1, self.n_steps+1)""" + return self._trainer.step + + @property + def n_steps(self): + """Trainer一共会运行多少步""" + return self._trainer.n_steps + + @property + def batch_size(self): + """train和evaluate时的batch_size为多大""" + return self._trainer.batch_size + + @property + def epoch(self): + """当前运行的epoch数,范围是[1, self.n_epochs+1)""" + return self._trainer.epoch + + @property + def n_epochs(self): + """一共会运行多少个epoch""" + return self._trainer.n_epochs + + @property + def optimizer(self): + """初始化Trainer时传递的Optimizer""" + return self._trainer.optimizer + + @property + def model(self): + """正在被Trainer训练的模型""" + return self._trainer.model + + @property + def pbar(self): + """如果在Callback中需要打印内容,请使用self.pbar.write(str)。否则可能出现命令行显示效果不太好的问题。在 + on_train_begin(), on_train_end(), on_exception()中请不要使用该属性,通过print输出即可。""" + return self._trainer.pbar + + @property + def update_every(self): + """Trainer中的模型多少次反向传播才进行一次梯度更新,在Trainer初始化时传入的。""" + return self._trainer.update_every + + @property + def batch_per_epoch(self): + """每个epoch一共有多少个batch,只有在on_epoch_begin之后才能调用该属性。""" + return self._trainer.batch_per_epoch + def on_train_begin(self): - # before the main training loop - pass + """ + 在Train过程开始之前调用。 - def on_epoch_begin(self, cur_epoch, total_epoch): - # at the beginning of each epoch + :return: + """ pass + + def on_epoch_begin(self): + """ + 在每个epoch开始之前调用一次 - def on_batch_begin(self, batch_x, batch_y, indices): - # at the beginning of each step/mini-batch + :return: + """ pass + + def on_batch_begin(self, batch_x, batch_y, indices): + """ + 每次采集到一个batch的数据则调用一次。这里对batch_x或batch_y删除添加内容是可以影响到Trainer中内容的。所以在这一步 + 可以进行一些负采样之类的操作 - def on_loss_begin(self, batch_y, predict_y): - # after data_forward, and before loss computation + :param dict batch_x: DataSet中被设置为input的field的batch。 + :param dict batch_y: DataSet中被设置为target的field的batch。 + :param list(int) indices: 这次采样使用到的indices,可以通过DataSet[indices]获取出这个batch采出的Instance,在一些 + 情况下可以帮助定位是哪个Sample导致了错误。仅在Trainer的prefetch为False时可用。 + :return: + """ pass + + def on_loss_begin(self, batch_y, predict_y): + """ + 在计算loss前调用,即这里修改batch_y或predict_y的值是可以影响到loss计算的。 - def on_backward_begin(self, loss, model): - # after loss computation, and before gradient backward + :param dict batch_y: 在DataSet中被设置为target的field的batch集合。 + :param dict predict_y: 模型的forward()返回的结果。 + :return: + """ pass + + def on_backward_begin(self, loss): + """ + 在loss得到之后,但在反向传播之前。可能可以进行loss是否为NaN的检查。 - def on_backward_end(self, model): + :param torch.Tensor loss: 计算得到的loss值 + :return: + """ pass + + def on_backward_end(self): + """ + 反向梯度传播已完成,但由于update_every的设置,可能并不是每一次调用都有梯度。到这一步,还没有更新参数。 - def on_step_end(self, optimizer): + :return: + """ pass + + def on_step_end(self): + """ + 到这里模型的参数已经按照梯度更新。但可能受update_every影响,并不是每次都更新了。 - def on_batch_end(self, *args): - # at the end of each step/mini-batch + :return: + """ pass + + def on_batch_end(self): + """ + 这一步与on_step_end是紧接着的。只是为了对称性加上了这一步。 - def on_valid_begin(self): + """ pass - - def on_valid_end(self, eval_result, metric_key, optimizer): + + def on_valid_begin(self): """ - 每次执行验证机的evaluation后会调用。传入eval_result + 如果Trainer中设置了验证,则发生验证前会调用该函数 - :param eval_result: Dict[str: Dict[str: float]], evaluation的结果 - :param metric_key: str - :param optimizer: :return: """ pass - - def on_epoch_end(self, cur_epoch, n_epoch, optimizer): + + def on_valid_end(self, eval_result, metric_key, optimizer, is_better_eval): """ - 每个epoch结束将会调用该方法 + 每次执行验证集的evaluation后会调用。 - :param cur_epoch: int, 当前的batch。从1开始。 - :param n_epoch: int, 总的batch数 - :param optimizer: 传入Trainer的optimizer。 + :param Dict[str: Dict[str: float]] eval_result: , evaluation的结果。一个例子为{'AccuracyMetric':{'acc':1.0}},即 + 传入的dict是有两层,第一层是metric的名称,第二层是metric的具体指标。 + :param str metric_key: 初始化Trainer时传入的metric_key。 + :param torch.Optimizer optimizer: Trainer中使用的优化器。 + :param bool is_better_eval: 当前dev结果是否比之前的好。 :return: """ pass - - def on_train_end(self, model): + + def on_epoch_end(self): + """ + 每个epoch结束将会调用该方法 + """ + pass + + def on_train_end(self): """ 训练结束,调用该方法 - - :param model: nn.Module, 传入Trainer的模型 - :return: """ pass - - def on_exception(self, exception, model): + + def on_exception(self, exception): """ 当训练过程出现异常,会触发该方法 :param exception: 某种类型的Exception,比如KeyboardInterrupt等 - :param model: 传入Trainer的模型 - :return: """ pass -def transfer(func): +def _transfer(func): """装饰器,将对CallbackManager的调用转发到各个Callback子类. - + :param func: :return: """ - + def wrapper(manager, *arg): returns = [] for callback in manager.callbacks: - for env_name, env_value in manager.env.items(): - setattr(callback, env_name, env_value) returns.append(getattr(callback, func.__name__)(*arg)) return returns - + return wrapper class CallbackManager(Callback): - """A manager for all callbacks passed into Trainer. - It collects resources inside Trainer and raise callbacks. - - """ - def __init__(self, env, callbacks=None): """ + 内部使用的Callback管理类 :param dict env: The key is the name of the Trainer attribute(str). The value is the attribute itself. - :param Callback callbacks: + :param List[Callback] callbacks: """ super(CallbackManager, self).__init__() # set attribute of trainer environment - self.env = env - + self.callbacks = [] if callbacks is not None: if isinstance(callbacks, list): @@ -135,108 +303,87 @@ class CallbackManager(Callback): raise TypeError(f"Expect sub-classes of Callback. Got {type(obj)}") else: raise TypeError(f"Expect callbacks in CallbackManager(callbacks) to be list. Got {type(callbacks)}.") - - @transfer + + for env_name, env_val in env.items(): + for callback in self.callbacks: + setattr(callback, '_' + env_name, env_val) # Callback.trainer + + @_transfer def on_train_begin(self): pass - - @transfer - def on_epoch_begin(self, cur_epoch, total_epoch): + + @_transfer + def on_epoch_begin(self): pass - - @transfer + + @_transfer def on_batch_begin(self, batch_x, batch_y, indices): pass - - @transfer + + @_transfer def on_loss_begin(self, batch_y, predict_y): pass - - @transfer - def on_backward_begin(self, loss, model): + + @_transfer + def on_backward_begin(self, loss): pass - - @transfer - def on_backward_end(self, model): + + @_transfer + def on_backward_end(self): pass - - @transfer - def on_step_end(self, optimizer): + + @_transfer + def on_step_end(self): pass - - @transfer + + @_transfer def on_batch_end(self): pass - - @transfer + + @_transfer def on_valid_begin(self): pass - - @transfer - def on_valid_end(self, eval_result, metric_key, optimizer): + + @_transfer + def on_valid_end(self, eval_result, metric_key, optimizer, is_better_eval): pass - - @transfer - def on_epoch_end(self, cur_epoch, n_epoch, optimizer): + + @_transfer + def on_epoch_end(self): pass - - @transfer - def on_train_end(self, model): + + @_transfer + def on_train_end(self): pass - - @transfer - def on_exception(self, exception, model): + + @_transfer + def on_exception(self, exception): pass -class DummyCallback(Callback): - def on_train_begin(self, *arg): - print(arg) - - def on_epoch_end(self, cur_epoch, n_epoch, optimizer): - print(cur_epoch, n_epoch, optimizer) - - -class EchoCallback(Callback): - def on_train_begin(self): - print("before_train") - - def on_epoch_begin(self, cur_epoch, total_epoch): - print("before_epoch") - - def on_batch_begin(self, batch_x, batch_y, indices): - print("before_batch") - - def on_loss_begin(self, batch_y, predict_y): - print("before_loss") - - def on_backward_begin(self, loss, model): - print("before_backward") - - def on_batch_end(self): - print("after_batch") +class GradientClipCallback(Callback): + """ + 别名::class:`fastNLP.GradientClipCallback` :class:`fastNLP.core.callback.GradientClipCallback` - def on_epoch_end(self, cur_epoch, n_epoch, optimizer): - print("after_epoch") + 每次backward前,将parameter的gradient clip到某个范围。 - def on_train_end(self, model): - print("after_train") + :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。如果为None则默认对Trainer + 的model中所有参数进行clip + :param float clip_value: 将gradient 限制到[-clip_value, clip_value]。clip_value应该为正数 + :param str clip_type: 支持'norm', 'value' + 两种:: + 1 'norm', 将gradient的norm rescale到[-clip_value, clip_value] + + 2 'value', 将gradient限制在[-clip_value, clip_value], 小于-clip_value的gradient被赋值为-clip_value; + 大于clip_value的gradient被赋值为clip_value. -class GradientClipCallback(Callback): + """ + def __init__(self, parameters=None, clip_value=1, clip_type='norm'): - """每次backward前,将parameter的gradient clip到某个范围。 - - :param parameters: None, torch.Tensor或List[torch.Tensor], 一般通过model.parameters()获得。如果为None则默认对Trainer - 的model中所有参数进行clip - :param clip_value: float, 将gradient 限制到[-clip_value, clip_value]。clip_value应该为正数 - :param clip_type: str, 支持'norm', 'value'两种。 - (1) 'norm', 将gradient的norm rescale到[-clip_value, clip_value] - (2) 'value', 将gradient限制在[-clip_value, clip_value], 小于-clip_value的gradient被赋值为-clip_value; 大于 - clip_value的gradient被赋值为clip_value. - """ + super().__init__() - + from torch import nn if clip_type == 'norm': self.clip_fun = nn.utils.clip_grad_norm_ @@ -246,36 +393,30 @@ class GradientClipCallback(Callback): raise ValueError("Only supports `norm` or `value` right now.") self.parameters = parameters self.clip_value = clip_value - - def on_backward_end(self, model): - self.clip_fun(model.parameters(), self.clip_value) - - -class CallbackException(BaseException): - def __init__(self, msg): - super(CallbackException, self).__init__(msg) - - -class EarlyStopError(CallbackException): - def __init__(self, msg): - super(EarlyStopError, self).__init__(msg) + + def on_backward_end(self): + if self.parameters is None: + self.clip_fun(self.model.parameters(), self.clip_value) + else: + self.clip_fun(self.parameters, self.clip_value) class EarlyStopCallback(Callback): - def __init__(self, patience): - """ + """ + 别名::class:`fastNLP.EarlyStopCallback` :class:`fastNLP.core.callback.EarlyStopCallback` + + 多少个epoch没有变好就停止训练,相关类 :class:`EarlyStopError` - :param int patience: 停止之前等待的epoch数 - """ + :param int patience: epoch的数量 + """ + + def __init__(self, patience): super(EarlyStopCallback, self).__init__() - self.trainer = None # override by CallbackManager self.patience = patience self.wait = 0 - self.epoch = 0 - - def on_valid_end(self, eval_result, metric_key, optimizer): - self.epoch += 1 - if not self.trainer._better_eval_result(eval_result): + + def on_valid_end(self, eval_result, metric_key, optimizer, is_better_eval): + if not is_better_eval: # current result is getting worse if self.wait == self.patience: raise EarlyStopError("Early stopping raised.") @@ -283,44 +424,135 @@ class EarlyStopCallback(Callback): self.wait += 1 else: self.wait = 0 - - def on_exception(self, exception, model): + + def on_exception(self, exception): if isinstance(exception, EarlyStopError): print("Early Stopping triggered in epoch {}!".format(self.epoch)) else: raise exception # 抛出陌生Error +class FitlogCallback(Callback): + """ + 别名: :class:`fastNLP.FitlogCallback` :class:`fastNLP.core.callback.FitlogCallback` + + 该callback将loss和progress自动写入到fitlog中; 如果Trainer有dev的数据,将自动把dev的结果写入到log中; 同时还支持传入 + 一个(或多个)test数据集进行测试(只有在trainer具有dev时才能使用),每次在dev上evaluate之后会在这些数据集上验证一下。 + 并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 + fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 + + :param DataSet,dict(DataSet) data: 传入DataSet对象,会使用多个Trainer中的metric对数据进行验证。如果需要传入多个 + DataSet请通过dict的方式传入,dict的key将作为对应dataset的name传递给fitlog。若tester不为None时,data需要通过 + dict的方式传入。如果仅传入DataSet, 则被命名为test + :param Tester tester: Tester对象,将在on_valid_end时调用。tester中的DataSet会被称为为`test` + :param int verbose: 是否在终端打印内容,0不打印 + :param bool log_exception: fitlog是否记录发生的exception信息 + """ + + def __init__(self, data=None, tester=None, verbose=0, log_exception=False): + super().__init__() + self.datasets = {} + self.testers = {} + self._log_exception = log_exception + if tester is not None: + assert isinstance(tester, Tester), "Only fastNLP.Tester allowed." + assert isinstance(data, dict) or data is None, "If tester is not None, only dict[DataSet] allowed for data." + if data is not None: + assert 'test' not in data, "Cannot use `test` as DataSet key, when tester is passed." + setattr(tester, 'verbose', 0) + self.testers['test'] = tester + + if isinstance(data, dict): + for key, value in data.items(): + assert isinstance(value, DataSet), f"Only DataSet object is allowed, not {type(value)}." + for key, value in data.items(): + self.datasets[key] = value + elif isinstance(data, DataSet): + self.datasets['test'] = data + else: + raise TypeError("data receives dict[DataSet] or DataSet object.") + + self.verbose = verbose + + def on_train_begin(self): + if (len(self.datasets)>0 or len(self.testers)>0 ) and self.trainer.dev_data is None: + raise RuntimeError("Trainer has no dev data, you cannot pass extra data to do evaluation.") + + if len(self.datasets)>0: + for key, data in self.datasets.items(): + tester = Tester(data=data, model=self.model, batch_size=self.batch_size, metrics=self.trainer.metrics, + verbose=0) + self.testers[key] = tester + fitlog.add_progress(total_steps=self.n_steps) + + def on_backward_begin(self, loss): + fitlog.add_loss(loss.item(), name='loss', step=self.step, epoch=self.epoch) + + def on_valid_end(self, eval_result, metric_key, optimizer, better_result): + if better_result: + eval_result = deepcopy(eval_result) + eval_result['step'] = self.step + eval_result['epoch'] = self.epoch + fitlog.add_best_metric(eval_result) + fitlog.add_metric(eval_result, step=self.step, epoch=self.epoch) + if len(self.testers)>0: + for key, tester in self.testers.items(): + try: + eval_result = tester.test() + if self.verbose!=0: + self.pbar.write("Evaluation on DataSet {}:".format(key)) + self.pbar.write(tester._format_eval_results(eval_result)) + fitlog.add_metric(eval_result, name=key, step=self.step, epoch=self.epoch) + if better_result: + fitlog.add_best_metric(eval_result, name=key) + except Exception: + self.pbar.write("Exception happens when evaluate on DataSet named `{}`.".format(key)) + + def on_train_end(self): + fitlog.finish() + + def on_exception(self, exception): + fitlog.finish(status=1) + if self._log_exception: + fitlog.add_other(str(exception), name='except_info') + class LRScheduler(Callback): - def __init__(self, lr_scheduler): - """对PyTorch LR Scheduler的包装 + """ + 别名::class:`fastNLP.LRScheduler` :class:`fastNLP.core.callback.LRScheduler` - :param lr_scheduler: PyTorch的lr_scheduler - """ + 对PyTorch LR Scheduler的包装以使得其可以被Trainer所使用 + + :param torch.optim.lr_scheduler._LRScheduler lr_scheduler: PyTorch的lr_scheduler + """ + + def __init__(self, lr_scheduler): + super(LRScheduler, self).__init__() import torch.optim if isinstance(lr_scheduler, torch.optim.lr_scheduler._LRScheduler): self.scheduler = lr_scheduler else: raise ValueError(f"Expect torch.optim.lr_scheduler for LRScheduler. Got {type(lr_scheduler)}.") - - def on_epoch_begin(self, cur_epoch, total_epoch): - self.scheduler.step() - print("scheduler step ", "lr=", self.trainer.optimizer.param_groups[0]["lr"]) + + def on_epoch_begin(self): + self.scheduler.step(self.epoch) class ControlC(Callback): - def __init__(self, quit_all): - """ + """ + 别名::class:`fastNLP.ControlC` :class:`fastNLP.core.callback.ControlC` - :param quit_all: 若为True,则检测到control+C 直接退出程序;否则只退出Trainer - """ + :param bool quit_all: 若为True,则检测到control+C 直接退出程序;否则只退出Trainer + """ + + def __init__(self, quit_all): + super(ControlC, self).__init__() if type(quit_all) != bool: raise ValueError("In KeyBoardInterrupt, quit_all arguemnt must be a bool.") self.quit_all = quit_all - - def on_exception(self, exception, model): + + def on_exception(self, exception): if isinstance(exception, KeyboardInterrupt): if self.quit_all is True: import sys @@ -335,7 +567,7 @@ class SmoothValue(object): def __init__(self, beta: float): self.beta, self.n, self.mov_avg = beta, 0, 0 self.smooth = None - + def add_value(self, val: float) -> None: "Add `val` to calculate updated smoothed value." self.n += 1 @@ -344,48 +576,58 @@ class SmoothValue(object): class LRFinder(Callback): - def __init__(self, n_batch, start_lr=1e-6, end_lr=10): - """用第一个 epoch 找最佳的学习率,从第二个epoch开始应用它 + """ + 别名::class:`fastNLP.LRFinder` :class:`fastNLP.core.callback.LRFinder` - :param n_batch: 一个epoch内的iteration数 - :param start_lr: 学习率下界 - :param end_lr: 学习率上界 - """ + 用第一个 epoch 找最佳的学习率,从第二个epoch开始应用它 + + :param float start_lr: 学习率下界 + :param float end_lr: 学习率上界 + """ + + def __init__(self, start_lr=1e-6, end_lr=10): + super(LRFinder, self).__init__() self.start_lr, self.end_lr = start_lr, end_lr - self.num_it = n_batch + self.stop = False self.best_loss = 0. self.best_lr = None self.loss_history = [] self.smooth_value = SmoothValue(0.8) self.opt = None - scale = (self.end_lr - self.start_lr) / self.num_it - - self.lr_gen = (self.start_lr + scale * (step + 1) for step in range(self.num_it)) self.find = None self.loader = ModelLoader() - - def on_epoch_begin(self, cur_epoch, total_epoch): - if cur_epoch == 1: + + @property + def lr_gen(self): + scale = (self.end_lr - self.start_lr) / self.batch_per_epoch + return (self.start_lr + scale * (step + 1) for step in range(self.batch_per_epoch)) + + @property + def num_it(self): + return self.batch_per_epoch + + def on_epoch_begin(self): + if self.epoch == 1: # first epoch self.opt = self.trainer.optimizer # pytorch optimizer self.opt.param_groups[0]["lr"] = self.start_lr # save model ModelSaver("tmp").save_pytorch(self.trainer.model, param_only=True) self.find = True - - def on_backward_begin(self, loss, model): + + def on_backward_begin(self, loss): if self.find: if torch.isnan(loss) or self.stop is True: self.stop = True return - loss_val = loss.detach().cpu().data + loss_val = loss.detach().mean().item() self.loss_history.append(loss_val) self.smooth_value.add_value(loss_val) if self.best_loss == 0. or self.smooth_value.smooth < self.best_loss: self.best_loss = self.smooth_value.smooth self.best_lr = self.opt.param_groups[0]["lr"] - + def on_batch_end(self, *args): if self.find: lr = next(self.lr_gen, None) @@ -394,24 +636,31 @@ class LRFinder(Callback): return self.opt.param_groups[0]["lr"] = lr # self.loader.load_pytorch(self.trainer.model, "tmp") - - def on_epoch_end(self, cur_epoch, n_epoch, optimizer): - if cur_epoch == 1: + + def on_epoch_end(self): + if self.epoch == 1: # first epoch self.opt.param_groups[0]["lr"] = self.best_lr self.find = False # reset model ModelLoader().load_pytorch(self.trainer.model, "tmp") - print("Model reset. \nFind best lr={}".format(self.best_lr)) + self.pbar.write("Model reset. \nFind best lr={}".format(self.best_lr)) class TensorboardCallback(Callback): """ - 接受以下一个或多个字符串作为参数: - - "model" - - "loss" - - "metric" + 别名::class:`fastNLP.TensorboardCallback` :class:`fastNLP.core.callback.TensorboardCallback` + + 接受以下一个或多个字符串作为参数: + - "model" + - "loss" + - "metric" + + .. warning:: + fastNLP 已停止对此功能的维护,请等待 fastNLP 兼容 PyTorch1.1 的下一个版本。 + 或者使用和 fastNLP 高度配合的 fitlog(参见 :doc:`/user/with_fitlog` )。 + """ - + def __init__(self, *options): super(TensorboardCallback, self).__init__() args = {"model", "loss", "metric"} @@ -421,15 +670,18 @@ class TensorboardCallback(Callback): self.options = options self._summary_writer = None self.graph_added = False - + def on_train_begin(self): save_dir = self.trainer.save_path if save_dir is None: path = os.path.join("./", 'tensorboard_logs_{}'.format(self.trainer.start_time)) else: path = os.path.join(save_dir, 'tensorboard_logs_{}'.format(self.trainer.start_time)) - self._summary_writer = SummaryWriter(path) - + if tensorboardX_flag: + self._summary_writer = SummaryWriter(path) + else: + self._summary_writer = None + def on_batch_begin(self, batch_x, batch_y, indices): if "model" in self.options and self.graph_added is False: # tesorboardX 这里有大bug,暂时没法画模型图 @@ -439,37 +691,53 @@ class TensorboardCallback(Callback): # args = args[0] if len(args) == 1 else args # self._summary_writer.add_graph(self.trainer.model, torch.zeros(32, 2)) self.graph_added = True - - def on_backward_begin(self, loss, model): - if "loss" in self.options: + + def on_backward_begin(self, loss): + if "loss" in self.options and self._summary_writer: self._summary_writer.add_scalar("loss", loss.item(), global_step=self.trainer.step) - - if "model" in self.options: + + if "model" in self.options and self._summary_writer: for name, param in self.trainer.model.named_parameters(): if param.requires_grad: self._summary_writer.add_scalar(name + "_mean", param.mean(), global_step=self.trainer.step) # self._summary_writer.add_scalar(name + "_std", param.std(), global_step=self.trainer.step) self._summary_writer.add_scalar(name + "_grad_mean", param.grad.mean(), global_step=self.trainer.step) - - def on_valid_end(self, eval_result, metric_key, optimizer): - if "metric" in self.options: + + def on_valid_end(self, eval_result, metric_key, optimizer, is_better_eval): + if "metric" in self.options and self._summary_writer: for name, metric in eval_result.items(): for metric_key, metric_val in metric.items(): self._summary_writer.add_scalar("valid_{}_{}".format(name, metric_key), metric_val, global_step=self.trainer.step) - - def on_train_end(self, model): - self._summary_writer.close() - del self._summary_writer - - def on_exception(self, exception, model): + + def on_train_end(self): + if self._summary_writer: + self._summary_writer.close() + del self._summary_writer + + def on_exception(self, exception): if hasattr(self, "_summary_writer"): self._summary_writer.close() del self._summary_writer -if __name__ == "__main__": - manager = CallbackManager(env={"n_epoch": 3}, callbacks=[DummyCallback(), DummyCallback()]) - manager.on_train_begin(10, 11, 12) - # print(manager.after_epoch()) +class CallbackException(BaseException): + """ + 当需要通过callback跳出训练的时候可以通过抛出CallbackException并在on_exception中捕获这个值。 + + :param str msg: Exception的信息。 + """ + + def __init__(self, msg): + super(CallbackException, self).__init__(msg) + + +class EarlyStopError(CallbackException): + """ + 用于EarlyStop时从Trainer训练循环中跳出。 + + """ + + def __init__(self, msg): + super(EarlyStopError, self).__init__(msg) diff --git a/fastNLP/core/const.py b/fastNLP/core/const.py new file mode 100644 index 00000000..89ff51a2 --- /dev/null +++ b/fastNLP/core/const.py @@ -0,0 +1,59 @@ +class Const: + """ + fastNLP中field命名常量。 + + .. todo:: + 把下面这段改成表格 + + 具体列表:: + + INPUT 模型的序列输入 words(复数words1, words2) + CHAR_INPUT 模型character输入 chars(复数chars1, chars2) + INPUT_LEN 序列长度 seq_len(复数seq_len1,seq_len2) + OUTPUT 模型输出 pred(复数pred1, pred2) + TARGET 真实目标 target(复数target1,target2) + LOSS 损失函数 loss (复数loss1,loss2) + + """ + INPUT = 'words' + CHAR_INPUT = 'chars' + INPUT_LEN = 'seq_len' + OUTPUT = 'pred' + TARGET = 'target' + LOSS = 'loss' + + @staticmethod + def INPUTS(i): + """得到第 i 个 ``INPUT`` 的命名""" + i = int(i) + 1 + return Const.INPUT + str(i) + + @staticmethod + def CHAR_INPUTS(i): + """得到第 i 个 ``CHAR_INPUT`` 的命名""" + i = int(i) + 1 + return Const.CHAR_INPUT + str(i) + + @staticmethod + def INPUT_LENS(i): + """得到第 i 个 ``INPUT_LEN`` 的命名""" + i = int(i) + 1 + return Const.INPUT_LEN + str(i) + + @staticmethod + def OUTPUTS(i): + """得到第 i 个 ``OUTPUT`` 的命名""" + i = int(i) + 1 + return Const.OUTPUT + str(i) + + @staticmethod + def TARGETS(i): + """得到第 i 个 ``TARGET`` 的命名""" + i = int(i) + 1 + return Const.TARGET + str(i) + + @staticmethod + def LOSSES(i): + """得到第 i 个 ``LOSS`` 的命名""" + i = int(i) + 1 + return Const.LOSS + str(i) diff --git a/fastNLP/core/dataset.py b/fastNLP/core/dataset.py index 601fa589..2f3e35ca 100644 --- a/fastNLP/core/dataset.py +++ b/fastNLP/core/dataset.py @@ -1,29 +1,304 @@ +""" +:class:`~fastNLP.core.dataset.DataSet` 是fastNLP中用于承载数据的容器。可以将DataSet看做是一个表格, +每一行是一个sample (在fastNLP中被称为 :mod:`~.instance` ), +每一列是一个feature (在fastNLP中称为 :mod:`.field` )。 + +.. csv-table:: Following is a demo layout of DataSet + :header: "sentence", "words", "seq_len" + + "This is the first instance .", "[This, is, the, first, instance, .]", 6 + "Second instance .", "[Second, instance, .]", 3 + "Third instance .", "[Third, instance, .]", 3 + "...", "[...]", "..." + +在fastNLP内部每一行是一个 :class:`~fastNLP.Instance` 对象; 每一列是一个 :class:`~fastNLP.FieldArray` 对象。 + +1 DataSet的创建 + 创建DataSet主要有以下的3种方式 + +1.1 传入dict + + Example:: + + from fastNLP import DataSet + data = {'sentence':["This is the first instance .", "Second instance .", "Third instance ."], + 'words': [['this', 'is', 'the', 'first', 'instance', '.'], ['Second', 'instance', '.'], ['Third', 'instance', '.'], + 'seq_len': [6, 3, 3]} + dataset = DataSet(data) + # 传入的dict的每个key的value应该为具有相同长度的list + +1.2 通过构建Instance + + Example:: + + from fastNLP import DataSet + from fastNLP import Instance + dataset = DataSet() + instance = Instance(sentence="This is the first instance", + words=['this', 'is', 'the', 'first', 'instance', '.'], + seq_len=6) + dataset.append(instance) + # 可以继续append更多内容,但是append的instance应该和第一个instance拥有完全相同的field + +1.3 通过list(Instance) + + Example:: + + from fastNLP import DataSet + from fastNLP import Instance + instances = [] + instances.append(Instance(sentence="This is the first instance", + words=['this', 'is', 'the', 'first', 'instance', '.'], + seq_len=6)) + instances.append(Instance(sentence="Second instance .", + words=['Second', 'instance', '.'], + seq_len=3)) + dataset = DataSet(instances) + +2 DataSet与预处理 + 常见的预处理有如下几种 + +2.1 从某个文本文件读取内容 # + + .. todo:: + 引用DataLoader + + Example:: + + from fastNLP import DataSet + from fastNLP import Instance + dataset = DataSet() + filepath='some/text/file' + # 假设文件中每行内容如下(sentence label): + # This is a fantastic day positive + # The bad weather negative + # ..... + with open(filepath, 'r') as f: + for line in f: + sent, label = line.strip().split('\t') + dataset.append(Instance(sentence=sent, label=label)) + +2.2 index, 返回结果为对DataSet对象的浅拷贝 + + Example:: + + import numpy as np + from fastNLP import DataSet + dataset = DataSet({'a': np.arange(10), 'b': [[_] for _ in range(10)]}) + d[0] # 使用一个下标获取一个instance + >>{'a': 0 type=int,'b': [2] type=list} # 得到一个instance + d[1:3] # 使用slice获取一个新的DataSet + >>DataSet({'a': 1 type=int, 'b': [2] type=list}, {'a': 2 type=int, 'b': [2] type=list}) + +2.3 对DataSet中的内容处理 + + Example:: + + from fastNLP import DataSet + data = {'sentence':["This is the first instance .", "Second instance .", "Third instance ."]} + dataset = DataSet(data) + # 将句子分成单词形式, 详见DataSet.apply()方法 + dataset.apply(lambda ins: ins['sentence'].split(), new_field_name='words') + # 或使用DataSet.apply_field() + dataset.apply_field(lambda sent:sent.split(), field_name='sentence', new_field_name='words') + # 除了匿名函数,也可以定义函数传递进去 + def get_words(instance): + sentence = instance['sentence'] + words = sentence.split() + return words + dataset.apply(get_words, new_field_name='words') + +2.4 删除DataSet的内容 + + Example:: + + from fastNLP import DataSet + dataset = DataSet({'a': list(range(-5, 5))}) + # 返回满足条件的instance,并放入DataSet中 + dropped_dataset = dataset.drop(lambda ins:ins['a']<0, inplace=False) + # 在dataset中删除满足条件的instance + dataset.drop(lambda ins:ins['a']<0) # dataset的instance数量减少 + # 删除第3个instance + dataset.delete_instance(2) + # 删除名为'a'的field + dataset.delete_field('a') + + +2.5 遍历DataSet的内容 + + Example:: + + for instance in dataset: + # do something + +2.6 一些其它操作 + + Example:: + + # 检查是否存在名为'a'的field + dataset.has_field('a') # 或 ('a' in dataset) + # 将名为'a'的field改名为'b' + dataset.rename_field('a', 'b') + # DataSet的长度 + len(dataset) + +3 DataSet与自然语言处理(NLP) + 在目前深度学习的模型中,大都依赖于随机梯度下降法(SGD)进行模型的优化。随机梯度下降需要将数据切分成一个一个的Batch, + 一个Batch进行一次前向计算(forward)与梯度后向传播(backward)。在自然语言处理的场景下,往往还需要对数据进行pad。这是 + 由于句子的长度一般是不同的,但是一次Batch中的每个field都必须是一个tensor,所以需要将所有句子都补齐到相同的长度。 + +3.1 DataSet与Batch + + 我们先看fastNLP中如何将数据分成一个一个的Batch的例子, 这里我们使用随机生成的数据来模拟一个二分类文本分类任务, + words和characters是输入,labels是文本类别 + + Example:: + + from fastNLP import DataSet + from fastNLP import Batch + from fastNLP import SequentialSampler + from fastNLP import EngChar2DPadder + + num_instances = 100 + # 假设每句话最少2个词,最多5个词; 词表的大小是100个; 一共26个字母,每个单词最短1个字母,最长5个字母 + lengths = [random.randint(2, 5) for _ in range(num_instances)] + data = {'words': [[random.randint(1, 100) for _ in range(lengths[idx]) ] for idx in range(num_instances)], + 'chars': [ + [[random.randint(1, 27) for _ in range(random.randint(1, 5))] + for _ in range(lengths[idx])] + for idx in range(num_instances)], + 'label': [random.randint(0, 1) for _ in range(num_instances)]} + + d = DataSet(data) + d.set_padder('chars', EngChar2DPadder()) # 因为英文character的pad方式与word的pad方式不一样 + + d.set_target('label') + d.set_input('words', 'chars') + + for batch_x, batch_y in Batch(d, sampler=SequentialSampler(), batch_size=2): + print("batch_x:", batch_x) + print("batch_y:", batch_y) + break + # 输出为 + # {'words': tensor([[49, 27, 20, 36, 63], + # [53, 82, 23, 11, 0]]), 'chars': tensor([[[13, 3, 14, 25, 1], + # [ 8, 20, 12, 0, 0], + # [27, 8, 0, 0, 0], + # [ 1, 15, 26, 0, 0], + # [11, 24, 17, 0, 0]], + # + # [[ 6, 14, 11, 27, 22], + # [18, 6, 4, 19, 0], + # [19, 22, 9, 0, 0], + # [10, 25, 0, 0, 0], + # [ 0, 0, 0, 0, 0]]])} + # {'label': tensor([0, 0])} + + 其中 :class:`~fastNLP.Batch` 是用于从DataSet中按照batch_size为大小取出batch的迭代器, + :class:`~fastNLP.SequentialSampler` 用于指示 Batch 以怎样的 + 顺序从DataSet中取出instance以组成一个batch, + 更详细的说明请参照 :class:`~fastNLP.Batch` 和 :class:`~fastNLP.SequentialSampler` 文档。 + + 通过DataSet.set_input('words', 'chars'), fastNLP将认为'words'和'chars'这两个field都是input,并将它们都放入迭代器 + 生成的第一个dict中; DataSet.set_target('labels'), fastNLP将认为'labels'这个field是target,并将其放入到迭代器的第 + 二个dict中。如上例中所打印结果。分为input和target的原因是由于它们在被 :class:`~fastNLP.Trainer` 所使用时会有所差异, + 详见 :class:`~fastNLP.Trainer` + + 当把某个field设置为'target'或者'input'的时候(两者不是互斥的,可以同时设为input和target),fastNLP不仅仅只是将其放 + 置到不同的dict中,而还会对被设置为input或target的field进行类型检查。类型检查的目的是为了看能否把该field转为 + pytorch的torch.LongTensor或torch.FloatTensor类型(也可以在Batch中设置输出numpy类型,参考 :class:`~fastNLP.Batch` ),如上例所示, + fastNLP已将words,chars和label转为了Tensor类型。如果field在每个instance都拥有相同的维度(不能超过两维),且最内层 + 的元素都为相同的type(int, float, np.int*, np.float*),则fastNLP默认将对该field进行pad。也支持全为str的field作为 + target和input,这种情况下,fastNLP默认不进行pad。另外,当某个field已经被设置为了target或者input后,之后append的 + instance对应的field必须要和前面已有的内容一致,否则会报错。 + + 可以查看field的dtype:: + + from fastNLP import DataSet + + d = DataSet({'a': [0, 1, 3], 'b':[[1.0, 2.0], [0.1, 0.2], [3]]}) + d.set_input('a', 'b') + d.a.dtype + >> numpy.int64 + d.b.dtype + >> numpy.float64 + # 默认情况下'a'这个field将被转换为torch.LongTensor,但如果需要其为torch.FloatTensor可以手动修改dtype + d.a.dtype = float # 请确保该field的确可以全部转换为float。 + + 如果某个field中出现了多种类型混合(比如一部分为str,一部分为int)的情况,fastNLP无法判断该field的类型,会报如下的 + 错误:: + + from fastNLP import DataSet + d = DataSet({'data': [1, 'a']}) + d.set_input('data') + >> RuntimeError: Mixed data types in Field data: [, ] + + 可以通过设置以忽略对该field进行类型检查:: + + from fastNLP import DataSet + d = DataSet({'data': [1, 'a']}) + d.set_ignore_type('data') + d.set_input('data') + + 当某个field被设置为忽略type之后,fastNLP将不对其进行pad。 + +3.2 DataSet与pad + + 在fastNLP里,pad是与一个field绑定的。即不同的field可以使用不同的pad方式,比如在英文任务中word需要的pad和 + character的pad方式往往是不同的。fastNLP是通过一个叫做 :class:`~fastNLP.Padder` 的子类来完成的。 + 默认情况下,所有field使用 :class:`~fastNLP.AutoPadder` + 。可以通过使用以下方式设置Padder(如果将padder设置为None,则该field不会进行pad操作)。 + 大多数情况下直接使用 :class:`~fastNLP.AutoPadder` 就可以了。 + 如果 :class:`~fastNLP.AutoPadder` 或 :class:`~fastNLP.EngChar2DPadder` 无法满足需求, + 也可以自己写一个 :class:`~fastNLP.Padder` 。 + + Example:: + + from fastNLP import DataSet + from fastNLP import EngChar2DPadder + import random + dataset = DataSet() + max_chars, max_words, sent_num = 5, 10, 20 + contents = [[ + [random.randint(1, 27) for _ in range(random.randint(1, max_chars))] + for _ in range(random.randint(1, max_words)) + ] for _ in range(sent_num)] + # 初始化时传入 + dataset.add_field('chars', contents, padder=EngChar2DPadder()) + # 直接设置 + dataset.set_padder('chars', EngChar2DPadder()) + # 也可以设置pad的value + dataset.set_pad_val('chars', -1) + + +""" +__all__ = [ + "DataSet" +] + import _pickle as pickle +import warnings import numpy as np -from fastNLP.core.fieldarray import AutoPadder -from fastNLP.core.fieldarray import FieldArray -from fastNLP.core.instance import Instance -from fastNLP.core.utils import get_func_signature -from fastNLP.io.base_loader import DataLoaderRegister +from .field import AutoPadder +from .field import FieldArray +from .instance import Instance +from .utils import _get_func_signature class DataSet(object): - """DataSet is the collection of examples. - DataSet provides instance-level interface. You can append and access an instance of the DataSet. - However, it stores data in a different way: Field-first, Instance-second. - """ + 别名::class:`fastNLP.DataSet` :class:`fastNLP.core.dataset.DataSet` - def __init__(self, data=None): - """ + fastNLP的数据容器,详细的使用方法见文档 :doc:`fastNLP.core.dataset` + + :param data: 如果为dict类型,则每个key的value应该为等长的list; 如果为list, + 每个元素应该为具有相同field的 :class:`~fastNLP.Instance` 。 - :param data: a dict or a list. - If `data` is a dict, the key is the name of a FieldArray and the value is the FieldArray. All values - must be of the same length. - If `data` is a list, it must be a list of Instance objects. - """ + """ + + def __init__(self, data=None): self.field_arrays = {} if data is not None: if isinstance(data, dict): @@ -32,51 +307,48 @@ class DataSet(object): length_set.add(len(value)) assert len(length_set) == 1, "Arrays must all be same length." for key, value in data.items(): - self.add_field(name=key, fields=value) + self.add_field(field_name=key, fields=value) elif isinstance(data, list): for ins in data: assert isinstance(ins, Instance), "Must be Instance type, not {}.".format(type(ins)) self.append(ins) - + else: raise ValueError("data only be dict or list type.") - + def __contains__(self, item): return item in self.field_arrays - + def __iter__(self): def iter_func(): for idx in range(len(self)): yield self[idx] - + return iter_func() - + def _inner_iter(self): class Iter_ptr: def __init__(self, dataset, idx): self.dataset = dataset self.idx = idx - + def __getitem__(self, item): assert item in self.dataset.field_arrays, "no such field:{} in Instance {}".format(item, self.dataset[ self.idx]) assert self.idx < len(self.dataset.field_arrays[item]), "index:{} out of range".format(self.idx) return self.dataset.field_arrays[item][self.idx] - + def __repr__(self): return self.dataset[self.idx].__repr__() - + def inner_iter_func(): for idx in range(len(self)): yield Iter_ptr(self, idx) - + return inner_iter_func() - + def __getitem__(self, idx): - """Fetch Instance(s) at the `idx` position(s) in the dataset. - Notice: This method returns a copy of the actual instance(s). Any change to the returned value would not modify - the origin instance(s) of the DataSet. - If you want to make in-place changes to all Instances, use `apply` method. + """给定int的index,返回一个Instance; 给定slice,返回包含这个slice内容的新的DataSet。 :param idx: can be int or slice. :return: If `idx` is int, return an Instance object. @@ -86,37 +358,41 @@ class DataSet(object): return Instance(**{name: self.field_arrays[name][idx] for name in self.field_arrays}) elif isinstance(idx, slice): if idx.start is not None and (idx.start >= len(self) or idx.start <= -len(self)): - raise RuntimeError(f"Start index {idx.start} out of range 0-{len(self)-1}") + raise RuntimeError(f"Start index {idx.start} out of range 0-{len(self) - 1}") data_set = DataSet() for field in self.field_arrays.values(): - data_set.add_field(name=field.name, fields=field.content[idx], padder=field.padder, - is_input=field.is_input, is_target=field.is_target) + data_set.add_field(field_name=field.name, fields=field.content[idx], padder=field.padder, + is_input=field.is_input, is_target=field.is_target, ignore_type=field.ignore_type) return data_set elif isinstance(idx, str): if idx not in self: raise KeyError("No such field called {} in DataSet.".format(idx)) return self.field_arrays[idx] + elif isinstance(idx, list): + dataset = DataSet() + for i in idx: + assert isinstance(i, int), "Only int index allowed." + instance = self[i] + dataset.append(instance) + for field_name, field in self.field_arrays.items(): + dataset.field_arrays[field_name].to(field) + return dataset else: raise KeyError("Unrecognized type {} for idx in __getitem__ method".format(type(idx))) - + def __getattr__(self, item): # Not tested. Don't use !! if item == "field_arrays": raise AttributeError if isinstance(item, str) and item in self.field_arrays: return self.field_arrays[item] - try: - reader = DataLoaderRegister.get_reader(item) - return reader - except AttributeError: - raise - + def __setstate__(self, state): self.__dict__ = state - + def __getstate__(self): return self.__dict__ - + def __len__(self): """Fetch the length of the dataset. @@ -126,207 +402,394 @@ class DataSet(object): return 0 field = iter(self.field_arrays.values()).__next__() return len(field) - + def __inner_repr__(self): if len(self) < 20: return ",\n".join([ins.__repr__() for ins in self]) else: return self[:5].__inner_repr__() + "\n...\n" + self[-5:].__inner_repr__() - + def __repr__(self): return "DataSet(" + self.__inner_repr__() + ")" + + def append(self, instance): + """ + 将一个instance对象append到DataSet后面。 - def append(self, ins): - """Add an instance to the DataSet. - If the DataSet is not empty, the instance must have the same field names as the rest instances in the DataSet. - - :param ins: an Instance object + :param instance: :class:`~fastNLP.Instance` 类型。若DataSet不为空,则instance应该拥有和DataSet完全一样的field。 """ if len(self.field_arrays) == 0: # DataSet has no field yet - for name, field in ins.fields.items(): + for name, field in instance.fields.items(): field = field.tolist() if isinstance(field, np.ndarray) else field self.field_arrays[name] = FieldArray(name, [field]) # 第一个样本,必须用list包装起来 else: - if len(self.field_arrays) != len(ins.fields): + if len(self.field_arrays) != len(instance.fields): raise ValueError( "DataSet object has {} fields, but attempt to append an Instance object with {} fields." - .format(len(self.field_arrays), len(ins.fields))) - for name, field in ins.fields.items(): + .format(len(self.field_arrays), len(instance.fields))) + for name, field in instance.fields.items(): assert name in self.field_arrays self.field_arrays[name].append(field) + + def add_fieldarray(self, field_name, fieldarray): + """ + 将fieldarray添加到DataSet中. - def add_field(self, name, fields, padder=AutoPadder(pad_val=0), is_input=False, is_target=False): - """Add a new field to the DataSet. + :param str field_name: 新加入的field的名称 + :param fieldarray: :class:`~fastNLP.FieldArray` 类型。需要加入DataSet的field的内容 + :return: + """ + if not isinstance(fieldarray, FieldArray): + raise TypeError("Only fastNLP.FieldArray supported.") + if len(self) != len(fieldarray): + raise RuntimeError(f"The field to add must have the same size as dataset. " + f"Dataset size {len(self)} != field size {len(fieldarray)}") + self.field_arrays[field_name] = fieldarray + + def add_field(self, field_name, fields, padder=AutoPadder(), is_input=False, is_target=False, ignore_type=False): + """ + 新增一个field - :param str name: the name of the field. - :param fields: a list of int, float, or other objects. - :param int padder: PadBase对象,如何对该Field进行padding。大部分情况使用默认值即可 - :param bool is_input: whether this field is model input. - :param bool is_target: whether this field is label or target. + :param str field_name: 新增的field的名称 + :param list fields: 需要新增的field的内容 + :param None, padder: :class:`~fastNLP.Padder` 类型, + 如果为None,则不进行pad,默认使用 :class:`~fastNLP.AutoPadder` 自动判断是否需要做pad。 + :param bool is_input: 新加入的field是否是input + :param bool is_target: 新加入的field是否是target + :param bool ignore_type: 是否忽略对新加入的field的类型检查 """ + if len(self.field_arrays) != 0: if len(self) != len(fields): - raise RuntimeError(f"The field to append must have the same size as dataset. " + raise RuntimeError(f"The field to add must have the same size as dataset. " f"Dataset size {len(self)} != field size {len(fields)}") - self.field_arrays[name] = FieldArray(name, fields, is_target=is_target, is_input=is_input, - padder=padder) + self.field_arrays[field_name] = FieldArray(field_name, fields, is_target=is_target, is_input=is_input, + padder=padder, ignore_type=ignore_type) + + def delete_instance(self, index): + """ + 删除第index个instance - def delete_field(self, name): - """Delete a field based on the field name. + :param int index: 需要删除的instance的index,从0开始 + """ + assert isinstance(index, int), "Only integer supported." + if len(self) <= index: + raise IndexError("{} is too large for as DataSet with {} instances.".format(index, len(self))) + if len(self) == 1: + self.field_arrays.clear() + else: + for field in self.field_arrays.values(): + field.pop(index) + + def delete_field(self, field_name): + """ + 删除名为field_name的field - :param name: the name of the field to be deleted. + :param str field_name: 需要删除的field的名称. """ - self.field_arrays.pop(name) + self.field_arrays.pop(field_name) + + def has_field(self, field_name): + """ + 判断DataSet中是否有名为field_name这个field + :param str field_name: field的名称 + :return bool: 表示是否有名为field_name这个field + """ + if isinstance(field_name, str): + return field_name in self.field_arrays + return False + def get_field(self, field_name): + """ + 获取field_name这个field + + :param str field_name: field的名称 + :return: :class:`~fastNLP.FieldArray` + """ if field_name not in self.field_arrays: raise KeyError("Field name {} not found in DataSet".format(field_name)) return self.field_arrays[field_name] - + def get_all_fields(self): - """Return all the fields with their names. + """ + 返回一个dict,key为field_name, value为对应的 :class:`~fastNLP.FieldArray` - :return field_arrays: the internal data structure of DataSet. + :return: dict: 返回如上所述的字典 """ return self.field_arrays + + def get_field_names(self) -> list: + """ + 返回一个list,包含所有 field 的名字 + :return: list: 返回如上所述的列表 + """ + return sorted(self.field_arrays.keys()) + def get_length(self): - """Fetch the length of the dataset. + """ + 获取DataSet的元素数量 - :return length: + :return: int: DataSet中Instance的个数。 """ return len(self) - + def rename_field(self, old_name, new_name): - """Rename a field. + """ + 将某个field重新命名. - :param str old_name: - :param str new_name: + :param str old_name: 原来的field名称。 + :param str new_name: 修改为new_name。 """ if old_name in self.field_arrays: self.field_arrays[new_name] = self.field_arrays.pop(old_name) self.field_arrays[new_name].name = new_name else: raise KeyError("DataSet has no field named {}.".format(old_name)) - + def set_target(self, *field_names, flag=True): - """Change the target flag of these fields. + """ + 将field_names的field设置为target + + Example:: - :param field_names: a sequence of str, indicating field names - :param bool flag: Set these fields as target if True. Unset them if False. + dataset.set_target('labels', 'seq_len') # 将labels和seq_len这两个field的target属性设置为True + dataset.set_target('labels', 'seq_lens', flag=False) # 将labels和seq_len的target属性设置为False + + :param str field_names: field的名称 + :param bool flag: 将field_name的target状态设置为flag """ + assert isinstance(flag, bool), "Only bool type supported." for name in field_names: if name in self.field_arrays: self.field_arrays[name].is_target = flag else: raise KeyError("{} is not a valid field name.".format(name)) + + def set_input(self, *field_names, flag=True): + """ + 将field_names的field设置为input + + Example:: - def set_input(self, *field_name, flag=True): - """Set the input flag of these fields. + dataset.set_input('words', 'seq_len') # 将words和seq_len这两个field的input属性设置为True + dataset.set_input('words', flag=False) # 将words这个field的input属性设置为False - :param field_name: a sequence of str, indicating field names. - :param bool flag: Set these fields as input if True. Unset them if False. + :param str field_names: field的名称 + :param bool flag: 将field_name的input状态设置为flag """ - for name in field_name: + for name in field_names: if name in self.field_arrays: self.field_arrays[name].is_input = flag else: raise KeyError("{} is not a valid field name.".format(name)) + + def set_ignore_type(self, *field_names, flag=True): + """ + 将field设置为忽略类型状态。当某个field被设置了ignore_type, 则在被设置为target或者input时将不进行类型检查, + 默认情况下也不进行pad。 + :param str field_names: field的名称 + :param bool flag: 将field_name的ignore_type状态设置为flag + :return: + """ + assert isinstance(flag, bool), "Only bool type supported." + for name in field_names: + if name in self.field_arrays: + self.field_arrays[name].ignore_type = flag + else: + raise KeyError("{} is not a valid field name.".format(name)) + def set_padder(self, field_name, padder): """ 为field_name设置padder - :param field_name: str, 设置field的padding方式为padder - :param padder: PadderBase类型或None. 设置为None即删除padder。即对该field不进行padding操作. - :return: + + Example:: + + from fastNLP import EngChar2DPadder + padder = EngChar2DPadder() + dataset.set_padder('chars', padder) # 则chars这个field会使用EngChar2DPadder进行pad操作 + + :param str field_name: 设置field的padding方式为padder + :param None, Padder padder: 设置为None即删除padder, 即对该field不进行pad操作。 """ + if field_name not in self.field_arrays: + raise KeyError("There is no field named {}.".format(field_name)) self.field_arrays[field_name].set_padder(padder) - + def set_pad_val(self, field_name, pad_val): """ - 为某个 + 为某个field设置对应的pad_val. - :param field_name: str,修改该field的pad_val - :param pad_val: int,该field的padder会以pad_val作为padding index - :return: + :param str field_name: 修改该field的pad_val + :param int pad_val: 该field的padder会以pad_val作为padding index """ + if field_name not in self.field_arrays: + raise KeyError("There is no field named {}.".format(field_name)) self.field_arrays[field_name].set_pad_val(pad_val) - + def get_input_name(self): - """Get all field names with `is_input` as True. + """ + 返回所有is_input被设置为True的field名称 - :return field_names: a list of str + :return list: 里面的元素为被设置为input的field名称 """ return [name for name, field in self.field_arrays.items() if field.is_input] - + def get_target_name(self): - """Get all field names with `is_target` as True. + """ + 返回所有is_target被设置为True的field名称 - :return field_names: a list of str + :return list: 里面的元素为被设置为target的field名称 """ return [name for name, field in self.field_arrays.items() if field.is_target] + + def apply_field(self, func, field_name, new_field_name=None, **kwargs): + """ + 将DataSet中的每个instance中的名为 `field_name` 的field传给func,并获取它的返回值。 - def apply(self, func, new_field_name=None, **kwargs): - """Apply a function to every instance of the DataSet. + :param callable func: input是instance中名为 `field_name` 的field的内容。 + :param str field_name: 传入func的是哪个field。 + :param None,str new_field_name: 将func返回的内容放入到 `new_field_name` 这个field中,如果名称与已有的field相同,则覆 + 盖之前的field。如果为None则不创建新的field。 + :param optional kwargs: 支持输入is_input,is_target,ignore_type + + 1. is_input: bool, 如果为True则将名为 `new_field_name` 的field设置为input + + 2. is_target: bool, 如果为True则将名为 `new_field_name` 的field设置为target + + 3. ignore_type: bool, 如果为True则将名为 `new_field_name` 的field的ignore_type设置为true, 忽略其类型 + :return: list(Any), 里面的元素为func的返回值,所以list长度为DataSet的长度 - :param func: a function that takes an instance as input. - :param str new_field_name: If not None, results of the function will be stored as a new field. - :param **kwargs: Accept parameters will be - (1) is_input: boolean, will be ignored if new_field is None. If True, the new field will be as input. - (2) is_target: boolean, will be ignored if new_field is None. If True, the new field will be as target. - :return results: if new_field_name is not passed, returned values of the function over all instances. """ - results = [func(ins) for ins in self._inner_iter()] + assert len(self) != 0, "Null DataSet cannot use apply_field()." + if field_name not in self: + raise KeyError("DataSet has no field named `{}`.".format(field_name)) + results = [] + idx = -1 + try: + for idx, ins in enumerate(self._inner_iter()): + results.append(func(ins[field_name])) + except Exception as e: + if idx != -1: + print("Exception happens at the `{}`th instance.".format(idx)) + raise e if not (new_field_name is None) and len(list(filter(lambda x: x is not None, results))) == 0: # all None - raise ValueError("{} always return None.".format(get_func_signature(func=func))) + raise ValueError("{} always return None.".format(_get_func_signature(func=func))) + + if new_field_name is not None: + self._add_apply_field(results, new_field_name, kwargs) + + return results + + def _add_apply_field(self, results, new_field_name, kwargs): + """ + 将results作为加入到新的field中,field名称为new_field_name + :param list(str) results: 一般是apply*()之后的结果 + :param str new_field_name: 新加入的field的名称 + :param dict kwargs: 用户apply*()时传入的自定义参数 + :return: + """ extra_param = {} if 'is_input' in kwargs: extra_param['is_input'] = kwargs['is_input'] if 'is_target' in kwargs: extra_param['is_target'] = kwargs['is_target'] - if new_field_name is not None: - if new_field_name in self.field_arrays: - # overwrite the field, keep same attributes - old_field = self.field_arrays[new_field_name] - if 'is_input' not in extra_param: - extra_param['is_input'] = old_field.is_input - if 'is_target' not in extra_param: - extra_param['is_target'] = old_field.is_target - self.add_field(name=new_field_name, fields=results, is_input=extra_param["is_input"], - is_target=extra_param["is_target"]) - else: - self.add_field(name=new_field_name, fields=results, is_input=extra_param.get("is_input", None), - is_target=extra_param.get("is_target", None)) + if 'ignore_type' in kwargs: + extra_param['ignore_type'] = kwargs['ignore_type'] + if new_field_name in self.field_arrays: + # overwrite the field, keep same attributes + old_field = self.field_arrays[new_field_name] + if 'is_input' not in extra_param: + extra_param['is_input'] = old_field.is_input + if 'is_target' not in extra_param: + extra_param['is_target'] = old_field.is_target + if 'ignore_type' not in extra_param: + extra_param['ignore_type'] = old_field.ignore_type + self.add_field(field_name=new_field_name, fields=results, is_input=extra_param["is_input"], + is_target=extra_param["is_target"], ignore_type=extra_param['ignore_type']) else: - return results + self.add_field(field_name=new_field_name, fields=results, is_input=extra_param.get("is_input", None), + is_target=extra_param.get("is_target", None), + ignore_type=extra_param.get("ignore_type", False)) + + def apply(self, func, new_field_name=None, **kwargs): + """ + 将DataSet中每个instance传入到func中,并获取它的返回值. - def drop(self, func): - """Drop instances if a condition holds. + :param callable func: 参数是DataSet中的Instance + :param None,str new_field_name: 将func返回的内容放入到new_field_name这个field中,如果名称与已有的field相同,则覆 + 盖之前的field。如果为None则不创建新的field。 + :param optional kwargs: 支持输入is_input,is_target,ignore_type - :param func: a function that takes an Instance object as input, and returns bool. - The instance will be dropped if the function returns True. + 1. is_input: bool, 如果为True则将 `new_field_name` 的field设置为input + 2. is_target: bool, 如果为True则将 `new_field_name` 的field设置为target + + 3. ignore_type: bool, 如果为True则将 `new_field_name` 的field的ignore_type设置为true, 忽略其类型 + + :return: list(Any), 里面的元素为func的返回值,所以list长度为DataSet的长度 """ - results = [ins for ins in self._inner_iter() if not func(ins)] - for name, old_field in self.field_arrays.items(): - self.field_arrays[name].content = [ins[name] for ins in results] + assert len(self) != 0, "Null DataSet cannot use apply()." + idx = -1 + try: + results = [] + for idx, ins in enumerate(self._inner_iter()): + results.append(func(ins)) + except Exception as e: + if idx != -1: + print("Exception happens at the `{}`th instance.".format(idx)) + raise e + # results = [func(ins) for ins in self._inner_iter()] + if not (new_field_name is None) and len(list(filter(lambda x: x is not None, results))) == 0: # all None + raise ValueError("{} always return None.".format(_get_func_signature(func=func))) + + if new_field_name is not None: + self._add_apply_field(results, new_field_name, kwargs) + + return results + + def drop(self, func, inplace=True): + """ + func接受一个Instance,返回bool值。返回值为True时,该Instance会被移除或者加入到返回的DataSet中。 + + :param callable func: 接受一个Instance作为参数,返回bool值。为True时删除该instance + :param bool inplace: 是否在当前DataSet中直接删除instance。如果为False,被删除的Instance的组成的新DataSet将作为 + :返回值 - def split(self, dev_ratio): - """Split the dataset into training and development(validation) set. + :return: DataSet + """ + if inplace: + results = [ins for ins in self._inner_iter() if not func(ins)] + for name, old_field in self.field_arrays.items(): + self.field_arrays[name].content = [ins[name] for ins in results] + return self + else: + results = [ins for ins in self if not func(ins)] + if len(results) != 0: + dataset = DataSet(results) + for field_name, field in self.field_arrays.items(): + dataset.field_arrays[field_name].to(field) + return dataset + else: + return DataSet() + + def split(self, ratio): + """ + 将DataSet按照ratio的比例拆分,返回两个DataSet - :param float dev_ratio: the ratio of test set in all data. - :return (train_set, dev_set): - train_set: the training set - dev_set: the development set + :param float ratio: 0 1: # list 跟 非list 混在一起 - raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, type_set)) + raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, list(type_set))) # >1维list inner_type_set = set() for l in content: @@ -213,7 +137,7 @@ class FieldArray(object): return self._basic_type_detection(inner_inner_type_set) else: # list 跟 非list 混在一起 - raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, inner_type_set)) + raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, list(inner_type_set))) else: # 一维list for content_type in type_set: @@ -222,7 +146,7 @@ class FieldArray(object): self.name, self.BASIC_TYPES, content_type)) self.content_dim = 1 return self._basic_type_detection(type_set) - + def _basic_type_detection(self, type_set): """ :param type_set: a set of Python types @@ -237,21 +161,21 @@ class FieldArray(object): return float else: # str 跟 int 或者 float 混在一起 - raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, type_set)) + raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, list(type_set))) else: # str, int, float混在一起 - raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, type_set)) - + raise RuntimeError("Mixed data types in Field {}: {}".format(self.name, list(type_set))) + def _1d_list_check(self, val): """如果不是1D list就报错 """ type_set = set((type(obj) for obj in val)) if any(obj not in self.BASIC_TYPES for obj in type_set): - raise ValueError("Mixed data types in Field {}: {}".format(self.name, type_set)) + raise ValueError("Mixed data types in Field {}: {}".format(self.name, list(type_set))) self._basic_type_detection(type_set) # otherwise: _basic_type_detection will raise error return True - + def _2d_list_check(self, val): """如果不是2D list 就报错 """ @@ -264,110 +188,132 @@ class FieldArray(object): inner_type_set.add(type(obj)) self._basic_type_detection(inner_type_set) return True - + @staticmethod def _map_to_np_type(basic_type): type_mapping = {int: np.int64, float: np.float64, str: np.str, np.ndarray: np.ndarray} return type_mapping[basic_type] - + def __repr__(self): return "FieldArray {}: {}".format(self.name, self.content.__repr__()) - + def append(self, val): - """Add a new item to the tail of FieldArray. - - :param val: int, float, str, or a list of one. - """ - if isinstance(val, list): - pass - elif isinstance(val, tuple): # 确保最外层是list - val = list(val) - elif isinstance(val, np.ndarray): - val = val.tolist() - elif any((isinstance(val, t) for t in self.BASIC_TYPES)): - pass - else: - raise RuntimeError( - "Unexpected data type {}. Should be list, np.array, or {}".format(type(val), self.BASIC_TYPES)) - - if self.is_input is True or self.is_target is True: - if type(val) == list: - if len(val) == 0: - raise ValueError("Cannot append an empty list.") - if self.content_dim == 2 and self._1d_list_check(val): - # 1维list检查 - pass - elif self.content_dim == 3 and self._2d_list_check(val): - # 2维list检查 - pass - else: - raise RuntimeError( - "Dimension not matched: expect dim={}, got {}.".format(self.content_dim - 1, val)) - elif type(val) in self.BASIC_TYPES and self.content_dim == 1: - # scalar检查 - if type(val) == float and self.pytype == int: - self.pytype = float - self.dtype = self._map_to_np_type(self.pytype) + """将val append到这个field的尾部。如果这个field已经被设置为input或者target,则在append之前会检查该类型是否与已有 + 的内容是匹配的。 + + :param Any val: 需要append的值。 + """ + if self.ignore_type is False: + if isinstance(val, list): + pass + elif isinstance(val, tuple): # 确保最外层是list + val = list(val) + elif isinstance(val, np.ndarray): + val = val.tolist() + elif any((isinstance(val, t) for t in self.BASIC_TYPES)): + pass else: raise RuntimeError( "Unexpected data type {}. Should be list, np.array, or {}".format(type(val), self.BASIC_TYPES)) + + if self.is_input is True or self.is_target is True: + if type(val) == list: + if len(val) == 0: + raise ValueError("Cannot append an empty list.") + if self.content_dim == 2 and self._1d_list_check(val): + # 1维list检查 + pass + elif self.content_dim == 3 and self._2d_list_check(val): + # 2维list检查 + pass + else: + raise RuntimeError( + "Dimension not matched: expect dim={}, got {}.".format(self.content_dim - 1, val)) + elif type(val) in self.BASIC_TYPES and self.content_dim == 1: + # scalar检查 + if type(val) == float and self.pytype == int: + self.pytype = float + self.dtype = self._map_to_np_type(self.pytype) + else: + raise RuntimeError( + "Unexpected data type {}. Should be list, np.array, or {}".format(type(val), self.BASIC_TYPES)) self.content.append(val) - + def __getitem__(self, indices): - return self.get(indices) - + return self.get(indices, pad=False) + def __setitem__(self, idx, val): assert isinstance(idx, int) self.content[idx] = val - + def get(self, indices, pad=True): - """Fetch instances based on indices. + """ + 根据给定的indices返回内容 - :param indices: an int, or a list of int. - :param pad: bool, 是否对返回的结果进行padding。 - :return: + :param int,List[int] indices: 获取indices对应的内容。 + :param bool pad: 是否对返回的结果进行padding。仅对indices为List[int]时有效 + :return: 根据给定的indices返回的内容,可能是单个值或List """ if isinstance(indices, int): return self.content[indices] if self.is_input is False and self.is_target is False: raise RuntimeError("Please specify either is_input or is_target is True for {}".format(self.name)) - + contents = [self.content[i] for i in indices] if self.padder is None or pad is False: return np.array(contents) else: return self.padder(contents, field_name=self.name, field_ele_dtype=self.dtype) - + def set_padder(self, padder): """ - 设置padding方式 + 设置padder,在这个field进行pad的时候用这个padder进行pad,如果为None则不进行pad。 - :param padder: PadderBase类型或None. 设置为None即删除padder. - :return: + :param padder: :class:`~fastNLP.Padder` 类型,设置为None即删除padder。 """ if padder is not None: - assert isinstance(padder, PadderBase), "padder must be of type PadderBase." - self.padder = padder - + assert isinstance(padder, Padder), "padder must be of type Padder." + self.padder = deepcopy(padder) + else: + self.padder = None + def set_pad_val(self, pad_val): """ 修改padder的pad_val. - :param pad_val: int。 - :return: + + :param int pad_val: 该field的pad值设置为该值。 """ if self.padder is not None: self.padder.set_pad_val(pad_val) - - + return self + def __len__(self): - """Returns the size of FieldArray. + """ + Returns the size of FieldArray. :return int length: """ return len(self.content) + + def to(self, other): + """ + 将other的属性复制给本FieldArray(other必须为FieldArray类型). + 属性包括 is_input, is_target, padder, ignore_type + + :param other: :class:`~fastNLP.FieldArray` 从哪个field拷贝属性 + :return: :class:`~fastNLP.FieldArray` + """ + assert isinstance(other, FieldArray), "Only support FieldArray type, not {}.".format(type(other)) + + self.is_input = other.is_input + self.is_target = other.is_target + self.padder = other.padder + self.ignore_type = other.ignore_type + + return self -def is_iterable(content): +def _is_iterable(content): try: _ = (e for e in content) except TypeError: @@ -375,24 +321,161 @@ def is_iterable(content): return True -class EngChar2DPadder(PadderBase): +class Padder: """ - 用于为英语执行character级别的2D padding操作。对应的field内容应该为[['T', 'h', 'i', 's'], ['a'], ['d', 'e', 'm', 'o']](这里为 - 了更直观,把它们写为str,但实际使用时它们应该是character的index)。 - padded过后的batch内容,形状为(batch_size, max_sentence_length, max_word_length). max_sentence_length最大句子长度。 - max_word_length最长的word的长度 + 别名::class:`fastNLP.Padder` :class:`fastNLP.core.field.Padder` + 所有padder都需要继承这个类,并覆盖__call__方法。 + 用于对batch进行padding操作。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前deepcopy一份。 + + .. py:function:: __call__(self, contents, field_name, field_ele_dtype): + 传入的是List内容。假设有以下的DataSet。 + + :param list(Any) contents: 传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 + deepcopy一份。 + :param str, field_name: field的名称。 + :param np.int64,np.float64,np.str,None, field_ele_dtype: 该field的内层元素的类型。如果该field的ignore_type为True,该这个值为None。 + :return: np.array([padded_element]) + """ - def __init__(self, pad_val=0, pad_length=0): + + def __init__(self, pad_val=0, **kwargs): + self.pad_val = pad_val + + def set_pad_val(self, pad_val): + self.pad_val = pad_val + + def __call__(self, contents, field_name, field_ele_dtype): + """ + 传入的是List内容。假设有以下的DataSet。 + + :param list(Any) contents: 传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 + deepcopy一份。 + :param str, field_name: field的名称。 + :param np.int64,np.float64,np.str,None, field_ele_dtype: 该field的内层元素的类型。如果该field的ignore_type为True,该这个值为None。 + :return: np.array([padded_element]) + + Example:: + + from fastNLP import DataSet + from fastNLP import Instance + dataset = DataSet() + dataset.append(Instance(sent='this is a demo', length=4, + chars=[['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['d', 'e', 'm', 'o']])) + dataset.append(Instance(sent='another one', length=2, + chars=[['a', 'n', 'o', 't', 'h', 'e', 'r'], ['o', 'n', 'e']])) + 如果调用 + batch = dataset.get([0,1], pad=True) + sent这个field的padder的__call__会接收到的内容会是 + [ + 'this is a demo', + 'another one' + ] + + length这个field的padder的__call__会接收到的内容会是 + [4, 2] + + chars这个field的padder的__call__会接收到的内容会是 + [ + [['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['d', 'e', 'm', 'o']], + [['a', 'n', 'o', 't', 'h', 'e', 'r'], ['o', 'n', 'e']] + ] + + 即把每个instance中某个field的内容合成一个List传入 + + """ + raise NotImplementedError + + +class AutoPadder(Padder): + """ + 别名::class:`fastNLP.AutoPadder` :class:`fastNLP.core.field.AutoPadder` + + 根据contents的数据自动判定是否需要做padding。 + + 1 如果元素类型(元素类型是指field中最里层元素的数据类型, 可以通过FieldArray.dtype查看,比如['This', 'is', ...]的元素类 + 型为np.str, [[1,2], ...]的元素类型为np.int64)的数据不为(np.int64, np.float64)则不会进行pad + + 2 如果元素类型为(np.int64, np.float64), + + 2.1 如果该field的内容为(np.int64, np.float64),比如为seq_len, 则不进行padding + + 2.2 如果该field的内容为List, 那么会将Batch中的List pad为一样长。若该List下还有里层的List需要padding,请使用其它padder。 + 即如果Instance中field形如[1, 2, 3, ...],则可以pad;若为[[1,2], [3,4, ...]]则不能进行pad + """ + + def __init__(self, pad_val=0): """ :param pad_val: int, padding的位置使用该index - :param pad_length: int, 如果为0则取一个batch中最大的单词长度作为padding长度。如果为大于0的数,则将所有单词的长度都pad或截 - 取到该长度. """ super().__init__(pad_val=pad_val) + + def _is_two_dimension(self, contents): + """ + 判断contents是不是只有两个维度。[[1,2], [3]]是两个维度. [[[1,2], [3, 4, 5]], [[4,5]]]有三个维度 + :param contents: + :return: + """ + value = contents[0] + if isinstance(value, (np.ndarray, list)): + value = value[0] + if isinstance(value, (np.ndarray, list)): + return False + return True + return False + + def __call__(self, contents, field_name, field_ele_dtype): + + if not _is_iterable(contents[0]): + array = np.array([content for content in contents], dtype=field_ele_dtype) + elif field_ele_dtype in (np.int64, np.float64) and self._is_two_dimension(contents): + max_len = max([len(content) for content in contents]) + array = np.full((len(contents), max_len), self.pad_val, dtype=field_ele_dtype) + for i, content in enumerate(contents): + array[i][:len(content)] = content + elif field_ele_dtype is None: + array = np.array(contents) # 当ignore_type=True时,直接返回contents + else: # should only be str + array = np.array([content for content in contents]) + return array - self.pad_length = pad_length +class EngChar2DPadder(Padder): + """ + 别名::class:`fastNLP.EngChar2DPadder` :class:`fastNLP.core.field.EngChar2DPadder` + + 用于为英语执行character级别的2D padding操作。对应的field内容应该类似[['T', 'h', 'i', 's'], ['a'], ['d', 'e', 'm', 'o']], + 但这个Padder只能处理index为int的情况。 + + padded过后的batch内容,形状为(batch_size, max_sentence_length, max_word_length). max_sentence_length为这个batch中最大句 + 子长度;max_word_length为这个batch中最长的word的长度 + + Example:: + + from fastNLP import DataSet + from fastNLP import EngChar2DPadder + from fastNLP import Vocabulary + dataset = DataSet({'sent': ['This is the first demo', 'This is the second demo']}) + dataset.apply(lambda ins:[list(word) for word in ins['sent'].split()], new_field_name='chars') + vocab = Vocabulary() + vocab.from_dataset(dataset, field_name='chars') + vocab.index_dataset(dataset, field_name='chars') + dataset.set_input('chars') + padder = EngChar2DPadder() + dataset.set_padder('chars', padder) # chars这个field的设置为了EnChar2DPadder + + """ + + def __init__(self, pad_val=0, pad_length=0): + """ + :param pad_val: int, pad的位置使用该index + :param pad_length: int, 如果为0则取一个batch中最大的单词长度作为padding长度。如果为大于0的数,则将所有单词的长度 + 都pad或截取到该长度. + """ + super().__init__(pad_val=pad_val) + + self.pad_length = pad_length + def _exactly_three_dims(self, contents, field_name): """ 检查传入的contents是否刚好是3维,如果不是3维就报错。理论上,第一个维度是batch,第二个维度是word,第三个维度是character @@ -411,10 +494,10 @@ class EngChar2DPadder(PadderBase): value = value[0] except: raise ValueError("Field:{} only has two dimensions.".format(field_name)) - - if is_iterable(value): + + if _is_iterable(value): raise ValueError("Field:{} has more than 3 dimension.".format(field_name)) - + def __call__(self, contents, field_name, field_ele_dtype): """ 期望输入类似于 @@ -441,12 +524,12 @@ class EngChar2DPadder(PadderBase): max_sent_length = max(len(word_lst) for word_lst in contents) batch_size = len(contents) dtype = type(contents[0][0][0]) - + padded_array = np.full((batch_size, max_sent_length, max_char_length), fill_value=self.pad_val, - dtype=dtype) + dtype=dtype) for b_idx, word_lst in enumerate(contents): for c_idx, char_lst in enumerate(word_lst): chars = char_lst[:max_char_length] padded_array[b_idx, c_idx, :len(chars)] = chars - - return padded_array \ No newline at end of file + + return padded_array diff --git a/fastNLP/core/instance.py b/fastNLP/core/instance.py index 5ac52e3f..07ae6495 100644 --- a/fastNLP/core/instance.py +++ b/fastNLP/core/instance.py @@ -1,38 +1,52 @@ -class Instance(object): - """An Instance is an example of data. - Example:: - ins = Instance(field_1=[1, 1, 1], field_2=[2, 2, 2]) - ins["field_1"] - >>[1, 1, 1] - ins.add_field("field_3", [3, 3, 3]) +""" +instance 模块实现了Instance 类在fastNLP中对应sample。一个sample可以认为是一个Instance类型的对象。 +便于理解的例子可以参考文档 :doc:`fastNLP.core.dataset` 中的表格 - :param fields: a dict of (str: list). +""" +__all__ = [ + "Instance" +] - """ +class Instance(object): + """ + 别名::class:`fastNLP.Instance` :class:`fastNLP.core.instance.Instance` + + Instance是fastNLP中对应一个sample的类。每个sample在fastNLP中是一个Instance对象。 + Instance一般与 :class:`~fastNLP.DataSet` 一起使用, Instance的初始化如下面的Example所示 + + Example:: + + >>>from fastNLP import Instance + >>>ins = Instance(field_1=[1, 1, 1], field_2=[2, 2, 2]) + >>>ins["field_1"] + [1, 1, 1] + >>>ins.add_field("field_3", [3, 3, 3]) + >>>ins = Instance(**{'x1': 1, 'x2':np.zeros((3, 4))}) + """ + def __init__(self, **fields): - """ - - :param fields: 可能是一维或者二维的 list or np.array - """ + self.fields = fields - + def add_field(self, field_name, field): - """Add a new field to the instance. + """ + 向Instance中增加一个field - :param field_name: str, the name of the field. + :param str field_name: 新增field的名称 + :param Any field: 新增field的内容 """ self.fields[field_name] = field - + def __getitem__(self, name): if name in self.fields: return self.fields[name] else: raise KeyError("{} not found".format(name)) - + def __setitem__(self, name, field): return self.add_field(name, field) - + def __repr__(self): s = '\'' return "{" + ",\n".join( diff --git a/fastNLP/core/losses.py b/fastNLP/core/losses.py index 9b8b8d8f..ddc2c49f 100644 --- a/fastNLP/core/losses.py +++ b/fastNLP/core/losses.py @@ -1,33 +1,50 @@ +""" +losses 模块定义了 fastNLP 中所需的各种损失函数,一般做为 :class:`~fastNLP.Trainer` 的参数使用。 + +""" +__all__ = [ + "LossBase", + + "LossFunc", + "LossInForward", + + "CrossEntropyLoss", + "BCELoss", + "L1Loss", + "NLLLoss" +] + import inspect from collections import defaultdict import torch import torch.nn.functional as F -from fastNLP.core.utils import CheckError -from fastNLP.core.utils import CheckRes -from fastNLP.core.utils import _build_args -from fastNLP.core.utils import _check_arg_dict_list -from fastNLP.core.utils import _check_function_or_method -from fastNLP.core.utils import get_func_signature +from .utils import _CheckError +from .utils import _CheckRes +from .utils import _build_args +from .utils import _check_arg_dict_list +from .utils import _check_function_or_method +from .utils import _get_func_signature class LossBase(object): - """Base class for all losses. - """ + 所有loss的基类。如果想了解其中的原理,请查看源码。 + """ + def __init__(self): self.param_map = {} self._checked = False - + def get_loss(self, *args, **kwargs): raise NotImplementedError - + def _init_param_map(self, key_map=None, **kwargs): - """Check the validity of key_map and other param map. Add these into self.param_map + """检查key_map和其他参数map,并将这些映射关系添加到self.param_map - :param key_map: dict - :param kwargs: + :param dict key_map: 表示key的映射关系 + :param kwargs: key word args里面的每一个的键-值对都会被构造成映射关系 :return: None """ value_counter = defaultdict(set) @@ -55,21 +72,21 @@ class LossBase(object): for value, key_set in value_counter.items(): if len(key_set) > 1: raise ValueError(f"Several parameters:{key_set} are provided with one output {value}.") - + # check consistence between signature and param_map func_spect = inspect.getfullargspec(self.get_loss) func_args = [arg for arg in func_spect.args if arg != 'self'] for func_param, input_param in self.param_map.items(): if func_param not in func_args: raise NameError( - f"Parameter `{func_param}` is not in {get_func_signature(self.get_loss)}. Please check the " + f"Parameter `{func_param}` is not in {_get_func_signature(self.get_loss)}. Please check the " f"initialization parameters, or change its signature.") - + # evaluate should not have varargs. # if func_spect.varargs: # raise NameError(f"Delete `*{func_spect.varargs}` in {get_func_signature(self.get_loss)}(Do not use " # f"positional argument.).") - + def _fast_param_map(self, pred_dict, target_dict): """Only used as inner function. When the pred_dict, target is unequivocal. Don't need users to pass key_map. such as pred_dict has one element, target_dict has one element @@ -84,34 +101,34 @@ class LossBase(object): fast_param['target'] = list(target_dict.values())[0] return fast_param return fast_param - + def __call__(self, pred_dict, target_dict, check=False): """ - :param pred_dict: A dict from forward function of the network. - :param target_dict: A dict from DataSet.batch_y. - :param check: Boolean. Force to check the mapping functions when it is running. + :param dict pred_dict: 模型的forward函数返回的dict + :param dict target_dict: DataSet.batch_y里的键-值对所组成的dict + :param Boolean check: 每一次执行映射函数的时候是否检查映射表,默认为不检查 :return: """ fast_param = self._fast_param_map(pred_dict, target_dict) if fast_param: loss = self.get_loss(**fast_param) return loss - + if not self._checked: # 1. check consistence between signature and param_map func_spect = inspect.getfullargspec(self.get_loss) func_args = set([arg for arg in func_spect.args if arg != 'self']) for func_arg, input_arg in self.param_map.items(): if func_arg not in func_args: - raise NameError(f"`{func_arg}` not in {get_func_signature(self.get_loss)}.") - + raise NameError(f"`{func_arg}` not in {_get_func_signature(self.get_loss)}.") + # 2. only part of the param_map are passed, left are not for arg in func_args: if arg not in self.param_map: self.param_map[arg] = arg # This param does not need mapping. self._evaluate_args = func_args self._reverse_param_map = {input_arg: func_arg for func_arg, input_arg in self.param_map.items()} - + # need to wrap inputs in dict. mapped_pred_dict = {} mapped_target_dict = {} @@ -131,7 +148,7 @@ class LossBase(object): not_duplicate_flag += 1 if not_duplicate_flag == 3: duplicated.append(input_arg) - + # missing if not self._checked: check_res = _check_arg_dict_list(self.get_loss, [mapped_pred_dict, mapped_target_dict]) @@ -141,37 +158,50 @@ class LossBase(object): for idx, func_arg in enumerate(missing): # Don't delete `` in this information, nor add `` replaced_missing[idx] = f"{self.param_map[func_arg]}" + f"(assign to `{func_arg}` " \ - f"in `{self.__class__.__name__}`)" - - check_res = CheckRes(missing=replaced_missing, - unused=check_res.unused, - duplicated=duplicated, - required=check_res.required, - all_needed=check_res.all_needed, - varargs=check_res.varargs) - + f"in `{self.__class__.__name__}`)" + + check_res = _CheckRes(missing=replaced_missing, + unused=check_res.unused, + duplicated=duplicated, + required=check_res.required, + all_needed=check_res.all_needed, + varargs=check_res.varargs) + if check_res.missing or check_res.duplicated: - raise CheckError(check_res=check_res, - func_signature=get_func_signature(self.get_loss)) + raise _CheckError(check_res=check_res, + func_signature=_get_func_signature(self.get_loss)) refined_args = _build_args(self.get_loss, **mapped_pred_dict, **mapped_target_dict) - + loss = self.get_loss(**refined_args) self._checked = True - + return loss class LossFunc(LossBase): - """A wrapper of user-provided loss function. + """ + 别名::class:`fastNLP.LossFunc` :class:`fastNLP.core.losses.LossFunc` + + 提供给用户使用自定义损失函数的类 + + :param func: 用户自行定义的损失函数,应当为一个函数或者callable(func)为True的ojbect + :param dict key_map: 参数映射表。键为Model/DataSet参数名,值为损失函数参数名。 + fastNLP的trainer将在训练时从模型返回值或者训练数据DataSet的target=True的field中 + 找到相对应的参数名为value的参数,并传入func中作为参数名为key的参数 + :param kwargs: 除了参数映射表以外可以用key word args的方式设置参数映射关系 + + Example:: + + >>> func = torch.nn.CrossEntropyLoss() + >>> loss_func = LossFunc(func, input="pred", target="label") + # 这表示构建了一个损失函数类,由func计算损失函数,其中将从模型返回值或者DataSet的target=True的field + # 当中找到一个参数名为`pred`的参数传入func一个参数名为`input`的参数;找到一个参数名为`label`的参数 + # 传入func作为一个名为`target`的参数 """ + def __init__(self, func, key_map=None, **kwargs): - """ - - :param func: a callable object, such as a function. - :param dict key_map: - :param kwargs: - """ + super(LossFunc, self).__init__() _check_function_or_method(func) if key_map is not None: @@ -181,78 +211,129 @@ class LossFunc(LossBase): if len(kwargs) > 0: for key, val in kwargs.items(): self.param_map.update({key: val}) - + self.get_loss = func class CrossEntropyLoss(LossBase): + """ + 别名::class:`fastNLP.CrossEntropyLoss` :class:`fastNLP.core.losses.CrossEntropyLoss` + + 交叉熵损失函数 + + :param pred: 参数映射表中 `pred` 的映射关系,None表示映射关系为 `pred` -> `pred` + :param target: 参数映射表中 `target` 的映射关系,None表示映射关系为 `target` -> `target` + :param padding_idx: padding的index,在计算loss时将忽略target中标号为padding_idx的内容 + + Example:: + + >>> loss = CrossEntropyLoss(pred='pred', target='label', padding_idx=0) + + """ + def __init__(self, pred=None, target=None, padding_idx=-100): - # TODO 需要做一些检查,F.cross_entropy在计算时,如果pred是(16, 10 ,4), target的形状按道理应该是(16, 10), 但实际却需要 - # TODO (16, 4) + # TODO 需要做一些检查,F.cross_entropy在计算时,如果pred是(16, 10 ,4), target的形状按道理应该是(16, 10), 但实际需要(16,4) super(CrossEntropyLoss, self).__init__() self._init_param_map(pred=pred, target=target) self.padding_idx = padding_idx - + def get_loss(self, pred, target): return F.cross_entropy(input=pred, target=target, ignore_index=self.padding_idx) class L1Loss(LossBase): + """ + 别名::class:`fastNLP.L1Loss` :class:`fastNLP.core.losses.L1Loss` + + L1损失函数 + + :param pred: 参数映射表中 `pred` 的映射关系,None表示映射关系为 `pred` -> `pred` + :param target: 参数映射表中 `target` 的映射关系,None表示映射关系为 `target` >`target` + + """ + def __init__(self, pred=None, target=None): super(L1Loss, self).__init__() self._init_param_map(pred=pred, target=target) - + def get_loss(self, pred, target): return F.l1_loss(input=pred, target=target) class BCELoss(LossBase): + """ + 别名::class:`fastNLP.BCELoss` :class:`fastNLP.core.losses.BCELoss` + + 二分类交叉熵损失函数 + + :param pred: 参数映射表中`pred`的映射关系,None表示映射关系为`pred`->`pred` + :param target: 参数映射表中`target`的映射关系,None表示映射关系为`target`->`target` + """ + def __init__(self, pred=None, target=None): super(BCELoss, self).__init__() self._init_param_map(pred=pred, target=target) - + def get_loss(self, pred, target): return F.binary_cross_entropy(input=pred, target=target) class NLLLoss(LossBase): + """ + 别名::class:`fastNLP.NLLLoss` :class:`fastNLP.core.losses.NLLLoss` + + 负对数似然损失函数 + + :param pred: 参数映射表中`pred`的映射关系,None表示映射关系为`pred`->`pred` + :param target: 参数映射表中`target`的映射关系,None表示映射关系为`target`->`target` + """ + def __init__(self, pred=None, target=None): super(NLLLoss, self).__init__() self._init_param_map(pred=pred, target=target) - + def get_loss(self, pred, target): return F.nll_loss(input=pred, target=target) class LossInForward(LossBase): + """ + 别名::class:`fastNLP.LossInForward` :class:`fastNLP.core.losses.LossInForward` + + 从forward()函数返回结果中获取loss + + :param str loss_key: 在forward函数中loss的键名,默认为loss + """ + def __init__(self, loss_key='loss'): super().__init__() if not isinstance(loss_key, str): raise TypeError(f"Only str allowed for loss_key, got {type(loss_key)}.") self.loss_key = loss_key - + def get_loss(self, **kwargs): if self.loss_key not in kwargs: - check_res = CheckRes(missing=[self.loss_key + f"(assign to `{self.loss_key}` " \ - f"in `{self.__class__.__name__}`"], - unused=[], - duplicated=[], - required=[], - all_needed=[], - varargs=[]) - raise CheckError(check_res=check_res, func_signature=get_func_signature(self.get_loss)) + check_res = _CheckRes( + missing=[self.loss_key + f"(assign to `{self.loss_key}` in `{self.__class__.__name__}`"], + unused=[], + duplicated=[], + required=[], + all_needed=[], + varargs=[]) + raise _CheckError(check_res=check_res, func_signature=_get_func_signature(self.get_loss)) return kwargs[self.loss_key] - + def __call__(self, pred_dict, target_dict, check=False): - + loss = self.get_loss(**pred_dict) - + if not (isinstance(loss, torch.Tensor) and len(loss.size()) == 0): if not isinstance(loss, torch.Tensor): raise TypeError(f"Loss excepted to be a torch.Tensor, got {type(loss)}") - raise RuntimeError(f"The size of loss excepts to be torch.Size([]), got {loss.size()}") - + loss = torch.sum(loss) / (loss.view(-1)).size(0) + # raise RuntimeError(f"The size of loss excepts to be torch.Size([]), got {loss.size()}") + return loss @@ -271,7 +352,7 @@ def squash(predict, truth, **kwargs): :param predict: Tensor, model output :param truth: Tensor, truth from dataset - :param **kwargs: extra arguments + :param kwargs: extra arguments :return predict , truth: predict & truth after processing """ return predict.view(-1, predict.size()[-1]), truth.view(-1, ) @@ -315,20 +396,20 @@ def mask(predict, truth, **kwargs): :param predict: Tensor, [batch_size , max_len , tag_size] :param truth: Tensor, [batch_size , max_len] - :param **kwargs: extra arguments, kwargs["mask"]: ByteTensor, [batch_size , max_len], the mask Tensor. The position that is 1 will be selected. + :param kwargs: extra arguments, kwargs["mask"]: ByteTensor, [batch_size , max_len], the mask Tensor. The position that is 1 will be selected. :return predict , truth: predict & truth after processing """ if kwargs.get("mask") is None: return predict, truth mask = kwargs["mask"] - + predict, truth = squash(predict, truth) mask = mask.view(-1, ) - + predict = torch.masked_select(predict.permute(1, 0), mask).view(predict.size()[-1], -1).permute(1, 0) truth = torch.masked_select(truth, mask) - + return predict, truth @@ -343,4 +424,3 @@ def make_mask(lens, tar_len): mask = [torch.ge(lens, i + 1) for i in range(tar_len)] mask = torch.stack(mask, 1) return mask - diff --git a/fastNLP/core/metrics.py b/fastNLP/core/metrics.py index dfb20480..f633a80f 100644 --- a/fastNLP/core/metrics.py +++ b/fastNLP/core/metrics.py @@ -1,49 +1,134 @@ +""" +metrics 模块实现了 fastNLP 所需的各种常用衡量指标,一般做为 :class:`~fastNLP.Trainer` 的参数使用。 + +""" +__all__ = [ + "MetricBase", + "AccuracyMetric", + "SpanFPreRecMetric", + "SQuADMetric" +] + import inspect from collections import defaultdict import numpy as np import torch -from fastNLP.core.utils import CheckError -from fastNLP.core.utils import CheckRes -from fastNLP.core.utils import _build_args -from fastNLP.core.utils import _check_arg_dict_list -from fastNLP.core.utils import get_func_signature -from fastNLP.core.utils import seq_lens_to_masks -from fastNLP.core.vocabulary import Vocabulary +from .utils import _CheckError +from .utils import _CheckRes +from .utils import _build_args +from .utils import _check_arg_dict_list +from .utils import _get_func_signature +from .utils import seq_len_to_mask +from .vocabulary import Vocabulary class MetricBase(object): - """Base class for all metrics. - - ``MetricBase`` handles validity check of its input dictionaries - ``pred_dict`` and ``target_dict``. - ``pred_dict`` is the output of ``forward()`` or prediction function of a model. - ``target_dict`` is the ground truth from DataSet where ``is_target`` is set ``True``. - ``MetricBase`` will do the following type checks: - - 1. whether self.evaluate has varargs, which is not supported. - 2. whether params needed by self.evaluate is not included in ``pred_dict``, ``target_dict``. - 3. whether params needed by self.evaluate duplicate in ``pred_dict``, ``target_dict``. - 4. whether params in ``pred_dict``, ``target_dict`` are not used by evaluate.(Might cause warning) - - Besides, before passing params into self.evaluate, this function will filter out params from output_dict and - target_dict which are not used in self.evaluate. (but if **kwargs presented in self.evaluate, no filtering - will be conducted.) - However, in some cases where type check is not necessary, ``_fast_param_map`` will be used. + """ + 所有metrics的基类,,所有的传入到Trainer, Tester的Metric需要继承自该对象,需要覆盖写入evaluate(), get_metric()方法。 + + evaluate(xxx)中传入的是一个batch的数据。 + + get_metric(xxx)当所有数据处理完毕,调用该方法得到最终的metric值 + + 以分类问题中,Accuracy计算为例 + 假设model的forward返回dict中包含'pred'这个key, 并且该key需要用于Accuracy:: + + class Model(nn.Module): + def __init__(xxx): + # do something + def forward(self, xxx): + # do something + return {'pred': pred, 'other_keys':xxx} # pred's shape: batch_size x num_classes + + 假设dataset中'label'这个field是需要预测的值,并且该field被设置为了target + 对应的AccMetric可以按如下的定义, version1, 只使用这一次:: + + class AccMetric(MetricBase): + def __init__(self): + super().__init__() + + # 根据你的情况自定义指标 + self.corr_num = 0 + self.total = 0 + + def evaluate(self, label, pred): # 这里的名称需要和dataset中target field与model返回的key是一样的,不然找不到对应的value + # dev或test时,每个batch结束会调用一次该方法,需要实现如何根据每个batch累加metric + self.total += label.size(0) + self.corr_num += label.eq(pred).sum().item() + + def get_metric(self, reset=True): # 在这里定义如何计算metric + acc = self.corr_num/self.total + if reset: # 是否清零以便重新计算 + self.corr_num = 0 + self.total = 0 + return {'acc': acc} # 需要返回一个dict,key为该metric的名称,该名称会显示到Trainer的progress bar中 + + + version2,如果需要复用Metric,比如下一次使用AccMetric时,dataset中目标field不叫label而叫y,或者model的输出不是pred:: + + class AccMetric(MetricBase): + def __init__(self, label=None, pred=None): + # 假设在另一场景使用时,目标field叫y,model给出的key为pred_y。则只需要在初始化AccMetric时, + # acc_metric = AccMetric(label='y', pred='pred_y')即可。 + # 当初始化为acc_metric = AccMetric(),即label=None, pred=None, fastNLP会直接使用'label', 'pred'作为key去索取对 + # 应的的值 + super().__init__() + self._init_param_map(label=label, pred=pred) # 该方法会注册label和pred. 仅需要注册evaluate()方法会用到的参数名即可 + # 如果没有注册该则效果与version1就是一样的 + + # 根据你的情况自定义指标 + self.corr_num = 0 + self.total = 0 + + def evaluate(self, label, pred): # 这里的参数名称需要和self._init_param_map()注册时一致。 + # dev或test时,每个batch结束会调用一次该方法,需要实现如何根据每个batch累加metric + self.total += label.size(0) + self.corr_num += label.eq(pred).sum().item() + + def get_metric(self, reset=True): # 在这里定义如何计算metric + acc = self.corr_num/self.total + if reset: # 是否清零以便重新计算 + self.corr_num = 0 + self.total = 0 + return {'acc': acc} # 需要返回一个dict,key为该metric的名称,该名称会显示到Trainer的progress bar中 + + + ``MetricBase`` 将会在输入的字典 ``pred_dict`` 和 ``target_dict`` 中进行检查. + ``pred_dict`` 是模型当中 ``forward()`` 函数或者 ``predict()`` 函数的返回值. + ``target_dict`` 是DataSet当中的ground truth, 判定ground truth的条件是field的 ``is_target`` 被设置为True. + + ``MetricBase`` 会进行以下的类型检测: + + 1. self.evaluate当中是否有varargs, 这是不支持的. + 2. self.evaluate当中所需要的参数是否既不在 ``pred_dict`` 也不在 ``target_dict`` . + 3. self.evaluate当中所需要的参数是否既在 ``pred_dict`` 也在 ``target_dict`` . + + 除此以外,在参数被传入self.evaluate以前,这个函数会检测 ``pred_dict`` 和 ``target_dict`` 当中没有被用到的参数 + 如果kwargs是self.evaluate的参数,则不会检测 + + + self.evaluate将计算一个批次(batch)的评价指标,并累计。 没有返回值 + self.get_metric将统计当前的评价指标并返回评价结果, 返回值需要是一个dict, key是指标名称,value是指标的值 """ + def __init__(self): self.param_map = {} # key is param in function, value is input param. self._checked = False - + def evaluate(self, *args, **kwargs): raise NotImplementedError - + + def get_metric(self, reset=True): + raise NotImplemented + def _init_param_map(self, key_map=None, **kwargs): - """Check the validity of key_map and other param map. Add these into self.param_map + """检查key_map和其他参数map,并将这些映射关系添加到self.param_map - :param key_map: dict - :param kwargs: + :param dict key_map: 表示key的映射关系 + :param kwargs: key word args里面的每一个的键-值对都会被构造成映射关系 :return: None """ value_counter = defaultdict(set) @@ -71,19 +156,16 @@ class MetricBase(object): for value, key_set in value_counter.items(): if len(key_set) > 1: raise ValueError(f"Several parameters:{key_set} are provided with one output {value}.") - + # check consistence between signature and param_map func_spect = inspect.getfullargspec(self.evaluate) func_args = [arg for arg in func_spect.args if arg != 'self'] for func_param, input_param in self.param_map.items(): if func_param not in func_args: raise NameError( - f"Parameter `{func_param}` is not in {get_func_signature(self.evaluate)}. Please check the " + f"Parameter `{func_param}` is not in {_get_func_signature(self.evaluate)}. Please check the " f"initialization parameters, or change its signature.") - - def get_metric(self, reset=True): - raise NotImplemented - + def _fast_param_map(self, pred_dict, target_dict): """Only used as inner function. When the pred_dict, target is unequivocal. Don't need users to pass key_map. such as pred_dict has one element, target_dict has one element @@ -95,49 +177,47 @@ class MetricBase(object): fast_param = {} if len(self.param_map) == 2 and len(pred_dict) == 1 and len(target_dict) == 1: fast_param['pred'] = list(pred_dict.values())[0] - fast_param['target'] = list(pred_dict.values())[0] + fast_param['target'] = list(target_dict.values())[0] return fast_param return fast_param - + def __call__(self, pred_dict, target_dict): """ - - This method will call self.evaluate method. - Before calling self.evaluate, it will first check the validity of output_dict, target_dict - (1) whether params needed by self.evaluate is not included in output_dict,target_dict. - (2) whether params needed by self.evaluate duplicate in pred_dict, target_dict - (3) whether params in output_dict, target_dict are not used by evaluate.(Might cause warning) - Besides, before passing params into self.evaluate, this function will filter out params from output_dict and - target_dict which are not used in self.evaluate. (but if **kwargs presented in self.evaluate, no filtering - will be conducted.) - This function also support _fast_param_map. - :param pred_dict: usually the output of forward or prediction function - :param target_dict: usually features set as target.. + 这个方法会调用self.evaluate 方法. + 在调用之前,会进行以下检测: + 1. self.evaluate当中是否有varargs, 这是不支持的. + 2. self.evaluate当中所需要的参数是否既不在``pred_dict``也不在``target_dict``. + 3. self.evaluate当中所需要的参数是否既在``pred_dict``也在``target_dict``. + + 除此以外,在参数被传入self.evaluate以前,这个函数会检测``pred_dict``和``target_dict``当中没有被用到的参数 + 如果kwargs是self.evaluate的参数,则不会检测 + :param pred_dict: 模型的forward函数或者predict函数返回的dict + :param target_dict: DataSet.batch_y里的键-值对所组成的dict(即is_target=True的fields的内容) :return: """ - if not callable(self.evaluate): - raise TypeError(f"{self.__class__.__name__}.evaluate has to be callable, not {type(self.evaluate)}.") - - fast_param = self._fast_param_map(pred_dict=pred_dict, target_dict=target_dict) + + fast_param = self._fast_param_map(pred_dict, target_dict) if fast_param: self.evaluate(**fast_param) return - + if not self._checked: + if not callable(self.evaluate): + raise TypeError(f"{self.__class__.__name__}.evaluate has to be callable, not {type(self.evaluate)}.") # 1. check consistence between signature and param_map func_spect = inspect.getfullargspec(self.evaluate) func_args = set([arg for arg in func_spect.args if arg != 'self']) for func_arg, input_arg in self.param_map.items(): if func_arg not in func_args: - raise NameError(f"`{func_arg}` not in {get_func_signature(self.evaluate)}.") - + raise NameError(f"`{func_arg}` not in {_get_func_signature(self.evaluate)}.") + # 2. only part of the param_map are passed, left are not for arg in func_args: if arg not in self.param_map: self.param_map[arg] = arg # This param does not need mapping. self._evaluate_args = func_args self._reverse_param_map = {input_arg: func_arg for func_arg, input_arg in self.param_map.items()} - + # need to wrap inputs in dict. mapped_pred_dict = {} mapped_target_dict = {} @@ -157,7 +237,7 @@ class MetricBase(object): not_duplicate_flag += 1 if not_duplicate_flag == 3: duplicated.append(input_arg) - + # missing if not self._checked: check_res = _check_arg_dict_list(self.evaluate, [mapped_pred_dict, mapped_target_dict]) @@ -168,141 +248,118 @@ class MetricBase(object): for idx, func_arg in enumerate(missing): # Don't delete `` in this information, nor add `` replaced_missing[idx] = f"{self.param_map[func_arg]}" + f"(assign to `{func_arg}` " \ - f"in `{self.__class__.__name__}`)" - - check_res = CheckRes(missing=replaced_missing, - unused=check_res.unused, - duplicated=duplicated, - required=check_res.required, - all_needed=check_res.all_needed, - varargs=check_res.varargs) - + f"in `{self.__class__.__name__}`)" + + check_res = _CheckRes(missing=replaced_missing, + unused=check_res.unused, + duplicated=duplicated, + required=check_res.required, + all_needed=check_res.all_needed, + varargs=check_res.varargs) + if check_res.missing or check_res.duplicated: - raise CheckError(check_res=check_res, - func_signature=get_func_signature(self.evaluate)) + raise _CheckError(check_res=check_res, + func_signature=_get_func_signature(self.evaluate)) refined_args = _build_args(self.evaluate, **mapped_pred_dict, **mapped_target_dict) - + self.evaluate(**refined_args) self._checked = True - + return class AccuracyMetric(MetricBase): - """Accuracy Metric - """ - def __init__(self, pred=None, target=None, seq_lens=None): + + 别名::class:`fastNLP.AccuracyMetric` :class:`fastNLP.core.metrics.AccuracyMetric` + + 准确率Metric(其它的Metric参见 :doc:`fastNLP.core.metrics` ) + + :param pred: 参数映射表中 `pred` 的映射关系,None表示映射关系为 `pred` -> `pred` + :param target: 参数映射表中 `target` 的映射关系,None表示映射关系为 `target` -> `target` + :param seq_len: 参数映射表中 `seq_len` 的映射关系,None表示映射关系为 `seq_len` -> `seq_len` + """ + + def __init__(self, pred=None, target=None, seq_len=None): + super().__init__() - - self._init_param_map(pred=pred, target=target, seq_lens=seq_lens) - + + self._init_param_map(pred=pred, target=target, seq_len=seq_len) + self.total = 0 self.acc_count = 0 - - def _fast_param_map(self, pred_dict, target_dict): - """Only used as inner function. When the pred_dict, target is unequivocal. Don't need users to pass key_map. - such as pred_dict has one element, target_dict has one element - - :param pred_dict: - :param target_dict: - :return: dict, if dict is not None, pass it to self.evaluate. Otherwise do mapping. - """ - fast_param = {} - targets = list(target_dict.values()) - if len(targets) == 1 and isinstance(targets[0], torch.Tensor): - if len(pred_dict) == 1: - pred = list(pred_dict.values())[0] - fast_param['pred'] = pred - elif len(pred_dict) == 2: - pred1 = list(pred_dict.values())[0] - pred2 = list(pred_dict.values())[1] - if not (isinstance(pred1, torch.Tensor) and isinstance(pred2, torch.Tensor)): - return fast_param - if len(pred1.size()) < len(pred2.size()) and len(pred1.size()) == 1: - seq_lens = pred1 - pred = pred2 - elif len(pred1.size()) > len(pred2.size()) and len(pred2.size()) == 1: - seq_lens = pred2 - pred = pred1 - else: - return fast_param - fast_param['pred'] = pred - fast_param['seq_lens'] = seq_lens - else: - return fast_param - fast_param['target'] = targets[0] - # TODO need to make sure they all have same batch_size - return fast_param - - def evaluate(self, pred, target, seq_lens=None): + + def evaluate(self, pred, target, seq_len=None): """ + evaluate函数将针对一个批次的预测结果做评价指标的累计 - :param pred: List of (torch.Tensor, or numpy.ndarray). Element's shape can be: - torch.Size([B,]), torch.Size([B, n_classes]), torch.Size([B, max_len]), torch.Size([B, max_len, n_classes]) - :param target: List of (torch.Tensor, or numpy.ndarray). Element's can be: - torch.Size([B,]), torch.Size([B,]), torch.Size([B, max_len]), torch.Size([B, max_len]) - :param seq_lens: List of (torch.Tensor, or numpy.ndarray). Element's can be: - None, None, torch.Size([B], torch.Size([B]). ignored if masks are provided. + :param torch.Tensor pred: 预测的tensor, tensor的形状可以是torch.Size([B,]), torch.Size([B, n_classes]), + torch.Size([B, max_len]), 或者torch.Size([B, max_len, n_classes]) + :param torch.Tensor target: 真实值的tensor, tensor的形状可以是Element's can be: torch.Size([B,]), + torch.Size([B,]), torch.Size([B, max_len]), 或者torch.Size([B, max_len]) + :param torch.Tensor seq_len: 序列长度标记, 标记的形状可以是None, None, torch.Size([B]), 或者torch.Size([B]). + 如果mask也被传进来的话seq_len会被忽略. """ # TODO 这里报错需要更改,因为pred是啥用户并不知道。需要告知用户真实的value if not isinstance(pred, torch.Tensor): - raise TypeError(f"`pred` in {get_func_signature(self.evaluate)} must be torch.Tensor," + raise TypeError(f"`pred` in {_get_func_signature(self.evaluate)} must be torch.Tensor," f"got {type(pred)}.") if not isinstance(target, torch.Tensor): - raise TypeError(f"`target` in {get_func_signature(self.evaluate)} must be torch.Tensor," + raise TypeError(f"`target` in {_get_func_signature(self.evaluate)} must be torch.Tensor," f"got {type(target)}.") - - if seq_lens is not None and not isinstance(seq_lens, torch.Tensor): - raise TypeError(f"`seq_lens` in {get_func_signature(self.evaluate)} must be torch.Tensor," - f"got {type(seq_lens)}.") - - if seq_lens is not None: - masks = seq_lens_to_masks(seq_lens=seq_lens, float=True) + + if seq_len is not None and not isinstance(seq_len, torch.Tensor): + raise TypeError(f"`seq_lens` in {_get_func_signature(self.evaluate)} must be torch.Tensor," + f"got {type(seq_len)}.") + + if seq_len is not None: + masks = seq_len_to_mask(seq_len=seq_len) else: masks = None - + if pred.size() == target.size(): pass elif len(pred.size()) == len(target.size()) + 1: pred = pred.argmax(dim=-1) else: - raise RuntimeError(f"In {get_func_signature(self.evaluate)}, when pred have " + raise RuntimeError(f"In {_get_func_signature(self.evaluate)}, when pred have " f"size:{pred.size()}, target should have size: {pred.size()} or " f"{pred.size()[:-1]}, got {target.size()}.") - - pred = pred.float() - target = target.float() - + + target = target.to(pred) if masks is not None: - self.acc_count += torch.sum(torch.eq(pred, target).float() * masks.float()).item() - self.total += torch.sum(masks.float()).item() + self.acc_count += torch.sum(torch.eq(pred, target).masked_fill(masks.eq(0), 0)).item() + self.total += torch.sum(masks).item() else: - self.acc_count += torch.sum(torch.eq(pred, target).float()).item() + self.acc_count += torch.sum(torch.eq(pred, target)).item() self.total += np.prod(list(pred.size())) - + def get_metric(self, reset=True): - """Returns computed metric. + """ + get_metric函数将根据evaluate函数累计的评价指标统计量来计算最终的评价结果. - :param bool reset: whether to recount next time. - :return evaluate_result: {"acc": float} + :param bool reset: 在调用完get_metric后是否清空评价指标统计量. + :return dict evaluate_result: {"acc": float} """ - evaluate_result = {'acc': round(self.acc_count / self.total, 6)} + evaluate_result = {'acc': round(float(self.acc_count) / (self.total + 1e-12), 6)} if reset: self.acc_count = 0 self.total = 0 return evaluate_result -def bmes_tag_to_spans(tags, ignore_labels=None): + +def _bmes_tag_to_spans(tags, ignore_labels=None): """ + 给定一个tags的lis,比如['S-song', 'B-singer', 'M-singer', 'E-singer', 'S-moive', 'S-actor']。 + 返回[('song', (0, 1)), ('singer', (1, 4)), ('moive', (4, 5)), ('actor', (5, 6))] (左闭右开区间) :param tags: List[str], :param ignore_labels: List[str], 在该list中的label将被忽略 :return: List[Tuple[str, List[int, int]]]. [(label,[start, end])] """ ignore_labels = set(ignore_labels) if ignore_labels else set() - + spans = [] prev_bmes_tag = None for idx, tag in enumerate(tags): @@ -310,25 +367,59 @@ def bmes_tag_to_spans(tags, ignore_labels=None): bmes_tag, label = tag[:1], tag[2:] if bmes_tag in ('b', 's'): spans.append((label, [idx, idx])) - elif bmes_tag in ('m', 'e') and prev_bmes_tag in ('b', 'm') and label==spans[-1][0]: + elif bmes_tag in ('m', 'e') and prev_bmes_tag in ('b', 'm') and label == spans[-1][0]: spans[-1][1][1] = idx else: spans.append((label, [idx, idx])) prev_bmes_tag = bmes_tag - return [(span[0], (span[1][0], span[1][1])) - for span in spans - if span[0] not in ignore_labels + return [(span[0], (span[1][0], span[1][1] + 1)) + for span in spans + if span[0] not in ignore_labels ] -def bio_tag_to_spans(tags, ignore_labels=None): + +def _bmeso_tag_to_spans(tags, ignore_labels=None): """ + 给定一个tags的lis,比如['O', 'B-singer', 'M-singer', 'E-singer', 'O', 'O']。 + 返回[('singer', (1, 4))] (左闭右开区间) :param tags: List[str], :param ignore_labels: List[str], 在该list中的label将被忽略 :return: List[Tuple[str, List[int, int]]]. [(label,[start, end])] """ ignore_labels = set(ignore_labels) if ignore_labels else set() + + spans = [] + prev_bmes_tag = None + for idx, tag in enumerate(tags): + tag = tag.lower() + bmes_tag, label = tag[:1], tag[2:] + if bmes_tag in ('b', 's'): + spans.append((label, [idx, idx])) + elif bmes_tag in ('m', 'e') and prev_bmes_tag in ('b', 'm') and label == spans[-1][0]: + spans[-1][1][1] = idx + elif bmes_tag == 'o': + pass + else: + spans.append((label, [idx, idx])) + prev_bmes_tag = bmes_tag + return [(span[0], (span[1][0], span[1][1] + 1)) + for span in spans + if span[0] not in ignore_labels + ] + +def _bio_tag_to_spans(tags, ignore_labels=None): + """ + 给定一个tags的lis,比如['O', 'B-singer', 'I-singer', 'I-singer', 'O', 'O']。 + 返回[('singer', (1, 4))] (左闭右开区间) + + :param tags: List[str], + :param ignore_labels: List[str], 在该list中的label将被忽略 + :return: List[Tuple[str, List[int, int]]]. [(label,[start, end])] + """ + ignore_labels = set(ignore_labels) if ignore_labels else set() + spans = [] prev_bio_tag = None for idx, tag in enumerate(tags): @@ -336,130 +427,139 @@ def bio_tag_to_spans(tags, ignore_labels=None): bio_tag, label = tag[:1], tag[2:] if bio_tag == 'b': spans.append((label, [idx, idx])) - elif bio_tag == 'i' and prev_bio_tag in ('b', 'i') and label==spans[-1][0]: + elif bio_tag == 'i' and prev_bio_tag in ('b', 'i') and label == spans[-1][0]: spans[-1][1][1] = idx - elif bio_tag == 'o': # o tag does not count + elif bio_tag == 'o': # o tag does not count pass else: spans.append((label, [idx, idx])) prev_bio_tag = bio_tag - return [(span[0], (span[1][0], span[1][1])) - for span in spans - if span[0] not in ignore_labels - ] + return [(span[0], (span[1][0], span[1][1] + 1)) for span in spans if span[0] not in ignore_labels] class SpanFPreRecMetric(MetricBase): """ + 别名::class:`fastNLP.SpanFPreRecMetric` :class:`fastNLP.core.metrics.SpanFPreRecMetric` + 在序列标注问题中,以span的方式计算F, pre, rec. - 最后得到的metric结果为 - { - 'f': xxx, # 这里使用f考虑以后可以计算f_beta值 - 'pre': xxx, - 'rec':xxx - } - 若only_gross=False, 即还会返回各个label的metric统计值 + 比如中文Part of speech中,会以character的方式进行标注,句子'中国在亚洲'对应的POS可能为(以BMES为例) + ['B-NN', 'E-NN', 'S-DET', 'B-NN', 'E-NN']。该metric就是为类似情况下的F1计算。 + 最后得到的metric结果为:: + { - 'f': xxx, - 'pre': xxx, - 'rec':xxx, - 'f-label': xxx, - 'pre-label': xxx, - 'rec-label':xxx, - ... - } + 'f': xxx, # 这里使用f考虑以后可以计算f_beta值 + 'pre': xxx, + 'rec':xxx + } + + 若only_gross=False, 即还会返回各个label的metric统计值:: + + { + 'f': xxx, + 'pre': xxx, + 'rec':xxx, + 'f-label': xxx, + 'pre-label': xxx, + 'rec-label':xxx, + ... + } + :param tag_vocab: 标签的 :class:`~fastNLP.Vocabulary` 。支持的标签为"B"(没有label);或"B-xxx"(xxx为某种label,比如POS中的NN), + 在解码时,会将相同xxx的认为是同一个label,比如['B-NN', 'E-NN']会被合并为一个'NN'. + :param str pred: 用该key在evaluate()时从传入dict中取出prediction数据。 为None,则使用'pred'取数据 + :param str target: 用该key在evaluate()时从传入dict中取出target数据。 为None,则使用'target'取数据 + :param str seq_len: 用该key在evaluate()时从传入dict中取出sequence length数据。为None,则使用'seq_len'取数据。 + :param str encoding_type: 目前支持bio, bmes + :param list ignore_labels: str 组成的list. 这个list中的class不会被用于计算。例如在POS tagging时传入['NN'],则不会计算'NN'这 + 个label + :param bool only_gross: 是否只计算总的f1, precision, recall的值;如果为False,不仅返回总的f1, pre, rec, 还会返回每个 + label的f1, pre, rec + :param str f_type: 'micro'或'macro'. 'micro':通过先计算总体的TP,FN和FP的数量,再计算f, precision, recall; 'macro': + 分布计算每个类别的f, precision, recall,然后做平均(各类别f的权重相同) + :param float beta: f_beta分数,f_beta = (1 + beta^2)*(pre*rec)/(beta^2*pre + rec). 常用为beta=0.5, 1, 2. 若为0.5 + 则精确率的权重高于召回率;若为1,则两者平等;若为2,则召回率权重高于精确率。 """ - def __init__(self, tag_vocab, pred=None, target=None, seq_lens=None, encoding_type='bio', ignore_labels=None, - only_gross=True, f_type='micro', beta=1): - """ - - :param tag_vocab: Vocabulary, 标签的vocabulary。支持的标签为"B"(没有label);或"B-xxx"(xxx为某种label,比如POS中的NN), - 在解码时,会将相同xxx的认为是同一个label,比如['B-NN', 'E-NN']会被合并为一个'NN'. - :param pred: str, 用该key在evaluate()时从传入dict中取出prediction数据。 为None,则使用'pred'取数据 - :param target: str, 用该key在evaluate()时从传入dict中取出target数据。 为None,则使用'target'取数据 - :param seq_lens: str, 用该key在evaluate()时从传入dict中取出sequence length数据。为None,则使用'seq_lens'取数据。 - :param encoding_type: str, 目前支持bio, bmes - :param ignore_labels, List[str]. 这个list中的class不会被用于计算。例如在POS tagging时传入['NN'],则不会计算'NN'这 - 个label - :param only_gross, bool. 是否只计算总的f1, precision, recall的值;如果为False,不仅返回总的f1, pre, rec, 还会返回每个 - label的f1, pre, rec - :param f_type, str. 'micro'或'macro'. 'micro':通过先计算总体的TP,FN和FP的数量,再计算f, precision, recall; 'macro': - 分布计算每个类别的f, precision, recall,然后做平均(各类别f的权重相同) - :param beta, float. f_beta分数,f_beta = (1 + beta^2)*(pre*rec)/(beta^2*pre + rec). 常用为beta=0.5, 1, 2. 若为0.5 - 则精确率的权重高于召回率;若为1,则两者平等;若为2,则召回率权重高于精确率。 - """ + + def __init__(self, tag_vocab, pred=None, target=None, seq_len=None, encoding_type='bio', ignore_labels=None, + only_gross=True, f_type='micro', beta=1): + encoding_type = encoding_type.lower() - if encoding_type not in ('bio', 'bmes'): - raise ValueError("Only support 'bio' or 'bmes' type.") + if not isinstance(tag_vocab, Vocabulary): raise TypeError("tag_vocab can only be fastNLP.Vocabulary, not {}.".format(type(tag_vocab))) if f_type not in ('micro', 'macro'): raise ValueError("f_type only supports `micro` or `macro`', got {}.".format(f_type)) - + self.encoding_type = encoding_type if self.encoding_type == 'bmes': - self.tag_to_span_func = bmes_tag_to_spans + self.tag_to_span_func = _bmes_tag_to_spans elif self.encoding_type == 'bio': - self.tag_to_span_func = bio_tag_to_spans + self.tag_to_span_func = _bio_tag_to_spans + elif self.encoding_type == 'bmeso': + self.tag_to_span_func = _bmeso_tag_to_spans + else: + raise ValueError("Only support 'bio', 'bmes', 'bmeso' type.") + self.ignore_labels = ignore_labels self.f_type = f_type self.beta = beta - self.beta_square = self.beta**2 + self.beta_square = self.beta ** 2 self.only_gross = only_gross - + super().__init__() - self._init_param_map(pred=pred, target=target, seq_lens=seq_lens) - + self._init_param_map(pred=pred, target=target, seq_len=seq_len) + self.tag_vocab = tag_vocab - + self._true_positives = defaultdict(int) self._false_positives = defaultdict(int) self._false_negatives = defaultdict(int) + + def evaluate(self, pred, target, seq_len): + """evaluate函数将针对一个批次的预测结果做评价指标的累计 - def evaluate(self, pred, target, seq_lens): - """ - A lot of design idea comes from allennlp's measure - :param pred: - :param target: - :param seq_lens: + :param pred: [batch, seq_len] 或者 [batch, seq_len, len(tag_vocab)], 预测的结果 + :param target: [batch, seq_len], 真实值 + :param seq_len: [batch] 文本长度标记 :return: """ if not isinstance(pred, torch.Tensor): - raise TypeError(f"`pred` in {get_func_signature(self.evaluate)} must be torch.Tensor," + raise TypeError(f"`pred` in {_get_func_signature(self.evaluate)} must be torch.Tensor," f"got {type(pred)}.") if not isinstance(target, torch.Tensor): - raise TypeError(f"`target` in {get_func_signature(self.evaluate)} must be torch.Tensor," + raise TypeError(f"`target` in {_get_func_signature(self.evaluate)} must be torch.Tensor," f"got {type(target)}.") - - if not isinstance(seq_lens, torch.Tensor): - raise TypeError(f"`seq_lens` in {get_func_signature(self.evaluate)} must be torch.Tensor," - f"got {type(seq_lens)}.") - + + if not isinstance(seq_len, torch.Tensor): + raise TypeError(f"`seq_lens` in {_get_func_signature(self.evaluate)} must be torch.Tensor," + f"got {type(seq_len)}.") + if pred.size() == target.size() and len(target.size()) == 2: pass elif len(pred.size()) == len(target.size()) + 1 and len(target.size()) == 2: - pred = pred.argmax(dim=-1) num_classes = pred.size(-1) + pred = pred.argmax(dim=-1) if (target >= num_classes).any(): raise ValueError("A gold label passed to SpanBasedF1Metric contains an " "id >= {}, the number of classes.".format(num_classes)) else: - raise RuntimeError(f"In {get_func_signature(self.evaluate)}, when pred have " + raise RuntimeError(f"In {_get_func_signature(self.evaluate)}, when pred have " f"size:{pred.size()}, target should have size: {pred.size()} or " f"{pred.size()[:-1]}, got {target.size()}.") - + batch_size = pred.size(0) + pred = pred.tolist() + target = target.tolist() for i in range(batch_size): - pred_tags = pred[i, :int(seq_lens[i])].tolist() - gold_tags = target[i, :int(seq_lens[i])].tolist() - + pred_tags = pred[i][:int(seq_len[i])] + gold_tags = target[i][:int(seq_len[i])] + pred_str_tags = [self.tag_vocab.to_word(tag) for tag in pred_tags] gold_str_tags = [self.tag_vocab.to_word(tag) for tag in gold_tags] - + pred_spans = self.tag_to_span_func(pred_str_tags, ignore_labels=self.ignore_labels) gold_spans = self.tag_to_span_func(gold_str_tags, ignore_labels=self.ignore_labels) - + for span in pred_spans: if span in gold_spans: self._true_positives[span[0]] += 1 @@ -468,10 +568,11 @@ class SpanFPreRecMetric(MetricBase): self._false_positives[span[0]] += 1 for span in gold_spans: self._false_negatives[span[0]] += 1 - + def get_metric(self, reset=True): + """get_metric函数将根据evaluate函数累计的评价指标统计量来计算最终的评价结果.""" evaluate_result = {} - if not self.only_gross or self.f_type=='macro': + if not self.only_gross or self.f_type == 'macro': tags = set(self._false_negatives.keys()) tags.update(set(self._false_positives.keys())) tags.update(set(self._true_positives.keys())) @@ -486,34 +587,37 @@ class SpanFPreRecMetric(MetricBase): f_sum += f pre_sum += pre rec_sum + rec - if not self.only_gross and tag!='': # tag!=''防止无tag的情况 + if not self.only_gross and tag != '': # tag!=''防止无tag的情况 f_key = 'f-{}'.format(tag) pre_key = 'pre-{}'.format(tag) rec_key = 'rec-{}'.format(tag) evaluate_result[f_key] = f evaluate_result[pre_key] = pre evaluate_result[rec_key] = rec - + if self.f_type == 'macro': - evaluate_result['f'] = f_sum/len(tags) - evaluate_result['pre'] = pre_sum/len(tags) - evaluate_result['rec'] = rec_sum/len(tags) - + evaluate_result['f'] = f_sum / len(tags) + evaluate_result['pre'] = pre_sum / len(tags) + evaluate_result['rec'] = rec_sum / len(tags) + if self.f_type == 'micro': f, pre, rec = self._compute_f_pre_rec(sum(self._true_positives.values()), sum(self._false_negatives.values()), sum(self._false_positives.values())) - evaluate_result['f'] = round(f, 6) - evaluate_result['pre'] = round(pre, 6) - evaluate_result['rec'] = round(rec, 6) - + evaluate_result['f'] = f + evaluate_result['pre'] = pre + evaluate_result['rec'] = rec + if reset: self._true_positives = defaultdict(int) self._false_positives = defaultdict(int) self._false_negatives = defaultdict(int) - + + for key, value in evaluate_result.items(): + evaluate_result[key] = round(value, 6) + return evaluate_result - + def _compute_f_pre_rec(self, tp, fn, fp): """ @@ -525,137 +629,9 @@ class SpanFPreRecMetric(MetricBase): pre = tp / (fp + tp + 1e-13) rec = tp / (fn + tp + 1e-13) f = (1 + self.beta_square) * pre * rec / (self.beta_square * pre + rec + 1e-13) - + return f, pre, rec -class BMESF1PreRecMetric(MetricBase): - """ - 按照BMES标注方式计算f1, precision, recall。由于可能存在非法tag,比如"BS",所以需要用以下的表格做转换,cur_B意思是当前tag是B, - next_B意思是后一个tag是B。则cur_B=S,即将当前被predict是B的tag标为S;next_M=B, 即将后一个被predict是M的tag标为B - | | next_B | next_M | next_E | next_S | end | - |:-----:|:-------:|:--------:|:--------:|:-------:|:-------:| - | start | 合法 | next_M=B | next_E=S | 合法 | - | - | cur_B | cur_B=S | 合法 | 合法 | cur_B=S | cur_B=S | - | cur_M | cur_M=E | 合法 | 合法 | cur_M=E | cur_M=E | - | cur_E | 合法 | next_M=B | next_E=S | 合法 | 合法 | - | cur_S | 合法 | next_M=B | next_E=S | 合法 | 合法 | - 举例: - prediction为BSEMS,会被认为是SSSSS. - - 本Metric不检验target的合法性,请务必保证target的合法性。 - pred的形状应该为(batch_size, max_len) 或 (batch_size, max_len, 4)。 - target形状为 (batch_size, max_len) - seq_lens形状为 (batch_size, ) - - """ - - def __init__(self, b_idx=0, m_idx=1, e_idx=2, s_idx=3, pred=None, target=None, seq_lens=None): - """ - 需要申明BMES这四种tag中,各种tag对应的idx。所有不为b_idx, m_idx, e_idx, s_idx的数字都认为是s_idx。 - - :param b_idx: int, Begin标签所对应的tag idx. - :param m_idx: int, Middle标签所对应的tag idx. - :param e_idx: int, End标签所对应的tag idx. - :param s_idx: int, Single标签所对应的tag idx - :param pred: str, 用该key在evaluate()时从传入dict中取出prediction数据。 为None,则使用'pred'取数据 - :param target: str, 用该key在evaluate()时从传入dict中取出target数据。 为None,则使用'target'取数据 - :param seq_lens: str, 用该key在evaluate()时从传入dict中取出seqence length数据。为None,则使用'seq_lens'取数据。 - """ - super().__init__() - - self._init_param_map(pred=pred, target=target, seq_lens=seq_lens) - - self.yt_wordnum = 0 - self.yp_wordnum = 0 - self.corr_num = 0 - - self.b_idx = b_idx - self.m_idx = m_idx - self.e_idx = e_idx - self.s_idx = s_idx - # 还原init处介绍的矩阵 - self._valida_matrix = { - -1: [(-1, -1), (1, self.b_idx), (1, self.s_idx), (-1, -1)], # magic start idx - self.b_idx:[(0, self.s_idx), (-1, -1), (-1, -1), (0, self.s_idx), (0, self.s_idx)], - self.m_idx:[(0, self.e_idx), (-1, -1), (-1, -1), (0, self.e_idx), (0, self.e_idx)], - self.e_idx:[(-1, -1), (1, self.b_idx), (1, self.s_idx), (-1, -1), (-1, -1)], - self.s_idx:[(-1, -1), (1, self.b_idx), (1, self.s_idx), (-1, -1), (-1, -1)], - } - - def _validate_tags(self, tags): - """ - 给定一个tag的Tensor,返回合法tag - - :param tags: Tensor, shape: (seq_len, ) - :return: 返回修改为合法tag的list - """ - assert len(tags)!=0 - assert isinstance(tags, torch.Tensor) and len(tags.size())==1 - padded_tags = [-1, *tags.tolist(), -1] - for idx in range(len(padded_tags)-1): - cur_tag = padded_tags[idx] - if cur_tag not in self._valida_matrix: - cur_tag = self.s_idx - if padded_tags[idx+1] not in self._valida_matrix: - padded_tags[idx+1] = self.s_idx - next_tag = padded_tags[idx+1] - shift_tag = self._valida_matrix[cur_tag][next_tag] - if shift_tag[0]!=-1: - padded_tags[idx+shift_tag[0]] = shift_tag[1] - - return padded_tags[1:-1] - - def evaluate(self, pred, target, seq_lens): - if not isinstance(pred, torch.Tensor): - raise TypeError(f"`pred` in {get_func_signature(self.evaluate)} must be torch.Tensor," - f"got {type(pred)}.") - if not isinstance(target, torch.Tensor): - raise TypeError(f"`target` in {get_func_signature(self.evaluate)} must be torch.Tensor," - f"got {type(target)}.") - - if not isinstance(seq_lens, torch.Tensor): - raise TypeError(f"`seq_lens` in {get_func_signature(self.evaluate)} must be torch.Tensor," - f"got {type(seq_lens)}.") - - if pred.size() == target.size() and len(target.size()) == 2: - pass - elif len(pred.size()) == len(target.size()) + 1 and len(target.size()) == 2: - pred = pred.argmax(dim=-1) - else: - raise RuntimeError(f"In {get_func_signature(self.evaluate)}, when pred have " - f"size:{pred.size()}, target should have size: {pred.size()} or " - f"{pred.size()[:-1]}, got {target.size()}.") - - for idx in range(len(pred)): - seq_len = seq_lens[idx] - target_tags = target[idx][:seq_len].tolist() - pred_tags = pred[idx][:seq_len] - pred_tags = self._validate_tags(pred_tags) - start_idx = 0 - for t_idx, (t_tag, p_tag) in enumerate(zip(target_tags, pred_tags)): - if t_tag in (self.s_idx, self.e_idx): - self.yt_wordnum += 1 - corr_flag = True - for i in range(start_idx, t_idx+1): - if target_tags[i]!=pred_tags[i]: - corr_flag = False - if corr_flag: - self.corr_num += 1 - start_idx = t_idx + 1 - if p_tag in (self.s_idx, self.e_idx): - self.yp_wordnum += 1 - - def get_metric(self, reset=True): - P = self.corr_num / (self.yp_wordnum + 1e-12) - R = self.corr_num / (self.yt_wordnum + 1e-12) - F = 2 * P * R / (P + R + 1e-12) - evaluate_result = {'f': round(F, 6), 'pre':round(P, 6), 'rec': round(R, 6)} - if reset: - self.yp_wordnum = 0 - self.yt_wordnum = 0 - self.corr_num = 0 - return evaluate_result - def _prepare_metrics(metrics): """ @@ -688,7 +664,7 @@ def _prepare_metrics(metrics): return _metrics -def accuracy_topk(y_true, y_prob, k=1): +def _accuracy_topk(y_true, y_prob, k=1): """Compute accuracy of y_true matching top-k probable labels in y_prob. :param y_true: ndarray, true label, [n_samples] @@ -704,7 +680,7 @@ def accuracy_topk(y_true, y_prob, k=1): return acc -def pred_topk(y_prob, k=1): +def _pred_topk(y_prob, k=1): """Return top-k predicted labels and corresponding probabilities. :param y_prob: ndarray, size [n_samples, n_classes], probabilities on labels @@ -720,3 +696,171 @@ def pred_topk(y_prob, k=1): (1, k)) y_prob_topk = y_prob[x_axis_index, y_pred_topk] return y_pred_topk, y_prob_topk + + +class SQuADMetric(MetricBase): + """ + 别名::class:`fastNLP.SQuADMetric` :class:`fastNLP.core.metrics.SQuADMetric` + + SQuAD数据集metric + + :param pred1: 参数映射表中`pred1`的映射关系,None表示映射关系为`pred1`->`pred1` + :param pred2: 参数映射表中`pred2`的映射关系,None表示映射关系为`pred2`->`pred2` + :param target1: 参数映射表中`target1`的映射关系,None表示映射关系为`target1`->`target1` + :param target2: 参数映射表中`target2`的映射关系,None表示映射关系为`target2`->`target2` + :param float beta: f_beta分数,f_beta = (1 + beta^2)*(pre*rec)/(beta^2*pre + rec). 常用为beta=0.5, 1, 2. 若为0.5 + 则精确率的权重高于召回率;若为1,则两者平等;若为2,则召回率权重高于精确率。 + :param bool right_open: right_open为true表示start跟end指针指向一个左闭右开区间,为false表示指向一个左闭右闭区间。 + :param bool print_predict_stat: True则输出预测答案是否为空与正确答案是否为空的统计信息, False则不输出 + + """ + + def __init__(self, pred1=None, pred2=None, target1=None, target2=None, + beta=1, right_open=True, print_predict_stat=False): + + super(SQuADMetric, self).__init__() + + self._init_param_map(pred1=pred1, pred2=pred2, target1=target1, target2=target2) + + self.print_predict_stat = print_predict_stat + + self.no_ans_correct = 0 + self.no_ans_wrong = 0 + + self.has_ans_correct = 0 + self.has_ans_wrong = 0 + + self.has_ans_f = 0. + + self.no2no = 0 + self.no2yes = 0 + self.yes2no = 0 + self.yes2yes = 0 + + self.f_beta = beta + + self.right_open = right_open + + def evaluate(self, pred1, pred2, target1, target2): + """evaluate函数将针对一个批次的预测结果做评价指标的累计 + + :param pred1: [batch]或者[batch, seq_len], 预测答案开始的index, 如果SQuAD2.0中答案为空则为0 + :param pred2: [batch]或者[batch, seq_len] 预测答案结束的index, 如果SQuAD2.0中答案为空则为0(左闭右闭区间)或者1(左闭右开区间) + :param target1: [batch], 正确答案开始的index, 如果SQuAD2.0中答案为空则为0 + :param target2: [batch], 正确答案结束的index, 如果SQuAD2.0中答案为空则为0(左闭右闭区间)或者1(左闭右开区间) + :return: None + """ + pred_start = pred1 + pred_end = pred2 + target_start = target1 + target_end = target2 + + if len(pred_start.size()) == 2: + start_inference = pred_start.max(dim=-1)[1].cpu().tolist() + else: + start_inference = pred_start.cpu().tolist() + if len(pred_end.size()) == 2: + end_inference = pred_end.max(dim=-1)[1].cpu().tolist() + else: + end_inference = pred_end.cpu().tolist() + + start, end = [], [] + max_len = pred_start.size(1) + t_start = target_start.cpu().tolist() + t_end = target_end.cpu().tolist() + + for s, e in zip(start_inference, end_inference): + start.append(min(s, e)) + end.append(max(s, e)) + for s, e, ts, te in zip(start, end, t_start, t_end): + if not self.right_open: + e += 1 + te += 1 + if ts == 0 and te == int(not self.right_open): + if s == 0 and e == int(not self.right_open): + self.no_ans_correct += 1 + self.no2no += 1 + else: + self.no_ans_wrong += 1 + self.no2yes += 1 + else: + if s == 0 and e == int(not self.right_open): + self.yes2no += 1 + else: + self.yes2yes += 1 + + if s == ts and e == te: + self.has_ans_correct += 1 + else: + self.has_ans_wrong += 1 + a = [0] * s + [1] * (e - s) + [0] * (max_len - e) + b = [0] * ts + [1] * (te - ts) + [0] * (max_len - te) + a, b = torch.tensor(a), torch.tensor(b) + + TP = int(torch.sum(a * b)) + pre = TP / int(torch.sum(a)) if int(torch.sum(a)) > 0 else 0 + rec = TP / int(torch.sum(b)) if int(torch.sum(b)) > 0 else 0 + + if pre + rec > 0: + f = (1 + (self.f_beta ** 2)) * pre * rec / ((self.f_beta ** 2) * pre + rec) + else: + f = 0 + self.has_ans_f += f + + def get_metric(self, reset=True): + """get_metric函数将根据evaluate函数累计的评价指标统计量来计算最终的评价结果.""" + evaluate_result = {} + + if self.no_ans_correct + self.no_ans_wrong + self.has_ans_correct + self.no_ans_wrong <= 0: + return evaluate_result + + evaluate_result['EM'] = 0 + evaluate_result[f'f_{self.f_beta}'] = 0 + + flag = 0 + + if self.no_ans_correct + self.no_ans_wrong > 0: + evaluate_result[f'noAns-f_{self.f_beta}'] = \ + round(100 * self.no_ans_correct / (self.no_ans_correct + self.no_ans_wrong), 3) + evaluate_result['noAns-EM'] = \ + round(100 * self.no_ans_correct / (self.no_ans_correct + self.no_ans_wrong), 3) + evaluate_result[f'f_{self.f_beta}'] += evaluate_result[f'noAns-f_{self.f_beta}'] + evaluate_result['EM'] += evaluate_result['noAns-EM'] + flag += 1 + + if self.has_ans_correct + self.has_ans_wrong > 0: + evaluate_result[f'hasAns-f_{self.f_beta}'] = \ + round(100 * self.has_ans_f / (self.has_ans_correct + self.has_ans_wrong), 3) + evaluate_result['hasAns-EM'] = \ + round(100 * self.has_ans_correct / (self.has_ans_correct + self.has_ans_wrong), 3) + evaluate_result[f'f_{self.f_beta}'] += evaluate_result[f'hasAns-f_{self.f_beta}'] + evaluate_result['EM'] += evaluate_result['hasAns-EM'] + flag += 1 + + if self.print_predict_stat: + evaluate_result['no2no'] = self.no2no + evaluate_result['no2yes'] = self.no2yes + evaluate_result['yes2no'] = self.yes2no + evaluate_result['yes2yes'] = self.yes2yes + + if flag <= 0: + return evaluate_result + + evaluate_result[f'f_{self.f_beta}'] = round(evaluate_result[f'f_{self.f_beta}'] / flag, 3) + evaluate_result['EM'] = round(evaluate_result['EM'] / flag, 3) + + if reset: + self.no_ans_correct = 0 + self.no_ans_wrong = 0 + + self.has_ans_correct = 0 + self.has_ans_wrong = 0 + + self.has_ans_f = 0. + + self.no2no = 0 + self.no2yes = 0 + self.yes2no = 0 + self.yes2yes = 0 + + return evaluate_result diff --git a/fastNLP/core/optimizer.py b/fastNLP/core/optimizer.py index 145f117c..ef619042 100644 --- a/fastNLP/core/optimizer.py +++ b/fastNLP/core/optimizer.py @@ -1,57 +1,82 @@ +""" +optimizer 模块定义了 fastNLP 中所需的各种优化器,一般做为 :class:`~fastNLP.Trainer` 的参数使用。 + +""" +__all__ = [ + "Optimizer", + "SGD", + "Adam" +] + import torch class Optimizer(object): """ + 别名::class:`fastNLP.Optimizer` :class:`fastNLP.core.optimizer.Optimizer` - :param model_params: a generator. E.g. ``model.parameters()`` for PyTorch models. - :param kwargs: additional parameters. + :param model_params: a generator. E.g. ``model.parameters()`` for PyTorch models. + :param kwargs: additional parameters. """ + def __init__(self, model_params, **kwargs): if model_params is not None and not hasattr(model_params, "__next__"): raise RuntimeError("model parameters should be a generator, rather than {}.".format(type(model_params))) self.model_params = model_params self.settings = kwargs + + def construct_from_pytorch(self, model_params): + raise NotImplementedError + + def _get_require_grads_param(self, params): + """ + 将params中不需要gradient的删除 + :param iterable params: parameters + :return: list(nn.Parameters) + """ + return [param for param in params if param.requires_grad] class SGD(Optimizer): """ + 别名::class:`fastNLP.SGD` :class:`fastNLP.core.optimizer.SGD` - :param float lr: learning rate. Default: 0.01 - :param float momentum: momentum. Default: 0 - :param model_params: a generator. E.g. ``model.parameters()`` for PyTorch models. + :param float lr: learning rate. Default: 0.01 + :param float momentum: momentum. Default: 0 + :param model_params: a generator. E.g. ``model.parameters()`` for PyTorch models. """ - + def __init__(self, lr=0.001, momentum=0, model_params=None): if not isinstance(lr, float): raise TypeError("learning rate has to be float.") super(SGD, self).__init__(model_params, lr=lr, momentum=momentum) - + def construct_from_pytorch(self, model_params): if self.model_params is None: # careful! generator cannot be assigned. - return torch.optim.SGD(model_params, **self.settings) + return torch.optim.SGD(self._get_require_grads_param(model_params), **self.settings) else: - return torch.optim.SGD(self.model_params, **self.settings) + return torch.optim.SGD(self._get_require_grads_param(self.model_params), **self.settings) class Adam(Optimizer): """ + 别名::class:`fastNLP.Adam` :class:`fastNLP.core.optimizer.Adam` - :param float lr: learning rate - :param float weight_decay: - :param model_params: a generator. E.g. ``model.parameters()`` for PyTorch models. + :param float lr: learning rate + :param float weight_decay: + :param model_params: a generator. E.g. ``model.parameters()`` for PyTorch models. """ - + def __init__(self, lr=0.001, weight_decay=0, betas=(0.9, 0.999), eps=1e-8, amsgrad=False, model_params=None): if not isinstance(lr, float): raise TypeError("learning rate has to be float.") super(Adam, self).__init__(model_params, lr=lr, betas=betas, eps=eps, amsgrad=amsgrad, weight_decay=weight_decay) - + def construct_from_pytorch(self, model_params): if self.model_params is None: # careful! generator cannot be assigned. - return torch.optim.Adam(model_params, **self.settings) + return torch.optim.Adam(self._get_require_grads_param(model_params), **self.settings) else: - return torch.optim.Adam(self.model_params, **self.settings) + return torch.optim.Adam(self._get_require_grads_param(self.model_params), **self.settings) diff --git a/fastNLP/core/predictor.py b/fastNLP/core/predictor.py index ae648e47..4f37e105 100644 --- a/fastNLP/core/predictor.py +++ b/fastNLP/core/predictor.py @@ -1,15 +1,20 @@ +""" + ..todo:: + 检查这个类是否需要 +""" from collections import defaultdict import torch -from fastNLP.core import Batch -from fastNLP.core import DataSet -from fastNLP.core import SequentialSampler -from fastNLP.core.utils import _build_args +from . import Batch +from . import DataSet +from . import SequentialSampler +from .utils import _build_args class Predictor(object): - """An interface for predicting outputs based on trained models. + """ + An interface for predicting outputs based on trained models. It does not care about evaluations of the model, which is different from Tester. This is a high-level model wrapper to be called by FastNLP. diff --git a/fastNLP/core/sampler.py b/fastNLP/core/sampler.py index 67ec2a8d..c5784f59 100644 --- a/fastNLP/core/sampler.py +++ b/fastNLP/core/sampler.py @@ -1,89 +1,93 @@ +""" +sampler 子类实现了 fastNLP 所需的各种采样器。 +""" +__all__ = [ + "Sampler", + "BucketSampler", + "SequentialSampler", + "RandomSampler" +] + from itertools import chain import numpy as np -import torch - -def convert_to_torch_tensor(data_list, use_cuda): - """Convert lists into (cuda) Tensors. - :param data_list: 2-level lists - :param use_cuda: bool, whether to use GPU or not - :return data_list: PyTorch Tensor of shape [batch_size, max_seq_len] +class Sampler(object): """ - data_list = torch.Tensor(data_list).long() - if torch.cuda.is_available() and use_cuda: - data_list = data_list.cuda() - return data_list + 别名::class:`fastNLP.Sampler` :class:`fastNLP.core.sampler.Sampler` + + `Sampler` 类的基类. 规定以何种顺序取出data中的元素 -class BaseSampler(object): - """The base class of all samplers. - - Sub-classes must implement the ``__call__`` method. - ``__call__`` takes a DataSet object and returns a list of int - the sampling indices. + 子类必须实现 ``__call__`` 方法. 输入 `DataSet` 对象, 返回其中元素的下标序列 """ - - def __call__(self, *args, **kwargs): + + def __call__(self, data_set): + """ + :param DataSet data_set: `DataSet` 对象, 需要Sample的数据 + :return result: list(int) 其中元素的下标序列, ``data_set`` 中元素会按 ``result`` 中顺序取出 + """ raise NotImplementedError -class SequentialSampler(BaseSampler): - """Sample data in the original order. +class SequentialSampler(Sampler): + """ + 别名::class:`fastNLP.SequentialSampler` :class:`fastNLP.core.sampler.SequentialSampler` + + 顺序取出元素的 `Sampler` """ + def __call__(self, data_set): - """ - - :param DataSet data_set: - :return result: a list of integers. - """ return list(range(len(data_set))) -class RandomSampler(BaseSampler): - """Sample data in random permutation order. +class RandomSampler(Sampler): + """ + 别名::class:`fastNLP.RandomSampler` :class:`fastNLP.core.sampler.RandomSampler` + + 随机化取元素的 `Sampler` """ + def __call__(self, data_set): - """ - - :param DataSet data_set: - :return result: a list of integers. - """ return list(np.random.permutation(len(data_set))) -class BucketSampler(BaseSampler): +class BucketSampler(Sampler): """ + 别名::class:`fastNLP.BucketSampler` :class:`fastNLP.core.sampler.BucketSampler` - :param int num_buckets: the number of buckets to use. - :param int batch_size: batch size per epoch. - :param str seq_lens_field_name: the field name indicating the field about sequence length. + 带Bucket的 `Random Sampler`. 可以随机地取出长度相似的元素 + :param int num_buckets: bucket的数量 + :param int batch_size: batch的大小 + :param str seq_len_field_name: 对应序列长度的 `field` 的名字 """ - def __init__(self, num_buckets=10, batch_size=32, seq_lens_field_name='seq_lens'): + + def __init__(self, num_buckets=10, batch_size=32, seq_len_field_name='seq_len'): self.num_buckets = num_buckets self.batch_size = batch_size - self.seq_lens_field_name = seq_lens_field_name - + self.seq_len_field_name = seq_len_field_name + def __call__(self, data_set): - - seq_lens = data_set.get_all_fields()[self.seq_lens_field_name].content + seq_lens = data_set.get_all_fields()[self.seq_len_field_name].content total_sample_num = len(seq_lens) - + bucket_indexes = [] + assert total_sample_num >= self.num_buckets, "The number of samples is smaller than the number of buckets." num_sample_per_bucket = total_sample_num // self.num_buckets for i in range(self.num_buckets): bucket_indexes.append([num_sample_per_bucket * i, num_sample_per_bucket * (i + 1)]) bucket_indexes[-1][1] = total_sample_num - + sorted_seq_lens = list(sorted([(idx, seq_len) for idx, seq_len in zip(range(total_sample_num), seq_lens)], key=lambda x: x[1])) - + batchs = [] - + left_init_indexes = [] for b_idx in range(self.num_buckets): start_idx = bucket_indexes[b_idx][0] @@ -98,7 +102,7 @@ class BucketSampler(BaseSampler): if (left_init_indexes) != 0: batchs.append(left_init_indexes) np.random.shuffle(batchs) - + return list(chain(*batchs)) @@ -136,10 +140,10 @@ def k_means_1d(x, k, max_iter=100): if len(sorted_x) < k: raise ValueError("too few buckets") gap = len(sorted_x) / k - + centroids = np.array([sorted_x[int(x * gap)] for x in range(k)]) assign = None - + for i in range(max_iter): # Cluster Assignment step assign = np.array([np.argmin([np.absolute(x_i - x) for x in centroids]) for x_i in x]) @@ -171,7 +175,7 @@ def k_means_bucketing(lengths, buckets): bucket_data = [[] for _ in buckets] num_buckets = len(buckets) _, assignments = k_means_1d(lengths, num_buckets) - + for idx, bucket_id in enumerate(assignments): if buckets[bucket_id] is None or lengths[idx] <= buckets[bucket_id]: bucket_data[bucket_id].append(idx) diff --git a/fastNLP/core/tester.py b/fastNLP/core/tester.py index 48e1f090..883e0d01 100644 --- a/fastNLP/core/tester.py +++ b/fastNLP/core/tester.py @@ -1,50 +1,109 @@ +""" +tester模块实现了 fastNLP 所需的Tester类,能在提供数据、模型以及metric的情况下进行性能测试。 + +Example:: + + import numpy as np + import torch + from torch import nn + from fastNLP import Tester + from fastNLP import DataSet + from fastNLP import AccuracyMetric + + class Model(nn.Module): + def __init__(self): + super().__init__() + self.fc = nn.Linear(1, 1) + def forward(self, a): + return {'pred': self.fc(a.unsqueeze(1)).squeeze(1)} + + model = Model() + + dataset = DataSet({'a': np.arange(10, dtype=float), 'b':np.arange(10, dtype=float)*2}) + + dataset.set_input('a') + dataset.set_target('b') + + tester = Tester(dataset, model, metrics=AccuracyMetric()) + eval_results = tester.test() + +这里Metric的映射规律是和 :class:`fastNLP.Trainer` 中一致的,具体使用请参考 :doc:`trainer 模块` 的1.3部分。 +Tester在验证进行之前会调用model.eval()提示当前进入了evaluation阶段,即会关闭nn.Dropout()等,在验证结束之后会调用model.train()恢复到训练状态。 + + +""" +import warnings + import torch -from torch import nn +import torch.nn as nn + +from .batch import Batch +from .dataset import DataSet +from .metrics import _prepare_metrics +from .sampler import SequentialSampler +from .utils import _CheckError +from .utils import _build_args +from .utils import _check_loss_evaluate +from .utils import _move_dict_value_to_device +from .utils import _get_func_signature +from .utils import _get_model_device +from .utils import _move_model_to_device -from fastNLP.core.batch import Batch -from fastNLP.core.dataset import DataSet -from fastNLP.core.metrics import _prepare_metrics -from fastNLP.core.sampler import SequentialSampler -from fastNLP.core.utils import CheckError -from fastNLP.core.utils import _build_args -from fastNLP.core.utils import _check_loss_evaluate -from fastNLP.core.utils import _move_dict_value_to_device -from fastNLP.core.utils import get_func_signature +__all__ = [ + "Tester" +] class Tester(object): - """An collection of model inference and evaluation of performance, used over validation/dev set and test set. + """ + 别名::class:`fastNLP.Tester` :class:`fastNLP.core.tester.Tester` - :param DataSet data: a validation/development set - :param torch.nn.modules.module model: a PyTorch model - :param MetricBase metrics: a metric object or a list of metrics (List[MetricBase]) - :param int batch_size: batch size for validation - :param bool use_cuda: whether to use CUDA in validation. - :param int verbose: the number of steps after which an information is printed. + Tester是在提供数据,模型以及metric的情况下进行性能测试的类。需要传入模型,数据以及metric进行验证。 - """ + :param data: 需要测试的数据集, :class:`~fastNLP.DataSet` 类型 + :param torch.nn.module model: 使用的模型 + :param metrics: :class:`~fastNLP.core.metrics.MetricBase` 或者一个列表的 :class:`~fastNLP.core.metrics.MetricBase` + :param int batch_size: evaluation时使用的batch_size有多大。 + :param str,int,torch.device,list(int) device: 将模型load到哪个设备。默认为None,即Trainer不对模型 + 的计算位置进行管理。支持以下的输入: - def __init__(self, data, model, metrics, batch_size=16, use_cuda=False, verbose=1): - super(Tester, self).__init__() + 1. str: ['cpu', 'cuda', 'cuda:0', 'cuda:1', ...] 依次为'cpu'中, 可见的第一个GPU中, 可见的第一个GPU中, + 可见的第二个GPU中; + + 2. torch.device:将模型装载到torch.device上。 + 3. int: 将使用device_id为该值的gpu进行训练 + + 4. list(int):如果多于1个device,将使用torch.nn.DataParallel包裹model, 并使用传入的device。 + + 5. None. 为None则不对模型进行任何处理,如果传入的model为torch.nn.DataParallel该值必须为None。 + + 如果模型是通过predict()进行预测的话,那么将不能使用多卡(DataParallel)进行验证,只会使用第一张卡上的模型。 + :param int verbose: 如果为0不输出任何信息; 如果为1,打印出验证结果。 + """ + + def __init__(self, data, model, metrics, batch_size=16, device=None, verbose=1): + super(Tester, self).__init__() + if not isinstance(data, DataSet): raise TypeError(f"The type of data must be `fastNLP.DataSet`, got `{type(data)}`.") if not isinstance(model, nn.Module): raise TypeError(f"The type of model must be `torch.nn.Module`, got `{type(model)}`.") - + self.metrics = _prepare_metrics(metrics) - + self.data = data - self.use_cuda = use_cuda + self._model = _move_model_to_device(model, device=device) self.batch_size = batch_size self.verbose = verbose - - if torch.cuda.is_available() and self.use_cuda: - self._model = model.cuda() - else: - self._model = model - self._model_device = model.parameters().__next__().device - + + # 如果是DataParallel将没有办法使用predict方法 + if isinstance(self._model, nn.DataParallel): + if hasattr(self._model.module, 'predict') and not hasattr(self._model, 'predict'): + warnings.warn("Cannot use DataParallel to test your model, because your model offer predict() function," + " while DataParallel has no predict() function.") + self._model = self._model.module + # check predict if hasattr(self._model, 'predict'): self._predict_func = self._model.predict @@ -54,14 +113,15 @@ class Tester(object): f"for evaluation, not `{type(self._predict_func)}`.") else: self._predict_func = self._model.forward - + def test(self): - """Start test or validation. - - :return eval_results: a dictionary whose keys are the class name of metrics to use, values are the evaluation results of these metrics. + """开始进行验证,并返回验证结果。 + :return Dict[Dict] : dict的二层嵌套结构,dict的第一层是metric的名称; 第二层是这个metric的指标。 + 一个AccuracyMetric的例子为{'AccuracyMetric': {'acc': 1.0}}。 """ # turn on the testing mode; clean up the history + self._model_device = _get_model_device(self._model) network = self._model self._mode(network, is_test=True) data_iterator = Batch(self.data, self.batch_size, sampler=SequentialSampler(), as_numpy=False) @@ -72,28 +132,28 @@ class Tester(object): _move_dict_value_to_device(batch_x, batch_y, device=self._model_device) pred_dict = self._data_forward(self._predict_func, batch_x) if not isinstance(pred_dict, dict): - raise TypeError(f"The return value of {get_func_signature(self._predict_func)} " + raise TypeError(f"The return value of {_get_func_signature(self._predict_func)} " f"must be `dict`, got {type(pred_dict)}.") for metric in self.metrics: metric(pred_dict, batch_y) for metric in self.metrics: eval_result = metric.get_metric() if not isinstance(eval_result, dict): - raise TypeError(f"The return value of {get_func_signature(metric.get_metric)} must be " + raise TypeError(f"The return value of {_get_func_signature(metric.get_metric)} must be " f"`dict`, got {type(eval_result)}") metric_name = metric.__class__.__name__ eval_results[metric_name] = eval_result - except CheckError as e: - prev_func_signature = get_func_signature(self._predict_func) + except _CheckError as e: + prev_func_signature = _get_func_signature(self._predict_func) _check_loss_evaluate(prev_func_signature=prev_func_signature, func_signature=e.func_signature, check_res=e.check_res, pred_dict=pred_dict, target_dict=batch_y, dataset=self.data, check_level=0) - + if self.verbose >= 1: print("[tester] \n{}".format(self._format_eval_results(eval_results))) self._mode(network, is_test=False) return eval_results - + def _mode(self, model, is_test=False): """Train mode or Test mode. This is for PyTorch currently. @@ -105,13 +165,13 @@ class Tester(object): model.eval() else: model.train() - + def _data_forward(self, func, x): """A forward pass of the model. """ x = _build_args(func, **x) y = func(**x) return y - + def _format_eval_results(self, results): """Override this method to support more print formats. diff --git a/fastNLP/core/trainer.py b/fastNLP/core/trainer.py index ddd35b28..702cb6e7 100644 --- a/fastNLP/core/trainer.py +++ b/fastNLP/core/trainer.py @@ -1,87 +1,428 @@ +r""" +Trainer在fastNLP中用于组织单任务的训练过程,可以避免用户在不同训练任务中重复撰以下步骤的代码 + + (1) epoch循环; + + (2) 将数据分成不同的Batch; + + (3) 对Batch进行pad; + + (4) 每个epoch结束或一定step后进行验证集验证; + + (5) 保存获得更好验证性能的模型。 + +1 Trainer的基本使用 + 下面的例子是使用神经网络来进行预测一个序列中是否有偶数个1。 + + Example:: + + import numpy as np + from torch import nn + import torch + import torch.nn.functional as F + from torch.optim import SGD + + from fastNLP import DataSet + from fastNLP import Trainer + from fastNLP import CrossEntropyLoss + from fastNLP import AccuracyMetric + from fastNLP.modules.decoder import MLP + + # 模型 + class Model(nn.Module): + def __init__(self, input_num): + super().__init__() + self.fcs = MLP([input_num, 40, 40, 2], 'relu') + + def forward(self, x): + x = self.fcs(x) + return {'pred': x} + model = Model(10) + + # 生成数据 + def generate_psedo_dataset(num_samples): + dataset = DataSet() + data = np.random.randint(2, size=(num_samples, 10)) + label = np.sum(data, axis=1)%2 + dataset = DataSet({'x':data.astype(float), 'label': label}) + dataset.set_input('x') + dataset.set_target('label') + return dataset + tr_dataset = generate_psedo_dataset(1000) + dev_data = generate_psedo_dataset(100) + + # 训练 + trainer = Trainer(tr_dataset, model, loss=CrossEntropyLoss(target='label'), + optimizer=SGD(model.parameters(), lr=0.1),n_epochs=1000, + dev_data = dev_data, metrics=AccuracyMetric(target='label')) + trainer.train() + + 由上面的例子可以看出通过使用Trainer,可以使得训练部分的代码大幅减少。 + 使用Trainer需要满足以下几个条件: + +1.1 模型 + 1 模型的forward()的参数名需要与DataSet中的名字对应。实际上fastNLP在将DataSet中的数据传递给模型forward()时,是 + 通过匹配名称实现的。所以上例中,如果Model的forward函数修改为forward(self, data), 则DataSet中的'x'这个field就应该 + 改名为'data'。 + + 2 传递给forward()的参数是DataSet中被设置为input的那些field。但如果forward()中没有对应的参数,则不会将数据传递 + 给forward()。例如,DataSet中'x1', 'x2'都是input,但是模型的函数为forward(self, x1), 那么'x2'不会传递给forward()。 + + 3 模型的forward()返回值需要为一个dict。 + +1.2 Loss + fastNLP中的为了不限制forward函数的返回内容数量(比如一些复杂任务需要返回多个内容,如Dependency Parsing, + :mod:`Loss` 与 :mod:`Metric` 都使用了通过名称来匹配相应内容的策略。如上面的例子中 + + Example:: + + trainer = Trainer(tr_dataset, model, loss=CrossEntropyLoss(target='label'), + optimizer=SGD(model.parameters(), lr=0.1),n_epochs=1000, + dev_data = dev_data, metrics=AccuracyMetric(target='label')) + + loss被设置为了 :class:`~fastNLP.CrossEntropyLoss` , 但在初始化的时候传入了target='label'这个参数, + :class:`~fastNLP.CrossEntropyLoss` 的初始化参数为(pred=None, target=None, padding_idx=-100)。 + + 这里的两个参数分别为计算CrossEntropy时需要使用到的模型的预测值与真实值。 + 其中 `pred` 一般来自于模型forward()的返回结果,`target` 一般是来自于DataSet中被设置为target的field。 + 由于每个人对真实值或者model的返回值取名并不一样,所以fastNLP的 :mod:`Loss` 提供一种类似于映射的机制来匹配对应的值, + 比如这里 :class:`~fastNLP.CrossEntropyLoss` 将尝试找到名为'label'的内容来作为真实值得到loss; + 而pred=None, 则 :class:`~fastNLP.CrossEntropyLoss` 使用'pred'作为名称匹配预测值, + 正好forward的返回值也叫pred,所以这里不需要申明pred。 + + 尽管fastNLP使用了映射机制来使得loss的计算变得比较灵活,但有些情况下loss必须在模型中进行计算,比如使用了CRF的模型。 + fastNLP中提供了 :class:`~fastNLP.LossInForward` 这个loss。 + 这个loss的原理是直接在forward()的返回结果中找到loss_key(默认寻找'loss')指定的那个tensor,并使用它作为loss。 + 如果Trainer初始化没有提供loss则默认使用 :class:`~fastNLP.LossInForward` 。 + + .. todo:: + 补充一个例子 详细例子可以参照 + +1.3 Metric + :mod:`Metric` 使用了与上述Loss一样的策略,即使用名称进行匹配。 + AccuracyMetric(target='label')的情况与CrossEntropyLoss 是同理的。 + + 在进行验证时,可能用到的计算与forward()中不太一致,没有办法直接从forward()的结果中得到预测值,这时模型可以提供一个predict()方法, + 如果提供的模型具有predict方法,则在模型验证时将调用predict()方法获取预测结果, + 传入到predict()的参数也是从DataSet中被设置为input的field中选择出来的; + 与forward()一样,返回值需要为一个dict。 + + .. todo:: + 补充一个例子 具体例子可以参考 + +2 Trainer的代码检查 + 由于在fastNLP中采取了映射的机制,所以难免可能存在对应出错的情况。Trainer提供一种映射检查机制,可以通过check_code_level来进行控制 + 比如下面的例子中,由于各种原因产生的报错 + +Example2.1 + :: + + import numpy as np + from torch import nn + import torch + from torch.optim import SGD + from fastNLP import Trainer + from fastNLP import DataSet + + class Model(nn.Module): + def __init__(self): + super().__init__() + self.fc = nn.Linear(1, 1) + def forward(self, x, b): + loss = torch.mean((self.fc(x)-b)**2) + return {'loss': loss} + model = Model() + + dataset = DataSet({'a': np.arange(10), 'b':np.arange(10)*2}) + dataset.set_input('a', 'b') + + trainer = Trainer(dataset, model, loss=None, optimizer=SGD(model.parameters(), lr=0.001)) + + trainer = Trainer(dataset, model, SGD(model.parameters())) + # 会报以下的错误 + # input fields after batch(if batch size is 2): + # a: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + # b: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + # There is no target field. + # .... + # NameError: + # Problems occurred when calling Model.forward(self, x, b) + # missing param: ['x'] + # unused field: ['a'] + # Suggestion: You need to provide ['x'] in DataSet and set it as input. + + 这里就是由于在Trainer初始化的时候,fastNLP会尝试使用一个batch_size=2的batch去运行一遍forward()以及backward()。这里有两类 + 信息可以为你提供参考 + + 1 'input fields after batch...'这部分显示的是train dataset经过Batch操作后,每个field对应的类型以及进行shape。这里 + 因为train dataset没有target所以没有显示。根据这里可以看出是否正确将需要的内容设置为了input或target。 + + 2 NameError,NameError发生在映射出错的情况。这里报错的原因是由于尝试进行forward计算时(可以通过Model.forward(self, x, b)判断 + 出当前是在调取forward),却没有获取到forward()函数中需要的'x';在报错信息中同时指出了缺'x',而'a'没有被使用,那么可能 + 就是由于field的名称不对。这里将dataset中'a'这个field的名称改为'x',或者model的参数从'x'修改为'a'都可以解决问题。 + + 下面的例子是由于loss计算的时候找不到需要的值 + +Example2.2 + :: + + import numpy as np + from torch import nn + from torch.optim import SGD + from fastNLP import Trainer + from fastNLP import DataSet + from fastNLP import L1Loss + import torch + + class Model(nn.Module): + def __init__(self): + super().__init__() + self.fc = nn.Linear(1, 1) + def forward(self, a): + return {'pred_b': self.fc(a.unsqueeze(1)).squeeze(1), 'No use':1} + + model = Model() + + dataset = DataSet({'a': np.arange(10, dtype=float), 'b':np.arange(10, dtype=float)*2}) + + dataset.set_input('a') + dataset.set_target('b') + + trainer = Trainer(dataset, model, loss=L1Loss(target='label'), optimizer=SGD(model.parameters(), lr=0.001)) + # 报错信息如下 + # input fields after batch(if batch size is 2): + # a: (1)type:torch.Tensor (2)dtype:torch.float32, (3)shape:torch.Size([2]) + # target fields after batch(if batch size is 2): + # b: (1)type:torch.Tensor (2)dtype:torch.float32, (3)shape:torch.Size([2]) + # .... + # NameError: + # Problems occurred when calling L1Loss.get_loss(self, pred, target) + # missing param: ['pred(assign to `pred` in `L1Loss`)', 'label(assign to `target` in `L1Loss`)'] + # unused field: ['b'] + # unused param: ['pred_b', 'No use'] + # target field: ['b'] + # param from Model.forward(self, a): ['pred_b', 'No use'] + # Suggestion: (1). Check key assignment for `target` when initialize L1Loss. Or provide `label` in DataSet or output of Model.forward(self, a). + # (2). Check key assignment for `pred` when initialize L1Loss. Or provide `pred` in DataSet or output of Model.forward(self, a). + + 报错信息也包含两部分: + + 1 第一部分与上面是一样的 + + 2 这里报错的原因是由于计算loss的时候找不到相应的值(通过L1Loss.get_loss(self, pred, target)判断出来的); + 报错的原因是因为 `pred` 和 `label` (我们在初始化L1Loss时将target指定为了label)都没有找到。 + 这里'unused field'是DataSet中出现了,但却没有被设置为input或者target的field; + 'unused param'是forward()中返回且没有被使用到的内容;'target field'是被设置为了target的field; + 'param from Model.forward(self, a)'是forward()返回的所有key。"Suggestion"是关于当前错误处理的建议。 + + 但是在一些情况下,比如forward()返回值只有一个,target也只有一个,fastNLP不会进行匹配,而直接将forward()的结果作为pred, + 将DataSet中的target设置为target。上面的例子在返回值中加入了一个'No use'则只是为了使得Loss去匹配结果。 + + + 下面是带有dev dataset时如果出现错误会发生的报错, + +Example2.3 + :: + + import numpy as np + from torch import nn + from torch.optim import SGD + from fastNLP import Trainer + from fastNLP import DataSet + from fastNLP import AccuracyMetric + import torch + + class Model(nn.Module): + def __init__(self): + super().__init__() + self.fc = nn.Linear(1, 1) + def forward(self, a, b): + loss = torch.mean((self.fc(a.float().unsqueeze(1))-b.float())**2) + return {'loss': loss} + def predict(self, a): # 使用predict()进行验证 + return {'output':self.fc(a.float().unsqueeze(1))} #这里return的值不包含'pred'这个key + model = Model() + + dataset = DataSet({'a': np.arange(10), 'b':np.arange(10)*2}) + dev_data = DataSet({'a': np.arange(10, 20), 'b':np.arange(10, 20)*2}) + + dataset.set_input('a', 'b') + dev_data.set_input('a') # 这里没有设置target + + trainer = Trainer(dataset, model, loss=None, optimizer=SGD(model.parameters(), lr=0.001), + dev_data=dev_data, metrics=AccuracyMetric()) + + # 报错信息 + # ... + # NameError: + # Problems occurred when calling AccuracyMetric.evaluate(self, pred, target, seq_len=None) + # missing param: ['pred(assign to `pred` in `AccuracyMetric`)', 'target(assign to `target` in `AccuracyMetric`)'] + # unused param: ['output'] + # target field: [] + # param from Model.predict(self, a): ['output'] + # Suggestion: (1). Check key assignment for `pred` when initialize AccuracyMetric. Or provide `pred` in DataSet or output of Model.predict(self, a). + # (2). Check key assignment for `target` when initialize AccuracyMetric. Or provide `target` in DataSet or output of Model.predict(self, a). + + 报错信息和前面都是类似的,但是可以通过'AccuracyMetric.evaluate(self, pred, target, seq_len=None)'看出这里是evaluation + 的时候发生了错误。这样避免了需要在完成一整个epoch的训练才能发现evaluation弄错的情况。这里的修改是通过在初始化metric的时候 + 指明通过'output'获取`pred`, 即AccuracyMetric(pred='output')。 + + 可以通过check_code_level调节检查的强度。默认为0,即进行检查。 + +3 Trainer与callback + 虽然Trainer本身已经集成了一些功能,但仍然不足以囊括训练过程中可能需要到的功能,比如负采样,learning rate decay, Early Stop等。 + 为了解决这个问题fastNLP引入了callback的机制,:class:`~fastNLP.Callback` 是一种在Trainer训练过程中特定阶段会运行的函数集合, + 所有的 :class:`~fastNLP.Callback` 都具有on_*(比如on_train_start, on_backward_begin)等函数。 + 如果 Callback 实现了该函数,则Trainer运行至对应阶段,会进行调用,例如:: + + from fastNLP import Callback, EarlyStopCallback, Trainer, CrossEntropyLoss, AccuracyMetric + from fastNLP.models import CNNText + + start_time = time.time() + + class MyCallback(Callback): + def on_epoch_end(self): + print('{:d}ms\n\n'.format(round((time.time()-start_time)*1000))) + + model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) + trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, loss=CrossEntropyLoss(), + metrics=AccuracyMetric(), callbacks=[MyCallback(),EarlyStopCallback(10)]) + trainer.train() + + 这里,我们通过继承 :class:`~fastNLP.Callback` 类定义了自己的 callback 的,并和内置的 :class:`~fastNLP.EarlyStopCallback` + 一起传给了 :class:`~fastNLP.Trainer` ,增强了 :class:`~fastNLP.Trainer` 的功能 + + fastNLP已经自带了很多callback函数供使用,可以参考 :doc:`fastNLP.core.callback` 。 + +""" +__all__ = [ + "Trainer" +] + import os import time -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta import numpy as np import torch -from torch import nn +import torch.nn as nn try: - from tqdm.autonotebook import tqdm + from tqdm.auto import tqdm except: - from fastNLP.core.utils import pseudo_tqdm as tqdm - -from fastNLP.core.batch import Batch -from fastNLP.core.callback import CallbackManager, CallbackException -from fastNLP.core.dataset import DataSet -from fastNLP.core.losses import _prepare_losser -from fastNLP.core.metrics import _prepare_metrics -from fastNLP.core.optimizer import Adam -from fastNLP.core.sampler import BaseSampler -from fastNLP.core.sampler import RandomSampler -from fastNLP.core.sampler import SequentialSampler -from fastNLP.core.tester import Tester -from fastNLP.core.utils import CheckError -from fastNLP.core.utils import _build_args -from fastNLP.core.utils import _check_forward_error -from fastNLP.core.utils import _check_loss_evaluate -from fastNLP.core.utils import _move_dict_value_to_device -from fastNLP.core.utils import get_func_signature + from .utils import _pseudo_tqdm as tqdm + +from .batch import Batch +from .callback import CallbackManager, CallbackException +from .dataset import DataSet +from .losses import _prepare_losser +from .metrics import _prepare_metrics +from .optimizer import Optimizer +from .sampler import Sampler +from .sampler import RandomSampler +from .sampler import SequentialSampler +from .tester import Tester +from .utils import _CheckError +from .utils import _build_args +from .utils import _check_forward_error +from .utils import _check_loss_evaluate +from .utils import _move_dict_value_to_device +from .utils import _get_func_signature +from .utils import _get_model_device +from .utils import _move_model_to_device class Trainer(object): - def __init__(self, train_data, model, loss=None, metrics=None, n_epochs=3, batch_size=32, print_every=50, - validate_every=-1, dev_data=None, save_path=None, optimizer=Adam(lr=0.01, weight_decay=0), - check_code_level=0, metric_key=None, sampler=RandomSampler(), prefetch=False, use_tqdm=True, - use_cuda=False, callbacks=None): - """ - :param DataSet train_data: the training data - :param torch.nn.modules.module model: a PyTorch model - :param LossBase loss: a loss object - :param MetricBase metrics: a metric object or a list of metrics (List[MetricBase]) - :param int n_epochs: the number of training epochs - :param int batch_size: batch size for training and validation - :param int print_every: step interval to print next training information. Default: -1(no print). - :param int validate_every: step interval to do next validation. Default: -1(validate every epoch). - :param DataSet dev_data: the validation data - :param str save_path: file path to save models - :param Optimizer optimizer: an optimizer object - :param int check_code_level: level of FastNLP code checker. -1: don't check, 0: ignore. 1: warning. 2: strict.\\ - `ignore` will not check unused field; `warning` when warn if some field are not used; `strict` means - it will raise error if some field are not used. 检查的原理是通过使用很小的batch(默认两个sample)来检查代码是 - 否能够运行,但是这个过程理论上不会修改任何参数,只是会检查能否运行。但如果(1)模型中存在将batch_size写为某个 - 固定值的情况;(2)模型中存在累加前向计算次数的,可能会多计算几次。以上情况建议将check_code_level设置为-1 - :param str metric_key: a single indicator used to decide the best model based on metric results. It must be one - of the keys returned by the FIRST metric in `metrics`. If the overall result gets better if the indicator gets - smaller, add "-" in front of the string. For example:: - - metric_key="-PPL" # language model gets better as perplexity gets smaller - :param BaseSampler sampler: method used to generate batch data. - :param prefetch: bool, 是否使用额外的进程对产生batch数据。 - :param bool use_tqdm: whether to use tqdm to show train progress. - :param callbacks: List[Callback]. 用于在train过程中起调节作用的回调函数。比如early stop,negative sampling等可以 - 通过callback机制实现。 - """ + """ + 别名::class:`fastNLP.Trainer` :class:`fastNLP.core.trainer.Trainer` + + Trainer在fastNLP中用于组织单任务的训练过程,可以避免用户在不同训练任务中重复撰写 + (1) epoch循环; + (2) 将数据分成不同的Batch; + (3) 对Batch进行pad; + (4) 每个epoch结束或一定step后进行验证集验证; + (5) 保存获得更好验证性能的模型等。 + + 详细的介绍参见 :doc:`fastNLP.core.trainer` + + :param train_data: 训练集, :class:`~fastNLP.DataSet` 类型。 + :param nn.modules model: 待训练的模型 + :param optimizer: `torch.optim.Optimizer` 优化器。如果为None,则Trainer使用默认的Adam(model.parameters(), lr=4e-3)这个优化器 + :param int batch_size: 训练和验证的时候的batch大小。 + :param loss: 使用的 :class:`~fastNLP.core.losses.LossBase` 对象。当为None时,默认使用 :class:`~fastNLP.LossInForward` + :param sampler: Batch数据生成的顺序, :class:`~fastNLP.Sampler` 类型。如果为None,默认使用 :class:`~fastNLP.RandomSampler` + :param update_every: int, 多少步更新一次梯度。用于希望累计梯度的场景,比如需要128的batch_size, 但是直接设为128 + 会导致内存不足,通过设置batch_size=32, update_every=4达到目的。当optimizer为None时,该参数无效。 + :param int n_epochs: 需要优化迭代多少次。 + :param int print_every: 多少次反向传播更新tqdm显示的loss; 如果use_tqdm=False, 则多少次反向传播打印loss。 + :param dev_data: 用于做验证的DataSet, :class:`~fastNLP.DataSet` 类型。 + :param metrics: 验证的评估函数。可以只使用一个 :class:`Metric` , + 也可以使用多个 :class:`Metric` ,通过列表传入。 + 如验证时取得了更好的验证结果(如果有多个Metric,以列表中第一个Metric为准),且save_path不为None, + 则保存当前模型。Metric种类详见 :doc:`metrics模块 ` 。仅在传入dev_data时有效。 + :param str,None metric_key: :class:`Metric` 有时会有多个指标, + 比如 :class:`~fastNLP.core.metrics.SpanFPreRecMetric` 中包含了'f', 'pre', 'rec'。此时需 + 要指定以哪个指标为准。另外有些指标是越小效果越好,比如语言模型的困惑度,这种情况下,在key前面增加一个'-'来表 + 明验证时,值越小越好(比如: "-ppl")。仅在传入dev_data时有效。 + :param int validate_every: 多少个step在验证集上验证一次; 如果为-1,则每个epoch结束验证一次。仅在传入dev_data时有效。 + :param str,None save_path: 将模型保存路径。如果为None,则不保存模型。如果dev_data为None,则保存最后一次迭代的模型。 + 保存的时候不仅保存了参数,还保存了模型结构。即便使用DataParallel,这里也只保存模型。 + :param prefetch: bool, 是否使用额外的进程对产生batch数据。理论上会使得Batch迭代更快。 + :param bool use_tqdm: 是否使用tqdm来显示训练进度; 如果为False,则将loss打印在终端中。 + :param str,int,torch.device,list(int) device: 将模型load到哪个设备。默认为None,即Trainer不对模型 + 的计算位置进行管理。支持以下的输入: + + 1. str: ['cpu', 'cuda', 'cuda:0', 'cuda:1', ...] 依次为'cpu'中, 可见的第一个GPU中, 可见的第一个GPU中, + 可见的第二个GPU中; + + 2. torch.device:将模型装载到torch.device上。 + + 3. int: 将使用device_id为该值的gpu进行训练 + + 4. list(int):如果多于1个device,将使用torch.nn.DataParallel包裹model, 并使用传入的device。 + + 5. None. 为None则不对模型进行任何处理,如果传入的model为torch.nn.DataParallel该值必须为None。 + + 已知可能会出现的问题:Adagrad优化器可能无法正常使用这个参数,请手动管理模型位置。 + + :param list(callbacks) callbacks: 用于在train过程中起调节作用的回调函数。比如early stop,negative sampling等可以 + 通过callback机制实现。 可使用的callback参见 :doc:`callback模块 ` + :param int check_code_level: 模型检查等级. -1: 不进行检查; 0: 仅出现错误时停止; 1: 如果有field没有被使用, + 报告警告信息; 2: 有任何field没有被使用都报错. 检查的原理是通过使用很小的batch(默认2个sample)来运行代码,但是 + 这个过程理论上不会修改任何参数,只是会检查能否运行。但如果(1)模型中存在将batch_size写为某个固定值的情况; + (2)模型中存在累加前向计算次数的,可能会多计算1次。以上情况建议将check_code_level设置为-1。 + """ + + def __init__(self, train_data, model, optimizer=None, loss=None, + batch_size=32, sampler=None, update_every=1, + n_epochs=10, print_every=5, + dev_data=None, metrics=None, metric_key=None, + validate_every=-1, save_path=None, + prefetch=False, use_tqdm=True, device=None, + callbacks=None, + check_code_level=0): super(Trainer, self).__init__() - if not isinstance(train_data, DataSet): raise TypeError(f"The type of train_data must be fastNLP.DataSet, got {type(train_data)}.") if not isinstance(model, nn.Module): raise TypeError(f"The type of model must be torch.nn.Module, got {type(model)}.") - + # check metrics and dev_data if (not metrics) and dev_data is not None: raise ValueError("No metric for dev_data evaluation.") if metrics and (dev_data is None): raise ValueError("No dev_data for evaluations, pass dev_data or set metrics to None. ") - + + # check update every + assert update_every >= 1, "update_every must be no less than 1." + self.update_every = int(update_every) + # check save_path if not (save_path is None or isinstance(save_path, str)): raise ValueError("save_path can only be None or `str`.") # prepare evaluate metrics = _prepare_metrics(metrics) - + # parse metric_key # increase_better is True. It means the exp result gets better if the indicator increases. # It is true by default. @@ -91,19 +432,20 @@ class Trainer(object): self.metric_key = metric_key[1:] if metric_key[0] == "+" or metric_key[0] == "-" else metric_key elif len(metrics) > 0: self.metric_key = metrics[0].__class__.__name__.lower().strip('metric') - + # prepare loss losser = _prepare_losser(loss) - + # sampler check - if not isinstance(sampler, BaseSampler): + if sampler is not None and not isinstance(sampler, Sampler): raise ValueError("The type of sampler should be fastNLP.BaseSampler, got {}.".format(type(sampler))) - + if check_code_level > -1: _check_code(dataset=train_data, model=model, losser=losser, metrics=metrics, dev_data=dev_data, metric_key=metric_key, check_level=check_code_level, batch_size=min(batch_size, DEFAULT_CHECK_BATCH_SIZE)) - + # _check_code 是 fastNLP 帮助你检查代码是否正确的方法 。如果你在错误栈中看到这行注释,请认真检查你的代码 + self.train_data = train_data self.dev_data = dev_data # If None, No validation. self.model = model @@ -111,73 +453,61 @@ class Trainer(object): self.metrics = metrics self.n_epochs = int(n_epochs) self.batch_size = int(batch_size) - self.use_cuda = bool(use_cuda) self.save_path = save_path self.print_every = int(print_every) - self.validate_every = int(validate_every) if validate_every!=0 else -1 + self.validate_every = int(validate_every) if validate_every != 0 else -1 self.best_metric_indicator = None self.best_dev_epoch = None self.best_dev_step = None self.best_dev_perf = None - self.sampler = sampler + self.sampler = sampler if sampler is not None else RandomSampler() self.prefetch = prefetch - self.callback_manager = CallbackManager(env={"trainer": self}, callbacks=callbacks) - + self.n_steps = (len(self.train_data) // self.batch_size + int( + len(self.train_data) % self.batch_size != 0)) * self.n_epochs + + self.model = _move_model_to_device(self.model, device=device) + if isinstance(optimizer, torch.optim.Optimizer): self.optimizer = optimizer + elif isinstance(optimizer, Optimizer): + self.optimizer = optimizer.construct_from_pytorch(model.parameters()) + elif optimizer is None: + self.optimizer = torch.optim.Adam(model.parameters(), lr=4e-3) else: - self.optimizer = optimizer.construct_from_pytorch(self.model.parameters()) - + raise TypeError("optimizer can only be torch.optim.Optimizer type, not {}.".format(type(optimizer))) + self.use_tqdm = use_tqdm + self.pbar = None self.print_every = abs(self.print_every) - + if self.dev_data is not None: self.tester = Tester(model=self.model, data=self.dev_data, metrics=self.metrics, batch_size=self.batch_size, - use_cuda=self.use_cuda, + device=None, # 由上面的部分处理device verbose=0) - + self.step = 0 self.start_time = None # start timestamp - + + self.callback_manager = CallbackManager(env={"trainer": self}, + callbacks=callbacks) + def train(self, load_best_model=True): """ - - 开始训练过程。主要有以下几个步骤:: - - for epoch in range(num_epochs): - # 使用Batch从DataSet中按批取出数据,并自动对DataSet中dtype为(float, int)的fields进行padding。并转换为Tensor。 - 非float,int类型的参数将不会被转换为Tensor,且不进行padding。 - for batch_x, batch_y in Batch(DataSet) - # batch_x是一个dict, 被设为input的field会出现在这个dict中, - key为DataSet中的field_name, value为该field的value - # batch_y也是一个dict,被设为target的field会出现在这个dict中, - key为DataSet中的field_name, value为该field的value - 2. 将batch_x的数据送入到model.forward函数中,并获取结果。这里我们就是通过匹配batch_x中的key与forward函数的形 - 参完成参数传递。例如, - forward(self, x, seq_lens) # fastNLP会在batch_x中找到key为"x"的value传递给x,key为"seq_lens"的 - value传递给seq_lens。若在batch_x中没有找到所有必须要传递的参数,就会报错。如果forward存在默认参数 - 而且默认参数这个key没有在batch_x中,则使用默认参数。 - 3. 将batch_y与model.forward的结果一并送入loss中计算loss。loss计算时一般都涉及到pred与target。但是在不同情况 - 中,可能pred称为output或prediction, target称为y或label。fastNLP通过初始化loss时传入的映射找到pred或 - target。比如在初始化Trainer时初始化loss为CrossEntropyLoss(pred='output', target='y'), 那么fastNLP计 - 算loss时,就会使用"output"在batch_y与forward的结果中找到pred;使用"y"在batch_y与forward的结果中找target - , 并完成loss的计算。 - 4. 获取到loss之后,进行反向求导并更新梯度 - 根据需要适时进行验证机测试 - 根据metrics进行evaluation,并根据是否提供了save_path判断是否存储模型 + 使用该函数使Trainer开始训练。 :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效,如果True, trainer将在返回之前重新加载dev表现 - 最好的模型参数。 - :return results: 返回一个字典类型的数据, 内含以下内容:: + 最好的模型参数。 + :return dict: 返回一个字典类型的数据, + 内含以下内容:: - seconds: float, 表示训练时长 - 以下三个内容只有在提供了dev_data的情况下会有。 - best_eval: Dict of Dict, 表示evaluation的结果 - best_epoch: int,在第几个epoch取得的最佳值 - best_step: int, 在第几个step(batch)更新取得的最佳值 + seconds: float, 表示训练时长 + 以下三个内容只有在提供了dev_data的情况下会有。 + best_eval: Dict of Dict, 表示evaluation的结果。第一层的key为Metric的名称,第二层的key为具体的Metric + best_epoch: int,在第几个epoch取得的最佳值 + best_step: int, 在第几个step(batch)更新取得的最佳值 """ results = {} @@ -186,25 +516,24 @@ class Trainer(object): results['seconds'] = 0. return results try: - if torch.cuda.is_available() and self.use_cuda: - self.model = self.model.cuda() - self._model_device = self.model.parameters().__next__().device + self._model_device = _get_model_device(self.model) self._mode(self.model, is_test=False) - + self._load_best_model = load_best_model self.start_time = str(datetime.now().strftime('%Y-%m-%d-%H-%M-%S')) start_time = time.time() print("training epochs started " + self.start_time, flush=True) - + try: self.callback_manager.on_train_begin() self._train() - self.callback_manager.on_train_end(self.model) + self.callback_manager.on_train_end() except (CallbackException, KeyboardInterrupt) as e: - self.callback_manager.on_exception(e, self.model) - - if self.dev_data is not None: - print("\nIn Epoch:{}/Step:{}, got best dev performance:".format(self.best_dev_epoch, self.best_dev_step) + - self.tester._format_eval_results(self.best_dev_perf),) + self.callback_manager.on_exception(e) + + if self.dev_data is not None and hasattr(self, 'best_dev_perf'): + print( + "\nIn Epoch:{}/Step:{}, got best dev performance:".format(self.best_dev_epoch, self.best_dev_step) + + self.tester._format_eval_results(self.best_dev_perf), ) results['best_eval'] = self.best_dev_perf results['best_epoch'] = self.best_dev_epoch results['best_step'] = self.best_dev_step @@ -218,49 +547,55 @@ class Trainer(object): finally: pass results['seconds'] = round(time.time() - start_time, 2) - + return results - + def _train(self): if not self.use_tqdm: - from fastNLP.core.utils import pseudo_tqdm as inner_tqdm + from fastNLP.core.utils import _pseudo_tqdm as inner_tqdm else: inner_tqdm = tqdm self.step = 0 + self.epoch = 0 start = time.time() - total_steps = (len(self.train_data) // self.batch_size + int( - len(self.train_data) % self.batch_size != 0)) * self.n_epochs - with inner_tqdm(total=total_steps, postfix='loss:{0:<6.5f}', leave=False, dynamic_ncols=True) as pbar: + + with inner_tqdm(total=self.n_steps, postfix='loss:{0:<6.5f}', leave=False, dynamic_ncols=True) as pbar: + self.pbar = pbar avg_loss = 0 data_iterator = Batch(self.train_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, prefetch=self.prefetch) - for epoch in range(1, self.n_epochs+1): + self.batch_per_epoch = data_iterator.num_batches + for epoch in range(1, self.n_epochs + 1): + self.epoch = epoch pbar.set_description_str(desc="Epoch {}/{}".format(epoch, self.n_epochs)) # early stopping - self.callback_manager.on_epoch_begin(epoch, self.n_epochs) + self.callback_manager.on_epoch_begin() for batch_x, batch_y in data_iterator: + self.step += 1 _move_dict_value_to_device(batch_x, batch_y, device=self._model_device) indices = data_iterator.get_batch_indices() # negative sampling; replace unknown; re-weight batch_y self.callback_manager.on_batch_begin(batch_x, batch_y, indices) prediction = self._data_forward(self.model, batch_x) - + # edit prediction self.callback_manager.on_loss_begin(batch_y, prediction) - loss = self._compute_loss(prediction, batch_y) + loss = self._compute_loss(prediction, batch_y).mean() avg_loss += loss.item() - + loss = loss / self.update_every + # Is loss NaN or inf? requires_grad = False - self.callback_manager.on_backward_begin(loss, self.model) + self.callback_manager.on_backward_begin(loss) self._grad_backward(loss) - self.callback_manager.on_backward_end(self.model) - + self.callback_manager.on_backward_end() + self._update() - self.callback_manager.on_step_end(self.optimizer) - - if (self.step+1) % self.print_every == 0: + self.callback_manager.on_step_end() + + if self.step % self.print_every == 0: + avg_loss = float(avg_loss) / self.print_every if self.use_tqdm: - print_output = "loss:{0:<6.5f}".format(avg_loss / self.print_every) + print_output = "loss:{0:<6.5f}".format(avg_loss) pbar.update(self.print_every) else: end = time.time() @@ -269,43 +604,45 @@ class Trainer(object): epoch, self.step, avg_loss, diff) pbar.set_postfix_str(print_output) avg_loss = 0 - self.step += 1 self.callback_manager.on_batch_end() - + if ((self.validate_every > 0 and self.step % self.validate_every == 0) or (self.validate_every < 0 and self.step % len(data_iterator) == 0)) \ and self.dev_data is not None: eval_res = self._do_validation(epoch=epoch, step=self.step) eval_str = "Evaluation at Epoch {}/{}. Step:{}/{}. ".format(epoch, self.n_epochs, self.step, - total_steps) + \ + self.n_steps) + \ self.tester._format_eval_results(eval_res) - pbar.write(eval_str) - + pbar.write(eval_str + '\n') + # ================= mini-batch end ==================== # - + # lr decay; early stopping - self.callback_manager.on_epoch_end(epoch, self.n_epochs, self.optimizer) + self.callback_manager.on_epoch_end() # =============== epochs end =================== # pbar.close() + self.pbar = None # ============ tqdm end ============== # - + def _do_validation(self, epoch, step): self.callback_manager.on_valid_begin() res = self.tester.test() - + + is_better_eval = False if self._better_eval_result(res): if self.save_path is not None: self._save_model(self.model, - "best_" + "_".join([self.model.__class__.__name__, self.metric_key, self.start_time])) - else: + "best_" + "_".join([self.model.__class__.__name__, self.metric_key, self.start_time])) + elif self._load_best_model: self._best_model_states = {name: param.cpu().clone() for name, param in self.model.named_parameters()} self.best_dev_perf = res self.best_dev_epoch = epoch self.best_dev_step = step + is_better_eval = True # get validation results; adjust optimizer - self.callback_manager.on_valid_end(res, self.metric_key, self.optimizer) + self.callback_manager.on_valid_end(res, self.metric_key, self.optimizer, is_better_eval) return res - + def _mode(self, model, is_test=False): """Train mode or Test mode. This is for PyTorch currently. @@ -317,20 +654,22 @@ class Trainer(object): model.eval() else: model.train() - + def _update(self): """Perform weight update on a model. """ - self.optimizer.step() - + if self.optimizer is not None and (self.step + 1) % self.update_every == 0: + self.optimizer.step() + def _data_forward(self, network, x): x = _build_args(network.forward, **x) y = network(**x) if not isinstance(y, dict): - raise TypeError(f"The return value of {get_func_signature(network.forward)} should be dict, got {type(y)}.") + raise TypeError( + f"The return value of {_get_func_signature(network.forward)} should be dict, got {type(y)}.") return y - + def _grad_backward(self, loss): """Compute gradient with link rules. @@ -338,9 +677,10 @@ class Trainer(object): For PyTorch, just do "loss.backward()" """ - self.model.zero_grad() + if self.step % self.update_every == 0: + self.model.zero_grad() loss.backward() - + def _compute_loss(self, predict, truth): """Compute loss given prediction and ground truth. @@ -349,7 +689,7 @@ class Trainer(object): :return: a scalar """ return self.losser(predict, truth) - + def _save_model(self, model, model_name, only_param=False): """ 存储不含有显卡信息的state_dict或model :param model: @@ -359,6 +699,10 @@ class Trainer(object): """ if self.save_path is not None: model_path = os.path.join(self.save_path, model_name) + if not os.path.exists(self.save_path): + os.makedirs(self.save_path, exist_ok=True) + if isinstance(model, nn.DataParallel): + model = model.module if only_param: state_dict = model.state_dict() for key in state_dict: @@ -367,8 +711,8 @@ class Trainer(object): else: model.cpu() torch.save(model, model_path) - model.cuda() - + model.to(self._model_device) + def _load_model(self, model, model_name, only_param=False): # 返回bool值指示是否成功reload模型 if self.save_path is not None: @@ -377,13 +721,16 @@ class Trainer(object): states = torch.load(model_path) else: states = torch.load(model_path).state_dict() - model.load_state_dict(states) + if isinstance(model, nn.DataParallel): + model.module.load_state_dict(states) + else: + model.load_state_dict(states) elif hasattr(self, "_best_model_states"): model.load_state_dict(self._best_model_states) else: return False return True - + def _better_eval_result(self, metrics): """Check if the current epoch yields better validation results. @@ -411,6 +758,7 @@ class Trainer(object): DEFAULT_CHECK_BATCH_SIZE = 2 DEFAULT_CHECK_NUM_BATCH = 2 + def _get_value_info(_dict): # given a dict value, return information about this dict's value. Return list of str strs = [] @@ -427,27 +775,28 @@ def _get_value_info(_dict): strs.append(_str) return strs + def _check_code(dataset, model, losser, metrics, batch_size=DEFAULT_CHECK_BATCH_SIZE, dev_data=None, metric_key=None, check_level=0): # check get_loss 方法 model_devcie = model.parameters().__next__().device - + batch = Batch(dataset=dataset, batch_size=batch_size, sampler=SequentialSampler()) for batch_count, (batch_x, batch_y) in enumerate(batch): _move_dict_value_to_device(batch_x, batch_y, device=model_devcie) # forward check - if batch_count==0: + if batch_count == 0: info_str = "" input_fields = _get_value_info(batch_x) target_fields = _get_value_info(batch_y) - if len(input_fields)>0: + if len(input_fields) > 0: info_str += "input fields after batch(if batch size is {}):\n".format(batch_size) info_str += "\n".join(input_fields) info_str += '\n' else: raise RuntimeError("There is no input field.") - if len(target_fields)>0: + if len(target_fields) > 0: info_str += "target fields after batch(if batch size is {}):\n".format(batch_size) info_str += "\n".join(target_fields) info_str += '\n' @@ -455,14 +804,14 @@ def _check_code(dataset, model, losser, metrics, batch_size=DEFAULT_CHECK_BATCH_ info_str += 'There is no target field.' print(info_str) _check_forward_error(forward_func=model.forward, dataset=dataset, - batch_x=batch_x, check_level=check_level) - + batch_x=batch_x, check_level=check_level) + refined_batch_x = _build_args(model.forward, **batch_x) pred_dict = model(**refined_batch_x) - func_signature = get_func_signature(model.forward) + func_signature = _get_func_signature(model.forward) if not isinstance(pred_dict, dict): raise TypeError(f"The return value of {func_signature} should be `dict`, not `{type(pred_dict)}`.") - + # loss check try: loss = losser(pred_dict, batch_y) @@ -470,23 +819,23 @@ def _check_code(dataset, model, losser, metrics, batch_size=DEFAULT_CHECK_BATCH_ if batch_count == 0: if not isinstance(loss, torch.Tensor): raise TypeError( - f"The return value of {get_func_signature(losser.get_loss)} should be `torch.Tensor`, " + f"The return value of {_get_func_signature(losser.get_loss)} should be `torch.Tensor`, " f"but got `{type(loss)}`.") if len(loss.size()) != 0: raise ValueError( - f"The size of return value of {get_func_signature(losser.get_loss)} is {loss.size()}, " + f"The size of return value of {_get_func_signature(losser.get_loss)} is {loss.size()}, " f"should be torch.size([])") loss.backward() - except CheckError as e: - # TODO: another error raised if CheckError caught - pre_func_signature = get_func_signature(model.forward) + except _CheckError as e: + # TODO: another error raised if _CheckError caught + pre_func_signature = _get_func_signature(model.forward) _check_loss_evaluate(prev_func_signature=pre_func_signature, func_signature=e.func_signature, check_res=e.check_res, pred_dict=pred_dict, target_dict=batch_y, dataset=dataset, check_level=check_level) model.zero_grad() if batch_count + 1 >= DEFAULT_CHECK_NUM_BATCH: break - + if dev_data is not None: tester = Tester(data=dev_data[:batch_size * DEFAULT_CHECK_NUM_BATCH], model=model, metrics=metrics, batch_size=batch_size, verbose=-1) @@ -500,7 +849,7 @@ def _check_eval_results(metrics, metric_key, metric_list): # metric_list: 多个用来做评价的指标,来自Trainer的初始化 if isinstance(metrics, tuple): loss, metrics = metrics - + if isinstance(metrics, dict): if len(metrics) == 1: # only single metric, just use it @@ -511,7 +860,7 @@ def _check_eval_results(metrics, metric_key, metric_list): if metrics_name not in metrics: raise RuntimeError(f"{metrics_name} is chosen to do validation, but got {metrics}") metric_dict = metrics[metrics_name] - + if len(metric_dict) == 1: indicator_val, indicator = list(metric_dict.values())[0], list(metric_dict.keys())[0] elif len(metric_dict) > 1 and metric_key is None: diff --git a/fastNLP/core/utils.py b/fastNLP/core/utils.py index 695efdfc..518c8213 100644 --- a/fastNLP/core/utils.py +++ b/fastNLP/core/utils.py @@ -1,59 +1,274 @@ +""" +utils模块实现了 fastNLP 内部和外部所需的很多工具。其中用户可以使用的是 :func:`cache_results` 修饰器。 +""" +__all__ = [ + "cache_results", + "seq_len_to_mask" +] + import _pickle import inspect import os import warnings -from collections import Counter -from collections import namedtuple +from collections import Counter, namedtuple import numpy as np import torch +import torch.nn as nn + -CheckRes = namedtuple('CheckRes', ['missing', 'unused', 'duplicated', 'required', 'all_needed', - 'varargs']) +_CheckRes = namedtuple('_CheckRes', ['missing', 'unused', 'duplicated', 'required', 'all_needed', + 'varargs']) -def save_pickle(obj, pickle_path, file_name): - """Save an object into a pickle file. +def _prepare_cache_filepath(filepath): + """ + 检查filepath是否可以作为合理的cache文件. 如果可以的话,会自动创造路径 + :param filepath: str. + :return: None, if not, this function will raise error + """ + _cache_filepath = os.path.abspath(filepath) + if os.path.isdir(_cache_filepath): + raise RuntimeError("The cache_file_path must be a file, not a directory.") + cache_dir = os.path.dirname(_cache_filepath) + if not os.path.exists(cache_dir): + os.makedirs(cache_dir) + - :param obj: an object - :param pickle_path: str, the directory where the pickle file is to be saved - :param file_name: str, the name of the pickle file. In general, it should be ended by "pkl". +# TODO 可以保存下缓存时的参数,如果load的时候发现参数不一致,发出警告。 +def cache_results(_cache_fp, _refresh=False, _verbose=1): + """ + 别名::class:`fastNLP.cache_results` :class:`fastNLP.core.uitls.cache_results` + + cache_results是fastNLP中用于cache数据的装饰器。通过下面的例子看一下如何使用:: + + import time + import numpy as np + from fastNLP import cache_results + + @cache_results('cache.pkl') + def process_data(): + # 一些比较耗时的工作,比如读取数据,预处理数据等,这里用time.sleep()代替耗时 + time.sleep(1) + return np.random.randint(10, size=(5,)) + + start_time = time.time() + print("res =",process_data()) + print(time.time() - start_time) + + start_time = time.time() + print("res =",process_data()) + print(time.time() - start_time) + + # 输出内容如下,可以看到两次结果相同,且第二次几乎没有花费时间 + # Save cache to cache.pkl. + # res = [5 4 9 1 8] + # 1.0042750835418701 + # Read cache from cache.pkl. + # res = [5 4 9 1 8] + # 0.0040721893310546875 + + 可以看到第二次运行的时候,只用了0.0001s左右,是由于第二次运行将直接从cache.pkl这个文件读取数据,而不会经过再次预处理 + + Example:: + + # 还是以上面的例子为例,如果需要重新生成另一个cache,比如另一个数据集的内容,通过如下的方式调用即可 + process_data(_cache_fp='cache2.pkl') # 完全不影响之前的‘cache.pkl' + + 上面的_cache_fp是cache_results会识别的参数,它将从'cache2.pkl'这里缓存/读取数据,即这里的'cache2.pkl'覆盖默认的 + 'cache.pkl'。如果在你的函数前面加上了@cache_results()则你的函数会增加三个参数[_cache_fp, _refresh, _verbose]。 + 上面的例子即为使用_cache_fp的情况,这三个参数不会传入到你的函数中,当然你写的函数参数名也不可能包含这三个名称。 + + Example:: + + process_data(_cache_fp='cache2.pkl', _refresh=True) # 这里强制重新生成一份对预处理的cache。 + # _verbose是用于控制输出信息的,如果为0,则不输出任何内容;如果为1,则会提醒当前步骤是读取的cache还是生成了新的cache + + :param str _cache_fp: 将返回结果缓存到什么位置;或从什么位置读取缓存。如果为None,cache_results没有任何效用,除非在 + 函数调用的时候传入_cache_fp这个参数。 + :param bool _refresh: 是否重新生成cache。 + :param int _verbose: 是否打印cache的信息。 + :return: """ - if not os.path.exists(pickle_path): - os.mkdir(pickle_path) - print("make dir {} before saving pickle file".format(pickle_path)) - with open(os.path.join(pickle_path, file_name), "wb") as f: - _pickle.dump(obj, f) - print("{} saved in {}".format(file_name, pickle_path)) + + def wrapper_(func): + signature = inspect.signature(func) + for key, _ in signature.parameters.items(): + if key in ('_cache_fp', '_refresh', '_verbose'): + raise RuntimeError("The function decorated by cache_results cannot have keyword `{}`.".format(key)) + + def wrapper(*args, **kwargs): + if '_cache_fp' in kwargs: + cache_filepath = kwargs.pop('_cache_fp') + assert isinstance(cache_filepath, str), "_cache_fp can only be str." + else: + cache_filepath = _cache_fp + if '_refresh' in kwargs: + refresh = kwargs.pop('_refresh') + assert isinstance(refresh, bool), "_refresh can only be bool." + else: + refresh = _refresh + if '_verbose' in kwargs: + verbose = kwargs.pop('_verbose') + assert isinstance(verbose, int), "_verbose can only be integer." + else: + verbose = _verbose + refresh_flag = True + + if cache_filepath is not None and refresh is False: + # load data + if os.path.exists(cache_filepath): + with open(cache_filepath, 'rb') as f: + results = _pickle.load(f) + if verbose == 1: + print("Read cache from {}.".format(cache_filepath)) + refresh_flag = False + + if refresh_flag: + results = func(*args, **kwargs) + if cache_filepath is not None: + if results is None: + raise RuntimeError("The return value is None. Delete the decorator.") + _prepare_cache_filepath(cache_filepath) + with open(cache_filepath, 'wb') as f: + _pickle.dump(results, f) + print("Save cache to {}.".format(cache_filepath)) + + return results + + return wrapper + + return wrapper_ + + +# def save_pickle(obj, pickle_path, file_name): +# """Save an object into a pickle file. +# +# :param obj: an object +# :param pickle_path: str, the directory where the pickle file is to be saved +# :param file_name: str, the name of the pickle file. In general, it should be ended by "pkl". +# """ +# if not os.path.exists(pickle_path): +# os.mkdir(pickle_path) +# print("make dir {} before saving pickle file".format(pickle_path)) +# with open(os.path.join(pickle_path, file_name), "wb") as f: +# _pickle.dump(obj, f) +# print("{} saved in {}".format(file_name, pickle_path)) +# +# +# def load_pickle(pickle_path, file_name): +# """Load an object from a given pickle file. +# +# :param pickle_path: str, the directory where the pickle file is. +# :param file_name: str, the name of the pickle file. +# :return obj: an object stored in the pickle +# """ +# with open(os.path.join(pickle_path, file_name), "rb") as f: +# obj = _pickle.load(f) +# print("{} loaded from {}".format(file_name, pickle_path)) +# return obj +# +# +# def pickle_exist(pickle_path, pickle_name): +# """Check if a given pickle file exists in the directory. +# +# :param pickle_path: the directory of target pickle file +# :param pickle_name: the filename of target pickle file +# :return: True if file exists else False +# """ +# if not os.path.exists(pickle_path): +# os.makedirs(pickle_path) +# file_name = os.path.join(pickle_path, pickle_name) +# if os.path.exists(file_name): +# return True +# else: +# return False + +def _move_model_to_device(model, device): + """ + 将model移动到device + + :param model: torch.nn.DataParallel or torch.nn.Module. 当为torch.nn.DataParallel, 则只是调用一次cuda。device必须为 + None。 + :param str,int,torch.device,list(int),list(torch.device) device: 将模型load到哪个设备。默认为None,即Trainer不对模型 + 的计算位置进行管理。支持以下的输入: + + 1. str: ['cpu', 'cuda', 'cuda:0', 'cuda:1', ...] 依次为'cpu'中, 可见的第一个GPU中, 可见的第一个GPU中, + 可见的第二个GPU中; + + 2. torch.device:将模型装载到torch.device上。 + 3. int: 将使用device_id为该值的gpu进行训练 -def load_pickle(pickle_path, file_name): - """Load an object from a given pickle file. + 4. list(int):如果多于1个device,将使用torch.nn.DataParallel包裹model, 并使用传入的device。 - :param pickle_path: str, the directory where the pickle file is. - :param file_name: str, the name of the pickle file. - :return obj: an object stored in the pickle + 5. None. 为None则不对模型进行任何处理,如果传入的model为torch.nn.DataParallel该值必须为None。 + + :return: torch.nn.DataParallel or torch.nn.Module """ - with open(os.path.join(pickle_path, file_name), "rb") as f: - obj = _pickle.load(f) - print("{} loaded from {}".format(file_name, pickle_path)) - return obj + if isinstance(model, torch.nn.parallel.DistributedDataParallel): + raise RuntimeError("model of `torch.nn.parallel.DistributedDataParallel` is not supported right now.") + + if device is None: + if isinstance(model, torch.nn.DataParallel): + model.cuda() + return model + else: + if not torch.cuda.is_available() and ( + device != 'cpu' or (isinstance(device, torch.device) and device.type != 'cpu')): + raise ValueError("There is no usable gpu. set `device` as `cpu` or `None`.") + + if isinstance(model, torch.nn.DataParallel): + raise RuntimeError("When model is `torch.nn.DataParallel`, the device has to be `None`.") + + if isinstance(device, int): + assert device > -1, "device can only be non-negative integer" + assert torch.cuda.device_count() > device, "Only has {} gpus, cannot use device {}.".format( + torch.cuda.device_count(), + device) + device = torch.device('cuda:{}'.format(device)) + elif isinstance(device, str): + device = torch.device(device) + if device.type == 'cuda' and device.index is not None: + assert device.index < torch.cuda.device_count(), "Only has {} gpus, cannot use device cuda:{}.".format( + torch.cuda.device_count(), + device) + elif isinstance(device, torch.device): + if device.type == 'cuda' and device.index is not None: + assert device.index < torch.cuda.device_count(), "Only has {} gpus, cannot use device cuda:{}.".format( + torch.cuda.device_count(), + device) + elif isinstance(device, list): + types = set([type(d) for d in device]) + assert len(types) == 1, "Mixed type in device, only `int` allowed." + assert list(types)[0] == int, "Only int supported for multiple devices." + assert len(set(device)) == len(device), "Duplicated device id found in device." + for d in device: + assert d > -1, "Only non-negative device id allowed." + if len(device) > 1: + output_device = device[0] + model = nn.DataParallel(model, device_ids=device, output_device=output_device) + device = torch.device(device[0]) + else: + raise TypeError("Unsupported device type.") + model = model.to(device) + return model -def pickle_exist(pickle_path, pickle_name): - """Check if a given pickle file exists in the directory. +def _get_model_device(model): + """ + 传入一个nn.Module的模型,获取它所在的device - :param pickle_path: the directory of target pickle file - :param pickle_name: the filename of target pickle file - :return: True if file exists else False + :param model: nn.Module + :return: torch.device,None 如果返回值为None,说明这个模型没有任何参数。 """ - if not os.path.exists(pickle_path): - os.makedirs(pickle_path) - file_name = os.path.join(pickle_path, pickle_name) - if os.path.exists(file_name): - return True + assert isinstance(model, nn.Module) + + parameters = list(model.parameters()) + if len(parameters) == 0: + return None else: - return False + return parameters[0].device def _build_args(func, **kwargs): @@ -126,30 +341,35 @@ def _check_arg_dict_list(func, args): missing = list(require_args - input_args) unused = list(input_args - all_args) varargs = [] if not spect.varargs else [spect.varargs] - return CheckRes(missing=missing, - unused=unused, - duplicated=duplicated, - required=list(require_args), - all_needed=list(all_args), - varargs=varargs) + return _CheckRes(missing=missing, + unused=unused, + duplicated=duplicated, + required=list(require_args), + all_needed=list(all_args), + varargs=varargs) -def get_func_signature(func): +def _get_func_signature(func): """ Given a function or method, return its signature. For example: - (1) function + + 1 function:: + def func(a, b='a', *args): xxxx get_func_signature(func) # 'func(a, b='a', *args)' - (2) method + + 2 method:: + class Demo: def __init__(self): xxx def forward(self, a, b='a', **args) demo = Demo() get_func_signature(demo.forward) # 'Demo.forward(self, a, b='a', **args)' + :param func: a function or a method :return: str or None """ @@ -195,9 +415,12 @@ def _move_dict_value_to_device(*args, device: torch.device, non_blocking=False): :param args: :return: """ + if not torch.cuda.is_available(): + return + if not isinstance(device, torch.device): raise TypeError(f"device must be `torch.device`, got `{type(device)}`") - + for arg in args: if isinstance(arg, dict): for key, value in arg.items(): @@ -207,15 +430,15 @@ def _move_dict_value_to_device(*args, device: torch.device, non_blocking=False): raise TypeError("Only support `dict` type right now.") -class CheckError(Exception): +class _CheckError(Exception): """ - CheckError. Used in losses.LossBase, metrics.MetricBase. + _CheckError. Used in losses.LossBase, metrics.MetricBase. """ - - def __init__(self, check_res: CheckRes, func_signature: str): + + def __init__(self, check_res: _CheckRes, func_signature: str): errs = [f'Problems occurred when calling `{func_signature}`'] - + if check_res.varargs: errs.append(f"\tvarargs: {check_res.varargs}(Does not support pass positional arguments, please delete it)") if check_res.missing: @@ -224,9 +447,9 @@ class CheckError(Exception): errs.append(f"\tduplicated param: {check_res.duplicated}") if check_res.unused: errs.append(f"\tunused param: {check_res.unused}") - + Exception.__init__(self, '\n'.join(errs)) - + self.check_res = check_res self.func_signature = func_signature @@ -236,7 +459,7 @@ WARNING_CHECK_LEVEL = 1 STRICT_CHECK_LEVEL = 2 -def _check_loss_evaluate(prev_func_signature: str, func_signature: str, check_res: CheckRes, +def _check_loss_evaluate(prev_func_signature: str, func_signature: str, check_res: _CheckRes, pred_dict: dict, target_dict: dict, dataset, check_level=0): errs = [] unuseds = [] @@ -246,7 +469,7 @@ def _check_loss_evaluate(prev_func_signature: str, func_signature: str, check_re # if check_res.varargs: # errs.append(f"\tvarargs: *{check_res.varargs}") # suggestions.append(f"Does not support pass positional arguments, please delete *{check_res.varargs}.") - + if check_res.unused: for _unused in check_res.unused: if _unused in target_dict: @@ -256,20 +479,19 @@ def _check_loss_evaluate(prev_func_signature: str, func_signature: str, check_re if _unused_field: unuseds.append(f"\tunused field: {_unused_field}") if _unused_param: - unuseds.append(f"\tunused param: {_unused_param}") # output from predict or forward - + unuseds.append(f"\tunused param: {_unused_param}") # output from predict or forward + module_name = func_signature.split('.')[0] if check_res.missing: errs.append(f"\tmissing param: {check_res.missing}") import re - mapped_missing = [] - unmapped_missing = [] + mapped_missing = [] # 提供了映射的参数 + unmapped_missing = [] # 没有指定映射的参数 input_func_map = {} - for _miss in check_res.missing: - if '(' in _miss: - # if they are like 'SomeParam(assign to xxx)' - _miss = _miss.split('(')[0] - matches = re.findall("(?<=`)[a-zA-Z0-9]*?(?=`)", _miss) + for _miss_ in check_res.missing: + # they shoudl like 'SomeParam(assign to xxx)' + _miss = _miss_.split('(')[0] + matches = re.findall("(?<=`)[a-zA-Z0-9]*?(?=`)", _miss_) if len(matches) == 2: fun_arg, module_name = matches input_func_map[_miss] = fun_arg @@ -279,50 +501,50 @@ def _check_loss_evaluate(prev_func_signature: str, func_signature: str, check_re mapped_missing.append(_miss) else: unmapped_missing.append(_miss) - - for _miss in mapped_missing: + + for _miss in mapped_missing + unmapped_missing: if _miss in dataset: - suggestions.append(f"Set {_miss} as target.") + suggestions.append(f"Set `{_miss}` as target.") else: _tmp = '' if check_res.unused: - _tmp = f"Check key assignment for `{input_func_map.get(_miss, _miss)}` when initialize {module_name}." + _tmp = f"Check key assignment for `{input_func_map.get(_miss,_miss)}` when initialize {module_name}." if _tmp: - _tmp += f' Or provide {_miss} in DataSet or output of {prev_func_signature}.' + _tmp += f' Or provide `{_miss}` in DataSet or output of {prev_func_signature}.' else: - _tmp = f'Provide {_miss} in DataSet or output of {prev_func_signature}.' + _tmp = f'Provide `{_miss}` in DataSet or output of {prev_func_signature}.' suggestions.append(_tmp) - for _miss in unmapped_missing: - if _miss in dataset: - suggestions.append(f"Set {_miss} as target.") - else: - _tmp = '' - if check_res.unused: - _tmp = f"Specify your assignment for `{input_func_map.get(_miss, _miss)}` when initialize {module_name}." - if _tmp: - _tmp += f' Or provide {_miss} in DataSet or output of {prev_func_signature}.' - else: - _tmp = f'Provide {_miss} in output of {prev_func_signature} or DataSet.' - suggestions.append(_tmp) - + # for _miss in unmapped_missing: + # if _miss in dataset: + # suggestions.append(f"Set `{_miss}` as target.") + # else: + # _tmp = '' + # if check_res.unused: + # _tmp = f"Specify your assignment for `{input_func_map.get(_miss, _miss)}` when initialize {module_name}." + # if _tmp: + # _tmp += f' Or provide `{_miss}` in DataSet or output of {prev_func_signature}.' + # else: + # _tmp = f'Provide `{_miss}` in output of {prev_func_signature} or DataSet.' + # suggestions.append(_tmp) + if check_res.duplicated: errs.append(f"\tduplicated param: {check_res.duplicated}.") suggestions.append(f"Delete {check_res.duplicated} in the output of " f"{prev_func_signature} or do not set {check_res.duplicated} as targets. ") - - if len(errs)>0: + + if len(errs) > 0: errs.extend(unuseds) elif check_level == STRICT_CHECK_LEVEL: errs.extend(unuseds) - + if len(errs) > 0: errs.insert(0, f'Problems occurred when calling {func_signature}') sugg_str = "" if len(suggestions) > 1: for idx, sugg in enumerate(suggestions): - if idx>0: + if idx > 0: sugg_str += '\t\t\t' - sugg_str += f'({idx+1}). {sugg}\n' + sugg_str += f'({idx + 1}). {sugg}\n' sugg_str = sugg_str[:-1] else: sugg_str += suggestions[0] @@ -337,14 +559,15 @@ def _check_loss_evaluate(prev_func_signature: str, func_signature: str, check_re _unused_warn = f'{check_res.unused} is not used by {module_name}.' warnings.warn(message=_unused_warn) + def _check_forward_error(forward_func, batch_x, dataset, check_level): check_res = _check_arg_dict_list(forward_func, batch_x) - func_signature = get_func_signature(forward_func) - + func_signature = _get_func_signature(forward_func) + errs = [] suggestions = [] _unused = [] - + # if check_res.varargs: # errs.append(f"\tvarargs: {check_res.varargs}") # suggestions.append(f"Does not support pass positional arguments, please delete *{check_res.varargs}.") @@ -365,20 +588,20 @@ def _check_forward_error(forward_func, batch_x, dataset, check_level): # _tmp += f"Or you might find it in `unused field:`, you can use DataSet.rename_field() to " \ # f"rename the field in `unused field:`." suggestions.append(_tmp) - + if check_res.unused: _unused = [f"\tunused field: {check_res.unused}"] - if len(errs)>0: + if len(errs) > 0: errs.extend(_unused) elif check_level == STRICT_CHECK_LEVEL: errs.extend(_unused) - + if len(errs) > 0: errs.insert(0, f'Problems occurred when calling {func_signature}') sugg_str = "" if len(suggestions) > 1: for idx, sugg in enumerate(suggestions): - sugg_str += f'({idx+1}). {sugg}' + sugg_str += f'({idx + 1}). {sugg}' else: sugg_str += suggestions[0] err_str = '\n' + '\n'.join(errs) + '\n\tSuggestion: ' + sugg_str @@ -389,72 +612,66 @@ def _check_forward_error(forward_func, batch_x, dataset, check_level): warnings.warn(message=_unused_warn) -def seq_lens_to_masks(seq_lens, float=False): +def seq_len_to_mask(seq_len): """ - Convert seq_lens to masks. - :param seq_lens: list, np.ndarray, or torch.LongTensor, shape should all be (B,) - :param float: if True, the return masks is in float type, otherwise it is byte. - :return: list, np.ndarray or torch.Tensor, shape will be (B, max_length) + 将一个表示sequence length的一维数组转换为二维的mask,不包含的位置为0。 + 转变 1-d seq_len到2-d mask. + + Example:: + + >>> seq_len = torch.arange(2, 16) + >>> mask = seq_len_to_mask(seq_len) + >>> print(mask.size()) + torch.Size([14, 15]) + >>> seq_len = np.arange(2, 16) + >>> mask = seq_len_to_mask(seq_len) + >>> print(mask.shape) + (14, 15) + + :param np.ndarray,torch.LongTensor seq_len: shape将是(B,) + :return: np.ndarray or torch.Tensor, shape将是(B, max_length)。 元素类似为bool或torch.uint8 """ - if isinstance(seq_lens, np.ndarray): - assert len(np.shape(seq_lens)) == 1, f"seq_lens can only have one dimension, got {len(np.shape(seq_lens))}." - assert seq_lens.dtype in (int, np.int32, np.int64), f"seq_lens can only be integer, not {seq_lens.dtype}." - raise NotImplemented - elif isinstance(seq_lens, torch.Tensor): - assert len(seq_lens.size()) == 1, f"seq_lens can only have one dimension, got {len(seq_lens.size())==1}." - batch_size = seq_lens.size(0) - max_len = seq_lens.max() - indexes = torch.arange(max_len).view(1, -1).repeat(batch_size, 1).to(seq_lens.device) - masks = indexes.lt(seq_lens.unsqueeze(1)) - - if float: - masks = masks.float() - - return masks - elif isinstance(seq_lens, list): - raise NotImplemented + if isinstance(seq_len, np.ndarray): + assert len(np.shape(seq_len)) == 1, f"seq_len can only have one dimension, got {len(np.shape(seq_len))}." + max_len = int(seq_len.max()) + broad_cast_seq_len = np.tile(np.arange(max_len), (len(seq_len), 1)) + mask = broad_cast_seq_len < seq_len.reshape(-1, 1) + + elif isinstance(seq_len, torch.Tensor): + assert seq_len.dim() == 1, f"seq_len can only have one dimension, got {seq_len.dim() == 1}." + batch_size = seq_len.size(0) + max_len = seq_len.max().long() + broad_cast_seq_len = torch.arange(max_len).expand(batch_size, -1).to(seq_len) + mask = broad_cast_seq_len.lt(seq_len.unsqueeze(1)) else: - raise NotImplemented - - -def seq_mask(seq_len, max_len): - """Create sequence mask. - - :param seq_len: list or torch.Tensor, the lengths of sequences in a batch. - :param max_len: int, the maximum sequence length in a batch. - :return mask: torch.LongTensor, [batch_size, max_len] - - """ - if not isinstance(seq_len, torch.Tensor): - seq_len = torch.LongTensor(seq_len) - seq_len = seq_len.view(-1, 1).long() # [batch_size, 1] - seq_range = torch.arange(start=0, end=max_len, dtype=torch.long, device=seq_len.device).view(1, -1) # [1, max_len] - return torch.gt(seq_len, seq_range) # [batch_size, max_len] + raise TypeError("Only support 1-d numpy.ndarray or 1-d torch.Tensor.") + + return mask -class pseudo_tqdm: +class _pseudo_tqdm: """ 当无法引入tqdm,或者Trainer中设置use_tqdm为false的时候,用该方法打印数据 """ - + def __init__(self, **kwargs): pass - + def write(self, info): print(info) - + def set_postfix_str(self, info): print(info) - + def __getattr__(self, item): def pass_func(*args, **kwargs): pass - + return pass_func - + def __enter__(self): return self - + def __exit__(self, exc_type, exc_val, exc_tb): del self diff --git a/fastNLP/core/vocabulary.py b/fastNLP/core/vocabulary.py index 50a79d24..43f590fd 100644 --- a/fastNLP/core/vocabulary.py +++ b/fastNLP/core/vocabulary.py @@ -1,24 +1,33 @@ +__all__ = [ + "Vocabulary" +] + +from functools import wraps from collections import Counter +from .dataset import DataSet + -def check_build_vocab(func): +def _check_build_vocab(func): """A decorator to make sure the indexing is built before used. """ - + + @wraps(func) # to solve missing docstring def _wrapper(self, *args, **kwargs): if self.word2idx is None or self.rebuild is True: self.build_vocab() return func(self, *args, **kwargs) - + return _wrapper -def check_build_status(func): +def _check_build_status(func): """A decorator to check whether the vocabulary updates after the last build. """ - + + @wraps(func) # to solve missing docstring def _wrapper(self, *args, **kwargs): if self.rebuild is False: self.rebuild = True @@ -27,27 +36,38 @@ def check_build_status(func): "Adding more words may cause unexpected behaviour of Vocabulary. ".format( self.max_size, func.__name__)) return func(self, *args, **kwargs) - + return _wrapper class Vocabulary(object): - """Use for word and index one to one mapping + """ + 别名::class:`fastNLP.Vocabulary` :class:`fastNLP.core.vocabulary.Vocabulary` + + 用于构建, 存储和使用 `str` 到 `int` 的一一映射 Example:: vocab = Vocabulary() word_list = "this is a word list".split() vocab.update(word_list) - vocab["word"] - vocab.to_word(5) - - :param int max_size: set the max number of words in Vocabulary. Default: None - :param int min_freq: set the min occur frequency of words in Vocabulary. Default: None - + vocab["word"] # str to int + vocab.to_word(5) # int to str + + :param int max_size: `Vocabulary` 的最大大小, 即能存储词的最大数量 + 若为 ``None`` , 则不限制大小. Default: ``None`` + :param int min_freq: 能被记录下的词在文本中的最小出现频率, 应大于或等于 1. + 若小于该频率, 词语将被视为 `unknown`. 若为 ``None`` , 所有文本中的词都被记录. Default: ``None`` + :param str optional padding: padding的字符. 如果设置为 ``None`` , + 则vocabulary中不考虑padding, 也不计入词表大小,为 ``None`` 的情况多在为label建立Vocabulary的情况. + Default: '' + :param str optional unknown: unknown的字符,所有未被记录的词在转为 `int` 时将被视为unknown. + 如果设置为 ``None`` ,则vocabulary中不考虑unknow, 也不计入词表大小. + 为 ``None`` 的情况多在为label建立Vocabulary的情况. + Default: '' """ - - def __init__(self, max_size=None, min_freq=None, unknown='', padding=''): + + def __init__(self, max_size=None, min_freq=None, padding='', unknown=''): self.max_size = max_size self.min_freq = min_freq self.word_count = Counter() @@ -56,51 +76,55 @@ class Vocabulary(object): self.word2idx = None self.idx2word = None self.rebuild = True - - @check_build_status + + @_check_build_status def update(self, word_lst): - """Add a list of words into the vocabulary. + """依次增加序列中词在词典中的出现频率 :param list word_lst: a list of strings """ self.word_count.update(word_lst) - - @check_build_status + + @_check_build_status def add(self, word): - """Add a single word into the vocabulary. + """ + 增加一个新词在词典中的出现频率 - :param str word: a word or token. + :param str word: 新词 """ self.word_count[word] += 1 - - @check_build_status + + @_check_build_status def add_word(self, word): - """Add a single word into the vocabulary. - - :param str word: a word or token. + """ + 增加一个新词在词典中的出现频率 + :param str word: 新词 """ self.add(word) - - @check_build_status + + @_check_build_status def add_word_lst(self, word_lst): - """Add a list of words into the vocabulary. - - :param list word_lst: a list of strings + """ + 依次增加序列中词在词典中的出现频率 + :param list[str] word_lst: 词的序列 """ self.update(word_lst) - + def build_vocab(self): - """Build a mapping from word to index, and filter the word using ``max_size`` and ``min_freq``. + """ + 根据已经出现的词和出现频率构建词典. 注意: 重复构建可能会改变词典的大小, + 但已经记录在词典中的词, 不会改变对应的 `int` """ - self.word2idx = {} + if self.word2idx is None: + self.word2idx = {} if self.padding is not None: - self.word2idx[self.padding] = 0 + self.word2idx[self.padding] = len(self.word2idx) if self.unknown is not None: - self.word2idx[self.unknown] = 1 - + self.word2idx[self.unknown] = len(self.word2idx) + max_size = min(self.max_size, len(self.word_count)) if self.max_size else None words = self.word_count.most_common(max_size) if self.min_freq is not None: @@ -111,32 +135,47 @@ class Vocabulary(object): self.word2idx.update({w: i + start_idx for i, (w, _) in enumerate(words)}) self.build_reverse_vocab() self.rebuild = False - + def build_reverse_vocab(self): - """Build "index to word" dict based on "word to index" dict. + """ + 基于 "word to index" dict, 构建 "index to word" dict. """ self.idx2word = {i: w for w, i in self.word2idx.items()} - - @check_build_vocab + + @_check_build_vocab def __len__(self): return len(self.word2idx) - - @check_build_vocab + + @_check_build_vocab def __contains__(self, item): - """Check if a word in vocabulary. + """ + 检查词是否被记录 :param item: the word :return: True or False """ return item in self.word2idx - + def has_word(self, w): - return self.__contains__(w) + """ + 检查词是否被记录 + + Example:: - @check_build_vocab + has_abc = vocab.has_word('abc') + # equals to + has_abc = 'abc' in vocab + + :param item: the word + :return: ``True`` or ``False`` + """ + return self.__contains__(w) + + @_check_build_vocab def __getitem__(self, w): - """To support usage like:: + """ + To support usage like:: vocab[w] """ @@ -146,49 +185,174 @@ class Vocabulary(object): return self.word2idx[self.unknown] else: raise ValueError("word {} not in vocabulary".format(w)) + + @_check_build_vocab + def index_dataset(self, *datasets, field_name, new_field_name=None): + """ + 将DataSet中对应field的词转为数字. + + Example:: + + # remember to use `field_name` + vocab.index_dataset(train_data, dev_data, test_data, field_name='words') + :param datasets: 需要转index的 class:`~fastNLP.DataSet` , 支持一个或多个(list) + :param str field_name: 需要转index的field, 若有多个 DataSet, 每个DataSet都必须有此 field. + 目前仅支持 ``str`` , ``list(str)`` , ``list(list(str))`` + :param str new_field_name: 保存结果的field_name. 若为 ``None`` , 将覆盖原field. + Default: ``None`` + """ + + def index_instance(ins): + """ + 有几种情况, str, 1d-list, 2d-list + :param ins: + :return: + """ + field = ins[field_name] + if isinstance(field, str): + return self.to_index(field) + elif isinstance(field, list): + if not isinstance(field[0], list): + return [self.to_index(w) for w in field] + else: + if isinstance(field[0][0], list): + raise RuntimeError("Only support field with 2 dimensions.") + return [[self.to_index(c) for c in w] for w in field] + + if new_field_name is None: + new_field_name = field_name + for idx, dataset in enumerate(datasets): + if isinstance(dataset, DataSet): + try: + dataset.apply(index_instance, new_field_name=new_field_name) + except Exception as e: + print("When processing the `{}` dataset, the following error occurred.".format(idx)) + raise e + else: + raise RuntimeError("Only DataSet type is allowed.") + + def from_dataset(self, *datasets, field_name): + """ + 使用dataset的对应field中词构建词典 + + Example:: + + # remember to use `field_name` + vocab.from_dataset(train_data1, train_data2, field_name='words') + + :param datasets: 需要转index的 class:`~fastNLP.DataSet` , 支持一个或多个(list) + :param field_name: 可为 ``str`` 或 ``list(str)`` . + 构建词典所使用的 field(s), 支持一个或多个field + 若有多个 DataSet, 每个DataSet都必须有这些field. + 目前仅支持的field结构: ``str`` , ``list(str)`` , ``list(list(str))`` + :return self: + """ + if isinstance(field_name, str): + field_name = [field_name] + elif not isinstance(field_name, list): + raise TypeError('invalid argument field_name: {}'.format(field_name)) + + def construct_vocab(ins): + for fn in field_name: + field = ins[fn] + if isinstance(field, str): + self.add_word(field) + elif isinstance(field, list): + if not isinstance(field[0], list): + self.add_word_lst(field) + else: + if isinstance(field[0][0], list): + raise RuntimeError("Only support field with 2 dimensions.") + [self.add_word_lst(w) for w in field] + + for idx, dataset in enumerate(datasets): + if isinstance(dataset, DataSet): + try: + dataset.apply(construct_vocab) + except Exception as e: + print("When processing the `{}` dataset, the following error occurred.".format(idx)) + raise e + else: + raise RuntimeError("Only DataSet type is allowed.") + return self + def to_index(self, w): - """ Turn a word to an index. If w is not in Vocabulary, return the unknown label. + """ + 将词转为数字. 若词不再词典中被记录, 将视为 unknown, 若 ``unknown=None`` , 将抛出 + ``ValueError`` + + Example:: + + index = vocab.to_index('abc') + # equals to + index = vocab['abc'] :param str w: a word + :return int index: the number """ return self.__getitem__(w) - + @property - @check_build_vocab + @_check_build_vocab def unknown_idx(self): + """ + unknown 对应的数字. + """ if self.unknown is None: return None return self.word2idx[self.unknown] - + @property - @check_build_vocab + @_check_build_vocab def padding_idx(self): + """ + padding 对应的数字 + """ if self.padding is None: return None return self.word2idx[self.padding] - - @check_build_vocab + + @_check_build_vocab def to_word(self, idx): - """given a word's index, return the word itself + """ + 给定一个数字, 将其转为对应的词. :param int idx: the index - :return str word: the indexed word + :return str word: the word """ return self.idx2word[idx] + + def clear(self): + """ + 删除Vocabulary中的词表数据。相当于重新初始化一下。 + :return: + """ + self.word_count.clear() + self.word2idx = None + self.idx2word = None + self.rebuild = True + def __getstate__(self): """Use to prepare data for pickle. """ + len(self) # make sure vocab has been built state = self.__dict__.copy() # no need to pickle idx2word as it can be constructed from word2idx del state['idx2word'] return state - + def __setstate__(self, state): """Use to restore state from pickle. """ self.__dict__.update(state) self.build_reverse_vocab() + + def __repr__(self): + return "Vocabulary({}...)".format(list(self.word_count.keys())[:5]) + + def __iter__(self): + return iter(list(self.word_count.keys())) diff --git a/fastNLP/io/__init__.py b/fastNLP/io/__init__.py index e69de29b..c8d6a441 100644 --- a/fastNLP/io/__init__.py +++ b/fastNLP/io/__init__.py @@ -0,0 +1,31 @@ +""" +用于IO的模块, 具体包括: + +1. 用于读入 embedding 的 :doc:`EmbedLoader ` 类, + +2. 用于读入数据的 :doc:`DataSetLoader ` 类 + +3. 用于保存和载入模型的类, 参考 :doc:`/fastNLP.io.model_io` + +这些类的使用方法如下: +""" +__all__ = [ + 'EmbedLoader', + + 'DataSetLoader', + 'CSVLoader', + 'JsonLoader', + 'ConllLoader', + 'SNLILoader', + 'SSTLoader', + 'PeopleDailyCorpusLoader', + 'Conll2003Loader', + + 'ModelLoader', + 'ModelSaver', +] + +from .embed_loader import EmbedLoader +from .dataset_loader import DataSetLoader, CSVLoader, JsonLoader, ConllLoader, SNLILoader, SSTLoader, \ + PeopleDailyCorpusLoader, Conll2003Loader +from .model_io import ModelLoader, ModelSaver diff --git a/fastNLP/io/base_loader.py b/fastNLP/io/base_loader.py index 5d5fe63a..4ab1e2d0 100644 --- a/fastNLP/io/base_loader.py +++ b/fastNLP/io/base_loader.py @@ -1,30 +1,42 @@ +__all__ = [ + "BaseLoader" +] + import _pickle as pickle import os class BaseLoader(object): - """Base loader for all loaders. + """ + 各个 Loader 的基类,提供了 API 的参考。 """ + def __init__(self): super(BaseLoader, self).__init__() - + @staticmethod def load_lines(data_path): - """按行读取,舍弃每行两侧空白字符,返回list of str + """ + 按行读取,舍弃每行两侧空白字符,返回list of str + + :param data_path: 读取数据的路径 """ with open(data_path, "r", encoding="utf=8") as f: text = f.readlines() return [line.strip() for line in text] - + @classmethod def load(cls, data_path): - """先按行读取,去除一行两侧空白,再提取每行的字符。返回list of list of str + """ + 先按行读取,去除一行两侧空白,再提取每行的字符。返回list of list of str + + :param data_path: """ with open(data_path, "r", encoding="utf-8") as f: text = f.readlines() return [[word for word in sent.strip()] for sent in text] - + @classmethod def load_with_cache(cls, data_path, cache_path): """缓存版的load @@ -40,22 +52,23 @@ class BaseLoader(object): class DataLoaderRegister: - """Register for all data sets. - - """ _readers = {} - + @classmethod def set_reader(cls, reader_cls, read_fn_name): # def wrapper(reader_cls): if read_fn_name in cls._readers: - raise KeyError('duplicate reader: {} and {} for read_func: {}'.format(cls._readers[read_fn_name], reader_cls, read_fn_name)) + raise KeyError( + 'duplicate reader: {} and {} for read_func: {}'.format(cls._readers[read_fn_name], reader_cls, + read_fn_name)) if hasattr(reader_cls, 'load'): cls._readers[read_fn_name] = reader_cls().load return reader_cls - + @classmethod def get_reader(cls, read_fn_name): if read_fn_name in cls._readers: return cls._readers[read_fn_name] raise AttributeError('no read function: {}'.format(read_fn_name)) + + # TODO 这个类使用在何处? diff --git a/fastNLP/io/config_io.py b/fastNLP/io/config_io.py index 5a64b96c..4acdbb96 100644 --- a/fastNLP/io/config_io.py +++ b/fastNLP/io/config_io.py @@ -1,31 +1,48 @@ +""" +用于读入和处理和保存 config 文件 + .. todo:: + 这个模块中的类可能被抛弃? +""" +__all__ = [ + "ConfigLoader", + "ConfigSection", + "ConfigSaver" +] + import configparser import json import os -from fastNLP.io.base_loader import BaseLoader +from .base_loader import BaseLoader class ConfigLoader(BaseLoader): - """Loader for configuration. + """ + 别名::class:`fastNLP.io.ConfigLoader` :class:`fastNLP.io.config_io.ConfigLoader` + + 读取配置文件的Loader - :param str data_path: path to the config + :param str data_path: 配置文件的路径 """ + def __init__(self, data_path=None): super(ConfigLoader, self).__init__() if data_path is not None: self.config = self.parse(super(ConfigLoader, self).load(data_path)) - + @staticmethod def parse(string): raise NotImplementedError - + @staticmethod def load_config(file_path, sections): - """Load section(s) of configuration into the ``sections`` provided. No returns. + """ + 把配置文件的section 存入提供的 ``sections`` 中 - :param str file_path: the path of config file - :param dict sections: the dict of ``{section_name(string): ConfigSection object}`` + :param str file_path: 配置文件的路径 + :param dict sections: 符合如下键值对组成的字典 `section_name(string)` : :class:`~fastNLP.io.ConfigSection` + Example:: test_args = ConfigSection() @@ -65,13 +82,16 @@ class ConfigLoader(BaseLoader): class ConfigSection(object): - """ConfigSection is the data structure storing all key-value pairs in one section in a config file. - """ + 别名::class:`fastNLP.io.ConfigSection` :class:`fastNLP.io.config_io.ConfigSection` + + ConfigSection是一个存储了一个section中所有键值对的数据结构,推荐使用此类的实例来配合 :meth:`ConfigLoader.load_config` 使用 + """ + def __init__(self): super(ConfigSection, self).__init__() - + def __getitem__(self, key): """ :param key: str, the name of the attribute @@ -84,7 +104,7 @@ class ConfigSection(object): if key in self.__dict__.keys(): return getattr(self, key) raise AttributeError("do NOT have attribute %s" % key) - + def __setitem__(self, key, value): """ :param key: str, the name of the attribute @@ -99,14 +119,14 @@ class ConfigSection(object): raise AttributeError("attr %s except %s but got %s" % (key, str(type(getattr(self, key))), str(type(value)))) setattr(self, key, value) - + def __contains__(self, item): """ :param item: The key of item. :return: True if the key in self.__dict__.keys() else False. """ return item in self.__dict__.keys() - + def __eq__(self, other): """Overwrite the == operator @@ -118,15 +138,15 @@ class ConfigSection(object): return False if getattr(self, k) != getattr(self, k): return False - + for k in other.__dict__.keys(): if k not in self.__dict__.keys(): return False if getattr(self, k) != getattr(self, k): return False - + return True - + def __ne__(self, other): """Overwrite the != operator @@ -134,25 +154,30 @@ class ConfigSection(object): :return: """ return not self.__eq__(other) - + @property def data(self): return self.__dict__ class ConfigSaver(object): - """ConfigSaver is used to save config file and solve related conflicts. + """ + 别名::class:`fastNLP.io.ConfigSaver` :class:`fastNLP.io.config_io.ConfigSaver` + + ConfigSaver 是用来存储配置文件并解决相关冲突的类 - :param str file_path: path to the config file + :param str file_path: 配置文件的路径 """ + def __init__(self, file_path): self.file_path = file_path if not os.path.exists(self.file_path): raise FileNotFoundError("file {} NOT found!".__format__(self.file_path)) - + def _get_section(self, sect_name): - """This is the function to get the section with the section name. + """ + This is the function to get the section with the section name. :param sect_name: The name of section what wants to load. :return: The section. @@ -160,25 +185,26 @@ class ConfigSaver(object): sect = ConfigSection() ConfigLoader().load_config(self.file_path, {sect_name: sect}) return sect - + def _read_section(self): - """This is the function to read sections from the config file. + """ + This is the function to read sections from the config file. :return: sect_list, sect_key_list sect_list: A list of ConfigSection(). sect_key_list: A list of names in sect_list. """ sect_name = None - + sect_list = {} sect_key_list = [] - + single_section = {} single_section_key = [] - + with open(self.file_path, 'r') as f: lines = f.readlines() - + for line in lines: if line.startswith('[') and line.endswith(']\n'): if sect_name is None: @@ -190,31 +216,32 @@ class ConfigSaver(object): sect_key_list.append(sect_name) sect_name = line[1: -2] continue - + if line.startswith('#'): single_section[line] = '#' single_section_key.append(line) continue - + if line.startswith('\n'): single_section_key.append('\n') continue - + if '=' not in line: raise RuntimeError("can NOT load config file {}".__format__(self.file_path)) - + key = line.split('=', maxsplit=1)[0].strip() value = line.split('=', maxsplit=1)[1].strip() + '\n' single_section[key] = value single_section_key.append(key) - + if sect_name is not None: sect_list[sect_name] = single_section, single_section_key sect_key_list.append(sect_name) return sect_list, sect_key_list - + def _write_section(self, sect_list, sect_key_list): - """This is the function to write config file with section list and name list. + """ + This is the function to write config file with section list and name list. :param sect_list: A list of ConfigSection() need to be writen into file. :param sect_key_list: A list of name of sect_list. @@ -233,12 +260,13 @@ class ConfigSaver(object): continue f.write(key + ' = ' + single_section[key]) f.write('\n') - + def save_config_file(self, section_name, section): - """This is the function to be called to change the config file with a single section and its name. + """ + 这个方法可以用来修改并保存配置文件中单独的一个 section - :param str section_name: The name of section what needs to be changed and saved. - :param ConfigSection section: The section with key and value what needs to be changed and saved. + :param str section_name: 需要保存的 section 的名字. + :param section: 你需要修改并保存的 section, :class:`~fastNLP.io.ConfigSaver` 类型 """ section_file = self._get_section(section_name) if len(section_file.__dict__.keys()) == 0: # the section not in the file before @@ -264,11 +292,11 @@ class ConfigSaver(object): break if not change_file: return - + sect_list, sect_key_list = self._read_section() if section_name not in sect_key_list: raise AttributeError() - + sect, sect_key = sect_list[section_name] for k in section.__dict__.keys(): if k not in sect_key: diff --git a/fastNLP/io/dataset_loader.py b/fastNLP/io/dataset_loader.py index 07b721c5..32cca88f 100644 --- a/fastNLP/io/dataset_loader.py +++ b/fastNLP/io/dataset_loader.py @@ -1,378 +1,195 @@ -import os +""" +dataset_loader模块实现了许多 DataSetLoader, 用于读取不同格式的数据, 并返回 `DataSet` , +得到的 :class:`~fastNLP.DataSet` 对象可以直接传入 :class:`~fastNLP.Trainer` 和 :class:`~fastNLP.Tester`, 用于模型的训练和测试。 +以SNLI数据集为例:: -from fastNLP.core.dataset import DataSet -from fastNLP.core.instance import Instance -from fastNLP.io.base_loader import DataLoaderRegister + loader = SNLILoader() + train_ds = loader.load('path/to/train') + dev_ds = loader.load('path/to/dev') + test_ds = loader.load('path/to/test') + # ... do stuff + +为 fastNLP 提供 DataSetLoader 的开发者请参考 :class:`~fastNLP.io.DataSetLoader` 的介绍。 +""" +__all__ = [ + 'DataInfo', + 'DataSetLoader', + 'CSVLoader', + 'JsonLoader', + 'ConllLoader', + 'SNLILoader', + 'SSTLoader', + 'PeopleDailyCorpusLoader', + 'Conll2003Loader', +] + +from nltk.tree import Tree + +from ..core.dataset import DataSet +from ..core.instance import Instance +from .file_reader import _read_csv, _read_json, _read_conll +from typing import Union, Dict +import os -def convert_seq_dataset(data): - """Create an DataSet instance that contains no labels. - :param data: list of list of strings, [num_examples, *]. - Example:: +def _download_from_url(url, path): + try: + from tqdm.auto import tqdm + except: + from ..core.utils import _pseudo_tqdm as tqdm + import requests + + """Download file""" + r = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, stream=True) + chunk_size = 16 * 1024 + total_size = int(r.headers.get('Content-length', 0)) + with open(path, "wb") as file, \ + tqdm(total=total_size, unit='B', unit_scale=1, desc=path.split('/')[-1]) as t: + for chunk in r.iter_content(chunk_size): + if chunk: + file.write(chunk) + t.update(len(chunk)) + return + + +def _uncompress(src, dst): + import zipfile + import gzip + import tarfile + import os + + def unzip(src, dst): + with zipfile.ZipFile(src, 'r') as f: + f.extractall(dst) + + def ungz(src, dst): + with gzip.open(src, 'rb') as f, open(dst, 'wb') as uf: + length = 16 * 1024 # 16KB + buf = f.read(length) + while buf: + uf.write(buf) + buf = f.read(length) + + def untar(src, dst): + with tarfile.open(src, 'r:gz') as f: + f.extractall(dst) + + fn, ext = os.path.splitext(src) + _, ext_2 = os.path.splitext(fn) + if ext == '.zip': + unzip(src, dst) + elif ext == '.gz' and ext_2 != '.tar': + ungz(src, dst) + elif (ext == '.gz' and ext_2 == '.tar') or ext_2 == '.tgz': + untar(src, dst) + else: + raise ValueError('unsupported file {}'.format(src)) - [ - [word_11, word_12, ...], - ... - ] - :return: a DataSet. +class DataInfo: """ - dataset = DataSet() - for word_seq in data: - dataset.append(Instance(word_seq=word_seq)) - return dataset - - -def convert_seq2tag_dataset(data): - """Convert list of data into DataSet. - - :param data: list of list of strings, [num_examples, *]. - Example:: + 经过处理的数据信息,包括一系列数据集(比如:分开的训练集、验证集和测试集)及它们所用的词表和词嵌入。 - [ - [ [word_11, word_12, ...], label_1 ], - [ [word_21, word_22, ...], label_2 ], - ... - ] - - :return: a DataSet. + :param vocabs: 从名称(字符串)到 :class:`~fastNLP.Vocabulary` 类型的dict + :param embeddings: 从名称(字符串)到一系列 embedding 的dict,参考 :class:`~fastNLP.io.EmbedLoader` + :param datasets: 从名称(字符串)到 :class:`~fastNLP.DataSet` 类型的dict """ - dataset = DataSet() - for sample in data: - dataset.append(Instance(word_seq=sample[0], label=sample[1])) - return dataset - - -def convert_seq2seq_dataset(data): - """Convert list of data into DataSet. - - :param data: list of list of strings, [num_examples, *]. - Example:: - - [ - [ [word_11, word_12, ...], [label_1, label_1, ...] ], - [ [word_21, word_22, ...], [label_2, label_1, ...] ], - ... - ] - - :return: a DataSet. - """ - dataset = DataSet() - for sample in data: - dataset.append(Instance(word_seq=sample[0], label_seq=sample[1])) - return dataset + + def __init__(self, vocabs: dict = None, embeddings: dict = None, datasets: dict = None): + self.vocabs = vocabs or {} + self.embeddings = embeddings or {} + self.datasets = datasets or {} class DataSetLoader: - """Interface for all DataSetLoaders. - - """ - - def load(self, path): - """Load data from a given file. - - :param str path: file path - :return: a DataSet object - """ - raise NotImplementedError - - def convert(self, data): - """Optional operation to build a DataSet. - - :param data: inner data structure (user-defined) to represent the data. - :return: a DataSet object - """ - raise NotImplementedError - - -class NativeDataSetLoader(DataSetLoader): - """A simple example of DataSetLoader - - """ - - def __init__(self): - super(NativeDataSetLoader, self).__init__() - - def load(self, path): - ds = DataSet.read_csv(path, headers=("raw_sentence", "label"), sep="\t") - ds.set_input("raw_sentence") - ds.set_target("label") - return ds - - -DataLoaderRegister.set_reader(NativeDataSetLoader, 'read_naive') - - -class RawDataSetLoader(DataSetLoader): - """A simple example of raw data reader - """ + 别名::class:`fastNLP.io.DataSetLoader` :class:`fastNLP.io.dataset_loader.DataSetLoader` - def __init__(self): - super(RawDataSetLoader, self).__init__() - - def load(self, data_path, split=None): - with open(data_path, "r", encoding="utf-8") as f: - lines = f.readlines() - lines = lines if split is None else [l.split(split) for l in lines] - lines = list(filter(lambda x: len(x) > 0, lines)) - return self.convert(lines) - - def convert(self, data): - return convert_seq_dataset(data) - - -DataLoaderRegister.set_reader(RawDataSetLoader, 'read_rawdata') - - -class DummyPOSReader(DataSetLoader): - """A simple reader for a dummy POS tagging dataset. - - In these datasets, each line are divided by "\t". The first Col is the vocabulary and the second - Col is the label. Different sentence are divided by an empty line. - E.g:: - - Tom label1 - and label2 - Jerry label1 - . label3 - (separated by an empty line) - Hello label4 - world label5 - ! label3 - - In this example, there are two sentences "Tom and Jerry ." and "Hello world !". Each word has its own label. + 定义了各种 DataSetLoader 所需的API 接口,开发者应该继承它实现各种的 DataSetLoader。 + + 开发者至少应该编写如下内容: + + - _load 函数:从一个数据文件中读取数据到一个 :class:`~fastNLP.DataSet` + - load 函数(可以使用基类的方法):从一个或多个数据文件中读取数据到一个或多个 :class:`~fastNLP.DataSet` + - process 函数:一个或多个从数据文件中读取数据,并处理成可以训练的一个或多个 :class:`~fastNLP.DataSet` + + **process 函数中可以 调用load 函数或 _load 函数** + """ - - def __init__(self): - super(DummyPOSReader, self).__init__() - - def load(self, data_path): - """ - :return data: three-level list - Example:: - [ - [ [word_11, word_12, ...], [label_1, label_1, ...] ], - [ [word_21, word_22, ...], [label_2, label_1, ...] ], - ... - ] + + def _download(self, url: str, path: str, uncompress=True) -> str: """ - with open(data_path, "r", encoding="utf-8") as f: - lines = f.readlines() - data = self.parse(lines) - return self.convert(data) + + 从 ``url`` 下载数据到 ``path``, 如果 ``uncompress`` 为 ``True`` ,自动解压。 - @staticmethod - def parse(lines): - data = [] - sentence = [] - for line in lines: - line = line.strip() - if len(line) > 1: - sentence.append(line.split('\t')) - else: - words = [] - labels = [] - for tokens in sentence: - words.append(tokens[0]) - labels.append(tokens[1]) - data.append([words, labels]) - sentence = [] - if len(sentence) != 0: - words = [] - labels = [] - for tokens in sentence: - words.append(tokens[0]) - labels.append(tokens[1]) - data.append([words, labels]) - return data - - def convert(self, data): - """Convert lists of strings into Instances with Fields. + :param url: 下载的网站 + :param path: 下载到的目录 + :param uncompress: 是否自动解压缩 + :return: 数据的存放路径 """ - return convert_seq2seq_dataset(data) - - -DataLoaderRegister.set_reader(DummyPOSReader, 'read_pos') - - -class DummyCWSReader(DataSetLoader): - """Load pku dataset for Chinese word segmentation. - """ - def __init__(self): - super(DummyCWSReader, self).__init__() - - def load(self, data_path, max_seq_len=32): - """Load pku dataset for Chinese word segmentation. - CWS (Chinese Word Segmentation) pku training dataset format: - 1. Each line is a sentence. - 2. Each word in a sentence is separated by space. - This function convert the pku dataset into three-level lists with labels . - B: beginning of a word - M: middle of a word - E: ending of a word - S: single character - - :param str data_path: path to the data set. - :param max_seq_len: int, the maximum length of a sequence. If a sequence is longer than it, split it into - several sequences. - :return: three-level lists + pdir = os.path.dirname(path) + os.makedirs(pdir, exist_ok=True) + _download_from_url(url, path) + if uncompress: + dst = os.path.join(pdir, 'data') + _uncompress(path, dst) + return dst + return path + + def load(self, paths: Union[str, Dict[str, str]]) -> Union[DataSet, Dict[str, DataSet]]: """ - assert isinstance(max_seq_len, int) and max_seq_len > 0 - with open(data_path, "r", encoding="utf-8") as f: - sentences = f.readlines() - data = [] - for sent in sentences: - tokens = sent.strip().split() - words = [] - labels = [] - for token in tokens: - if len(token) == 1: - words.append(token) - labels.append("S") - else: - words.append(token[0]) - labels.append("B") - for idx in range(1, len(token) - 1): - words.append(token[idx]) - labels.append("M") - words.append(token[-1]) - labels.append("E") - num_samples = len(words) // max_seq_len - if len(words) % max_seq_len != 0: - num_samples += 1 - for sample_idx in range(num_samples): - start = sample_idx * max_seq_len - end = (sample_idx + 1) * max_seq_len - seq_words = words[start:end] - seq_labels = labels[start:end] - data.append([seq_words, seq_labels]) - return self.convert(data) - - def convert(self, data): - return convert_seq2seq_dataset(data) - - -class DummyClassificationReader(DataSetLoader): - """Loader for a dummy classification data set""" - - def __init__(self): - super(DummyClassificationReader, self).__init__() - - def load(self, data_path): - assert os.path.exists(data_path) - with open(data_path, "r", encoding="utf-8") as f: - lines = f.readlines() - data = self.parse(lines) - return self.convert(data) + 从指定一个或多个路径中的文件中读取数据,返回一个或多个数据集 :class:`~fastNLP.DataSet` 。 + 如果处理多个路径,传入的 dict 中的 key 与返回的 dict 中的 key 保存一致。 - @staticmethod - def parse(lines): - """每行第一个token是标签,其余是字/词;由空格分隔。 - - :param lines: lines from dataset - :return: list(list(list())): the three level of lists are words, sentence, and dataset + :param Union[str, Dict[str, str]] paths: 文件路径 + :return: :class:`~fastNLP.DataSet` 类的对象或存储多个 :class:`~fastNLP.DataSet` 的字典 """ - dataset = list() - for line in lines: - line = line.strip().split() - label = line[0] - words = line[1:] - if len(words) <= 1: - continue - - sentence = [words, label] - dataset.append(sentence) - return dataset - - def convert(self, data): - return convert_seq2tag_dataset(data) - - -class ConllLoader(DataSetLoader): - """loader for conll format files""" - - def __init__(self): - super(ConllLoader, self).__init__() - - def load(self, data_path): - with open(data_path, "r", encoding="utf-8") as f: - lines = f.readlines() - data = self.parse(lines) - return self.convert(data) + if isinstance(paths, str): + return self._load(paths) + return {name: self._load(path) for name, path in paths.items()} + + def _load(self, path: str) -> DataSet: + """从指定路径的文件中读取数据,返回 :class:`~fastNLP.DataSet` 类型的对象 - @staticmethod - def parse(lines): + :param str path: 文件路径 + :return: 一个 :class:`~fastNLP.DataSet` 类型的对象 """ - :param list lines: a list containing all lines in a conll file. - :return: a 3D list + raise NotImplementedError + + def process(self, paths: Union[str, Dict[str, str]], **options) -> DataInfo: """ - sentences = list() - tokens = list() - for line in lines: - if line[0] == "#": - # skip the comments - continue - if line == "\n": - sentences.append(tokens) - tokens = [] - continue - tokens.append(line.split()) - return sentences - - def convert(self, data): - pass + 对于特定的任务和数据集,读取并处理数据,返回处理DataInfo类对象或字典。 + + 从指定一个或多个路径中的文件中读取数据,DataInfo对象中可以包含一个或多个数据集 。 + 如果处理多个路径,传入的 dict 的 key 与返回DataInfo中的 dict 中的 key 保存一致。 + + 返回的 :class:`DataInfo` 对象有如下属性: + + - vocabs: 由从数据集中获取的词表组成的字典,每个词表 + - embeddings: (可选) 数据集对应的词嵌入 + - datasets: 一个dict,包含一系列 :class:`~fastNLP.DataSet` 类型的对象。其中 field 的命名参考 :mod:`~fastNLP.core.const` + + :param paths: 原始数据读取的路径 + :param options: 根据不同的任务和数据集,设计自己的参数 + :return: 返回一个 DataInfo + """ + raise NotImplementedError -class DummyLMReader(DataSetLoader): - """A Dummy Language Model Dataset Reader +class PeopleDailyCorpusLoader(DataSetLoader): """ - def __init__(self): - super(DummyLMReader, self).__init__() - - def load(self, data_path): - if not os.path.exists(data_path): - raise FileNotFoundError("file {} not found.".format(data_path)) - with open(data_path, "r", encoding="utf=8") as f: - text = " ".join(f.readlines()) - tokens = text.strip().split() - data = self.sentence_cut(tokens) - return self.convert(data) - - def sentence_cut(self, tokens, sentence_length=15): - start_idx = 0 - data_set = [] - for idx in range(len(tokens) // sentence_length): - x = tokens[start_idx * idx: start_idx * idx + sentence_length] - y = tokens[start_idx * idx + 1: start_idx * idx + sentence_length + 1] - if start_idx * idx + sentence_length + 1 >= len(tokens): - # ad hoc - y.extend([""]) - data_set.append([x, y]) - return data_set + 别名::class:`fastNLP.io.PeopleDailyCorpusLoader` :class:`fastNLP.io.dataset_loader.PeopleDailyCorpusLoader` - def convert(self, data): - pass - - -class PeopleDailyCorpusLoader(DataSetLoader): - """人民日报数据集 + 读取人民日报数据集 """ - def __init__(self): + + def __init__(self, pos=True, ner=True): super(PeopleDailyCorpusLoader, self).__init__() - self.pos = True - self.ner = True - - def load(self, data_path, pos=True, ner=True): - """ - - :param str data_path: 数据路径 - :param bool pos: 是否使用词性标签 - :param bool ner: 是否使用命名实体标签 - :return: a DataSet object - """ - self.pos, self.ner = pos, ner + self.pos = pos + self.ner = ner + + def _load(self, data_path): with open(data_path, "r", encoding="utf-8") as f: sents = f.readlines() examples = [] @@ -416,13 +233,19 @@ class PeopleDailyCorpusLoader(DataSetLoader): example.append(sent_ner) examples.append(example) return self.convert(examples) - + def convert(self, data): + """ + + :param data: python 内置对象 + :return: 一个 :class:`~fastNLP.DataSet` 类型的对象 + """ data_set = DataSet() for item in data: sent_words = item[0] if self.pos is True and self.ner is True: - instance = Instance(words=sent_words, pos_tags=item[1], ner=item[2]) + instance = Instance( + words=sent_words, pos_tags=item[1], ner=item[2]) elif self.pos is True: instance = Instance(words=sent_words, pos_tags=item[1]) elif self.ner is True: @@ -434,243 +257,77 @@ class PeopleDailyCorpusLoader(DataSetLoader): return data_set -class Conll2003Loader(DataSetLoader): - """Loader for conll2003 dataset - - More information about the given dataset cound be found on - https://sites.google.com/site/ermasoftware/getting-started/ne-tagging-conll2003-data - +class ConllLoader(DataSetLoader): """ - def __init__(self): - super(Conll2003Loader, self).__init__() - - def load(self, dataset_path): - with open(dataset_path, "r", encoding="utf-8") as f: - lines = f.readlines() - parsed_data = [] - sentence = [] - tokens = [] - for line in lines: - if '-DOCSTART- -X- -X- O' in line or line == '\n': - if sentence != []: - parsed_data.append((sentence, tokens)) - sentence = [] - tokens = [] - continue - - temp = line.strip().split(" ") - sentence.append(temp[0]) - tokens.append(temp[1:4]) - - return self.convert(parsed_data) - - def convert(self, parsed_data): - dataset = DataSet() - for sample in parsed_data: - label0_list = list(map( - lambda labels: labels[0], sample[1])) - label1_list = list(map( - lambda labels: labels[1], sample[1])) - label2_list = list(map( - lambda labels: labels[2], sample[1])) - dataset.append(Instance(tokens=sample[0], - pos=label0_list, - chucks=label1_list, - ner=label2_list)) - - return dataset - - -class SNLIDataSetReader(DataSetLoader): - """A data set loader for SNLI data set. - + 别名::class:`fastNLP.io.ConllLoader` :class:`fastNLP.io.dataset_loader.ConllLoader` + + 读取Conll格式的数据. 数据格式详见 http://conll.cemantix.org/2012/data.html + + 列号从0开始, 每列对应内容为:: + + Column Type + 0 Document ID + 1 Part number + 2 Word number + 3 Word itself + 4 Part-of-Speech + 5 Parse bit + 6 Predicate lemma + 7 Predicate Frameset ID + 8 Word sense + 9 Speaker/Author + 10 Named Entities + 11:N Predicate Arguments + N Coreference + + :param headers: 每一列数据的名称,需为List or Tuple of str。``header`` 与 ``indexes`` 一一对应 + :param indexes: 需要保留的数据列下标,从0开始。若为 ``None`` ,则所有列都保留。Default: ``None`` + :param dropna: 是否忽略非法数据,若 ``False`` ,遇到非法数据时抛出 ``ValueError`` 。Default: ``False`` """ - def __init__(self): - super(SNLIDataSetReader, self).__init__() - - def load(self, path_list): - """ - - :param list path_list: A list of file name, in the order of premise file, hypothesis file, and label file. - :return: A DataSet object. - """ - assert len(path_list) == 3 - line_set = [] - for file in path_list: - if not os.path.exists(file): - raise FileNotFoundError("file {} NOT found".format(file)) - - with open(file, 'r', encoding='utf-8') as f: - lines = f.readlines() - line_set.append(lines) - - premise_lines, hypothesis_lines, label_lines = line_set - assert len(premise_lines) == len(hypothesis_lines) and len(premise_lines) == len(label_lines) - - data_set = [] - for premise, hypothesis, label in zip(premise_lines, hypothesis_lines, label_lines): - p = premise.strip().split() - h = hypothesis.strip().split() - l = label.strip() - data_set.append([p, h, l]) - - return self.convert(data_set) - - def convert(self, data): - """Convert a 3D list to a DataSet object. - - :param data: A 3D tensor. - Example:: - [ - [ [premise_word_11, premise_word_12, ...], [hypothesis_word_11, hypothesis_word_12, ...], [label_1] ], - [ [premise_word_21, premise_word_22, ...], [hypothesis_word_21, hypothesis_word_22, ...], [label_2] ], - ... - ] - - :return: A DataSet object. - """ - - data_set = DataSet() - - for example in data: - p, h, l = example - # list, list, str - instance = Instance() - instance.add_field("premise", p) - instance.add_field("hypothesis", h) - instance.add_field("truth", l) - data_set.append(instance) - data_set.apply(lambda ins: len(ins["premise"]), new_field_name="premise_len") - data_set.apply(lambda ins: len(ins["hypothesis"]), new_field_name="hypothesis_len") - data_set.set_input("premise", "hypothesis", "premise_len", "hypothesis_len") - data_set.set_target("truth") - return data_set - - -class ConllCWSReader(object): - def __init__(self): - pass - - def load(self, path, cut_long_sent=False): - """ - 返回的DataSet只包含raw_sentence这个field,内容为str。 - 假定了输入为conll的格式,以空行隔开两个句子,每行共7列,即 - :: - - 1 编者按 编者按 NN O 11 nmod:topic - 2 : : PU O 11 punct - 3 7月 7月 NT DATE 4 compound:nn - 4 12日 12日 NT DATE 11 nmod:tmod - 5 , , PU O 11 punct - - 1 这 这 DT O 3 det - 2 款 款 M O 1 mark:clf - 3 飞行 飞行 NN O 8 nsubj - 4 从 从 P O 5 case - 5 外型 外型 NN O 8 nmod:prep - - """ - datalist = [] - with open(path, 'r', encoding='utf-8') as f: - sample = [] - for line in f: - if line.startswith('\n'): - datalist.append(sample) - sample = [] - elif line.startswith('#'): - continue - else: - sample.append(line.strip().split()) - if len(sample) > 0: - datalist.append(sample) - + + def __init__(self, headers, indexes=None, dropna=False): + super(ConllLoader, self).__init__() + if not isinstance(headers, (list, tuple)): + raise TypeError( + 'invalid headers: {}, should be list of strings'.format(headers)) + self.headers = headers + self.dropna = dropna + if indexes is None: + self.indexes = list(range(len(self.headers))) + else: + if len(indexes) != len(headers): + raise ValueError + self.indexes = indexes + + def _load(self, path): ds = DataSet() - for sample in datalist: - # print(sample) - res = self.get_char_lst(sample) - if res is None: - continue - line = ' '.join(res) - if cut_long_sent: - sents = cut_long_sentence(line) - else: - sents = [line] - for raw_sentence in sents: - ds.append(Instance(raw_sentence=raw_sentence)) + for idx, data in _read_conll(path, indexes=self.indexes, dropna=self.dropna): + ins = {h: data[i] for i, h in enumerate(self.headers)} + ds.append(Instance(**ins)) return ds - def get_char_lst(self, sample): - if len(sample) == 0: - return None - text = [] - for w in sample: - t1, t2, t3, t4 = w[1], w[3], w[6], w[7] - if t3 == '_': - return None - text.append(t1) - return text - -class NaiveCWSReader(DataSetLoader): +class Conll2003Loader(ConllLoader): """ - 这个reader假设了分词数据集为以下形式, 即已经用空格分割好内容了 - 例如:: - - 这是 fastNLP , 一个 非常 good 的 包 . - - 或者,即每个part后面还有一个pos tag - 例如:: + 别名::class:`fastNLP.io.Conll2003Loader` :class:`fastNLP.io.dataset_loader.Conll2003Loader` - 也/D 在/P 團員/Na 之中/Ng ,/COMMACATEGORY + 读取Conll2003数据 + 关于数据集的更多信息,参考: + https://sites.google.com/site/ermasoftware/getting-started/ne-tagging-conll2003-data """ - - def __init__(self, in_word_splitter=None): - super(NaiveCWSReader, self).__init__() - self.in_word_splitter = in_word_splitter - - def load(self, filepath, in_word_splitter=None, cut_long_sent=False): - """ - 允许使用的情况有(默认以\t或空格作为seg) - 这是 fastNLP , 一个 非常 good 的 包 . - 和 - 也/D 在/P 團員/Na 之中/Ng ,/COMMACATEGORY - 如果splitter不为None则认为是第二种情况, 且我们会按splitter分割"也/D", 然后取第一部分. 例如"也/D".split('/')[0] - - :param filepath: - :param in_word_splitter: - :param cut_long_sent: - :return: - """ - if in_word_splitter == None: - in_word_splitter = self.in_word_splitter - dataset = DataSet() - with open(filepath, 'r') as f: - for line in f: - line = line.strip() - if len(line.replace(' ', '')) == 0: # 不能接受空行 - continue - - if not in_word_splitter is None: - words = [] - for part in line.split(): - word = part.split(in_word_splitter)[0] - words.append(word) - line = ' '.join(words) - if cut_long_sent: - sents = cut_long_sentence(line) - else: - sents = [line] - for sent in sents: - instance = Instance(raw_sentence=sent) - dataset.append(instance) - - return dataset + + def __init__(self): + headers = [ + 'tokens', 'pos', 'chunks', 'ner', + ] + super(Conll2003Loader, self).__init__(headers=headers) -def cut_long_sentence(sent, max_sample_length=200): +def _cut_long_sentence(sent, max_sample_length=200): """ - 将长于max_sample_length的sentence截成多段,只会在有空格的地方发生截断。所以截取的句子可能长于或者短于max_sample_length + 将长于max_sample_length的sentence截成多段,只会在有空格的地方发生截断。 + 所以截取的句子可能长于或者短于max_sample_length :param sent: str. :param max_sample_length: int. @@ -697,136 +354,161 @@ def cut_long_sentence(sent, max_sample_length=200): return cutted_sentence -class ZhConllPOSReader(object): - """读取中文Conll格式。返回“字级别”的标签,使用BMES记号扩展原来的词级别标签。 - +class SSTLoader(DataSetLoader): """ - def __init__(self): - pass + 别名::class:`fastNLP.io.SSTLoader` :class:`fastNLP.io.dataset_loader.SSTLoader` + + 读取SST数据集, DataSet包含fields:: - def load(self, path): + words: list(str) 需要分类的文本 + target: str 文本的标签 + + 数据来源: https://nlp.stanford.edu/sentiment/trainDevTestTrees_PTB.zip + + :param subtree: 是否将数据展开为子树,扩充数据量. Default: ``False`` + :param fine_grained: 是否使用SST-5标准,若 ``False`` , 使用SST-2。Default: ``False`` + """ + + def __init__(self, subtree=False, fine_grained=False): + self.subtree = subtree + + tag_v = {'0': 'very negative', '1': 'negative', '2': 'neutral', + '3': 'positive', '4': 'very positive'} + if not fine_grained: + tag_v['0'] = tag_v['1'] + tag_v['4'] = tag_v['3'] + self.tag_v = tag_v + + def _load(self, path): """ - 返回的DataSet, 包含以下的field - words:list of str, - tag: list of str, 被加入了BMES tag, 比如原来的序列为['VP', 'NN', 'NN', ..],会被认为是["S-VP", "B-NN", "M-NN",..] - 假定了输入为conll的格式,以空行隔开两个句子,每行共7列,即 - :: - - 1 编者按 编者按 NN O 11 nmod:topic - 2 : : PU O 11 punct - 3 7月 7月 NT DATE 4 compound:nn - 4 12日 12日 NT DATE 11 nmod:tmod - 5 , , PU O 11 punct - - 1 这 这 DT O 3 det - 2 款 款 M O 1 mark:clf - 3 飞行 飞行 NN O 8 nsubj - 4 从 从 P O 5 case - 5 外型 外型 NN O 8 nmod:prep + :param str path: 存储数据的路径 + :return: 一个 :class:`~fastNLP.DataSet` 类型的对象 """ datalist = [] with open(path, 'r', encoding='utf-8') as f: - sample = [] - for line in f: - if line.startswith('\n'): - datalist.append(sample) - sample = [] - elif line.startswith('#'): - continue - else: - sample.append(line.split('\t')) - if len(sample) > 0: - datalist.append(sample) - + datas = [] + for l in f: + datas.extend([(s, self.tag_v[t]) + for s, t in self._get_one(l, self.subtree)]) ds = DataSet() - for sample in datalist: - # print(sample) - res = self.get_one(sample) - if res is None: - continue - char_seq = [] - pos_seq = [] - for word, tag in zip(res[0], res[1]): - char_seq.extend(list(word)) - if len(word) == 1: - pos_seq.append('S-{}'.format(tag)) - elif len(word) > 1: - pos_seq.append('B-{}'.format(tag)) - for _ in range(len(word) - 2): - pos_seq.append('M-{}'.format(tag)) - pos_seq.append('E-{}'.format(tag)) - else: - raise ValueError("Zero length of word detected.") + for words, tag in datas: + ds.append(Instance(words=words, target=tag)) + return ds + + @staticmethod + def _get_one(data, subtree): + tree = Tree.fromstring(data) + if subtree: + return [(t.leaves(), t.label()) for t in tree.subtrees()] + return [(tree.leaves(), tree.label())] - ds.append(Instance(words=char_seq, - tag=pos_seq)) +class JsonLoader(DataSetLoader): + """ + 别名::class:`fastNLP.io.JsonLoader` :class:`fastNLP.io.dataset_loader.JsonLoader` + + 读取json格式数据.数据必须按行存储,每行是一个包含各类属性的json对象 + + :param dict fields: 需要读入的json属性名称, 和读入后在DataSet中存储的field_name + ``fields`` 的 `key` 必须是json对象的属性名. ``fields`` 的 `value` 为读入后在DataSet存储的 `field_name` , + `value` 也可为 ``None`` , 这时读入后的 `field_name` 与json对象对应属性同名 + ``fields`` 可为 ``None`` , 这时,json对象所有属性都保存在DataSet中. Default: ``None`` + :param bool dropna: 是否忽略非法数据,若 ``True`` 则忽略,若 ``False`` ,在遇到非法数据时,抛出 ``ValueError`` . + Default: ``False`` + """ + + def __init__(self, fields=None, dropna=False): + super(JsonLoader, self).__init__() + self.dropna = dropna + self.fields = None + self.fields_list = None + if fields: + self.fields = {} + for k, v in fields.items(): + self.fields[k] = k if v is None else v + self.fields_list = list(self.fields.keys()) + + def _load(self, path): + ds = DataSet() + for idx, d in _read_json(path, fields=self.fields_list, dropna=self.dropna): + if self.fields: + ins = {self.fields[k]: v for k, v in d.items()} + else: + ins = d + ds.append(Instance(**ins)) return ds - def get_one(self, sample): - if len(sample) == 0: - return None - text = [] - pos_tags = [] - for w in sample: - t1, t2, t3, t4 = w[1], w[3], w[6], w[7] - if t3 == '_': - return None - text.append(t1) - pos_tags.append(t2) - return text, pos_tags +class SNLILoader(JsonLoader): + """ + 别名::class:`fastNLP.io.SNLILoader` :class:`fastNLP.io.dataset_loader.SNLILoader` -class ConllxDataLoader(object): - """返回“词级别”的标签信息,包括词、词性、(句法)头依赖、(句法)边标签。跟``ZhConllPOSReader``完全不同。 + 读取SNLI数据集,读取的DataSet包含fields:: + words1: list(str),第一句文本, premise + words2: list(str), 第二句文本, hypothesis + target: str, 真实标签 + + 数据来源: https://nlp.stanford.edu/projects/snli/snli_1.0.zip """ - def load(self, path): - datalist = [] - with open(path, 'r', encoding='utf-8') as f: - sample = [] - for line in f: - if line.startswith('\n'): - datalist.append(sample) - sample = [] - elif line.startswith('#'): - continue - else: - sample.append(line.split('\t')) - if len(sample) > 0: - datalist.append(sample) + + def __init__(self): + fields = { + 'sentence1_parse': 'words1', + 'sentence2_parse': 'words2', + 'gold_label': 'target', + } + super(SNLILoader, self).__init__(fields=fields) + + def _load(self, path): + ds = super(SNLILoader, self)._load(path) + + def parse_tree(x): + t = Tree.fromstring(x) + return t.leaves() + + ds.apply(lambda ins: parse_tree( + ins['words1']), new_field_name='words1') + ds.apply(lambda ins: parse_tree( + ins['words2']), new_field_name='words2') + ds.drop(lambda x: x['target'] == '-') + return ds + + +class CSVLoader(DataSetLoader): + """ + 别名::class:`fastNLP.io.CSVLoader` :class:`fastNLP.io.dataset_loader.CSVLoader` - data = [self.get_one(sample) for sample in datalist] - data_list = list(filter(lambda x: x is not None, data)) + 读取CSV格式的数据集。返回 ``DataSet`` + :param List[str] headers: CSV文件的文件头.定义每一列的属性名称,即返回的DataSet中`field`的名称 + 若为 ``None`` ,则将读入文件的第一行视作 ``headers`` . Default: ``None`` + :param str sep: CSV文件中列与列之间的分隔符. Default: "," + :param bool dropna: 是否忽略非法数据,若 ``True`` 则忽略,若 ``False`` ,在遇到非法数据时,抛出 ``ValueError`` . + Default: ``False`` + """ + + def __init__(self, headers=None, sep=",", dropna=False): + self.headers = headers + self.sep = sep + self.dropna = dropna + + def _load(self, path): ds = DataSet() - for example in data_list: - ds.append(Instance(words=example[0], - pos_tags=example[1], - heads=example[2], - labels=example[3])) + for idx, data in _read_csv(path, headers=self.headers, + sep=self.sep, dropna=self.dropna): + ds.append(Instance(**data)) return ds - def get_one(self, sample): - sample = list(map(list, zip(*sample))) - if len(sample) == 0: - return None - for w in sample[7]: - if w == '_': - print('Error Sample {}'.format(sample)) - return None - # return word_seq, pos_seq, head_seq, head_tag_seq - return sample[1], sample[3], list(map(int, sample[6])), sample[7] - -def add_seg_tag(data): +def _add_seg_tag(data): """ :param data: list of ([word], [pos], [heads], [head_tags]) :return: list of ([word], [pos]) """ - + _processed = [] for word_list, pos_list, _, _ in data: new_sample = [] diff --git a/fastNLP/io/embed_loader.py b/fastNLP/io/embed_loader.py index 1615fb7f..fb024e73 100644 --- a/fastNLP/io/embed_loader.py +++ b/fastNLP/io/embed_loader.py @@ -1,126 +1,155 @@ +__all__ = [ + "EmbedLoader" +] + +import os +import warnings + import numpy as np -import torch -from fastNLP.core.vocabulary import Vocabulary -from fastNLP.io.base_loader import BaseLoader +from ..core.vocabulary import Vocabulary +from .base_loader import BaseLoader class EmbedLoader(BaseLoader): - """docstring for EmbedLoader""" + """ + 别名::class:`fastNLP.io.EmbedLoader` :class:`fastNLP.io.embed_loader.EmbedLoader` + 用于读取预训练的embedding, 读取结果可直接载入为模型参数。 + """ + def __init__(self): super(EmbedLoader, self).__init__() - + @staticmethod - def _load_glove(emb_file): - """Read file as a glove embedding - - file format: - embeddings are split by line, - for one embedding, word and numbers split by space - Example:: - - word_1 float_1 float_2 ... float_emb_dim - word_2 float_1 float_2 ... float_emb_dim - ... + def load_with_vocab(embed_filepath, vocab, dtype=np.float32, normalize=True, error='ignore'): """ - emb = {} - with open(emb_file, 'r', encoding='utf-8') as f: - for line in f: - line = list(filter(lambda w: len(w) > 0, line.strip().split(' '))) - if len(line) > 2: - emb[line[0]] = torch.Tensor(list(map(float, line[1:]))) - return emb - - @staticmethod - def _load_pretrain(emb_file, emb_type): - """Read txt data from embedding file and convert to np.array as pre-trained embedding - - :param str emb_file: the pre-trained embedding file path - :param str emb_type: the pre-trained embedding data format - :return: a dict of ``{str: np.array}`` + 从embed_filepath这个预训练的词向量中抽取出vocab这个词表的词的embedding。EmbedLoader将自动判断embed_filepath是 + word2vec(第一行只有两个元素)还是glove格式的数据。 + + :param str embed_filepath: 预训练的embedding的路径。 + :param vocab: 词表 :class:`~fastNLP.Vocabulary` 类型,读取出现在vocab中的词的embedding。 + 没有出现在vocab中的词的embedding将通过找到的词的embedding的正态分布采样出来,以使得整个Embedding是同分布的。 + :param dtype: 读出的embedding的类型 + :param bool normalize: 是否将每个vector归一化到norm为1 + :param str error: `ignore` , `strict` ; 如果 `ignore` ,错误将自动跳过; 如果 `strict` , 错误将抛出。 + 这里主要可能出错的地方在于词表有空行或者词表出现了维度不一致。 + :return numpy.ndarray: shape为 [len(vocab), dimension], dimension由pretrain的embedding决定。 """ - if emb_type == 'glove': - return EmbedLoader._load_glove(emb_file) - else: - raise Exception("embedding type {} not support yet".format(emb_type)) - + assert isinstance(vocab, Vocabulary), "Only fastNLP.Vocabulary is supported." + if not os.path.exists(embed_filepath): + raise FileNotFoundError("`{}` does not exist.".format(embed_filepath)) + with open(embed_filepath, 'r', encoding='utf-8') as f: + hit_flags = np.zeros(len(vocab), dtype=bool) + line = f.readline().strip() + parts = line.split() + start_idx = 0 + if len(parts) == 2: + dim = int(parts[1]) + start_idx += 1 + else: + dim = len(parts) - 1 + f.seek(0) + matrix = np.random.randn(len(vocab), dim).astype(dtype) + for idx, line in enumerate(f, start_idx): + try: + parts = line.strip().split() + if parts[0] in vocab: + index = vocab.to_index(parts[0]) + matrix[index] = np.fromstring(' '.join(parts[1:]), sep=' ', dtype=dtype, count=dim) + hit_flags[index] = True + except Exception as e: + if error == 'ignore': + warnings.warn("Error occurred at the {} line.".format(idx)) + else: + print("Error occurred at the {} line.".format(idx)) + raise e + total_hits = sum(hit_flags) + print("Found {} out of {} words in the pre-training embedding.".format(total_hits, len(vocab))) + found_vectors = matrix[hit_flags] + if len(found_vectors) != 0: + mean = np.mean(found_vectors, axis=0, keepdims=True) + std = np.std(found_vectors, axis=0, keepdims=True) + unfound_vec_num = len(vocab) - total_hits + r_vecs = np.random.randn(unfound_vec_num, dim).astype(dtype) * std + mean + matrix[hit_flags == False] = r_vecs + + if normalize: + matrix /= np.linalg.norm(matrix, axis=1, keepdims=True) + + return matrix + @staticmethod - def load_embedding(emb_dim, emb_file, emb_type, vocab): - """Load the pre-trained embedding and combine with the given dictionary. - - :param int emb_dim: the dimension of the embedding. Should be the same as pre-trained embedding. - :param str emb_file: the pre-trained embedding file path. - :param str emb_type: the pre-trained embedding format, support glove now - :param Vocabulary vocab: a mapping from word to index, can be provided by user or built from pre-trained embedding - :return (embedding_tensor, vocab): - embedding_tensor - Tensor of shape (len(word_dict), emb_dim); - vocab - input vocab or vocab built by pre-train - + def load_without_vocab(embed_filepath, dtype=np.float32, padding='', unknown='', normalize=True, + error='ignore'): """ - pretrain = EmbedLoader._load_pretrain(emb_file, emb_type) - if vocab is None: - # build vocabulary from pre-trained embedding - vocab = Vocabulary() - for w in pretrain.keys(): - vocab.add(w) - embedding_tensor = torch.randn(len(vocab), emb_dim) - for w, v in pretrain.items(): - if len(v.shape) > 1 or emb_dim != v.shape[0]: - raise ValueError( - "Pretrained embedding dim is {}. Dimension dismatched. Required {}".format(v.shape, (emb_dim,))) - if vocab.has_word(w): - embedding_tensor[vocab[w]] = v - return embedding_tensor, vocab - - @staticmethod - def parse_glove_line(line): - line = line.split() - if len(line) <= 2: - raise RuntimeError("something goes wrong in parsing glove embedding") - return line[0], line[1:] - - @staticmethod - def str_list_2_vec(line): - try: - return torch.Tensor(list(map(float, line))) - except Exception: - raise RuntimeError("something goes wrong in parsing glove embedding") - - - @staticmethod - def fast_load_embedding(emb_dim, emb_file, vocab): - """Fast load the pre-trained embedding and combine with the given dictionary. - This loading method uses line-by-line operation. - - :param int emb_dim: the dimension of the embedding. Should be the same as pre-trained embedding. - :param str emb_file: the pre-trained embedding file path. - :param Vocabulary vocab: a mapping from word to index, can be provided by user or built from pre-trained embedding - :return embedding_matrix: numpy.ndarray - + 从embed_filepath中读取预训练的word vector。根据预训练的词表读取embedding并生成一个对应的Vocabulary。 + + :param str embed_filepath: 预训练的embedding的路径。 + :param dtype: 读出的embedding的类型 + :param str padding: the padding tag for vocabulary. + :param str unknown: the unknown tag for vocabulary. + :param bool normalize: 是否将每个vector归一化到norm为1 + :param str error: `ignore` , `strict` ; 如果 `ignore` ,错误将自动跳过; 如果 `strict` , 错误将抛出。这里主要可能出错的地 + 方在于词表有空行或者词表出现了维度不一致。 + :return numpy.ndarray: shape为 [len(vocab), dimension], dimension由pretrain的embedding决定。 + :return numpy.ndarray: Vocabulary Embedding的shape是[词表大小+x, 词表维度], "词表大小+x"是由于最终的大小还取决与 + 是否使用padding, 以及unknown有没有在词表中找到对应的词。 Vocabulary中的词的顺序与Embedding的顺序是一一对应的。 """ - if vocab is None: - raise RuntimeError("You must provide a vocabulary.") - embedding_matrix = np.zeros(shape=(len(vocab), emb_dim), dtype=np.float32) - hit_flags = np.zeros(shape=(len(vocab),), dtype=int) - with open(emb_file, "r", encoding="utf-8") as f: - startline = f.readline() - if len(startline.split()) > 2: + vocab = Vocabulary(padding=padding, unknown=unknown) + vec_dict = {} + found_unknown = False + found_pad = False + + with open(embed_filepath, 'r', encoding='utf-8') as f: + line = f.readline() + start = 1 + dim = -1 + if len(line.strip().split()) != 2: f.seek(0) - for line in f: - word, vector = EmbedLoader.parse_glove_line(line) - if word in vocab: - vector = EmbedLoader.str_list_2_vec(vector) - if len(vector.shape) > 1 or emb_dim != vector.shape[0]: - raise ValueError("Pre-trained embedding dim is {}. Expect {}.".format(vector.shape, (emb_dim,))) - embedding_matrix[vocab[word]] = vector - hit_flags[vocab[word]] = 1 - - if np.sum(hit_flags) < len(vocab): - # some words from vocab are missing in pre-trained embedding - # we normally sample each dimension - vocab_embed = embedding_matrix[np.where(hit_flags)] - sampled_vectors = np.random.normal(vocab_embed.mean(axis=0), vocab_embed.std(axis=0), - size=(len(vocab) - np.sum(hit_flags), emb_dim)) - embedding_matrix[np.where(1 - hit_flags)] = sampled_vectors - return embedding_matrix + start = 0 + for idx, line in enumerate(f, start=start): + try: + parts = line.strip().split() + word = parts[0] + if dim == -1: + dim = len(parts) - 1 + vec = np.fromstring(' '.join(parts[1:]), sep=' ', dtype=dtype, count=dim) + vec_dict[word] = vec + vocab.add_word(word) + if unknown is not None and unknown == word: + found_unknown = True + if found_pad is not None and padding == word: + found_pad = True + except Exception as e: + if error == 'ignore': + warnings.warn("Error occurred at the {} line.".format(idx)) + pass + else: + print("Error occurred at the {} line.".format(idx)) + raise e + if dim == -1: + raise RuntimeError("{} is an empty file.".format(embed_filepath)) + matrix = np.random.randn(len(vocab), dim).astype(dtype) + if (unknown is not None and not found_unknown) or (padding is not None and not found_pad): + start_idx = 0 + if padding is not None: + start_idx += 1 + if unknown is not None: + start_idx += 1 + + mean = np.mean(matrix[start_idx:], axis=0, keepdims=True) + std = np.std(matrix[start_idx:], axis=0, keepdims=True) + if (unknown is not None and not found_unknown): + matrix[start_idx - 1] = np.random.randn(1, dim).astype(dtype) * std + mean + if (padding is not None and not found_pad): + matrix[0] = np.random.randn(1, dim).astype(dtype) * std + mean + + for key, vec in vec_dict.items(): + index = vocab.to_index(key) + matrix[index] = vec + + if normalize: + matrix /= np.linalg.norm(matrix, axis=1, keepdims=True) + + return matrix, vocab diff --git a/fastNLP/io/file_reader.py b/fastNLP/io/file_reader.py new file mode 100644 index 00000000..5963bb56 --- /dev/null +++ b/fastNLP/io/file_reader.py @@ -0,0 +1,118 @@ +""" +此模块用于给其它模块提供读取文件的函数,没有为用户提供 API +""" +import json + + +def _read_csv(path, encoding='utf-8', headers=None, sep=',', dropna=True): + """ + Construct a generator to read csv items. + + :param path: file path + :param encoding: file's encoding, default: utf-8 + :param headers: file's headers, if None, make file's first line as headers. default: None + :param sep: separator for each column. default: ',' + :param dropna: weather to ignore and drop invalid data, + :if False, raise ValueError when reading invalid data. default: True + :return: generator, every time yield (line number, csv item) + """ + with open(path, 'r', encoding=encoding) as f: + start_idx = 0 + if headers is None: + headers = f.readline().rstrip('\r\n') + headers = headers.split(sep) + start_idx += 1 + elif not isinstance(headers, (list, tuple)): + raise TypeError("headers should be list or tuple, not {}." \ + .format(type(headers))) + for line_idx, line in enumerate(f, start_idx): + contents = line.rstrip('\r\n').split(sep) + if len(contents) != len(headers): + if dropna: + continue + else: + raise ValueError("Line {} has {} parts, while header has {} parts." \ + .format(line_idx, len(contents), len(headers))) + _dict = {} + for header, content in zip(headers, contents): + _dict[header] = content + yield line_idx, _dict + + +def _read_json(path, encoding='utf-8', fields=None, dropna=True): + """ + Construct a generator to read json items. + + :param path: file path + :param encoding: file's encoding, default: utf-8 + :param fields: json object's fields that needed, if None, all fields are needed. default: None + :param dropna: weather to ignore and drop invalid data, + :if False, raise ValueError when reading invalid data. default: True + :return: generator, every time yield (line number, json item) + """ + if fields: + fields = set(fields) + with open(path, 'r', encoding=encoding) as f: + for line_idx, line in enumerate(f): + data = json.loads(line) + if fields is None: + yield line_idx, data + continue + _res = {} + for k, v in data.items(): + if k in fields: + _res[k] = v + if len(_res) < len(fields): + if dropna: + continue + else: + raise ValueError('invalid instance at line: {}'.format(line_idx)) + yield line_idx, _res + + +def _read_conll(path, encoding='utf-8', indexes=None, dropna=True): + """ + Construct a generator to read conll items. + + :param path: file path + :param encoding: file's encoding, default: utf-8 + :param indexes: conll object's column indexes that needed, if None, all columns are needed. default: None + :param dropna: weather to ignore and drop invalid data, + :if False, raise ValueError when reading invalid data. default: True + :return: generator, every time yield (line number, conll item) + """ + def parse_conll(sample): + sample = list(map(list, zip(*sample))) + sample = [sample[i] for i in indexes] + for f in sample: + if len(f) <= 0: + raise ValueError('empty field') + return sample + with open(path, 'r', encoding=encoding) as f: + sample = [] + start = next(f) + if '-DOCSTART-' not in start: + sample.append(start.split()) + for line_idx, line in enumerate(f, 1): + if line.startswith('\n'): + if len(sample): + try: + res = parse_conll(sample) + sample = [] + yield line_idx, res + except Exception as e: + if dropna: + continue + raise ValueError('invalid instance at line: {}'.format(line_idx)) + elif line.startswith('#'): + continue + else: + sample.append(line.split()) + if len(sample) > 0: + try: + res = parse_conll(sample) + yield line_idx, res + except Exception as e: + if dropna: + return + raise ValueError('invalid instance at line: {}'.format(line_idx)) diff --git a/fastNLP/io/logger.py b/fastNLP/io/logger.py deleted file mode 100644 index 9e9730db..00000000 --- a/fastNLP/io/logger.py +++ /dev/null @@ -1,35 +0,0 @@ -import logging -import os - - -def create_logger(logger_name, log_path, log_format=None, log_level=logging.INFO): - """Create a logger. - - :param str logger_name: - :param str log_path: - :param log_format: - :param log_level: - :return: logger - - To use a logger:: - - logger.debug("this is a debug message") - logger.info("this is a info message") - logger.warning("this is a warning message") - logger.error("this is an error message") - """ - logger = logging.getLogger(logger_name) - logger.setLevel(log_level) - if log_path is None: - handler = logging.StreamHandler() - else: - os.stat(os.path.dirname(os.path.abspath(log_path))) - handler = logging.FileHandler(log_path) - handler.setLevel(log_level) - if log_format is None: - log_format = "[%(asctime)s %(name)-13s %(levelname)s %(process)d %(thread)d " \ - "%(filename)s:%(lineno)-5d] %(message)s" - formatter = logging.Formatter(log_format) - handler.setFormatter(formatter) - logger.addHandler(handler) - return logger diff --git a/fastNLP/io/model_io.py b/fastNLP/io/model_io.py index 422eb919..ffaa4ef5 100644 --- a/fastNLP/io/model_io.py +++ b/fastNLP/io/model_io.py @@ -1,53 +1,72 @@ +""" +用于载入和保存模型 +""" +__all__ = [ + "ModelLoader", + "ModelSaver" +] + import torch -from fastNLP.io.base_loader import BaseLoader +from .base_loader import BaseLoader class ModelLoader(BaseLoader): """ - Loader for models. - """ + 别名::class:`fastNLP.io.ModelLoader` :class:`fastNLP.io.model_io.ModelLoader` + 用于读取模型 + """ + def __init__(self): super(ModelLoader, self).__init__() - + @staticmethod def load_pytorch(empty_model, model_path): - """Load model parameters from ".pkl" files into the empty PyTorch model. + """ + 从 ".pkl" 文件读取 PyTorch 模型 - :param empty_model: a PyTorch model with initialized parameters. - :param str model_path: the path to the saved model. + :param empty_model: 初始化参数的 PyTorch 模型 + :param str model_path: 模型保存的路径 """ empty_model.load_state_dict(torch.load(model_path)) - + @staticmethod def load_pytorch_model(model_path): - """Load the entire model. + """ + 读取整个模型 - :param str model_path: the path to the saved model. + :param str model_path: 模型保存的路径 """ return torch.load(model_path) class ModelSaver(object): - """Save a model + """ + 别名::class:`fastNLP.io.ModelSaver` :class:`fastNLP.io.model_io.ModelSaver` - :param str save_path: the path to the saving directory. - Example:: + 用于保存模型 + + Example:: - saver = ModelSaver("./save/model_ckpt_100.pkl") - saver.save_pytorch(model) + saver = ModelSaver("./save/model_ckpt_100.pkl") + saver.save_pytorch(model) """ - + def __init__(self, save_path): - self.save_path = save_path + """ + :param save_path: 模型保存的路径 + """ + self.save_path = save_path + def save_pytorch(self, model, param_only=True): - """Save a pytorch model into ".pkl" file. + """ + 把 PyTorch 模型存入 ".pkl" 文件 - :param model: a PyTorch model - :param bool param_only: whether only to save the model parameters or the entire model. + :param model: PyTorch 模型 + :param bool param_only: 是否只保存模型的参数(否则保存整个模型) """ if param_only is True: diff --git a/fastNLP/models/__init__.py b/fastNLP/models/__init__.py index a83c3936..14314049 100644 --- a/fastNLP/models/__init__.py +++ b/fastNLP/models/__init__.py @@ -1,6 +1,34 @@ +""" +fastNLP 在 :mod:`~fastNLP.models` 模块中内置了如 :class:`~fastNLP.models.CNNText` 、 +:class:`~fastNLP.models.SeqLabeling` 等完整的模型,以供用户直接使用。 + +.. todo:: + 这些模型的介绍(与主页一致) + + +""" +__all__ = [ + "CNNText", + + "SeqLabeling", + "AdvSeqLabel", + + "ESIM", + + "StarTransEnc", + "STSeqLabel", + "STNLICls", + "STSeqCls", + + "BiaffineParser", + "GraphParser" +] + from .base_model import BaseModel +from .bert import BertForMultipleChoice, BertForQuestionAnswering, BertForSequenceClassification, \ + BertForTokenClassification from .biaffine_parser import BiaffineParser, GraphParser -from .char_language_model import CharLM from .cnn_text_classification import CNNText -from .sequence_modeling import SeqLabeling, AdvSeqLabel +from .sequence_labeling import SeqLabeling, AdvSeqLabel from .snli import ESIM +from .star_transformer import StarTransEnc, STSeqCls, STNLICls, STSeqLabel diff --git a/fastNLP/models/base_model.py b/fastNLP/models/base_model.py index ec532014..2646d580 100644 --- a/fastNLP/models/base_model.py +++ b/fastNLP/models/base_model.py @@ -1,18 +1,18 @@ import torch -from fastNLP.modules.decoder.MLP import MLP +from ..modules.decoder.mlp import MLP class BaseModel(torch.nn.Module): """Base PyTorch model for all models. """ - + def __init__(self): super(BaseModel, self).__init__() - + def fit(self, train_data, dev_data=None, **train_args): pass - + def predict(self, *args, **kwargs): raise NotImplementedError @@ -21,9 +21,9 @@ class NaiveClassifier(BaseModel): def __init__(self, in_feature_dim, out_feature_dim): super(NaiveClassifier, self).__init__() self.mlp = MLP([in_feature_dim, in_feature_dim, out_feature_dim]) - + def forward(self, x): return {"predict": torch.sigmoid(self.mlp(x))} - + def predict(self, x): return {"predict": torch.sigmoid(self.mlp(x)) > 0.5} diff --git a/fastNLP/models/bert.py b/fastNLP/models/bert.py index e87f6f5d..960132ad 100644 --- a/fastNLP/models/bert.py +++ b/fastNLP/models/bert.py @@ -2,361 +2,292 @@ bert.py is modified from huggingface/pytorch-pretrained-BERT, which is licensed under the Apache License 2.0. """ -import copy -import json -import math -import os - import torch from torch import nn -CONFIG_FILE = 'bert_config.json' -MODEL_WEIGHTS = 'pytorch_model.bin' - - -def gelu(x): - return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0))) - - -def swish(x): - return x * torch.sigmoid(x) - - -ACT2FN = {"gelu": gelu, "relu": torch.nn.functional.relu, "swish": swish} - - -class BertLayerNorm(nn.Module): - def __init__(self, hidden_size, eps=1e-12): - super(BertLayerNorm, self).__init__() - self.weight = nn.Parameter(torch.ones(hidden_size)) - self.bias = nn.Parameter(torch.zeros(hidden_size)) - self.variance_epsilon = eps - - def forward(self, x): - u = x.mean(-1, keepdim=True) - s = (x - u).pow(2).mean(-1, keepdim=True) - x = (x - u) / torch.sqrt(s + self.variance_epsilon) - return self.weight * x + self.bias - - -class BertEmbeddings(nn.Module): - def __init__(self, vocab_size, hidden_size, max_position_embeddings, type_vocab_size, hidden_dropout_prob): - super(BertEmbeddings, self).__init__() - self.word_embeddings = nn.Embedding(vocab_size, hidden_size) - self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size) - self.token_type_embeddings = nn.Embedding(type_vocab_size, hidden_size) - - # self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load - # any TensorFlow checkpoint file - self.LayerNorm = BertLayerNorm(hidden_size, eps=1e-12) - self.dropout = nn.Dropout(hidden_dropout_prob) - - def forward(self, input_ids, token_type_ids=None): - seq_length = input_ids.size(1) - position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device) - position_ids = position_ids.unsqueeze(0).expand_as(input_ids) - if token_type_ids is None: - token_type_ids = torch.zeros_like(input_ids) - - words_embeddings = self.word_embeddings(input_ids) - position_embeddings = self.position_embeddings(position_ids) - token_type_embeddings = self.token_type_embeddings(token_type_ids) - - embeddings = words_embeddings + position_embeddings + token_type_embeddings - embeddings = self.LayerNorm(embeddings) - embeddings = self.dropout(embeddings) - return embeddings - - -class BertSelfAttention(nn.Module): - def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob): - super(BertSelfAttention, self).__init__() - if hidden_size % num_attention_heads != 0: - raise ValueError( - "The hidden size (%d) is not a multiple of the number of attention " - "heads (%d)" % (hidden_size, num_attention_heads)) - self.num_attention_heads = num_attention_heads - self.attention_head_size = int(hidden_size / num_attention_heads) - self.all_head_size = self.num_attention_heads * self.attention_head_size - - self.query = nn.Linear(hidden_size, self.all_head_size) - self.key = nn.Linear(hidden_size, self.all_head_size) - self.value = nn.Linear(hidden_size, self.all_head_size) - - self.dropout = nn.Dropout(attention_probs_dropout_prob) - - def transpose_for_scores(self, x): - new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) - x = x.view(*new_x_shape) - return x.permute(0, 2, 1, 3) - - def forward(self, hidden_states, attention_mask): - mixed_query_layer = self.query(hidden_states) - mixed_key_layer = self.key(hidden_states) - mixed_value_layer = self.value(hidden_states) - - query_layer = self.transpose_for_scores(mixed_query_layer) - key_layer = self.transpose_for_scores(mixed_key_layer) - value_layer = self.transpose_for_scores(mixed_value_layer) - - # Take the dot product between "query" and "key" to get the raw attention scores. - attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) - attention_scores = attention_scores / math.sqrt(self.attention_head_size) - # Apply the attention mask is (precomputed for all layers in BertModel forward() function) - attention_scores = attention_scores + attention_mask - - # Normalize the attention scores to probabilities. - attention_probs = nn.Softmax(dim=-1)(attention_scores) - - # This is actually dropping out entire tokens to attend to, which might - # seem a bit unusual, but is taken from the original Transformer paper. - attention_probs = self.dropout(attention_probs) - - context_layer = torch.matmul(attention_probs, value_layer) - context_layer = context_layer.permute(0, 2, 1, 3).contiguous() - new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) - context_layer = context_layer.view(*new_context_layer_shape) - return context_layer - - -class BertSelfOutput(nn.Module): - def __init__(self, hidden_size, hidden_dropout_prob): - super(BertSelfOutput, self).__init__() - self.dense = nn.Linear(hidden_size, hidden_size) - self.LayerNorm = BertLayerNorm(hidden_size, eps=1e-12) - self.dropout = nn.Dropout(hidden_dropout_prob) - - def forward(self, hidden_states, input_tensor): - hidden_states = self.dense(hidden_states) - hidden_states = self.dropout(hidden_states) - hidden_states = self.LayerNorm(hidden_states + input_tensor) - return hidden_states - - -class BertAttention(nn.Module): - def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob): - super(BertAttention, self).__init__() - self.self = BertSelfAttention(hidden_size, num_attention_heads, attention_probs_dropout_prob) - self.output = BertSelfOutput(hidden_size, hidden_dropout_prob) - - def forward(self, input_tensor, attention_mask): - self_output = self.self(input_tensor, attention_mask) - attention_output = self.output(self_output, input_tensor) - return attention_output - - -class BertIntermediate(nn.Module): - def __init__(self, hidden_size, intermediate_size, hidden_act): - super(BertIntermediate, self).__init__() - self.dense = nn.Linear(hidden_size, intermediate_size) - self.intermediate_act_fn = ACT2FN[hidden_act] \ - if isinstance(hidden_act, str) else hidden_act - - def forward(self, hidden_states): - hidden_states = self.dense(hidden_states) - hidden_states = self.intermediate_act_fn(hidden_states) - return hidden_states - - -class BertOutput(nn.Module): - def __init__(self, hidden_size, intermediate_size, hidden_dropout_prob): - super(BertOutput, self).__init__() - self.dense = nn.Linear(intermediate_size, hidden_size) - self.LayerNorm = BertLayerNorm(hidden_size, eps=1e-12) - self.dropout = nn.Dropout(hidden_dropout_prob) - - def forward(self, hidden_states, input_tensor): - hidden_states = self.dense(hidden_states) - hidden_states = self.dropout(hidden_states) - hidden_states = self.LayerNorm(hidden_states + input_tensor) - return hidden_states - - -class BertLayer(nn.Module): - def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob, - intermediate_size, hidden_act): - super(BertLayer, self).__init__() - self.attention = BertAttention(hidden_size, num_attention_heads, attention_probs_dropout_prob, - hidden_dropout_prob) - self.intermediate = BertIntermediate(hidden_size, intermediate_size, hidden_act) - self.output = BertOutput(hidden_size, intermediate_size, hidden_dropout_prob) - - def forward(self, hidden_states, attention_mask): - attention_output = self.attention(hidden_states, attention_mask) - intermediate_output = self.intermediate(attention_output) - layer_output = self.output(intermediate_output, attention_output) - return layer_output - - -class BertEncoder(nn.Module): - def __init__(self, num_hidden_layers, hidden_size, num_attention_heads, attention_probs_dropout_prob, - hidden_dropout_prob, - intermediate_size, hidden_act): - super(BertEncoder, self).__init__() - layer = BertLayer(hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob, - intermediate_size, hidden_act) - self.layer = nn.ModuleList([copy.deepcopy(layer) for _ in range(num_hidden_layers)]) - - def forward(self, hidden_states, attention_mask, output_all_encoded_layers=True): - all_encoder_layers = [] - for layer_module in self.layer: - hidden_states = layer_module(hidden_states, attention_mask) - if output_all_encoded_layers: - all_encoder_layers.append(hidden_states) - if not output_all_encoded_layers: - all_encoder_layers.append(hidden_states) - return all_encoder_layers - - -class BertPooler(nn.Module): - def __init__(self, hidden_size): - super(BertPooler, self).__init__() - self.dense = nn.Linear(hidden_size, hidden_size) - self.activation = nn.Tanh() - - def forward(self, hidden_states): - # We "pool" the model by simply taking the hidden state corresponding - # to the first token. - first_token_tensor = hidden_states[:, 0] - pooled_output = self.dense(first_token_tensor) - pooled_output = self.activation(pooled_output) - return pooled_output - - -class BertModel(nn.Module): - """Bidirectional Embedding Representations from Transformers. - - If you want to use pre-trained weights, please download from the following sources provided by pytorch-pretrained-BERT. - sources:: - - 'bert-base-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased.tar.gz", - 'bert-large-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-uncased.tar.gz", - 'bert-base-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-cased.tar.gz", - 'bert-large-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-cased.tar.gz", - 'bert-base-multilingual-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-uncased.tar.gz", - 'bert-base-multilingual-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-cased.tar.gz", - 'bert-base-chinese': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese.tar.gz", - - - Construct a BERT model with pre-trained weights:: - - model = BertModel.from_pretrained("path/to/weights/directory") - +from .base_model import BaseModel +from ..core.const import Const +from ..modules.encoder import BertModel + + +class BertForSequenceClassification(BaseModel): + """BERT model for classification. + This module is composed of the BERT model with a linear layer on top of + the pooled output. + Params: + `config`: a BertConfig class instance with the configuration to build a new model. + `num_labels`: the number of classes for the classifier. Default = 2. + Inputs: + `input_ids`: a torch.LongTensor of shape [batch_size, sequence_length] + with the word token indices in the vocabulary. Items in the batch should begin with the special "CLS" token. (see the tokens preprocessing logic in the scripts + `extract_features.py`, `run_classifier.py` and `run_squad.py`) + `token_type_ids`: an optional torch.LongTensor of shape [batch_size, sequence_length] with the token + types indices selected in [0, 1]. Type 0 corresponds to a `sentence A` and type 1 corresponds to + a `sentence B` token (see BERT paper for more details). + `attention_mask`: an optional torch.LongTensor of shape [batch_size, sequence_length] with indices + selected in [0, 1]. It's a mask to be used if the input sequence length is smaller than the max + input sequence length in the current batch. It's the mask that we typically use for attention when + a batch has varying length sentences. + `labels`: labels for the classification output: torch.LongTensor of shape [batch_size] + with indices selected in [0, ..., num_labels]. + Outputs: + if `labels` is not `None`: + Outputs the CrossEntropy classification loss of the output with the labels. + if `labels` is `None`: + Outputs the classification logits of shape [batch_size, num_labels]. + Example usage: + ```python + # Already been converted into WordPiece token ids + input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]]) + input_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]]) + token_type_ids = torch.LongTensor([[0, 0, 1], [0, 1, 0]]) + config = BertConfig(vocab_size_or_config_json_file=32000, hidden_size=768, + num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072) + num_labels = 2 + model = BertForSequenceClassification(config, num_labels) + logits = model(input_ids, token_type_ids, input_mask) + ``` """ - - def __init__(self, vocab_size, - hidden_size=768, - num_hidden_layers=12, - num_attention_heads=12, - intermediate_size=3072, - hidden_act="gelu", - hidden_dropout_prob=0.1, - attention_probs_dropout_prob=0.1, - max_position_embeddings=512, - type_vocab_size=2, - initializer_range=0.02, **kwargs): - super(BertModel, self).__init__() - self.embeddings = BertEmbeddings(vocab_size, hidden_size, max_position_embeddings, - type_vocab_size, hidden_dropout_prob) - self.encoder = BertEncoder(num_hidden_layers, hidden_size, num_attention_heads, - attention_probs_dropout_prob, hidden_dropout_prob, intermediate_size, - hidden_act) - self.pooler = BertPooler(hidden_size) - self.initializer_range = initializer_range - - self.apply(self.init_bert_weights) - - def init_bert_weights(self, module): - if isinstance(module, (nn.Linear, nn.Embedding)): - # Slightly different from the TF version which uses truncated_normal for initialization - # cf https://github.com/pytorch/pytorch/pull/5617 - module.weight.data.normal_(mean=0.0, std=self.initializer_range) - elif isinstance(module, BertLayerNorm): - module.bias.data.zero_() - module.weight.data.fill_(1.0) - if isinstance(module, nn.Linear) and module.bias is not None: - module.bias.data.zero_() - - def forward(self, input_ids, token_type_ids=None, attention_mask=None, output_all_encoded_layers=True): - if attention_mask is None: - attention_mask = torch.ones_like(input_ids) - if token_type_ids is None: - token_type_ids = torch.zeros_like(input_ids) - - # We create a 3D attention mask from a 2D tensor mask. - # Sizes are [batch_size, 1, 1, to_seq_length] - # So we can broadcast to [batch_size, num_heads, from_seq_length, to_seq_length] - # this attention mask is more simple than the triangular masking of causal attention - # used in OpenAI GPT, we just need to prepare the broadcast dimension here. - extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) - - # Since attention_mask is 1.0 for positions we want to attend and 0.0 for - # masked positions, this operation will create a tensor which is 0.0 for - # positions we want to attend and -10000.0 for masked positions. - # Since we are adding it to the raw scores before the softmax, this is - # effectively the same as removing these entirely. - extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype) # fp16 compatibility - extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 - - embedding_output = self.embeddings(input_ids, token_type_ids) - encoded_layers = self.encoder(embedding_output, - extended_attention_mask, - output_all_encoded_layers=output_all_encoded_layers) - sequence_output = encoded_layers[-1] - pooled_output = self.pooler(sequence_output) - if not output_all_encoded_layers: - encoded_layers = encoded_layers[-1] - return encoded_layers, pooled_output - - @classmethod - def from_pretrained(cls, pretrained_model_dir, state_dict=None, *inputs, **kwargs): - # Load config - config_file = os.path.join(pretrained_model_dir, CONFIG_FILE) - config = json.load(open(config_file, "r")) - # config = BertConfig.from_json_file(config_file) - # logger.info("Model config {}".format(config)) - # Instantiate model. - model = cls(*inputs, **config, **kwargs) - if state_dict is None: - weights_path = os.path.join(pretrained_model_dir, MODEL_WEIGHTS) - state_dict = torch.load(weights_path) - - old_keys = [] - new_keys = [] - for key in state_dict.keys(): - new_key = None - if 'gamma' in key: - new_key = key.replace('gamma', 'weight') - if 'beta' in key: - new_key = key.replace('beta', 'bias') - if new_key: - old_keys.append(key) - new_keys.append(new_key) - for old_key, new_key in zip(old_keys, new_keys): - state_dict[new_key] = state_dict.pop(old_key) - - missing_keys = [] - unexpected_keys = [] - error_msgs = [] - # copy state_dict so _load_from_state_dict can modify it - metadata = getattr(state_dict, '_metadata', None) - state_dict = state_dict.copy() - if metadata is not None: - state_dict._metadata = metadata - - def load(module, prefix=''): - local_metadata = {} if metadata is None else metadata.get(prefix[:-1], {}) - module._load_from_state_dict( - state_dict, prefix, local_metadata, True, missing_keys, unexpected_keys, error_msgs) - for name, child in module._modules.items(): - if child is not None: - load(child, prefix + name + '.') - - load(model, prefix='' if hasattr(model, 'bert') else 'bert.') - if len(missing_keys) > 0: - print("Weights of {} not initialized from pretrained model: {}".format( - model.__class__.__name__, missing_keys)) - if len(unexpected_keys) > 0: - print("Weights from pretrained model not used in {}: {}".format( - model.__class__.__name__, unexpected_keys)) - return model + def __init__(self, config, num_labels, bert_dir): + super(BertForSequenceClassification, self).__init__() + self.num_labels = num_labels + self.bert = BertModel.from_pretrained(bert_dir) + self.dropout = nn.Dropout(config.hidden_dropout_prob) + self.classifier = nn.Linear(config.hidden_size, num_labels) + + def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None): + _, pooled_output = self.bert(input_ids, token_type_ids, attention_mask, output_all_encoded_layers=False) + pooled_output = self.dropout(pooled_output) + logits = self.classifier(pooled_output) + + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) + return {Const.OUTPUT: logits, Const.LOSS: loss} + else: + return {Const.OUTPUT: logits} + + def predict(self, input_ids, token_type_ids=None, attention_mask=None): + logits = self.forward(input_ids, token_type_ids, attention_mask) + return {Const.OUTPUT: torch.argmax(logits, dim=-1)} + + +class BertForMultipleChoice(BaseModel): + """BERT model for multiple choice tasks. + This module is composed of the BERT model with a linear layer on top of + the pooled output. + Params: + `config`: a BertConfig class instance with the configuration to build a new model. + `num_choices`: the number of classes for the classifier. Default = 2. + Inputs: + `input_ids`: a torch.LongTensor of shape [batch_size, num_choices, sequence_length] + with the word token indices in the vocabulary(see the tokens preprocessing logic in the scripts + `extract_features.py`, `run_classifier.py` and `run_squad.py`) + `token_type_ids`: an optional torch.LongTensor of shape [batch_size, num_choices, sequence_length] + with the token types indices selected in [0, 1]. Type 0 corresponds to a `sentence A` + and type 1 corresponds to a `sentence B` token (see BERT paper for more details). + `attention_mask`: an optional torch.LongTensor of shape [batch_size, num_choices, sequence_length] with indices + selected in [0, 1]. It's a mask to be used if the input sequence length is smaller than the max + input sequence length in the current batch. It's the mask that we typically use for attention when + a batch has varying length sentences. + `labels`: labels for the classification output: torch.LongTensor of shape [batch_size] + with indices selected in [0, ..., num_choices]. + Outputs: + if `labels` is not `None`: + Outputs the CrossEntropy classification loss of the output with the labels. + if `labels` is `None`: + Outputs the classification logits of shape [batch_size, num_labels]. + Example usage: + ```python + # Already been converted into WordPiece token ids + input_ids = torch.LongTensor([[[31, 51, 99], [15, 5, 0]], [[12, 16, 42], [14, 28, 57]]]) + input_mask = torch.LongTensor([[[1, 1, 1], [1, 1, 0]],[[1,1,0], [1, 0, 0]]]) + token_type_ids = torch.LongTensor([[[0, 0, 1], [0, 1, 0]],[[0, 1, 1], [0, 0, 1]]]) + config = BertConfig(vocab_size_or_config_json_file=32000, hidden_size=768, + num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072) + num_choices = 2 + model = BertForMultipleChoice(config, num_choices, bert_dir) + logits = model(input_ids, token_type_ids, input_mask) + ``` + """ + def __init__(self, config, num_choices, bert_dir): + super(BertForMultipleChoice, self).__init__() + self.num_choices = num_choices + self.bert = BertModel.from_pretrained(bert_dir) + self.dropout = nn.Dropout(config.hidden_dropout_prob) + self.classifier = nn.Linear(config.hidden_size, 1) + + def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None): + flat_input_ids = input_ids.view(-1, input_ids.size(-1)) + flat_token_type_ids = token_type_ids.view(-1, token_type_ids.size(-1)) + flat_attention_mask = attention_mask.view(-1, attention_mask.size(-1)) + _, pooled_output = self.bert(flat_input_ids, flat_token_type_ids, flat_attention_mask, output_all_encoded_layers=False) + pooled_output = self.dropout(pooled_output) + logits = self.classifier(pooled_output) + reshaped_logits = logits.view(-1, self.num_choices) + + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(reshaped_logits, labels) + return {Const.OUTPUT: reshaped_logits, Const.LOSS: loss} + else: + return {Const.OUTPUT: reshaped_logits} + + def predict(self, input_ids, token_type_ids=None, attention_mask=None): + logits = self.forward(input_ids, token_type_ids, attention_mask)[Const.OUTPUT] + return {Const.OUTPUT: torch.argmax(logits, dim=-1)} + + +class BertForTokenClassification(BaseModel): + """BERT model for token-level classification. + This module is composed of the BERT model with a linear layer on top of + the full hidden state of the last layer. + Params: + `config`: a BertConfig class instance with the configuration to build a new model. + `num_labels`: the number of classes for the classifier. Default = 2. + `bert_dir`: a dir which contains the bert parameters within file `pytorch_model.bin` + Inputs: + `input_ids`: a torch.LongTensor of shape [batch_size, sequence_length] + with the word token indices in the vocabulary(see the tokens preprocessing logic in the scripts + `extract_features.py`, `run_classifier.py` and `run_squad.py`) + `token_type_ids`: an optional torch.LongTensor of shape [batch_size, sequence_length] with the token + types indices selected in [0, 1]. Type 0 corresponds to a `sentence A` and type 1 corresponds to + a `sentence B` token (see BERT paper for more details). + `attention_mask`: an optional torch.LongTensor of shape [batch_size, sequence_length] with indices + selected in [0, 1]. It's a mask to be used if the input sequence length is smaller than the max + input sequence length in the current batch. It's the mask that we typically use for attention when + a batch has varying length sentences. + `labels`: labels for the classification output: torch.LongTensor of shape [batch_size, sequence_length] + with indices selected in [0, ..., num_labels]. + Outputs: + if `labels` is not `None`: + Outputs the CrossEntropy classification loss of the output with the labels. + if `labels` is `None`: + Outputs the classification logits of shape [batch_size, sequence_length, num_labels]. + Example usage: + ```python + # Already been converted into WordPiece token ids + input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]]) + input_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]]) + token_type_ids = torch.LongTensor([[0, 0, 1], [0, 1, 0]]) + config = BertConfig(vocab_size_or_config_json_file=32000, hidden_size=768, + num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072) + num_labels = 2 + bert_dir = 'your-bert-file-dir' + model = BertForTokenClassification(config, num_labels, bert_dir) + logits = model(input_ids, token_type_ids, input_mask) + ``` + """ + def __init__(self, config, num_labels, bert_dir): + super(BertForTokenClassification, self).__init__() + self.num_labels = num_labels + self.bert = BertModel.from_pretrained(bert_dir) + self.dropout = nn.Dropout(config.hidden_dropout_prob) + self.classifier = nn.Linear(config.hidden_size, num_labels) + + def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None): + sequence_output, _ = self.bert(input_ids, token_type_ids, attention_mask, output_all_encoded_layers=False) + sequence_output = self.dropout(sequence_output) + logits = self.classifier(sequence_output) + + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + # Only keep active parts of the loss + if attention_mask is not None: + active_loss = attention_mask.view(-1) == 1 + active_logits = logits.view(-1, self.num_labels)[active_loss] + active_labels = labels.view(-1)[active_loss] + loss = loss_fct(active_logits, active_labels) + else: + loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) + return {Const.OUTPUT: logits, Const.LOSS: loss} + else: + return {Const.OUTPUT: logits} + + def predict(self, input_ids, token_type_ids=None, attention_mask=None): + logits = self.forward(input_ids, token_type_ids, attention_mask)[Const.OUTPUT] + return {Const.OUTPUT: torch.argmax(logits, dim=-1)} + + +class BertForQuestionAnswering(BaseModel): + """BERT model for Question Answering (span extraction). + This module is composed of the BERT model with a linear layer on top of + the sequence output that computes start_logits and end_logits + Params: + `config`: a BertConfig class instance with the configuration to build a new model. + `bert_dir`: a dir which contains the bert parameters within file `pytorch_model.bin` + Inputs: + `input_ids`: a torch.LongTensor of shape [batch_size, sequence_length] + with the word token indices in the vocabulary(see the tokens preprocessing logic in the scripts + `extract_features.py`, `run_classifier.py` and `run_squad.py`) + `token_type_ids`: an optional torch.LongTensor of shape [batch_size, sequence_length] with the token + types indices selected in [0, 1]. Type 0 corresponds to a `sentence A` and type 1 corresponds to + a `sentence B` token (see BERT paper for more details). + `attention_mask`: an optional torch.LongTensor of shape [batch_size, sequence_length] with indices + selected in [0, 1]. It's a mask to be used if the input sequence length is smaller than the max + input sequence length in the current batch. It's the mask that we typically use for attention when + a batch has varying length sentences. + `start_positions`: position of the first token for the labeled span: torch.LongTensor of shape [batch_size]. + Positions are clamped to the length of the sequence and position outside of the sequence are not taken + into account for computing the loss. + `end_positions`: position of the last token for the labeled span: torch.LongTensor of shape [batch_size]. + Positions are clamped to the length of the sequence and position outside of the sequence are not taken + into account for computing the loss. + Outputs: + if `start_positions` and `end_positions` are not `None`: + Outputs the total_loss which is the sum of the CrossEntropy loss for the start and end token positions. + if `start_positions` or `end_positions` is `None`: + Outputs a tuple of start_logits, end_logits which are the logits respectively for the start and end + position tokens of shape [batch_size, sequence_length]. + Example usage: + ```python + # Already been converted into WordPiece token ids + input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]]) + input_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]]) + token_type_ids = torch.LongTensor([[0, 0, 1], [0, 1, 0]]) + config = BertConfig(vocab_size_or_config_json_file=32000, hidden_size=768, + num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072) + bert_dir = 'your-bert-file-dir' + model = BertForQuestionAnswering(config, bert_dir) + start_logits, end_logits = model(input_ids, token_type_ids, input_mask) + ``` + """ + def __init__(self, config, bert_dir): + super(BertForQuestionAnswering, self).__init__() + self.bert = BertModel.from_pretrained(bert_dir) + # TODO check with Google if it's normal there is no dropout on the token classifier of SQuAD in the TF version + # self.dropout = nn.Dropout(config.hidden_dropout_prob) + self.qa_outputs = nn.Linear(config.hidden_size, 2) + + def forward(self, input_ids, token_type_ids=None, attention_mask=None, start_positions=None, end_positions=None): + sequence_output, _ = self.bert(input_ids, token_type_ids, attention_mask, output_all_encoded_layers=False) + logits = self.qa_outputs(sequence_output) + start_logits, end_logits = logits.split(1, dim=-1) + start_logits = start_logits.squeeze(-1) + end_logits = end_logits.squeeze(-1) + + if start_positions is not None and end_positions is not None: + # If we are on multi-GPU, split add a dimension + if len(start_positions.size()) > 1: + start_positions = start_positions.squeeze(-1) + if len(end_positions.size()) > 1: + end_positions = end_positions.squeeze(-1) + # sometimes the start/end positions are outside our model inputs, we ignore these terms + ignored_index = start_logits.size(1) + start_positions.clamp_(0, ignored_index) + end_positions.clamp_(0, ignored_index) + + loss_fct = nn.CrossEntropyLoss(ignore_index=ignored_index) + start_loss = loss_fct(start_logits, start_positions) + end_loss = loss_fct(end_logits, end_positions) + total_loss = (start_loss + end_loss) / 2 + return {Const.OUTPUTS(0): start_logits, Const.OUTPUTS(1): end_logits, Const.LOSS: total_loss} + else: + return {Const.OUTPUTS(0): start_logits, Const.OUTPUTS(1): end_logits} + + def predict(self, input_ids, token_type_ids=None, attention_mask=None, **kwargs): + logits = self.forward(input_ids, token_type_ids, attention_mask) + start_logits = logits[Const.OUTPUTS(0)] + end_logits = logits[Const.OUTPUTS(1)] + return {Const.OUTPUTS(0): torch.argmax(start_logits, dim=-1), + Const.OUTPUTS(1): torch.argmax(end_logits, dim=-1)} diff --git a/fastNLP/models/biaffine_parser.py b/fastNLP/models/biaffine_parser.py index dc294eb3..8533e7af 100644 --- a/fastNLP/models/biaffine_parser.py +++ b/fastNLP/models/biaffine_parser.py @@ -1,22 +1,31 @@ -from collections import defaultdict +""" +Biaffine Dependency Parser 的 Pytorch 实现. +""" +__all__ = [ + "BiaffineParser", + "GraphParser" +] import numpy as np import torch -from torch import nn -from torch.nn import functional as F +import torch.nn as nn +import torch.nn.functional as F + +from collections import defaultdict -from fastNLP.core.losses import LossFunc -from fastNLP.core.metrics import MetricBase -from fastNLP.core.utils import seq_lens_to_masks -from fastNLP.models.base_model import BaseModel -from fastNLP.modules.dropout import TimestepDropout -from fastNLP.modules.encoder.transformer import TransformerEncoder -from fastNLP.modules.encoder.variational_rnn import VarLSTM -from fastNLP.modules.utils import initial_parameter -from fastNLP.modules.utils import seq_mask +from ..core.const import Const as C +from ..core.losses import LossFunc +from ..core.metrics import MetricBase +from ..modules.dropout import TimestepDropout +from ..modules.encoder.transformer import TransformerEncoder +from ..modules.encoder.variational_rnn import VarLSTM +from ..modules.utils import initial_parameter +from ..modules.utils import get_embeddings +from .base_model import BaseModel +from ..core.utils import seq_len_to_mask -def mst(scores): +def _mst(scores): """ with some modification to support parser output for MST decoding https://github.com/tdozat/Parser/blob/0739216129cd39d69997d28cbc4133b360ea3934/lib/models/nn.py#L692 @@ -42,7 +51,7 @@ def mst(scores): scores[roots, new_heads] / root_scores)] heads[roots] = new_heads heads[new_root] = 0 - + edges = defaultdict(set) vertices = set((0,)) for dep, head in enumerate(heads[tokens]): @@ -71,7 +80,7 @@ def mst(scores): heads[changed_cycle] = new_head edges[new_head].add(changed_cycle) edges[old_head].remove(changed_cycle) - + return heads @@ -86,7 +95,7 @@ def _find_cycle(vertices, edges): _lowlinks = {} _onstack = defaultdict(lambda: False) _SCCs = [] - + def _strongconnect(v): nonlocal _index _indices[v] = _index @@ -94,38 +103,49 @@ def _find_cycle(vertices, edges): _index += 1 _stack.append(v) _onstack[v] = True - + for w in edges[v]: if w not in _indices: _strongconnect(w) _lowlinks[v] = min(_lowlinks[v], _lowlinks[w]) elif _onstack[w]: _lowlinks[v] = min(_lowlinks[v], _indices[w]) - + if _lowlinks[v] == _indices[v]: SCC = set() while True: w = _stack.pop() _onstack[w] = False SCC.add(w) - if not(w != v): + if not (w != v): break _SCCs.append(SCC) - + for v in vertices: if v not in _indices: _strongconnect(v) - + return [SCC for SCC in _SCCs if len(SCC) > 1] class GraphParser(BaseModel): - """Graph based Parser helper class, support greedy decoding and MST(Maximum Spanning Tree) decoding """ + 基于图的parser base class, 支持贪婪解码和最大生成树解码 + """ + def __init__(self): super(GraphParser, self).__init__() + + @staticmethod + def greedy_decoder(arc_matrix, mask=None): + """ + 贪心解码方式, 输入图, 输出贪心解码的parsing结果, 不保证合法的构成树 - def _greedy_decoder(self, arc_matrix, mask=None): + :param arc_matrix: [batch, seq_len, seq_len] 输入图矩阵 + :param mask: [batch, seq_len] 输入图的padding mask, 有内容的部分为 1, 否则为 0. + 若为 ``None`` 时, 默认为全1向量. Default: ``None`` + :return heads: [batch, seq_len] 每个元素在树中对应的head(parent)预测结果 + """ _, seq_len, _ = arc_matrix.shape matrix = arc_matrix + torch.diag(arc_matrix.new(seq_len).fill_(-np.inf)) flip_mask = (mask == 0).byte() @@ -134,24 +154,37 @@ class GraphParser(BaseModel): if mask is not None: heads *= mask.long() return heads + + @staticmethod + def mst_decoder(arc_matrix, mask=None): + """ + 用最大生成树算法, 计算parsing结果, 保证输出合法的树结构 - def _mst_decoder(self, arc_matrix, mask=None): + :param arc_matrix: [batch, seq_len, seq_len] 输入图矩阵 + :param mask: [batch, seq_len] 输入图的padding mask, 有内容的部分为 1, 否则为 0. + 若为 ``None`` 时, 默认为全1向量. Default: ``None`` + :return heads: [batch, seq_len] 每个元素在树中对应的head(parent)预测结果 + """ batch_size, seq_len, _ = arc_matrix.shape matrix = arc_matrix.clone() ans = matrix.new_zeros(batch_size, seq_len).long() lens = (mask.long()).sum(1) if mask is not None else torch.zeros(batch_size) + seq_len - batch_idx = torch.arange(batch_size, dtype=torch.long, device=lens.device) for i, graph in enumerate(matrix): len_i = lens[i] - ans[i, :len_i] = torch.as_tensor(mst(graph.detach()[:len_i, :len_i].cpu().numpy()), device=ans.device) + ans[i, :len_i] = torch.as_tensor(_mst(graph.detach()[:len_i, :len_i].cpu().numpy()), device=ans.device) if mask is not None: ans *= mask.long() return ans class ArcBiaffine(nn.Module): - """helper module for Biaffine Dependency Parser predicting arc """ + Biaffine Dependency Parser 的子模块, 用于构建预测边的图 + + :param hidden_size: 输入的特征维度 + :param bias: 是否使用bias. Default: ``True`` + """ + def __init__(self, hidden_size, bias=True): super(ArcBiaffine, self).__init__() self.U = nn.Parameter(torch.Tensor(hidden_size, hidden_size), requires_grad=True) @@ -161,13 +194,13 @@ class ArcBiaffine(nn.Module): else: self.register_parameter("bias", None) initial_parameter(self) - + def forward(self, head, dep): """ - :param head arc-head tensor = [batch, length, emb_dim] - :param dep arc-dependent tensor = [batch, length, emb_dim] - :return output tensor = [bacth, length, length] + :param head: arc-head tensor [batch, length, hidden] + :param dep: arc-dependent tensor [batch, length, hidden] + :return output: tensor [bacth, length, length] """ output = dep.matmul(self.U) output = output.bmm(head.transpose(-1, -2)) @@ -177,41 +210,72 @@ class ArcBiaffine(nn.Module): class LabelBilinear(nn.Module): - """helper module for Biaffine Dependency Parser predicting label """ + Biaffine Dependency Parser 的子模块, 用于构建预测边类别的图 + + :param in1_features: 输入的特征1维度 + :param in2_features: 输入的特征2维度 + :param num_label: 边类别的个数 + :param bias: 是否使用bias. Default: ``True`` + """ + def __init__(self, in1_features, in2_features, num_label, bias=True): super(LabelBilinear, self).__init__() self.bilinear = nn.Bilinear(in1_features, in2_features, num_label, bias=bias) self.lin = nn.Linear(in1_features + in2_features, num_label, bias=False) - + def forward(self, x1, x2): + """ + + :param x1: [batch, seq_len, hidden] 输入特征1, 即label-head + :param x2: [batch, seq_len, hidden] 输入特征2, 即label-dep + :return output: [batch, seq_len, num_cls] 每个元素对应类别的概率图 + """ output = self.bilinear(x1, x2) output += self.lin(torch.cat([x1, x2], dim=2)) return output + class BiaffineParser(GraphParser): - """Biaffine Dependency Parser implemantation. - refer to ` Deep Biaffine Attention for Neural Dependency Parsing (Dozat and Manning, 2016) - `_ . """ + 别名::class:`fastNLP.models.BiaffineParser` :class:`fastNLP.models.baffine_parser.BiaffineParser` + + Biaffine Dependency Parser 实现. + 论文参考 `Deep Biaffine Attention for Neural Dependency Parsing (Dozat and Manning, 2016) `_ . + + :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 + embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, + 此时就以传入的对象作为embedding + :param pos_vocab_size: part-of-speech 词典大小 + :param pos_emb_dim: part-of-speech 向量维度 + :param num_label: 边的类别个数 + :param rnn_layers: rnn encoder的层数 + :param rnn_hidden_size: rnn encoder 的隐状态维度 + :param arc_mlp_size: 边预测的MLP维度 + :param label_mlp_size: 类别预测的MLP维度 + :param dropout: dropout概率. + :param encoder: encoder类别, 可选 ('lstm', 'var-lstm', 'transformer'). Default: lstm + :param use_greedy_infer: 是否在inference时使用贪心算法. + 若 ``False`` , 使用更加精确但相对缓慢的MST算法. Default: ``False`` + """ + def __init__(self, - word_vocab_size, - word_emb_dim, - pos_vocab_size, - pos_emb_dim, - num_label, - rnn_layers=1, - rnn_hidden_size=200, - arc_mlp_size=100, - label_mlp_size=100, - dropout=0.3, - encoder='lstm', - use_greedy_infer=False): - + init_embed, + pos_vocab_size, + pos_emb_dim, + num_label, + rnn_layers=1, + rnn_hidden_size=200, + arc_mlp_size=100, + label_mlp_size=100, + dropout=0.3, + encoder='lstm', + use_greedy_infer=False): super(BiaffineParser, self).__init__() rnn_out_size = 2 * rnn_hidden_size word_hid_dim = pos_hid_dim = rnn_hidden_size - self.word_embedding = nn.Embedding(num_embeddings=word_vocab_size, embedding_dim=word_emb_dim) + self.word_embedding = get_embeddings(init_embed) + word_emb_dim = self.word_embedding.embedding_dim self.pos_embedding = nn.Embedding(num_embeddings=pos_vocab_size, embedding_dim=pos_emb_dim) self.word_fc = nn.Linear(word_emb_dim, word_hid_dim) self.pos_fc = nn.Linear(pos_emb_dim, pos_hid_dim) @@ -242,20 +306,20 @@ class BiaffineParser(GraphParser): if (d_k * n_head) != rnn_out_size: raise ValueError('unsupported rnn_out_size: {} for transformer'.format(rnn_out_size)) self.position_emb = nn.Embedding(num_embeddings=self.max_len, - embedding_dim=rnn_out_size,) + embedding_dim=rnn_out_size, ) self.encoder = TransformerEncoder(num_layers=rnn_layers, model_size=rnn_out_size, inner_size=1024, key_size=d_k, value_size=d_v, num_head=n_head, - dropout=dropout,) + dropout=dropout, ) else: raise ValueError('unsupported encoder type: {}'.format(encoder)) - + self.mlp = nn.Sequential(nn.Linear(rnn_out_size, arc_mlp_size * 2 + label_mlp_size * 2), - nn.ELU(), - TimestepDropout(p=dropout),) + nn.ELU(), + TimestepDropout(p=dropout), ) self.arc_mlp_size = arc_mlp_size self.label_mlp_size = label_mlp_size self.arc_predictor = ArcBiaffine(arc_mlp_size, bias=True) @@ -263,7 +327,7 @@ class BiaffineParser(GraphParser): self.use_greedy_infer = use_greedy_infer self.reset_parameters() self.dropout = dropout - + def reset_parameters(self): for m in self.modules(): if isinstance(m, nn.Embedding): @@ -274,167 +338,210 @@ class BiaffineParser(GraphParser): else: for p in m.parameters(): nn.init.normal_(p, 0, 0.1) + + def forward(self, words1, words2, seq_len, target1=None): + """模型forward阶段 + + :param words1: [batch_size, seq_len] 输入word序列 + :param words2: [batch_size, seq_len] 输入pos序列 + :param seq_len: [batch_size, seq_len] 输入序列长度 + :param target1: [batch_size, seq_len] 输入真实标注的heads, 仅在训练阶段有效, + 用于训练label分类器. 若为 ``None`` , 使用预测的heads输入到label分类器 + Default: ``None`` + :return dict: parsing + 结果:: + + pred1: [batch_size, seq_len, seq_len] 边预测logits + pred2: [batch_size, seq_len, num_label] label预测logits + pred3: [batch_size, seq_len] heads的预测结果, 在 ``target1=None`` 时预测 - def forward(self, word_seq, pos_seq, seq_lens, gold_heads=None): - """ - :param word_seq: [batch_size, seq_len] sequence of word's indices - :param pos_seq: [batch_size, seq_len] sequence of word's indices - :param seq_lens: [batch_size, seq_len] sequence of length masks - :param gold_heads: [batch_size, seq_len] sequence of golden heads - :return dict: parsing results - arc_pred: [batch_size, seq_len, seq_len] - label_pred: [batch_size, seq_len, seq_len] - mask: [batch_size, seq_len] - head_pred: [batch_size, seq_len] if gold_heads is not provided, predicting the heads """ # prepare embeddings - batch_size, seq_len = word_seq.shape + batch_size, length = words1.shape # print('forward {} {}'.format(batch_size, seq_len)) - + # get sequence mask - mask = seq_mask(seq_lens, seq_len).long() - - word = self.word_embedding(word_seq) # [N,L] -> [N,L,C_0] - pos = self.pos_embedding(pos_seq) # [N,L] -> [N,L,C_1] - + mask = seq_len_to_mask(seq_len).long() + + word = self.word_embedding(words1) # [N,L] -> [N,L,C_0] + pos = self.pos_embedding(words2) # [N,L] -> [N,L,C_1] + word, pos = self.word_fc(word), self.pos_fc(pos) word, pos = self.word_norm(word), self.pos_norm(pos) - x = torch.cat([word, pos], dim=2) # -> [N,L,C] - + x = torch.cat([word, pos], dim=2) # -> [N,L,C] + # encoder, extract features if self.encoder_name.endswith('lstm'): - sort_lens, sort_idx = torch.sort(seq_lens, dim=0, descending=True) + sort_lens, sort_idx = torch.sort(seq_len, dim=0, descending=True) x = x[sort_idx] x = nn.utils.rnn.pack_padded_sequence(x, sort_lens, batch_first=True) - feat, _ = self.encoder(x) # -> [N,L,C] + feat, _ = self.encoder(x) # -> [N,L,C] feat, _ = nn.utils.rnn.pad_packed_sequence(feat, batch_first=True) _, unsort_idx = torch.sort(sort_idx, dim=0, descending=False) feat = feat[unsort_idx] else: - seq_range = torch.arange(seq_len, dtype=torch.long, device=x.device)[None,:] + seq_range = torch.arange(length, dtype=torch.long, device=x.device)[None, :] x = x + self.position_emb(seq_range) feat = self.encoder(x, mask.float()) - + # for arc biaffine # mlp, reduce dim feat = self.mlp(feat) arc_sz, label_sz = self.arc_mlp_size, self.label_mlp_size - arc_dep, arc_head = feat[:,:,:arc_sz], feat[:,:,arc_sz:2*arc_sz] - label_dep, label_head = feat[:,:,2*arc_sz:2*arc_sz+label_sz], feat[:,:,2*arc_sz+label_sz:] - + arc_dep, arc_head = feat[:, :, :arc_sz], feat[:, :, arc_sz:2 * arc_sz] + label_dep, label_head = feat[:, :, 2 * arc_sz:2 * arc_sz + label_sz], feat[:, :, 2 * arc_sz + label_sz:] + # biaffine arc classifier - arc_pred = self.arc_predictor(arc_head, arc_dep) # [N, L, L] - + arc_pred = self.arc_predictor(arc_head, arc_dep) # [N, L, L] + # use gold or predicted arc to predict label - if gold_heads is None or not self.training: + if target1 is None or not self.training: # use greedy decoding in training if self.training or self.use_greedy_infer: - heads = self._greedy_decoder(arc_pred, mask) + heads = self.greedy_decoder(arc_pred, mask) else: - heads = self._mst_decoder(arc_pred, mask) + heads = self.mst_decoder(arc_pred, mask) head_pred = heads else: - assert self.training # must be training mode - if gold_heads is None: - heads = self._greedy_decoder(arc_pred, mask) + assert self.training # must be training mode + if target1 is None: + heads = self.greedy_decoder(arc_pred, mask) head_pred = heads else: head_pred = None - heads = gold_heads - - batch_range = torch.arange(start=0, end=batch_size, dtype=torch.long, device=word_seq.device).unsqueeze(1) + heads = target1 + + batch_range = torch.arange(start=0, end=batch_size, dtype=torch.long, device=words1.device).unsqueeze(1) label_head = label_head[batch_range, heads].contiguous() - label_pred = self.label_predictor(label_head, label_dep) # [N, L, num_label] - res_dict = {'arc_pred': arc_pred, 'label_pred': label_pred, 'mask': mask} + label_pred = self.label_predictor(label_head, label_dep) # [N, L, num_label] + res_dict = {C.OUTPUTS(0): arc_pred, C.OUTPUTS(1): label_pred} if head_pred is not None: - res_dict['head_pred'] = head_pred + res_dict[C.OUTPUTS(2)] = head_pred return res_dict - + @staticmethod - def loss(arc_pred, label_pred, arc_true, label_true, mask): + def loss(pred1, pred2, target1, target2, seq_len): """ - Compute loss. - - :param arc_pred: [batch_size, seq_len, seq_len] - :param label_pred: [batch_size, seq_len, n_tags] - :param arc_true: [batch_size, seq_len] - :param label_true: [batch_size, seq_len] - :param mask: [batch_size, seq_len] - :return: loss value + 计算parser的loss + + :param pred1: [batch_size, seq_len, seq_len] 边预测logits + :param pred2: [batch_size, seq_len, num_label] label预测logits + :param target1: [batch_size, seq_len] 真实边的标注 + :param target2: [batch_size, seq_len] 真实类别的标注 + :param seq_len: [batch_size, seq_len] 真实目标的长度 + :return loss: scalar """ - - batch_size, seq_len, _ = arc_pred.shape + + batch_size, length, _ = pred1.shape + mask = seq_len_to_mask(seq_len) flip_mask = (mask == 0) - _arc_pred = arc_pred.clone() + _arc_pred = pred1.clone() _arc_pred.masked_fill_(flip_mask.unsqueeze(1), -float('inf')) arc_logits = F.log_softmax(_arc_pred, dim=2) - label_logits = F.log_softmax(label_pred, dim=2) + label_logits = F.log_softmax(pred2, dim=2) batch_index = torch.arange(batch_size, device=arc_logits.device, dtype=torch.long).unsqueeze(1) - child_index = torch.arange(seq_len, device=arc_logits.device, dtype=torch.long).unsqueeze(0) - arc_loss = arc_logits[batch_index, child_index, arc_true] - label_loss = label_logits[batch_index, child_index, label_true] - + child_index = torch.arange(length, device=arc_logits.device, dtype=torch.long).unsqueeze(0) + arc_loss = arc_logits[batch_index, child_index, target1] + label_loss = label_logits[batch_index, child_index, target2] + byte_mask = flip_mask.byte() arc_loss.masked_fill_(byte_mask, 0) label_loss.masked_fill_(byte_mask, 0) arc_nll = -arc_loss.mean() label_nll = -label_loss.mean() return arc_nll + label_nll + + def predict(self, words1, words2, seq_len): + """模型预测API - def predict(self, word_seq, pos_seq, seq_lens): - """ + :param words1: [batch_size, seq_len] 输入word序列 + :param words2: [batch_size, seq_len] 输入pos序列 + :param seq_len: [batch_size, seq_len] 输入序列长度 + :return dict: parsing + 结果:: + + pred1: [batch_size, seq_len] heads的预测结果 + pred2: [batch_size, seq_len, num_label] label预测logits - :param word_seq: - :param pos_seq: - :param seq_lens: - :return: arc_pred: [B, L] - label_pred: [B, L] """ - res = self(word_seq, pos_seq, seq_lens) + res = self(words1, words2, seq_len) output = {} - output['arc_pred'] = res.pop('head_pred') - _, label_pred = res.pop('label_pred').max(2) - output['label_pred'] = label_pred + output[C.OUTPUTS(0)] = res.pop(C.OUTPUTS(2)) + _, label_pred = res.pop(C.OUTPUTS(1)).max(2) + output[C.OUTPUTS(1)] = label_pred return output class ParserLoss(LossFunc): - def __init__(self, arc_pred=None, label_pred=None, arc_true=None, label_true=None): + """ + 别名::class:`fastNLP.models.ParserLoss` :class:`fastNLP.models.baffine_parser.ParserLoss` + + 计算parser的loss + + :param pred1: [batch_size, seq_len, seq_len] 边预测logits + :param pred2: [batch_size, seq_len, num_label] label预测logits + :param target1: [batch_size, seq_len] 真实边的标注 + :param target2: [batch_size, seq_len] 真实类别的标注 + :param seq_len: [batch_size, seq_len] 真实目标的长度 + :return loss: scalar + """ + + def __init__(self, pred1=None, pred2=None, + target1=None, target2=None, + seq_len=None): super(ParserLoss, self).__init__(BiaffineParser.loss, - arc_pred=arc_pred, - label_pred=label_pred, - arc_true=arc_true, - label_true=label_true) + pred1=pred1, + pred2=pred2, + target1=target1, + target2=target2, + seq_len=seq_len) class ParserMetric(MetricBase): - def __init__(self, arc_pred=None, label_pred=None, - arc_true=None, label_true=None, seq_lens=None): + """ + 别名::class:`fastNLP.models.ParserMetric` :class:`fastNLP.models.baffine_parser.ParserMetric` + + 评估parser的性能 + + :param pred1: 边预测logits + :param pred2: label预测logits + :param target1: 真实边的标注 + :param target2: 真实类别的标注 + :param seq_len: 序列长度 + :return dict: 评估结果:: + + UAS: 不带label时, 边预测的准确率 + LAS: 同时预测边和label的准确率 + """ + + def __init__(self, pred1=None, pred2=None, + target1=None, target2=None, seq_len=None): + super().__init__() - self._init_param_map(arc_pred=arc_pred, label_pred=label_pred, - arc_true=arc_true, label_true=label_true, - seq_lens=seq_lens) + self._init_param_map(pred1=pred1, pred2=pred2, + target1=target1, target2=target2, + seq_len=seq_len) self.num_arc = 0 self.num_label = 0 self.num_sample = 0 - + def get_metric(self, reset=True): - res = {'UAS': self.num_arc*1.0 / self.num_sample, 'LAS': self.num_label*1.0 / self.num_sample} + res = {'UAS': self.num_arc * 1.0 / self.num_sample, 'LAS': self.num_label * 1.0 / self.num_sample} if reset: self.num_sample = self.num_label = self.num_arc = 0 return res - - def evaluate(self, arc_pred, label_pred, arc_true, label_true, seq_lens=None): + + def evaluate(self, pred1, pred2, target1, target2, seq_len=None): """Evaluate the performance of prediction. """ - if seq_lens is None: - seq_mask = arc_pred.new_ones(arc_pred.size(), dtype=torch.long) + if seq_len is None: + seq_mask = pred1.new_ones(pred1.size(), dtype=torch.long) else: - seq_mask = seq_lens_to_masks(seq_lens.long(), float=False).long() + seq_mask = seq_len_to_mask(seq_len.long()).long() # mask out tag - seq_mask[:,0] = 0 - head_pred_correct = (arc_pred == arc_true).long() * seq_mask - label_pred_correct = (label_pred == label_true).long() * head_pred_correct + seq_mask[:, 0] = 0 + head_pred_correct = (pred1 == target1).long() * seq_mask + label_pred_correct = (pred2 == target2).long() * head_pred_correct self.num_arc += head_pred_correct.sum().item() self.num_label += label_pred_correct.sum().item() self.num_sample += seq_mask.sum().item() diff --git a/fastNLP/models/char_language_model.py b/fastNLP/models/char_language_model.py deleted file mode 100644 index 5fbde3cc..00000000 --- a/fastNLP/models/char_language_model.py +++ /dev/null @@ -1,131 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - -from fastNLP.modules.encoder.lstm import LSTM - - -class Highway(nn.Module): - """Highway network""" - - def __init__(self, input_size): - super(Highway, self).__init__() - self.fc1 = nn.Linear(input_size, input_size, bias=True) - self.fc2 = nn.Linear(input_size, input_size, bias=True) - - def forward(self, x): - t = F.sigmoid(self.fc1(x)) - return torch.mul(t, F.relu(self.fc2(x))) + torch.mul(1 - t, x) - - -class CharLM(nn.Module): - """CNN + highway network + LSTM - # Input: - 4D tensor with shape [batch_size, in_channel, height, width] - # Output: - 2D Tensor with shape [batch_size, vocab_size] - # Arguments: - char_emb_dim: the size of each character's attention - word_emb_dim: the size of each word's attention - vocab_size: num of unique words - num_char: num of characters - use_gpu: True or False - """ - - def __init__(self, char_emb_dim, word_emb_dim, - vocab_size, num_char): - super(CharLM, self).__init__() - self.char_emb_dim = char_emb_dim - self.word_emb_dim = word_emb_dim - self.vocab_size = vocab_size - - # char attention layer - self.char_embed = nn.Embedding(num_char, char_emb_dim) - - # convolutions of filters with different sizes - self.convolutions = [] - - # list of tuples: (the number of filter, width) - self.filter_num_width = [(25, 1), (50, 2), (75, 3), (100, 4), (125, 5), (150, 6)] - - for out_channel, filter_width in self.filter_num_width: - self.convolutions.append( - nn.Conv2d( - 1, # in_channel - out_channel, # out_channel - kernel_size=(char_emb_dim, filter_width), # (height, width) - bias=True - ) - ) - - self.highway_input_dim = sum([x for x, y in self.filter_num_width]) - - self.batch_norm = nn.BatchNorm1d(self.highway_input_dim, affine=False) - - # highway net - self.highway1 = Highway(self.highway_input_dim) - self.highway2 = Highway(self.highway_input_dim) - - # LSTM - self.lstm_num_layers = 2 - - self.lstm = LSTM(self.highway_input_dim, hidden_size=self.word_emb_dim, num_layers=self.lstm_num_layers, - dropout=0.5) - # output layer - self.dropout = nn.Dropout(p=0.5) - self.linear = nn.Linear(self.word_emb_dim, self.vocab_size) - - def forward(self, x): - # Input: Variable of Tensor with shape [num_seq, seq_len, max_word_len+2] - # Return: Variable of Tensor with shape [num_words, len(word_dict)] - lstm_batch_size = x.size()[0] - lstm_seq_len = x.size()[1] - - x = x.contiguous().view(-1, x.size()[2]) - # [num_seq*seq_len, max_word_len+2] - - x = self.char_embed(x) - # [num_seq*seq_len, max_word_len+2, char_emb_dim] - - x = torch.transpose(x.view(x.size()[0], 1, x.size()[1], -1), 2, 3) - # [num_seq*seq_len, 1, max_word_len+2, char_emb_dim] - - x = self.conv_layers(x) - # [num_seq*seq_len, total_num_filters] - - x = self.batch_norm(x) - # [num_seq*seq_len, total_num_filters] - - x = self.highway1(x) - x = self.highway2(x) - # [num_seq*seq_len, total_num_filters] - - x = x.contiguous().view(lstm_batch_size, lstm_seq_len, -1) - # [num_seq, seq_len, total_num_filters] - - x = self.lstm(x) - # [seq_len, num_seq, hidden_size] - - x = self.dropout(x) - # [seq_len, num_seq, hidden_size] - - x = x.contiguous().view(lstm_batch_size * lstm_seq_len, -1) - # [num_seq*seq_len, hidden_size] - - x = self.linear(x) - # [num_seq*seq_len, vocab_size] - return x - - def conv_layers(self, x): - chosen_list = list() - for conv in self.convolutions: - feature_map = F.tanh(conv(x)) - # (batch_size, out_channel, 1, max_word_len-width+1) - chosen = torch.max(feature_map, 3)[0] - # (batch_size, out_channel, 1) - chosen = chosen.squeeze() - # (batch_size, out_channel) - chosen_list.append(chosen) - - # (batch_size, total_num_filers) - return torch.cat(chosen_list, 1) diff --git a/fastNLP/models/cnn_text_classification.py b/fastNLP/models/cnn_text_classification.py index f3898c00..3a71a80a 100644 --- a/fastNLP/models/cnn_text_classification.py +++ b/fastNLP/models/cnn_text_classification.py @@ -1,57 +1,68 @@ -# python: 3.6 -# encoding: utf-8 +__all__ = [ + "CNNText" +] import torch import torch.nn as nn -# import torch.nn.functional as F -import fastNLP.modules.encoder as encoder +from ..core.const import Const as C +from ..modules import encoder class CNNText(torch.nn.Module): """ - Text classification model by character CNN, the implementation of paper - 'Yoon Kim. 2014. Convolution Neural Networks for Sentence - Classification.' - """ + 别名::class:`fastNLP.models.CNNText` :class:`fastNLP.models.cnn_text_classification.CNNText` - def __init__(self, embed_num, - embed_dim, + 使用CNN进行文本分类的模型 + 'Yoon Kim. 2014. Convolution Neural Networks for Sentence Classification.' + + :param tuple(int,int),torch.FloatTensor,nn.Embedding,numpy.ndarray init_embed: Embedding的大小(传入tuple(int, int), + 第一个int为vocab_zie, 第二个int为embed_dim); 如果为Tensor, Embedding, ndarray等则直接使用该值初始化Embedding + :param int num_classes: 一共有多少类 + :param int,tuple(int) out_channels: 输出channel的数量。如果为list,则需要与kernel_sizes的数量保持一致 + :param int,tuple(int) kernel_sizes: 输出channel的kernel大小。 + :param int padding: 对句子前后的pad的大小, 用0填充。 + :param float dropout: Dropout的大小 + """ + + def __init__(self, init_embed, num_classes, kernel_nums=(3, 4, 5), kernel_sizes=(3, 4, 5), padding=0, dropout=0.5): super(CNNText, self).__init__() - + # no support for pre-trained embedding currently - self.embed = encoder.Embedding(embed_num, embed_dim) + self.embed = encoder.Embedding(init_embed) self.conv_pool = encoder.ConvMaxpool( - in_channels=embed_dim, + in_channels=self.embed.embedding_dim, out_channels=kernel_nums, kernel_sizes=kernel_sizes, padding=padding) self.dropout = nn.Dropout(dropout) - self.fc = encoder.Linear(sum(kernel_nums), num_classes) - - def forward(self, word_seq): + self.fc = nn.Linear(sum(kernel_nums), num_classes) + + def forward(self, words, seq_len=None): """ - :param word_seq: torch.LongTensor, [batch_size, seq_len] + :param torch.LongTensor words: [batch_size, seq_len],句子中word的index + :param torch.LongTensor seq_len: [batch,] 每个句子的长度 :return output: dict of torch.LongTensor, [batch_size, num_classes] """ - x = self.embed(word_seq) # [N,L] -> [N,L,C] + x = self.embed(words) # [N,L] -> [N,L,C] x = self.conv_pool(x) # [N,L,C] -> [N,C] x = self.dropout(x) x = self.fc(x) # [N,C] -> [N, N_class] - return {'pred': x} - - def predict(self, word_seq): + return {C.OUTPUT: x} + + def predict(self, words, seq_len=None): """ + :param torch.LongTensor words: [batch_size, seq_len],句子中word的index + :param torch.LongTensor seq_len: [batch,] 每个句子的长度 - :param word_seq: torch.LongTensor, [batch_size, seq_len] - :return predict: dict of torch.LongTensor, [batch_size, seq_len] + :return predict: dict of torch.LongTensor, [batch_size, ] """ - output = self(word_seq) - _, predict = output['pred'].max(dim=1) - return {'pred': predict} + output = self(words, seq_len) + _, predict = output[C.OUTPUT].max(dim=1) + return {C.OUTPUT: predict} diff --git a/fastNLP/models/enas_controller.py b/fastNLP/models/enas_controller.py index ae9bcfd2..e83c6b51 100644 --- a/fastNLP/models/enas_controller.py +++ b/fastNLP/models/enas_controller.py @@ -5,9 +5,9 @@ import os import torch import torch.nn.functional as F -import fastNLP -import fastNLP.models.enas_utils as utils -from fastNLP.models.enas_utils import Node + +from . import enas_utils as utils +from .enas_utils import Node def _construct_dags(prev_nodes, activations, func_names, num_blocks): diff --git a/fastNLP/models/enas_model.py b/fastNLP/models/enas_model.py index cc91e675..b6b683c0 100644 --- a/fastNLP/models/enas_model.py +++ b/fastNLP/models/enas_model.py @@ -1,17 +1,18 @@ -# Code Modified from https://github.com/carpedm20/ENAS-pytorch - -"""Module containing the shared RNN model.""" -import numpy as np +""" +Module containing the shared RNN model. +Code Modified from https://github.com/carpedm20/ENAS-pytorch +""" import collections +import numpy as np import torch -from torch import nn +import torch.nn as nn import torch.nn.functional as F from torch.autograd import Variable -import fastNLP.models.enas_utils as utils -from fastNLP.models.base_model import BaseModel -import fastNLP.modules.encoder as encoder +from . import enas_utils as utils +from .base_model import BaseModel + def _get_dropped_weights(w_raw, dropout_p, is_training): """Drops out weights to implement DropConnect. @@ -36,12 +37,13 @@ def _get_dropped_weights(w_raw, dropout_p, is_training): The above TODO is the reason for the hacky check for `torch.nn.Parameter`. """ dropped_w = F.dropout(w_raw, p=dropout_p, training=is_training) - + if isinstance(dropped_w, torch.nn.Parameter): dropped_w = dropped_w.clone() - + return dropped_w + class EmbeddingDropout(torch.nn.Embedding): """Class for dropping out embeddings by zero'ing out parameters in the embedding matrix. @@ -54,6 +56,7 @@ class EmbeddingDropout(torch.nn.Embedding): See 'A Theoretically Grounded Application of Dropout in Recurrent Neural Networks', (Gal and Ghahramani, 2016). """ + def __init__(self, num_embeddings, embedding_dim, @@ -84,14 +87,14 @@ class EmbeddingDropout(torch.nn.Embedding): assert (dropout >= 0.0) and (dropout < 1.0), ('Dropout must be >= 0.0 ' 'and < 1.0') self.scale = scale - + def forward(self, inputs): # pylint:disable=arguments-differ """Embeds `inputs` with the dropped out embedding weight matrix.""" if self.training: dropout = self.dropout else: dropout = 0 - + if dropout: mask = self.weight.data.new(self.weight.size(0), 1) mask.bernoulli_(1 - dropout) @@ -102,7 +105,7 @@ class EmbeddingDropout(torch.nn.Embedding): masked_weight = self.weight if self.scale and self.scale != 1: masked_weight = masked_weight * self.scale - + return F.embedding(inputs, masked_weight, max_norm=self.max_norm, @@ -115,7 +118,7 @@ class LockedDropout(nn.Module): # code from https://github.com/salesforce/awd-lstm-lm/blob/master/locked_dropout.py def __init__(self): super().__init__() - + def forward(self, x, dropout=0.5): if not self.training or not dropout: return x @@ -127,11 +130,12 @@ class LockedDropout(nn.Module): class ENASModel(BaseModel): """Shared RNN model.""" + def __init__(self, embed_num, num_classes, num_blocks=4, cuda=False, shared_hid=1000, shared_embed=1000): super(ENASModel, self).__init__() - + self.use_cuda = cuda - + self.shared_hid = shared_hid self.num_blocks = num_blocks self.decoder = nn.Linear(self.shared_hid, num_classes) @@ -140,16 +144,16 @@ class ENASModel(BaseModel): dropout=0.1) self.lockdrop = LockedDropout() self.dag = None - + # Tie weights # self.decoder.weight = self.encoder.weight - + # Since W^{x, c} and W^{h, c} are always summed, there # is no point duplicating their bias offset parameter. Likewise for # W^{x, h} and W^{h, h}. self.w_xc = nn.Linear(shared_embed, self.shared_hid) self.w_xh = nn.Linear(shared_embed, self.shared_hid) - + # The raw weights are stored here because the hidden-to-hidden weights # are weight dropped on the forward pass. self.w_hc_raw = torch.nn.Parameter( @@ -158,10 +162,10 @@ class ENASModel(BaseModel): torch.Tensor(self.shared_hid, self.shared_hid)) self.w_hc = None self.w_hh = None - + self.w_h = collections.defaultdict(dict) self.w_c = collections.defaultdict(dict) - + for idx in range(self.num_blocks): for jdx in range(idx + 1, self.num_blocks): self.w_h[idx][jdx] = nn.Linear(self.shared_hid, @@ -170,48 +174,47 @@ class ENASModel(BaseModel): self.w_c[idx][jdx] = nn.Linear(self.shared_hid, self.shared_hid, bias=False) - + self._w_h = nn.ModuleList([self.w_h[idx][jdx] for idx in self.w_h for jdx in self.w_h[idx]]) self._w_c = nn.ModuleList([self.w_c[idx][jdx] for idx in self.w_c for jdx in self.w_c[idx]]) - + self.batch_norm = None # if args.mode == 'train': # self.batch_norm = nn.BatchNorm1d(self.shared_hid) # else: # self.batch_norm = None - + self.reset_parameters() self.static_init_hidden = utils.keydefaultdict(self.init_hidden) - + def setDAG(self, dag): if self.dag is None: self.dag = dag - + def forward(self, word_seq, hidden=None): inputs = torch.transpose(word_seq, 0, 1) - + time_steps = inputs.size(0) batch_size = inputs.size(1) - - + self.w_hh = _get_dropped_weights(self.w_hh_raw, 0.5, self.training) self.w_hc = _get_dropped_weights(self.w_hc_raw, 0.5, self.training) - + # hidden = self.static_init_hidden[batch_size] if hidden is None else hidden hidden = self.static_init_hidden[batch_size] - + embed = self.encoder(inputs) - + embed = self.lockdrop(embed, 0.65 if self.training else 0) - + # The norm of hidden states are clipped here because # otherwise ENAS is especially prone to exploding activations on the # forward pass. This could probably be fixed in a more elegant way, but @@ -227,7 +230,7 @@ class ENASModel(BaseModel): for step in range(time_steps): x_t = embed[step] logit, hidden = self.cell(x_t, hidden, self.dag) - + hidden_norms = hidden.norm(dim=-1) max_norm = 25.0 if hidden_norms.data.max() > max_norm: @@ -238,60 +241,60 @@ class ENASModel(BaseModel): # because the PyTorch slicing and slice assignment is too # flaky. hidden_norms = hidden_norms.data.cpu().numpy() - + clipped_num += 1 if hidden_norms.max() > max_clipped_norm: max_clipped_norm = hidden_norms.max() - + clip_select = hidden_norms > max_norm clip_norms = hidden_norms[clip_select] - + mask = np.ones(hidden.size()) - normalizer = max_norm/clip_norms + normalizer = max_norm / clip_norms normalizer = normalizer[:, np.newaxis] - + mask[clip_select] = normalizer - + if self.use_cuda: hidden *= torch.autograd.Variable( torch.FloatTensor(mask).cuda(), requires_grad=False) else: hidden *= torch.autograd.Variable( - torch.FloatTensor(mask), requires_grad=False) + torch.FloatTensor(mask), requires_grad=False) logits.append(logit) h1tohT.append(hidden) - + h1tohT = torch.stack(h1tohT) output = torch.stack(logits) raw_output = output - + output = self.lockdrop(output, 0.4 if self.training else 0) - - #Pooling + + # Pooling output = torch.mean(output, 0) - + decoded = self.decoder(output) - + extra_out = {'dropped': decoded, 'hiddens': h1tohT, 'raw': raw_output} return {'pred': decoded, 'hidden': hidden, 'extra_out': extra_out} - + def cell(self, x, h_prev, dag): """Computes a single pass through the discovered RNN cell.""" c = {} h = {} f = {} - + f[0] = self.get_f(dag[-1][0].name) c[0] = torch.sigmoid(self.w_xc(x) + F.linear(h_prev, self.w_hc, None)) - h[0] = (c[0]*f[0](self.w_xh(x) + F.linear(h_prev, self.w_hh, None)) + - (1 - c[0])*h_prev) - + h[0] = (c[0] * f[0](self.w_xh(x) + F.linear(h_prev, self.w_hh, None)) + + (1 - c[0]) * h_prev) + leaf_node_ids = [] q = collections.deque() q.append(0) - + # Computes connections from the parent nodes `node_id` # to their child nodes `next_id` recursively, skipping leaf nodes. A # leaf node is a node whose id == `self.num_blocks`. @@ -307,10 +310,10 @@ class ENASModel(BaseModel): while True: if len(q) == 0: break - + node_id = q.popleft() nodes = dag[node_id] - + for next_node in nodes: next_id = next_node.id if next_id == self.num_blocks: @@ -318,38 +321,38 @@ class ENASModel(BaseModel): assert len(nodes) == 1, ('parent of leaf node should have ' 'only one child') continue - + w_h = self.w_h[node_id][next_id] w_c = self.w_c[node_id][next_id] - + f[next_id] = self.get_f(next_node.name) c[next_id] = torch.sigmoid(w_c(h[node_id])) - h[next_id] = (c[next_id]*f[next_id](w_h(h[node_id])) + - (1 - c[next_id])*h[node_id]) - + h[next_id] = (c[next_id] * f[next_id](w_h(h[node_id])) + + (1 - c[next_id]) * h[node_id]) + q.append(next_id) - + # Instead of averaging loose ends, perhaps there should # be a set of separate unshared weights for each "loose" connection # between each node in a cell and the output. # # As it stands, all weights W^h_{ij} are doing double duty by # connecting both from i to j, as well as from i to the output. - + # average all the loose ends leaf_nodes = [h[node_id] for node_id in leaf_node_ids] output = torch.mean(torch.stack(leaf_nodes, 2), -1) - + # stabilizing the Updates of omega if self.batch_norm is not None: output = self.batch_norm(output) - + return output, h[self.num_blocks - 1] - + def init_hidden(self, batch_size): zeros = torch.zeros(batch_size, self.shared_hid) return utils.get_variable(zeros, self.use_cuda, requires_grad=False) - + def get_f(self, name): name = name.lower() if name == 'relu': @@ -361,22 +364,21 @@ class ENASModel(BaseModel): elif name == 'sigmoid': f = torch.sigmoid return f - - + @property def num_parameters(self): def size(p): return np.prod(p.size()) + return sum([size(param) for param in self.parameters()]) - - + def reset_parameters(self): init_range = 0.025 # init_range = 0.025 if self.args.mode == 'train' else 0.04 for param in self.parameters(): param.data.uniform_(-init_range, init_range) self.decoder.bias.data.fill_(0) - + def predict(self, word_seq): """ diff --git a/fastNLP/models/enas_trainer.py b/fastNLP/models/enas_trainer.py index 22e323ce..ef596b03 100644 --- a/fastNLP/models/enas_trainer.py +++ b/fastNLP/models/enas_trainer.py @@ -1,30 +1,25 @@ # Code Modified from https://github.com/carpedm20/ENAS-pytorch - -import os -import time -from datetime import datetime -from datetime import timedelta - +import math import numpy as np +import time import torch -import math -from torch import nn + +from datetime import datetime, timedelta + +from torch.optim import Adam try: - from tqdm.autonotebook import tqdm + from tqdm.auto import tqdm except: - from fastNLP.core.utils import pseudo_tqdm as tqdm - -from fastNLP.core.batch import Batch -from fastNLP.core.callback import CallbackManager, CallbackException -from fastNLP.core.dataset import DataSet -from fastNLP.core.utils import CheckError -from fastNLP.core.utils import _move_dict_value_to_device -import fastNLP -import fastNLP.models.enas_utils as utils -from fastNLP.core.utils import _build_args + from ..core.utils import _pseudo_tqdm as tqdm -from torch.optim import Adam +from ..core.trainer import Trainer +from ..core.batch import Batch +from ..core.callback import CallbackManager, CallbackException +from ..core.dataset import DataSet +from ..core.utils import _move_dict_value_to_device +from . import enas_utils as utils +from ..core.utils import _build_args def _get_no_grad_ctx_mgr(): @@ -34,8 +29,9 @@ def _get_no_grad_ctx_mgr(): return torch.no_grad() -class ENASTrainer(fastNLP.Trainer): +class ENASTrainer(Trainer): """A class to wrap training code.""" + def __init__(self, train_data, model, controller, **kwargs): """Constructor for training algorithm. :param DataSet train_data: the training data @@ -48,30 +44,31 @@ class ENASTrainer(fastNLP.Trainer): self.controller_step = 0 self.shared_step = 0 self.max_length = 35 - + self.shared = model self.controller = controller - + self.shared_optim = Adam( self.shared.parameters(), lr=20.0, weight_decay=1e-7) - + self.controller_optim = Adam( self.controller.parameters(), lr=3.5e-4) - + def train(self, load_best_model=True): """ :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效,如果True, trainer将在返回之前重新加载dev表现 最好的模型参数。 - :return results: 返回一个字典类型的数据, 内含以下内容:: + :return results: 返回一个字典类型的数据, + 内含以下内容:: - seconds: float, 表示训练时长 - 以下三个内容只有在提供了dev_data的情况下会有。 - best_eval: Dict of Dict, 表示evaluation的结果 - best_epoch: int,在第几个epoch取得的最佳值 - best_step: int, 在第几个step(batch)更新取得的最佳值 + seconds: float, 表示训练时长 + 以下三个内容只有在提供了dev_data的情况下会有。 + best_eval: Dict of Dict, 表示evaluation的结果 + best_epoch: int,在第几个epoch取得的最佳值 + best_step: int, 在第几个step(batch)更新取得的最佳值 """ results = {} @@ -80,25 +77,26 @@ class ENASTrainer(fastNLP.Trainer): results['seconds'] = 0. return results try: - if torch.cuda.is_available() and self.use_cuda: + if torch.cuda.is_available() and "cuda" in self.device: self.model = self.model.cuda() self._model_device = self.model.parameters().__next__().device self._mode(self.model, is_test=False) - + self.start_time = str(datetime.now().strftime('%Y-%m-%d-%H-%M-%S')) start_time = time.time() print("training epochs started " + self.start_time, flush=True) - + try: self.callback_manager.on_train_begin() self._train() - self.callback_manager.on_train_end(self.model) + self.callback_manager.on_train_end() except (CallbackException, KeyboardInterrupt) as e: - self.callback_manager.on_exception(e, self.model) - + self.callback_manager.on_exception(e) + if self.dev_data is not None: - print("\nIn Epoch:{}/Step:{}, got best dev performance:".format(self.best_dev_epoch, self.best_dev_step) + - self.tester._format_eval_results(self.best_dev_perf),) + print( + "\nIn Epoch:{}/Step:{}, got best dev performance:".format(self.best_dev_epoch, self.best_dev_step) + + self.tester._format_eval_results(self.best_dev_perf), ) results['best_eval'] = self.best_dev_perf results['best_epoch'] = self.best_dev_epoch results['best_step'] = self.best_dev_step @@ -112,12 +110,12 @@ class ENASTrainer(fastNLP.Trainer): finally: pass results['seconds'] = round(time.time() - start_time, 2) - + return results - + def _train(self): if not self.use_tqdm: - from fastNLP.core.utils import pseudo_tqdm as inner_tqdm + from fastNLP.core.utils import _pseudo_tqdm as inner_tqdm else: inner_tqdm = tqdm self.step = 0 @@ -128,21 +126,21 @@ class ENASTrainer(fastNLP.Trainer): avg_loss = 0 data_iterator = Batch(self.train_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, prefetch=self.prefetch) - for epoch in range(1, self.n_epochs+1): + for epoch in range(1, self.n_epochs + 1): pbar.set_description_str(desc="Epoch {}/{}".format(epoch, self.n_epochs)) last_stage = (epoch > self.n_epochs + 1 - self.final_epochs) if epoch == self.n_epochs + 1 - self.final_epochs: print('Entering the final stage. (Only train the selected structure)') # early stopping - self.callback_manager.on_epoch_begin(epoch, self.n_epochs) - + self.callback_manager.on_epoch_begin() + # 1. Training the shared parameters omega of the child models self.train_shared(pbar) - + # 2. Training the controller parameters theta if not last_stage: self.train_controller() - + if ((self.validate_every > 0 and self.step % self.validate_every == 0) or (self.validate_every < 0 and self.step % len(data_iterator) == 0)) \ and self.dev_data is not None: @@ -151,16 +149,15 @@ class ENASTrainer(fastNLP.Trainer): eval_res = self._do_validation(epoch=epoch, step=self.step) eval_str = "Evaluation at Epoch {}/{}. Step:{}/{}. ".format(epoch, self.n_epochs, self.step, total_steps) + \ - self.tester._format_eval_results(eval_res) + self.tester._format_eval_results(eval_res) pbar.write(eval_str) - + # lr decay; early stopping - self.callback_manager.on_epoch_end(epoch, self.n_epochs, self.optimizer) + self.callback_manager.on_epoch_end() # =============== epochs end =================== # pbar.close() # ============ tqdm end ============== # - - + def get_loss(self, inputs, targets, hidden, dags): """Computes the loss for the same batch for M models. @@ -169,7 +166,7 @@ class ENASTrainer(fastNLP.Trainer): """ if not isinstance(dags, list): dags = [dags] - + loss = 0 for dag in dags: self.shared.setDAG(dag) @@ -177,14 +174,14 @@ class ENASTrainer(fastNLP.Trainer): inputs['hidden'] = hidden result = self.shared(**inputs) output, hidden, extra_out = result['pred'], result['hidden'], result['extra_out'] - + self.callback_manager.on_loss_begin(targets, result) sample_loss = self._compute_loss(result, targets) loss += sample_loss - + assert len(dags) == 1, 'there are multiple `hidden` for multple `dags`' return loss, hidden, extra_out - + def train_shared(self, pbar=None, max_step=None, dag=None): """Train the language model for 400 steps of minibatches of 64 examples. @@ -202,9 +199,9 @@ class ENASTrainer(fastNLP.Trainer): model = self.shared model.train() self.controller.eval() - + hidden = self.shared.init_hidden(self.batch_size) - + abs_max_grad = 0 abs_max_hidden_norm = 0 step = 0 @@ -213,15 +210,15 @@ class ENASTrainer(fastNLP.Trainer): train_idx = 0 avg_loss = 0 data_iterator = Batch(self.train_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, - prefetch=self.prefetch) - + prefetch=self.prefetch) + for batch_x, batch_y in data_iterator: _move_dict_value_to_device(batch_x, batch_y, device=self._model_device) indices = data_iterator.get_batch_indices() # negative sampling; replace unknown; re-weight batch_y self.callback_manager.on_batch_begin(batch_x, batch_y, indices) # prediction = self._data_forward(self.model, batch_x) - + dags = self.controller.sample(1) inputs, targets = batch_x, batch_y # self.callback_manager.on_loss_begin(batch_y, prediction) @@ -230,18 +227,18 @@ class ENASTrainer(fastNLP.Trainer): hidden, dags) hidden.detach_() - + avg_loss += loss.item() - + # Is loss NaN or inf? requires_grad = False - self.callback_manager.on_backward_begin(loss, self.model) + self.callback_manager.on_backward_begin(loss) self._grad_backward(loss) - self.callback_manager.on_backward_end(self.model) - + self.callback_manager.on_backward_end() + self._update() - self.callback_manager.on_step_end(self.optimizer) - - if (self.step+1) % self.print_every == 0: + self.callback_manager.on_step_end() + + if (self.step + 1) % self.print_every == 0: if self.use_tqdm: print_output = "loss:{0:<6.5f}".format(avg_loss / self.print_every) pbar.update(self.print_every) @@ -257,30 +254,29 @@ class ENASTrainer(fastNLP.Trainer): self.shared_step += 1 self.callback_manager.on_batch_end() # ================= mini-batch end ==================== # - - + def get_reward(self, dag, entropies, hidden, valid_idx=0): """Computes the perplexity of a single sampled model on a minibatch of validation data. """ if not isinstance(entropies, np.ndarray): entropies = entropies.data.cpu().numpy() - + data_iterator = Batch(self.dev_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, - prefetch=self.prefetch) - + prefetch=self.prefetch) + for inputs, targets in data_iterator: valid_loss, hidden, _ = self.get_loss(inputs, targets, hidden, dag) valid_loss = utils.to_item(valid_loss.data) - + valid_ppl = math.exp(valid_loss) - + R = 80 / valid_ppl - + rewards = R + 1e-4 * entropies - + return rewards, hidden - + def train_controller(self): """Fixes the shared parameters and updates the controller parameters. @@ -298,13 +294,13 @@ class ENASTrainer(fastNLP.Trainer): # Why can't we call shared.eval() here? Leads to loss # being uniformly zero for the controller. # self.shared.eval() - + avg_reward_base = None baseline = None adv_history = [] entropy_history = [] reward_history = [] - + hidden = self.shared.init_hidden(self.batch_size) total_loss = 0 valid_idx = 0 @@ -312,7 +308,7 @@ class ENASTrainer(fastNLP.Trainer): # sample models dags, log_probs, entropies = self.controller.sample( with_details=True) - + # calculate reward np_entropies = entropies.data.cpu().numpy() # No gradients should be backpropagated to the @@ -322,40 +318,39 @@ class ENASTrainer(fastNLP.Trainer): np_entropies, hidden, valid_idx) - - + reward_history.extend(rewards) entropy_history.extend(np_entropies) - + # moving average baseline if baseline is None: baseline = rewards else: decay = 0.95 baseline = decay * baseline + (1 - decay) * rewards - + adv = rewards - baseline adv_history.extend(adv) - + # policy loss - loss = -log_probs*utils.get_variable(adv, - self.use_cuda, - requires_grad=False) - + loss = -log_probs * utils.get_variable(adv, + 'cuda' in self.device, + requires_grad=False) + loss = loss.sum() # or loss.mean() - + # update self.controller_optim.zero_grad() loss.backward() - + self.controller_optim.step() - + total_loss += utils.to_item(loss.data) - + if ((step % 50) == 0) and (step > 0): reward_history, adv_history, entropy_history = [], [], [] total_loss = 0 - + self.controller_step += 1 # prev_valid_idx = valid_idx # valid_idx = ((valid_idx + self.max_length) % @@ -364,16 +359,16 @@ class ENASTrainer(fastNLP.Trainer): # # validation data, we reset the hidden states. # if prev_valid_idx > valid_idx: # hidden = self.shared.init_hidden(self.batch_size) - + def derive(self, sample_num=10, valid_idx=0): """We are always deriving based on the very first batch of validation data? This seems wrong... """ hidden = self.shared.init_hidden(self.batch_size) - + dags, _, entropies = self.controller.sample(sample_num, with_details=True) - + max_R = 0 best_dag = None for dag in dags: @@ -381,5 +376,5 @@ class ENASTrainer(fastNLP.Trainer): if R.max() > max_R: max_R = R.max() best_dag = dag - + self.model.setDAG(best_dag) diff --git a/fastNLP/models/enas_utils.py b/fastNLP/models/enas_utils.py index e5027d81..4e402a9a 100644 --- a/fastNLP/models/enas_utils.py +++ b/fastNLP/models/enas_utils.py @@ -1,24 +1,20 @@ # Code Modified from https://github.com/carpedm20/ENAS-pytorch -from __future__ import print_function - from collections import defaultdict import collections -from datetime import datetime -import os -import json import numpy as np - import torch from torch.autograd import Variable + def detach(h): if type(h) == Variable: return Variable(h.data) else: return tuple(detach(v) for v in h) + def get_variable(inputs, cuda=False, **kwargs): if type(inputs) in [list, np.ndarray]: inputs = torch.Tensor(inputs) @@ -28,10 +24,12 @@ def get_variable(inputs, cuda=False, **kwargs): out = Variable(inputs, **kwargs) return out + def update_lr(optimizer, lr): for param_group in optimizer.param_groups: param_group['lr'] = lr + Node = collections.namedtuple('Node', ['id', 'name']) @@ -48,9 +46,9 @@ def to_item(x): """Converts x, possibly scalar and possibly tensor, to a Python scalar.""" if isinstance(x, (float, int)): return x - + if float(torch.__version__[0:3]) < 0.4: assert (x.dim() == 1) and (len(x) == 1) return x[0] - + return x.item() diff --git a/fastNLP/models/sequence_labeling.py b/fastNLP/models/sequence_labeling.py new file mode 100644 index 00000000..8e6a5db1 --- /dev/null +++ b/fastNLP/models/sequence_labeling.py @@ -0,0 +1,233 @@ +""" + 本模块实现了两种序列标注模型 +""" +__all__ = [ + "SeqLabeling", + "AdvSeqLabel" +] + +import torch +import torch.nn as nn + +from .base_model import BaseModel +from ..modules import decoder, encoder +from ..modules.decoder.crf import allowed_transitions +from ..core.utils import seq_len_to_mask +from ..core.const import Const as C + + +class SeqLabeling(BaseModel): + """ + 别名::class:`fastNLP.models.SeqLabeling` :class:`fastNLP.models.sequence_labeling.SeqLabeling` + + 一个基础的Sequence labeling的模型。 + 用于做sequence labeling的基础类。结构包含一层Embedding,一层LSTM(单向,一层),一层FC,以及一层CRF。 + + :param tuple(int,int),torch.FloatTensor,nn.Embedding,numpy.ndarray init_embed: Embedding的大小(传入tuple(int, int), + 第一个int为vocab_zie, 第二个int为embed_dim); 如果为Tensor, Embedding, ndarray等则直接使用该值初始化Embedding + :param int hidden_size: LSTM隐藏层的大小 + :param int num_classes: 一共有多少类 + """ + + def __init__(self, init_embed, hidden_size, num_classes): + super(SeqLabeling, self).__init__() + + self.Embedding = encoder.embedding.Embedding(init_embed) + self.Rnn = encoder.lstm.LSTM(self.Embedding.embedding_dim, hidden_size) + self.Linear = nn.Linear(hidden_size, num_classes) + self.Crf = decoder.crf.ConditionalRandomField(num_classes) + self.mask = None + + def forward(self, words, seq_len, target): + """ + :param torch.LongTensor words: [batch_size, max_len],序列的index + :param torch.LongTensor seq_len: [batch_size,], 这个序列的长度 + :param torch.LongTensor target: [batch_size, max_len], 序列的目标值 + :return y: If truth is None, return list of [decode path(list)]. Used in testing and predicting. + If truth is not None, return loss, a scalar. Used in training. + """ + assert words.shape[0] == seq_len.shape[0] + assert target.shape == words.shape + self.mask = self._make_mask(words, seq_len) + + x = self.Embedding(words) + # [batch_size, max_len, word_emb_dim] + x, _ = self.Rnn(x, seq_len) + # [batch_size, max_len, hidden_size * direction] + x = self.Linear(x) + # [batch_size, max_len, num_classes] + return {C.LOSS: self._internal_loss(x, target)} + + def predict(self, words, seq_len): + """ + 用于在预测时使用 + + :param torch.LongTensor words: [batch_size, max_len] + :param torch.LongTensor seq_len: [batch_size,] + :return: {'pred': xx}, [batch_size, max_len] + """ + self.mask = self._make_mask(words, seq_len) + + x = self.Embedding(words) + # [batch_size, max_len, word_emb_dim] + x, _ = self.Rnn(x, seq_len) + # [batch_size, max_len, hidden_size * direction] + x = self.Linear(x) + # [batch_size, max_len, num_classes] + pred = self._decode(x) + return {C.OUTPUT: pred} + + def _internal_loss(self, x, y): + """ + Negative log likelihood loss. + :param x: Tensor, [batch_size, max_len, tag_size] + :param y: Tensor, [batch_size, max_len] + :return loss: a scalar Tensor + + """ + x = x.float() + y = y.long() + assert x.shape[:2] == y.shape + assert y.shape == self.mask.shape + total_loss = self.Crf(x, y, self.mask) + return torch.mean(total_loss) + + def _make_mask(self, x, seq_len): + batch_size, max_len = x.size(0), x.size(1) + mask = seq_len_to_mask(seq_len) + mask = mask.view(batch_size, max_len) + mask = mask.to(x).float() + return mask + + def _decode(self, x): + """ + :param torch.FloatTensor x: [batch_size, max_len, tag_size] + :return prediction: [batch_size, max_len] + """ + tag_seq, _ = self.Crf.viterbi_decode(x, self.mask) + return tag_seq + + +class AdvSeqLabel(nn.Module): + """ + 别名::class:`fastNLP.models.AdvSeqLabel` :class:`fastNLP.models.sequence_labeling.AdvSeqLabel` + + 更复杂的Sequence Labelling模型。结构为Embedding, LayerNorm, 双向LSTM(两层),FC,LayerNorm,DropOut,FC,CRF。 + + :param tuple(int,int),torch.FloatTensor,nn.Embedding,numpy.ndarray init_embed: Embedding的大小(传入tuple(int, int), + 第一个int为vocab_zie, 第二个int为embed_dim); 如果为Tensor, Embedding, ndarray等则直接使用该值初始化Embedding + :param int hidden_size: LSTM的隐层大小 + :param int num_classes: 有多少个类 + :param float dropout: LSTM中以及DropOut层的drop概率 + :param dict id2words: tag id转为其tag word的表。用于在CRF解码时防止解出非法的顺序,比如'BMES'这个标签规范中,'S' + 不能出现在'B'之后。这里也支持类似与'B-NN',即'-'前为标签类型的指示,后面为具体的tag的情况。这里不但会保证 + 'B-NN'后面不为'S-NN'还会保证'B-NN'后面不会出现'M-xx'(任何非'M-NN'和'E-NN'的情况。) + :param str encoding_type: 支持"BIO", "BMES", "BEMSO", 只有在id2words不为None的情况有用。 + """ + + def __init__(self, init_embed, hidden_size, num_classes, dropout=0.3, id2words=None, encoding_type='bmes'): + + super().__init__() + + self.Embedding = encoder.embedding.Embedding(init_embed) + self.norm1 = torch.nn.LayerNorm(self.Embedding.embedding_dim) + self.Rnn = encoder.LSTM(input_size=self.Embedding.embedding_dim, hidden_size=hidden_size, num_layers=2, + dropout=dropout, + bidirectional=True, batch_first=True) + self.Linear1 = nn.Linear(hidden_size * 2, hidden_size * 2 // 3) + self.norm2 = torch.nn.LayerNorm(hidden_size * 2 // 3) + self.relu = torch.nn.LeakyReLU() + self.drop = torch.nn.Dropout(dropout) + self.Linear2 = nn.Linear(hidden_size * 2 // 3, num_classes) + + if id2words is None: + self.Crf = decoder.crf.ConditionalRandomField(num_classes, include_start_end_trans=False) + else: + self.Crf = decoder.crf.ConditionalRandomField(num_classes, include_start_end_trans=False, + allowed_transitions=allowed_transitions(id2words, + encoding_type=encoding_type)) + + def _decode(self, x): + """ + :param torch.FloatTensor x: [batch_size, max_len, tag_size] + :return torch.LongTensor, [batch_size, max_len] + """ + tag_seq, _ = self.Crf.viterbi_decode(x, self.mask) + return tag_seq + + def _internal_loss(self, x, y): + """ + Negative log likelihood loss. + :param x: Tensor, [batch_size, max_len, tag_size] + :param y: Tensor, [batch_size, max_len] + :return loss: a scalar Tensor + + """ + x = x.float() + y = y.long() + assert x.shape[:2] == y.shape + assert y.shape == self.mask.shape + total_loss = self.Crf(x, y, self.mask) + return torch.mean(total_loss) + + def _make_mask(self, x, seq_len): + batch_size, max_len = x.size(0), x.size(1) + mask = seq_len_to_mask(seq_len) + mask = mask.view(batch_size, max_len) + mask = mask.to(x).float() + return mask + + def _forward(self, words, seq_len, target=None): + """ + :param torch.LongTensor words: [batch_size, mex_len] + :param torch.LongTensor seq_len:[batch_size, ] + :param torch.LongTensor target: [batch_size, max_len] + :return y: If truth is None, return list of [decode path(list)]. Used in testing and predicting. + If truth is not None, return loss, a scalar. Used in training. + """ + + words = words.long() + seq_len = seq_len.long() + self.mask = self._make_mask(words, seq_len) + + # seq_len = seq_len.long() + target = target.long() if target is not None else None + + if next(self.parameters()).is_cuda: + words = words.cuda() + self.mask = self.mask.cuda() + + x = self.Embedding(words) + x = self.norm1(x) + # [batch_size, max_len, word_emb_dim] + + x, _ = self.Rnn(x, seq_len=seq_len) + + x = self.Linear1(x) + x = self.norm2(x) + x = self.relu(x) + x = self.drop(x) + x = self.Linear2(x) + if target is not None: + return {"loss": self._internal_loss(x, target)} + else: + return {"pred": self._decode(x)} + + def forward(self, words, seq_len, target): + """ + + :param torch.LongTensor words: [batch_size, mex_len] + :param torch.LongTensor seq_len: [batch_size, ] + :param torch.LongTensor target: [batch_size, max_len], 目标 + :return torch.Tensor: a scalar loss + """ + return self._forward(words, seq_len, target) + + def predict(self, words, seq_len): + """ + + :param torch.LongTensor words: [batch_size, mex_len] + :param torch.LongTensor seq_len: [batch_size, ] + :return torch.LongTensor: [batch_size, max_len] + """ + return self._forward(words, seq_len) diff --git a/fastNLP/models/sequence_modeling.py b/fastNLP/models/sequence_modeling.py deleted file mode 100644 index cb9e9478..00000000 --- a/fastNLP/models/sequence_modeling.py +++ /dev/null @@ -1,225 +0,0 @@ -import torch - -from fastNLP.models.base_model import BaseModel -from fastNLP.modules import decoder, encoder -from fastNLP.modules.decoder.CRF import allowed_transitions -from fastNLP.modules.utils import seq_mask - - -class SeqLabeling(BaseModel): - """ - PyTorch Network for sequence labeling - """ - - def __init__(self, args): - super(SeqLabeling, self).__init__() - vocab_size = args["vocab_size"] - word_emb_dim = args["word_emb_dim"] - hidden_dim = args["rnn_hidden_units"] - num_classes = args["num_classes"] - - self.Embedding = encoder.embedding.Embedding(vocab_size, word_emb_dim) - self.Rnn = encoder.lstm.LSTM(word_emb_dim, hidden_dim) - self.Linear = encoder.linear.Linear(hidden_dim, num_classes) - self.Crf = decoder.CRF.ConditionalRandomField(num_classes) - self.mask = None - - def forward(self, word_seq, word_seq_origin_len, truth=None): - """ - :param word_seq: LongTensor, [batch_size, mex_len] - :param word_seq_origin_len: LongTensor, [batch_size,], the origin lengths of the sequences. - :param truth: LongTensor, [batch_size, max_len] - :return y: If truth is None, return list of [decode path(list)]. Used in testing and predicting. - If truth is not None, return loss, a scalar. Used in training. - """ - assert word_seq.shape[0] == word_seq_origin_len.shape[0] - if truth is not None: - assert truth.shape == word_seq.shape - self.mask = self.make_mask(word_seq, word_seq_origin_len) - - x = self.Embedding(word_seq) - # [batch_size, max_len, word_emb_dim] - x = self.Rnn(x) - # [batch_size, max_len, hidden_size * direction] - x = self.Linear(x) - # [batch_size, max_len, num_classes] - return {"loss": self._internal_loss(x, truth) if truth is not None else None, - "predict": self.decode(x)} - - def loss(self, x, y): - """ Since the loss has been computed in forward(), this function simply returns x.""" - return x - - def _internal_loss(self, x, y): - """ - Negative log likelihood loss. - :param x: Tensor, [batch_size, max_len, tag_size] - :param y: Tensor, [batch_size, max_len] - :return loss: a scalar Tensor - - """ - x = x.float() - y = y.long() - assert x.shape[:2] == y.shape - assert y.shape == self.mask.shape - total_loss = self.Crf(x, y, self.mask) - return torch.mean(total_loss) - - def make_mask(self, x, seq_len): - batch_size, max_len = x.size(0), x.size(1) - mask = seq_mask(seq_len, max_len) - mask = mask.view(batch_size, max_len) - mask = mask.to(x).float() - return mask - - def decode(self, x, pad=True): - """ - :param x: FloatTensor, [batch_size, max_len, tag_size] - :param pad: pad the output sequence to equal lengths - :return prediction: list of [decode path(list)] - """ - max_len = x.shape[1] - tag_seq = self.Crf.viterbi_decode(x, self.mask) - # pad prediction to equal length - if pad is True: - for pred in tag_seq: - if len(pred) < max_len: - pred += [0] * (max_len - len(pred)) - return tag_seq - - -class AdvSeqLabel(SeqLabeling): - """ - Advanced Sequence Labeling Model - """ - - def __init__(self, args, emb=None, id2words=None): - super(AdvSeqLabel, self).__init__(args) - - vocab_size = args["vocab_size"] - word_emb_dim = args["word_emb_dim"] - hidden_dim = args["rnn_hidden_units"] - num_classes = args["num_classes"] - dropout = args['dropout'] - - self.Embedding = encoder.embedding.Embedding(vocab_size, word_emb_dim, init_emb=emb) - self.norm1 = torch.nn.LayerNorm(word_emb_dim) - # self.Rnn = encoder.lstm.LSTM(word_emb_dim, hidden_dim, num_layers=2, dropout=dropout, bidirectional=True) - self.Rnn = torch.nn.LSTM(input_size=word_emb_dim, hidden_size=hidden_dim, num_layers=2, dropout=dropout, - bidirectional=True, batch_first=True) - self.Linear1 = encoder.Linear(hidden_dim * 2, hidden_dim * 2 // 3) - self.norm2 = torch.nn.LayerNorm(hidden_dim * 2 // 3) - # self.batch_norm = torch.nn.BatchNorm1d(hidden_dim * 2 // 3) - self.relu = torch.nn.LeakyReLU() - self.drop = torch.nn.Dropout(dropout) - self.Linear2 = encoder.Linear(hidden_dim * 2 // 3, num_classes) - - if id2words is None: - self.Crf = decoder.CRF.ConditionalRandomField(num_classes, include_start_end_trans=False) - else: - self.Crf = decoder.CRF.ConditionalRandomField(num_classes, include_start_end_trans=False, - allowed_transitions=allowed_transitions(id2words, - encoding_type="bmes")) - - def forward(self, word_seq, word_seq_origin_len, truth=None): - """ - :param word_seq: LongTensor, [batch_size, mex_len] - :param word_seq_origin_len: LongTensor, [batch_size, ] - :param truth: LongTensor, [batch_size, max_len] - :return y: If truth is None, return list of [decode path(list)]. Used in testing and predicting. - If truth is not None, return loss, a scalar. Used in training. - """ - - word_seq = word_seq.long() - word_seq_origin_len = word_seq_origin_len.long() - self.mask = self.make_mask(word_seq, word_seq_origin_len) - sent_len, idx_sort = torch.sort(word_seq_origin_len, descending=True) - _, idx_unsort = torch.sort(idx_sort, descending=False) - - # word_seq_origin_len = word_seq_origin_len.long() - truth = truth.long() if truth is not None else None - - batch_size = word_seq.size(0) - max_len = word_seq.size(1) - if next(self.parameters()).is_cuda: - word_seq = word_seq.cuda() - idx_sort = idx_sort.cuda() - idx_unsort = idx_unsort.cuda() - self.mask = self.mask.cuda() - - x = self.Embedding(word_seq) - x = self.norm1(x) - # [batch_size, max_len, word_emb_dim] - - sent_variable = x[idx_sort] - sent_packed = torch.nn.utils.rnn.pack_padded_sequence(sent_variable, sent_len, batch_first=True) - - x, _ = self.Rnn(sent_packed) - # print(x) - # [batch_size, max_len, hidden_size * direction] - - sent_output = torch.nn.utils.rnn.pad_packed_sequence(x, batch_first=True)[0] - x = sent_output[idx_unsort] - - x = x.contiguous() - # x = x.view(batch_size * max_len, -1) - x = self.Linear1(x) - # x = self.batch_norm(x) - x = self.norm2(x) - x = self.relu(x) - x = self.drop(x) - x = self.Linear2(x) - # x = x.view(batch_size, max_len, -1) - # [batch_size, max_len, num_classes] - # TODO seq_lens的key这样做不合理 - return {"loss": self._internal_loss(x, truth) if truth is not None else None, - "predict": self.decode(x), - 'word_seq_origin_len': word_seq_origin_len} - - def predict(self, **x): - out = self.forward(**x) - return {"predict": out["predict"]} - - def loss(self, **kwargs): - assert 'loss' in kwargs - return kwargs['loss'] - - -if __name__ == '__main__': - args = { - 'vocab_size': 20, - 'word_emb_dim': 100, - 'rnn_hidden_units': 100, - 'num_classes': 10, - } - model = AdvSeqLabel(args) - data = [] - for i in range(20): - word_seq = torch.randint(20, (15,)).long() - word_seq_len = torch.LongTensor([15]) - truth = torch.randint(10, (15,)).long() - data.append((word_seq, word_seq_len, truth)) - optimizer = torch.optim.Adam(model.parameters(), lr=0.01) - print(model) - curidx = 0 - for i in range(1000): - endidx = min(len(data), curidx + 5) - b_word, b_len, b_truth = [], [], [] - for word_seq, word_seq_len, truth in data[curidx: endidx]: - b_word.append(word_seq) - b_len.append(word_seq_len) - b_truth.append(truth) - word_seq = torch.stack(b_word, dim=0) - word_seq_len = torch.cat(b_len, dim=0) - truth = torch.stack(b_truth, dim=0) - res = model(word_seq, word_seq_len, truth) - loss = res['loss'] - pred = res['predict'] - print('loss: {} acc {}'.format(loss.item(), - ((pred.data == truth).long().sum().float() / word_seq_len.sum().float()))) - optimizer.zero_grad() - loss.backward() - optimizer.step() - curidx = endidx - if curidx == len(data): - curidx = 0 diff --git a/fastNLP/models/snli.py b/fastNLP/models/snli.py index 6a7d8d84..395a9bbf 100644 --- a/fastNLP/models/snli.py +++ b/fastNLP/models/snli.py @@ -1,114 +1,152 @@ +__all__ = [ + "ESIM" +] + import torch import torch.nn as nn -import torch.nn.functional as F - -from fastNLP.models.base_model import BaseModel -from fastNLP.modules import decoder as Decoder -from fastNLP.modules import encoder as Encoder -from fastNLP.modules import aggregator as Aggregator +from .base_model import BaseModel +from ..core.const import Const +from ..modules import decoder as Decoder +from ..modules import encoder as Encoder +from ..modules import aggregator as Aggregator +from ..core.utils import seq_len_to_mask my_inf = 10e12 class ESIM(BaseModel): """ - PyTorch Network for SNLI task using ESIM model. - """ + 别名::class:`fastNLP.models.ESIM` :class:`fastNLP.models.snli.ESIM` - def __init__(self, **kwargs): - super(ESIM, self).__init__() - self.vocab_size = kwargs["vocab_size"] - self.embed_dim = kwargs["embed_dim"] - self.hidden_size = kwargs["hidden_size"] - self.batch_first = kwargs["batch_first"] - self.dropout = kwargs["dropout"] - self.n_labels = kwargs["num_classes"] - self.gpu = kwargs["gpu"] and torch.cuda.is_available() + ESIM模型的一个PyTorch实现。 + ESIM模型的论文: Enhanced LSTM for Natural Language Inference (arXiv: 1609.06038) + :param int vocab_size: 词表大小 + :param int embed_dim: 词嵌入维度 + :param int hidden_size: LSTM隐层大小 + :param float dropout: dropout大小,默认为0 + :param int num_classes: 标签数目,默认为3 + :param numpy.array init_embedding: 初始词嵌入矩阵,形状为(vocab_size, embed_dim),默认为None,即随机初始化词嵌入矩阵 + """ + + def __init__(self, vocab_size, embed_dim, hidden_size, dropout=0.0, num_classes=3, init_embedding=None): + + super(ESIM, self).__init__() + self.vocab_size = vocab_size + self.embed_dim = embed_dim + self.hidden_size = hidden_size + self.dropout = dropout + self.n_labels = num_classes + self.drop = nn.Dropout(self.dropout) - + self.embedding = Encoder.Embedding( - self.vocab_size, self.embed_dim, dropout=self.dropout, - init_emb=kwargs["init_embedding"] if "inin_embedding" in kwargs.keys() else None, + (self.vocab_size, self.embed_dim), dropout=self.dropout, ) - - self.embedding_layer = Encoder.Linear(self.embed_dim, self.hidden_size) - + + self.embedding_layer = nn.Linear(self.embed_dim, self.hidden_size) + self.encoder = Encoder.LSTM( input_size=self.embed_dim, hidden_size=self.hidden_size, num_layers=1, bias=True, - batch_first=self.batch_first, bidirectional=True + batch_first=True, bidirectional=True ) - - self.bi_attention = Aggregator.Bi_Attention() - self.mean_pooling = Aggregator.MeanPoolWithMask() + + self.bi_attention = Aggregator.BiAttention() + self.mean_pooling = Aggregator.AvgPoolWithMask() self.max_pooling = Aggregator.MaxPoolWithMask() - - self.inference_layer = Encoder.Linear(self.hidden_size * 4, self.hidden_size) - + + self.inference_layer = nn.Linear(self.hidden_size * 4, self.hidden_size) + self.decoder = Encoder.LSTM( input_size=self.hidden_size, hidden_size=self.hidden_size, num_layers=1, bias=True, - batch_first=self.batch_first, bidirectional=True + batch_first=True, bidirectional=True ) - + self.output = Decoder.MLP([4 * self.hidden_size, self.hidden_size, self.n_labels], 'tanh', dropout=self.dropout) - - def forward(self, premise, hypothesis, premise_len, hypothesis_len): + + def forward(self, words1, words2, seq_len1=None, seq_len2=None, target=None): """ Forward function - - :param premise: A Tensor represents premise: [batch size(B), premise seq len(PL)]. - :param hypothesis: A Tensor represents hypothesis: [B, hypothesis seq len(HL)]. - :param premise_len: A Tensor record which is a real word and which is a padding word in premise: [B, PL]. - :param hypothesis_len: A Tensor record which is a real word and which is a padding word in hypothesis: [B, HL]. - :return: prediction: A Dict with Tensor of classification result: [B, n_labels(N)]. + + :param torch.Tensor words1: [batch size(B), premise seq len(PL)] premise的token表示 + :param torch.Tensor words2: [B, hypothesis seq len(HL)] hypothesis的token表示 + :param torch.LongTensor seq_len1: [B] premise的长度 + :param torch.LongTensor seq_len2: [B] hypothesis的长度 + :param torch.LongTensor target: [B] 真实目标值 + :return: dict prediction: [B, n_labels(N)] 预测结果 """ - - premise0 = self.embedding_layer(self.embedding(premise)) - hypothesis0 = self.embedding_layer(self.embedding(hypothesis)) - + + premise0 = self.embedding_layer(self.embedding(words1)) + hypothesis0 = self.embedding_layer(self.embedding(words2)) + + if seq_len1 is not None: + seq_len1 = seq_len_to_mask(seq_len1) + else: + seq_len1 = torch.ones(premise0.size(0), premise0.size(1)) + seq_len1 = (seq_len1.long()).to(device=premise0.device) + if seq_len2 is not None: + seq_len2 = seq_len_to_mask(seq_len2) + else: + seq_len2 = torch.ones(hypothesis0.size(0), hypothesis0.size(1)) + seq_len2 = (seq_len2.long()).to(device=hypothesis0.device) + _BP, _PSL, _HP = premise0.size() _BH, _HSL, _HH = hypothesis0.size() - _BPL, _PLL = premise_len.size() - _HPL, _HLL = hypothesis_len.size() - + _BPL, _PLL = seq_len1.size() + _HPL, _HLL = seq_len2.size() + assert _BP == _BH and _BPL == _HPL and _BP == _BPL assert _HP == _HH assert _PSL == _PLL and _HSL == _HLL - + B, PL, H = premise0.size() B, HL, H = hypothesis0.size() - + a0 = self.encoder(self.drop(premise0)) # a0: [B, PL, H * 2] b0 = self.encoder(self.drop(hypothesis0)) # b0: [B, HL, H * 2] - + a = torch.mean(a0.view(B, PL, -1, H), dim=2) # a: [B, PL, H] b = torch.mean(b0.view(B, HL, -1, H), dim=2) # b: [B, HL, H] - - ai, bi = self.bi_attention(a, b, premise_len, hypothesis_len) - + + ai, bi = self.bi_attention(a, b, seq_len1, seq_len2) + ma = torch.cat((a, ai, a - ai, a * ai), dim=2) # ma: [B, PL, 4 * H] mb = torch.cat((b, bi, b - bi, b * bi), dim=2) # mb: [B, HL, 4 * H] - + f_ma = self.inference_layer(ma) f_mb = self.inference_layer(mb) - + vat = self.decoder(self.drop(f_ma)) vbt = self.decoder(self.drop(f_mb)) - + va = torch.mean(vat.view(B, PL, -1, H), dim=2) # va: [B, PL, H] vb = torch.mean(vbt.view(B, HL, -1, H), dim=2) # vb: [B, HL, H] - - va_ave = self.mean_pooling(va, premise_len, dim=1) # va_ave: [B, H] - va_max, va_arg_max = self.max_pooling(va, premise_len, dim=1) # va_max: [B, H] - vb_ave = self.mean_pooling(vb, hypothesis_len, dim=1) # vb_ave: [B, H] - vb_max, vb_arg_max = self.max_pooling(vb, hypothesis_len, dim=1) # vb_max: [B, H] - + + va_ave = self.mean_pooling(va, seq_len1, dim=1) # va_ave: [B, H] + va_max, va_arg_max = self.max_pooling(va, seq_len1, dim=1) # va_max: [B, H] + vb_ave = self.mean_pooling(vb, seq_len2, dim=1) # vb_ave: [B, H] + vb_max, vb_arg_max = self.max_pooling(vb, seq_len2, dim=1) # vb_max: [B, H] + v = torch.cat((va_ave, va_max, vb_ave, vb_max), dim=1) # v: [B, 4 * H] - - prediction = F.tanh(self.output(v)) # prediction: [B, N] - - return {'pred': prediction} - - def predict(self, premise, hypothesis, premise_len, hypothesis_len): - return self.forward(premise, hypothesis, premise_len, hypothesis_len) - + + prediction = torch.tanh(self.output(v)) # prediction: [B, N] + + if target is not None: + func = nn.CrossEntropyLoss() + loss = func(prediction, target) + return {Const.OUTPUT: prediction, Const.LOSS: loss} + + return {Const.OUTPUT: prediction} + + def predict(self, words1, words2, seq_len1=None, seq_len2=None, target=None): + """ Predict function + + :param torch.Tensor words1: [batch size(B), premise seq len(PL)] premise的token表示 + :param torch.Tensor words2: [B, hypothesis seq len(HL)] hypothesis的token表示 + :param torch.LongTensor seq_len1: [B] premise的长度 + :param torch.LongTensor seq_len2: [B] hypothesis的长度 + :param torch.LongTensor target: [B] 真实目标值 + :return: dict prediction: [B, n_labels(N)] 预测结果 + """ + prediction = self.forward(words1, words2, seq_len1, seq_len2)[Const.OUTPUT] + return {Const.OUTPUT: torch.argmax(prediction, dim=-1)} diff --git a/fastNLP/models/star_transformer.py b/fastNLP/models/star_transformer.py new file mode 100644 index 00000000..4c944a54 --- /dev/null +++ b/fastNLP/models/star_transformer.py @@ -0,0 +1,307 @@ +""" +Star-Transformer 的 Pytorch 实现。 +""" +__all__ = [ + "StarTransEnc", + "STNLICls", + "STSeqCls", + "STSeqLabel", +] + +import torch +from torch import nn + +from ..modules.encoder.star_transformer import StarTransformer +from ..core.utils import seq_len_to_mask +from ..modules.utils import get_embeddings +from ..core.const import Const + + +class StarTransEnc(nn.Module): + """ + 别名::class:`fastNLP.models.StarTransEnc` :class:`fastNLP.models.star_transformer.StarTransEnc` + + 带word embedding的Star-Transformer Encoder + + :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 + embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, + 此时就以传入的对象作为embedding + :param hidden_size: 模型中特征维度. + :param num_layers: 模型层数. + :param num_head: 模型中multi-head的head个数. + :param head_dim: 模型中multi-head中每个head特征维度. + :param max_len: 模型能接受的最大输入长度. + :param emb_dropout: 词嵌入的dropout概率. + :param dropout: 模型除词嵌入外的dropout概率. + """ + + def __init__(self, init_embed, + hidden_size, + num_layers, + num_head, + head_dim, + max_len, + emb_dropout, + dropout): + super(StarTransEnc, self).__init__() + self.embedding = get_embeddings(init_embed) + emb_dim = self.embedding.embedding_dim + self.emb_fc = nn.Linear(emb_dim, hidden_size) + self.emb_drop = nn.Dropout(emb_dropout) + self.encoder = StarTransformer(hidden_size=hidden_size, + num_layers=num_layers, + num_head=num_head, + head_dim=head_dim, + dropout=dropout, + max_len=max_len) + + def forward(self, x, mask): + """ + :param FloatTensor x: [batch, length, hidden] 输入的序列 + :param ByteTensor mask: [batch, length] 输入序列的padding mask, 在没有内容(padding 部分) 为 0, + 否则为 1 + :return: [batch, length, hidden] 编码后的输出序列 + + [batch, hidden] 全局 relay 节点, 详见论文 + """ + x = self.embedding(x) + x = self.emb_fc(self.emb_drop(x)) + nodes, relay = self.encoder(x, mask) + return nodes, relay + + +class _Cls(nn.Module): + def __init__(self, in_dim, num_cls, hid_dim, dropout=0.1): + super(_Cls, self).__init__() + self.fc = nn.Sequential( + nn.Linear(in_dim, hid_dim), + nn.LeakyReLU(), + nn.Dropout(dropout), + nn.Linear(hid_dim, num_cls), + ) + + def forward(self, x): + h = self.fc(x) + return h + + +class _NLICls(nn.Module): + def __init__(self, in_dim, num_cls, hid_dim, dropout=0.1): + super(_NLICls, self).__init__() + self.fc = nn.Sequential( + nn.Dropout(dropout), + nn.Linear(in_dim * 4, hid_dim), # 4 + nn.LeakyReLU(), + nn.Dropout(dropout), + nn.Linear(hid_dim, num_cls), + ) + + def forward(self, x1, x2): + x = torch.cat([x1, x2, torch.abs(x1 - x2), x1 * x2], 1) + h = self.fc(x) + return h + + +class STSeqLabel(nn.Module): + """ + 别名::class:`fastNLP.models.STSeqLabel` :class:`fastNLP.models.star_transformer.STSeqLabel` + + 用于序列标注的Star-Transformer模型 + + :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 + embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, + 此时就以传入的对象作为embedding + :param num_cls: 输出类别个数 + :param hidden_size: 模型中特征维度. Default: 300 + :param num_layers: 模型层数. Default: 4 + :param num_head: 模型中multi-head的head个数. Default: 8 + :param head_dim: 模型中multi-head中每个head特征维度. Default: 32 + :param max_len: 模型能接受的最大输入长度. Default: 512 + :param cls_hidden_size: 分类器隐层维度. Default: 600 + :param emb_dropout: 词嵌入的dropout概率. Default: 0.1 + :param dropout: 模型除词嵌入外的dropout概率. Default: 0.1 + """ + + def __init__(self, init_embed, num_cls, + hidden_size=300, + num_layers=4, + num_head=8, + head_dim=32, + max_len=512, + cls_hidden_size=600, + emb_dropout=0.1, + dropout=0.1, ): + super(STSeqLabel, self).__init__() + self.enc = StarTransEnc(init_embed=init_embed, + hidden_size=hidden_size, + num_layers=num_layers, + num_head=num_head, + head_dim=head_dim, + max_len=max_len, + emb_dropout=emb_dropout, + dropout=dropout) + self.cls = _Cls(hidden_size, num_cls, cls_hidden_size) + + def forward(self, words, seq_len): + """ + + :param words: [batch, seq_len] 输入序列 + :param seq_len: [batch,] 输入序列的长度 + :return output: [batch, num_cls, seq_len] 输出序列中每个元素的分类的概率 + """ + mask = seq_len_to_mask(seq_len) + nodes, _ = self.enc(words, mask) + output = self.cls(nodes) + output = output.transpose(1, 2) # make hidden to be dim 1 + return {Const.OUTPUT: output} # [bsz, n_cls, seq_len] + + def predict(self, words, seq_len): + """ + + :param words: [batch, seq_len] 输入序列 + :param seq_len: [batch,] 输入序列的长度 + :return output: [batch, seq_len] 输出序列中每个元素的分类 + """ + y = self.forward(words, seq_len) + _, pred = y[Const.OUTPUT].max(1) + return {Const.OUTPUT: pred} + + +class STSeqCls(nn.Module): + """ + 别名::class:`fastNLP.models.STSeqCls` :class:`fastNLP.models.star_transformer.STSeqCls` + + 用于分类任务的Star-Transformer + + :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 + embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, + 此时就以传入的对象作为embedding + :param num_cls: 输出类别个数 + :param hidden_size: 模型中特征维度. Default: 300 + :param num_layers: 模型层数. Default: 4 + :param num_head: 模型中multi-head的head个数. Default: 8 + :param head_dim: 模型中multi-head中每个head特征维度. Default: 32 + :param max_len: 模型能接受的最大输入长度. Default: 512 + :param cls_hidden_size: 分类器隐层维度. Default: 600 + :param emb_dropout: 词嵌入的dropout概率. Default: 0.1 + :param dropout: 模型除词嵌入外的dropout概率. Default: 0.1 + """ + + def __init__(self, init_embed, num_cls, + hidden_size=300, + num_layers=4, + num_head=8, + head_dim=32, + max_len=512, + cls_hidden_size=600, + emb_dropout=0.1, + dropout=0.1, ): + super(STSeqCls, self).__init__() + self.enc = StarTransEnc(init_embed=init_embed, + hidden_size=hidden_size, + num_layers=num_layers, + num_head=num_head, + head_dim=head_dim, + max_len=max_len, + emb_dropout=emb_dropout, + dropout=dropout) + self.cls = _Cls(hidden_size, num_cls, cls_hidden_size) + + def forward(self, words, seq_len): + """ + + :param words: [batch, seq_len] 输入序列 + :param seq_len: [batch,] 输入序列的长度 + :return output: [batch, num_cls] 输出序列的分类的概率 + """ + mask = seq_len_to_mask(seq_len) + nodes, relay = self.enc(words, mask) + y = 0.5 * (relay + nodes.max(1)[0]) + output = self.cls(y) # [bsz, n_cls] + return {Const.OUTPUT: output} + + def predict(self, words, seq_len): + """ + + :param words: [batch, seq_len] 输入序列 + :param seq_len: [batch,] 输入序列的长度 + :return output: [batch, num_cls] 输出序列的分类 + """ + y = self.forward(words, seq_len) + _, pred = y[Const.OUTPUT].max(1) + return {Const.OUTPUT: pred} + + +class STNLICls(nn.Module): + """ + 别名::class:`fastNLP.models.STNLICls` :class:`fastNLP.models.star_transformer.STNLICls` + + 用于自然语言推断(NLI)的Star-Transformer + + :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 + embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, + 此时就以传入的对象作为embedding + :param num_cls: 输出类别个数 + :param hidden_size: 模型中特征维度. Default: 300 + :param num_layers: 模型层数. Default: 4 + :param num_head: 模型中multi-head的head个数. Default: 8 + :param head_dim: 模型中multi-head中每个head特征维度. Default: 32 + :param max_len: 模型能接受的最大输入长度. Default: 512 + :param cls_hidden_size: 分类器隐层维度. Default: 600 + :param emb_dropout: 词嵌入的dropout概率. Default: 0.1 + :param dropout: 模型除词嵌入外的dropout概率. Default: 0.1 + """ + + def __init__(self, init_embed, num_cls, + hidden_size=300, + num_layers=4, + num_head=8, + head_dim=32, + max_len=512, + cls_hidden_size=600, + emb_dropout=0.1, + dropout=0.1, ): + super(STNLICls, self).__init__() + self.enc = StarTransEnc(init_embed=init_embed, + hidden_size=hidden_size, + num_layers=num_layers, + num_head=num_head, + head_dim=head_dim, + max_len=max_len, + emb_dropout=emb_dropout, + dropout=dropout) + self.cls = _NLICls(hidden_size, num_cls, cls_hidden_size) + + def forward(self, words1, words2, seq_len1, seq_len2): + """ + + :param words1: [batch, seq_len] 输入序列1 + :param words2: [batch, seq_len] 输入序列2 + :param seq_len1: [batch,] 输入序列1的长度 + :param seq_len2: [batch,] 输入序列2的长度 + :return output: [batch, num_cls] 输出分类的概率 + """ + mask1 = seq_len_to_mask(seq_len1) + mask2 = seq_len_to_mask(seq_len2) + + def enc(seq, mask): + nodes, relay = self.enc(seq, mask) + return 0.5 * (relay + nodes.max(1)[0]) + + y1 = enc(words1, mask1) + y2 = enc(words2, mask2) + output = self.cls(y1, y2) # [bsz, n_cls] + return {Const.OUTPUT: output} + + def predict(self, words1, words2, seq_len1, seq_len2): + """ + + :param words1: [batch, seq_len] 输入序列1 + :param words2: [batch, seq_len] 输入序列2 + :param seq_len1: [batch,] 输入序列1的长度 + :param seq_len2: [batch,] 输入序列2的长度 + :return output: [batch, num_cls] 输出分类的概率 + """ + y = self.forward(words1, words2, seq_len1, seq_len2) + _, pred = y[Const.OUTPUT].max(1) + return {Const.OUTPUT: pred} diff --git a/fastNLP/modules/__init__.py b/fastNLP/modules/__init__.py index 37223394..194fda4e 100644 --- a/fastNLP/modules/__init__.py +++ b/fastNLP/modules/__init__.py @@ -1,3 +1,51 @@ +""" +大部分用于的 NLP 任务神经网络都可以看做由编码 :mod:`~fastNLP.modules.encoder` 、 +聚合 :mod:`~fastNLP.modules.aggregator` 、解码 :mod:`~fastNLP.modules.decoder` 三种模块组成。 + +.. image:: figures/text_classification.png + +:mod:`~fastNLP.modules` 中实现了 fastNLP 提供的诸多模块组件,可以帮助用户快速搭建自己所需的网络。 +三种模块的功能和常见组件如下: + ++-----------------------+-----------------------+-----------------------+ +| module type | functionality | example | ++=======================+=======================+=======================+ +| encoder | 将输入编码为具有具 | embedding, RNN, CNN, | +| | 有表示能力的向量 | transformer | ++-----------------------+-----------------------+-----------------------+ +| aggregator | 从多个向量中聚合信息 | self-attention, | +| | | max-pooling | ++-----------------------+-----------------------+-----------------------+ +| decoder | 将具有某种表示意义的 | MLP, CRF | +| | 向量解码为需要的输出 | | +| | 形式 | | ++-----------------------+-----------------------+-----------------------+ + +""" +__all__ = [ + # "BertModel", + "ConvolutionCharEncoder", + "LSTMCharEncoder", + "ConvMaxpool", + "Embedding", + "LSTM", + "StarTransformer", + "TransformerEncoder", + "VarRNN", + "VarLSTM", + "VarGRU", + + "MaxPool", + "MaxPoolWithMask", + "AvgPool", + "MultiHeadAttention", + + "MLP", + "ConditionalRandomField", + "viterbi_decode", + "allowed_transitions", +] + from . import aggregator from . import decoder from . import encoder @@ -5,9 +53,4 @@ from .aggregator import * from .decoder import * from .dropout import TimestepDropout from .encoder import * - -__version__ = '0.0.0' - -__all__ = ['encoder', - 'decoder', - 'aggregator'] +from .utils import get_embeddings diff --git a/fastNLP/modules/aggregator/__init__.py b/fastNLP/modules/aggregator/__init__.py index 2fabb89e..a82138e7 100644 --- a/fastNLP/modules/aggregator/__init__.py +++ b/fastNLP/modules/aggregator/__init__.py @@ -1,10 +1,14 @@ -from .max_pool import MaxPool -from .max_pool import MaxPoolWithMask -from .avg_pool import AvgPool -from .avg_pool import MeanPoolWithMask -from .kmax_pool import KMaxPool +__all__ = [ + "MaxPool", + "MaxPoolWithMask", + "AvgPool", + + "MultiHeadAttention", +] -from .attention import Attention -from .attention import Bi_Attention -from .self_attention import SelfAttention +from .pooling import MaxPool +from .pooling import MaxPoolWithMask +from .pooling import AvgPool +from .pooling import AvgPoolWithMask +from .attention import MultiHeadAttention diff --git a/fastNLP/modules/aggregator/attention.py b/fastNLP/modules/aggregator/attention.py index ef9d159d..4101b033 100644 --- a/fastNLP/modules/aggregator/attention.py +++ b/fastNLP/modules/aggregator/attention.py @@ -1,152 +1,233 @@ +__all__ = [ + "MultiHeadAttention" +] + import math import torch import torch.nn.functional as F from torch import nn -from fastNLP.modules.dropout import TimestepDropout -from fastNLP.modules.utils import mask_softmax - - -class Attention(torch.nn.Module): - def __init__(self, normalize=False): - super(Attention, self).__init__() - self.normalize = normalize - - def forward(self, query, memory, mask): - similarities = self._atten_forward(query, memory) - if self.normalize: - return mask_softmax(similarities, mask) - return similarities +from ..dropout import TimestepDropout - def _atten_forward(self, query, memory): - raise NotImplementedError +from ..utils import initial_parameter -class DotAtte(nn.Module): - def __init__(self, key_size, value_size, dropout=0.1): - super(DotAtte, self).__init__() +class DotAttention(nn.Module): + """ + .. todo:: + 补上文档 + """ + + def __init__(self, key_size, value_size, dropout=0): + super(DotAttention, self).__init__() self.key_size = key_size self.value_size = value_size self.scale = math.sqrt(key_size) self.drop = nn.Dropout(dropout) self.softmax = nn.Softmax(dim=2) - + def forward(self, Q, K, V, mask_out=None): """ - :param Q: [batch, seq_len, key_size] - :param K: [batch, seq_len, key_size] - :param V: [batch, seq_len, value_size] - :param mask_out: [batch, seq_len] + :param Q: [batch, seq_len_q, key_size] + :param K: [batch, seq_len_k, key_size] + :param V: [batch, seq_len_k, value_size] + :param mask_out: [batch, 1, seq_len] or [batch, seq_len_q, seq_len_k] """ output = torch.matmul(Q, K.transpose(1, 2)) / self.scale if mask_out is not None: - output.masked_fill_(mask_out, -float('inf')) + output.masked_fill_(mask_out, -1e8) output = self.softmax(output) output = self.drop(output) return torch.matmul(output, V) -class MultiHeadAtte(nn.Module): - def __init__(self, input_size, key_size, value_size, num_head, dropout=0.1): - """ +class MultiHeadAttention(nn.Module): + """ + 别名::class:`fastNLP.modules.MultiHeadAttention` :class:`fastNLP.modules.aggregator.attention.MultiHeadAttention` - :param input_size: int, 输入维度的大小。同时也是输出维度的大小。 - :param key_size: int, 每个head的维度大小。 - :param value_size: int,每个head中value的维度。 - :param num_head: int,head的数量。 - :param dropout: float。 - """ - super(MultiHeadAtte, self).__init__() + + :param input_size: int, 输入维度的大小。同时也是输出维度的大小。 + :param key_size: int, 每个head的维度大小。 + :param value_size: int,每个head中value的维度。 + :param num_head: int,head的数量。 + :param dropout: float。 + """ + + def __init__(self, input_size, key_size, value_size, num_head, dropout=0.1): + super(MultiHeadAttention, self).__init__() self.input_size = input_size self.key_size = key_size self.value_size = value_size self.num_head = num_head - + in_size = key_size * num_head self.q_in = nn.Linear(input_size, in_size) self.k_in = nn.Linear(input_size, in_size) self.v_in = nn.Linear(input_size, in_size) - self.attention = DotAtte(key_size=key_size, value_size=value_size) + # follow the paper, do not apply dropout within dot-product + self.attention = DotAttention(key_size=key_size, value_size=value_size, dropout=0) self.out = nn.Linear(value_size * num_head, input_size) self.drop = TimestepDropout(dropout) self.reset_parameters() - + def reset_parameters(self): sqrt = math.sqrt nn.init.normal_(self.q_in.weight, mean=0, std=sqrt(2.0 / (self.input_size + self.key_size))) nn.init.normal_(self.k_in.weight, mean=0, std=sqrt(2.0 / (self.input_size + self.key_size))) nn.init.normal_(self.v_in.weight, mean=0, std=sqrt(2.0 / (self.input_size + self.value_size))) nn.init.xavier_normal_(self.out.weight) - + def forward(self, Q, K, V, atte_mask_out=None): """ - :param Q: [batch, seq_len, model_size] - :param K: [batch, seq_len, model_size] - :param V: [batch, seq_len, model_size] + :param Q: [batch, seq_len_q, model_size] + :param K: [batch, seq_len_k, model_size] + :param V: [batch, seq_len_k, model_size] :param seq_mask: [batch, seq_len] """ - batch, seq_len, _ = Q.size() + batch, sq, _ = Q.size() + sk = K.size(1) d_k, d_v, n_head = self.key_size, self.value_size, self.num_head # input linear - q = self.q_in(Q).view(batch, seq_len, n_head, d_k) - k = self.k_in(K).view(batch, seq_len, n_head, d_k) - v = self.v_in(V).view(batch, seq_len, n_head, d_k) - + q = self.q_in(Q).view(batch, sq, n_head, d_k) + k = self.k_in(K).view(batch, sk, n_head, d_k) + v = self.v_in(V).view(batch, sk, n_head, d_v) + # transpose q, k and v to do batch attention - q = q.permute(2, 0, 1, 3).contiguous().view(-1, seq_len, d_k) - k = k.permute(2, 0, 1, 3).contiguous().view(-1, seq_len, d_k) - v = v.permute(2, 0, 1, 3).contiguous().view(-1, seq_len, d_v) + q = q.permute(2, 0, 1, 3).contiguous().view(-1, sq, d_k) + k = k.permute(2, 0, 1, 3).contiguous().view(-1, sk, d_k) + v = v.permute(2, 0, 1, 3).contiguous().view(-1, sk, d_v) if atte_mask_out is not None: atte_mask_out = atte_mask_out.repeat(n_head, 1, 1) - atte = self.attention(q, k, v, atte_mask_out).view(n_head, batch, seq_len, d_v) - + atte = self.attention(q, k, v, atte_mask_out).view(n_head, batch, sq, d_v) + # concat all heads, do output linear - atte = atte.permute(1, 2, 0, 3).contiguous().view(batch, seq_len, -1) + atte = atte.permute(1, 2, 0, 3).contiguous().view(batch, sq, -1) output = self.drop(self.out(atte)) return output -class Bi_Attention(nn.Module): +class BiAttention(nn.Module): + r"""Bi Attention module + + .. todo:: + 这个模块的负责人来继续完善一下 + + Calculate Bi Attention matrix `e` + + .. math:: + + \begin{array}{ll} \\ + e_ij = {a}^{\mathbf{T}}_{i}{b}_{j} \\ + a_i = + b_j = + \end{array} + + """ + def __init__(self): - super(Bi_Attention, self).__init__() + super(BiAttention, self).__init__() self.inf = 10e12 - + def forward(self, in_x1, in_x2, x1_len, x2_len): - # in_x1: [batch_size, x1_seq_len, hidden_size] - # in_x2: [batch_size, x2_seq_len, hidden_size] - # x1_len: [batch_size, x1_seq_len] - # x2_len: [batch_size, x2_seq_len] - + """ + :param torch.Tensor in_x1: [batch_size, x1_seq_len, hidden_size] 第一句的特征表示 + :param torch.Tensor in_x2: [batch_size, x2_seq_len, hidden_size] 第二句的特征表示 + :param torch.Tensor x1_len: [batch_size, x1_seq_len] 第一句的0/1mask矩阵 + :param torch.Tensor x2_len: [batch_size, x2_seq_len] 第二句的0/1mask矩阵 + :return: torch.Tensor out_x1: [batch_size, x1_seq_len, hidden_size] 第一句attend到的特征表示 + torch.Tensor out_x2: [batch_size, x2_seq_len, hidden_size] 第一句attend到的特征表示 + + """ + assert in_x1.size()[0] == in_x2.size()[0] assert in_x1.size()[2] == in_x2.size()[2] # The batch size and hidden size must be equal. assert in_x1.size()[1] == x1_len.size()[1] and in_x2.size()[1] == x2_len.size()[1] # The seq len in in_x and x_len must be equal. assert in_x1.size()[0] == x1_len.size()[0] and x1_len.size()[0] == x2_len.size()[0] - + batch_size = in_x1.size()[0] x1_max_len = in_x1.size()[1] x2_max_len = in_x2.size()[1] - + in_x2_t = torch.transpose(in_x2, 1, 2) # [batch_size, hidden_size, x2_seq_len] - + attention_matrix = torch.bmm(in_x1, in_x2_t) # [batch_size, x1_seq_len, x2_seq_len] - + a_mask = x1_len.le(0.5).float() * -self.inf # [batch_size, x1_seq_len] a_mask = a_mask.view(batch_size, x1_max_len, -1) a_mask = a_mask.expand(-1, -1, x2_max_len) # [batch_size, x1_seq_len, x2_seq_len] b_mask = x2_len.le(0.5).float() * -self.inf b_mask = b_mask.view(batch_size, -1, x2_max_len) b_mask = b_mask.expand(-1, x1_max_len, -1) # [batch_size, x1_seq_len, x2_seq_len] - + attention_a = F.softmax(attention_matrix + a_mask, dim=2) # [batch_size, x1_seq_len, x2_seq_len] attention_b = F.softmax(attention_matrix + b_mask, dim=1) # [batch_size, x1_seq_len, x2_seq_len] - + out_x1 = torch.bmm(attention_a, in_x2) # [batch_size, x1_seq_len, hidden_size] attention_b_t = torch.transpose(attention_b, 1, 2) out_x2 = torch.bmm(attention_b_t, in_x1) # [batch_size, x2_seq_len, hidden_size] - + return out_x1, out_x2 + + +class SelfAttention(nn.Module): + """ + Self Attention Module. + + :param int input_size: 输入tensor的hidden维度 + :param int attention_unit: 输出tensor的hidden维度 + :param int attention_hops: + :param float drop: dropout概率,默认值为0.5 + :param str initial_method: 初始化参数方法 + """ + + def __init__(self, input_size, attention_unit=300, attention_hops=10, drop=0.5, initial_method=None, ): + super(SelfAttention, self).__init__() + + self.attention_hops = attention_hops + self.ws1 = nn.Linear(input_size, attention_unit, bias=False) + self.ws2 = nn.Linear(attention_unit, attention_hops, bias=False) + self.I = torch.eye(attention_hops, requires_grad=False) + self.I_origin = self.I + self.drop = nn.Dropout(drop) + self.tanh = nn.Tanh() + initial_parameter(self, initial_method) + + def _penalization(self, attention): + """ + compute the penalization term for attention module + """ + baz = attention.size(0) + size = self.I.size() + if len(size) != 3 or size[0] != baz: + self.I = self.I_origin.expand(baz, -1, -1) + self.I = self.I.to(device=attention.device) + attention_t = torch.transpose(attention, 1, 2).contiguous() + mat = torch.bmm(attention, attention_t) - self.I[:attention.size(0)] + ret = (torch.sum(torch.sum((mat ** 2), 2), 1).squeeze() + 1e-10) ** 0.5 + return torch.sum(ret) / size[0] + + def forward(self, input, input_origin): + """ + :param torch.Tensor input: [baz, senLen, h_dim] 要做attention的矩阵 + :param torch.Tensor input_origin: [baz , senLen] 原始token的index组成的矩阵,含有pad部分内容 + :return torch.Tensor output1: [baz, multi-head , h_dim] 经过attention操作后输入矩阵的结果 + :return torch.Tensor output2: [1] attention惩罚项,是一个标量 + """ + input = input.contiguous() + size = input.size() # [bsz, len, nhid] + + input_origin = input_origin.expand(self.attention_hops, -1, -1) # [hops,baz, len] + input_origin = input_origin.transpose(0, 1).contiguous() # [baz, hops,len] + + y1 = self.tanh(self.ws1(self.drop(input))) # [baz,len,dim] -->[bsz,len, attention-unit] + attention = self.ws2(y1).transpose(1, 2).contiguous() + # [bsz,len, attention-unit]--> [bsz, len, hop]--> [baz,hop,len] + + attention = attention + (-999999 * (input_origin == 0).float()) # remove the weight on padding token. + attention = F.softmax(attention, 2) # [baz ,hop, len] + return torch.bmm(attention, input), self._penalization(attention) # output1 --> [baz ,hop ,nhid] diff --git a/fastNLP/modules/aggregator/avg_pool.py b/fastNLP/modules/aggregator/avg_pool.py deleted file mode 100644 index e6f3fd4b..00000000 --- a/fastNLP/modules/aggregator/avg_pool.py +++ /dev/null @@ -1,36 +0,0 @@ -# python: 3.6 -# encoding: utf-8 - -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class AvgPool(nn.Module): - """1-d average pooling module.""" - - def __init__(self, stride=None, padding=0): - super(AvgPool, self).__init__() - self.stride = stride - self.padding = padding - - def forward(self, x): - # [N,C,L] -> [N,C] - kernel_size = x.size(2) - x = F.max_pool1d( - input=x, - kernel_size=kernel_size, - stride=self.stride, - padding=self.padding) - return x.squeeze(dim=-1) - - -class MeanPoolWithMask(nn.Module): - def __init__(self): - super(MeanPoolWithMask, self).__init__() - self.inf = 10e12 - - def forward(self, tensor, mask, dim=0): - masks = mask.view(mask.size(0), mask.size(1), -1).float() - return torch.sum(tensor * masks, dim=dim) / torch.sum(masks, dim=1) - diff --git a/fastNLP/modules/aggregator/kmax_pool.py b/fastNLP/modules/aggregator/kmax_pool.py deleted file mode 100644 index 4d71130e..00000000 --- a/fastNLP/modules/aggregator/kmax_pool.py +++ /dev/null @@ -1,20 +0,0 @@ -# python: 3.6 -# encoding: utf-8 - -import torch -import torch.nn as nn -# import torch.nn.functional as F - - -class KMaxPool(nn.Module): - """K max-pooling module.""" - - def __init__(self, k=1): - super(KMaxPool, self).__init__() - self.k = k - - def forward(self, x): - # [N,C,L] -> [N,C*k] - x, index = torch.topk(x, self.k, dim=-1, sorted=False) - x = torch.reshape(x, (x.size(0), -1)) - return x diff --git a/fastNLP/modules/aggregator/max_pool.py b/fastNLP/modules/aggregator/max_pool.py deleted file mode 100644 index 60d68497..00000000 --- a/fastNLP/modules/aggregator/max_pool.py +++ /dev/null @@ -1,38 +0,0 @@ -# python: 3.6 -# encoding: utf-8 - -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class MaxPool(nn.Module): - """1-d max-pooling module.""" - - def __init__(self, stride=None, padding=0, dilation=1): - super(MaxPool, self).__init__() - self.stride = stride - self.padding = padding - self.dilation = dilation - - def forward(self, x): - x = torch.transpose(x, 1, 2) # [N,L,C] -> [N,C,L] - kernel_size = x.size(2) - x = F.max_pool1d( # [N,L,C] -> [N,C,1] - input=x, - kernel_size=kernel_size, - stride=self.stride, - padding=self.padding, - dilation=self.dilation) - return x.squeeze(dim=-1) # [N,C,1] -> [N,C] - - -class MaxPoolWithMask(nn.Module): - def __init__(self): - super(MaxPoolWithMask, self).__init__() - self.inf = 10e12 - - def forward(self, tensor, mask, dim=0): - masks = mask.view(mask.size(0), mask.size(1), -1) - masks = masks.expand(-1, -1, tensor.size(2)).float() - return torch.max(tensor + masks.le(0.5).float() * -self.inf, dim=dim) diff --git a/fastNLP/modules/aggregator/pooling.py b/fastNLP/modules/aggregator/pooling.py new file mode 100644 index 00000000..51438aae --- /dev/null +++ b/fastNLP/modules/aggregator/pooling.py @@ -0,0 +1,146 @@ +__all__ = [ + "MaxPool", + "MaxPoolWithMask", + "AvgPool" +] +import torch +import torch.nn as nn + + +class MaxPool(nn.Module): + """ + 别名::class:`fastNLP.modules.MaxPool` :class:`fastNLP.modules.aggregator.pooling.MaxPool` + + Max-pooling模块。 + + :param stride: 窗口移动大小,默认为kernel_size + :param padding: padding的内容,默认为0 + :param dilation: 控制窗口内元素移动距离的大小 + :param dimension: MaxPool的维度,支持1,2,3维。 + :param kernel_size: max pooling的窗口大小,默认为tensor最后k维,其中k为dimension + :param ceil_mode: + """ + + def __init__(self, stride=None, padding=0, dilation=1, dimension=1, kernel_size=None, ceil_mode=False): + + super(MaxPool, self).__init__() + assert (1 <= dimension) and (dimension <= 3) + self.dimension = dimension + self.stride = stride + self.padding = padding + self.dilation = dilation + self.kernel_size = kernel_size + self.ceil_mode = ceil_mode + + def forward(self, x): + if self.dimension == 1: + pooling = nn.MaxPool1d( + stride=self.stride, padding=self.padding, dilation=self.dilation, + kernel_size=self.kernel_size if self.kernel_size is not None else x.size(-1), + return_indices=False, ceil_mode=self.ceil_mode + ) + x = torch.transpose(x, 1, 2) # [N,L,C] -> [N,C,L] + elif self.dimension == 2: + pooling = nn.MaxPool2d( + stride=self.stride, padding=self.padding, dilation=self.dilation, + kernel_size=self.kernel_size if self.kernel_size is not None else (x.size(-2), x.size(-1)), + return_indices=False, ceil_mode=self.ceil_mode + ) + else: + pooling = nn.MaxPool2d( + stride=self.stride, padding=self.padding, dilation=self.dilation, + kernel_size=self.kernel_size if self.kernel_size is not None else (x.size(-3), x.size(-2), x.size(-1)), + return_indices=False, ceil_mode=self.ceil_mode + ) + x = pooling(x) + return x.squeeze(dim=-1) # [N,C,1] -> [N,C] + + +class MaxPoolWithMask(nn.Module): + """ + 别名::class:`fastNLP.modules.MaxPoolWithMask` :class:`fastNLP.modules.aggregator.pooling.MaxPoolWithMask` + + 带mask矩阵的max pooling。在做max-pooling的时候不会考虑mask值为0的位置。 + """ + + def __init__(self): + super(MaxPoolWithMask, self).__init__() + self.inf = 10e12 + + def forward(self, tensor, mask, dim=1): + """ + :param torch.FloatTensor tensor: [batch_size, seq_len, channels] 初始tensor + :param torch.LongTensor mask: [batch_size, seq_len] 0/1的mask矩阵 + :param int dim: 需要进行max pooling的维度 + :return: + """ + masks = mask.view(mask.size(0), mask.size(1), -1) + masks = masks.expand(-1, -1, tensor.size(2)).float() + return torch.max(tensor + masks.le(0.5).float() * -self.inf, dim=dim)[0] + + +class KMaxPool(nn.Module): + """K max-pooling module.""" + + def __init__(self, k=1): + super(KMaxPool, self).__init__() + self.k = k + + def forward(self, x): + """ + :param torch.Tensor x: [N, C, L] 初始tensor + :return: torch.Tensor x: [N, C*k] k-max pool后的结果 + """ + x, index = torch.topk(x, self.k, dim=-1, sorted=False) + x = torch.reshape(x, (x.size(0), -1)) + return x + + +class AvgPool(nn.Module): + """ + 别名::class:`fastNLP.modules.AvgPool` :class:`fastNLP.modules.aggregator.pooling.AvgPool` + + 给定形如[batch_size, max_len, hidden_size]的输入,在最后一维进行avg pooling. 输出为[batch_size, hidden_size] + """ + + def __init__(self, stride=None, padding=0): + super(AvgPool, self).__init__() + self.stride = stride + self.padding = padding + + def forward(self, x): + """ + :param torch.Tensor x: [N, C, L] 初始tensor + :return: torch.Tensor x: [N, C] avg pool后的结果 + """ + # [N,C,L] -> [N,C] + kernel_size = x.size(2) + pooling = nn.AvgPool1d( + kernel_size=kernel_size, + stride=self.stride, + padding=self.padding) + x = pooling(x) + return x.squeeze(dim=-1) + + +class AvgPoolWithMask(nn.Module): + """ + 别名::class:`fastNLP.modules.AvgPoolWithMask` :class:`fastNLP.modules.aggregator.pooling.AvgPoolWithMask` + + 给定形如[batch_size, max_len, hidden_size]的输入,在最后一维进行avg pooling. 输出为[batch_size, hidden_size], pooling + 的时候只会考虑mask为1的位置 + """ + + def __init__(self): + super(AvgPoolWithMask, self).__init__() + self.inf = 10e12 + + def forward(self, tensor, mask, dim=1): + """ + :param torch.FloatTensor tensor: [batch_size, seq_len, channels] 初始tensor + :param torch.LongTensor mask: [batch_size, seq_len] 0/1的mask矩阵 + :param int dim: 需要进行max pooling的维度 + :return: + """ + masks = mask.view(mask.size(0), mask.size(1), -1).float() + return torch.sum(tensor * masks.float(), dim=dim) / torch.sum(masks.float(), dim=1) diff --git a/fastNLP/modules/aggregator/self_attention.py b/fastNLP/modules/aggregator/self_attention.py deleted file mode 100644 index b0f03791..00000000 --- a/fastNLP/modules/aggregator/self_attention.py +++ /dev/null @@ -1,68 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.autograd import Variable - -from fastNLP.modules.utils import initial_parameter - - -class SelfAttention(nn.Module): - """Self Attention Module. - - :param int input_size: - :param int attention_unit: - :param int attention_hops: - :param float drop: - :param str initial_method: - :param bool use_cuda: - """ - - def __init__(self, input_size, attention_unit=350, attention_hops=10, drop=0.5, initial_method=None, - use_cuda=False): - super(SelfAttention, self).__init__() - - self.attention_hops = attention_hops - self.ws1 = nn.Linear(input_size, attention_unit, bias=False) - self.ws2 = nn.Linear(attention_unit, attention_hops, bias=False) - if use_cuda: - self.I = Variable(torch.eye(attention_hops).cuda(), requires_grad=False) - else: - self.I = Variable(torch.eye(attention_hops), requires_grad=False) - self.I_origin = self.I - self.drop = nn.Dropout(drop) - self.tanh = nn.Tanh() - initial_parameter(self, initial_method) - - def penalization(self, attention): - """ - compute the penalization term for attention module - """ - baz = attention.size(0) - size = self.I.size() - if len(size) != 3 or size[0] != baz: - self.I = self.I_origin.expand(baz, -1, -1) - attentionT = torch.transpose(attention, 1, 2).contiguous() - mat = torch.bmm(attention, attentionT) - self.I[:attention.size(0)] - ret = (torch.sum(torch.sum((mat ** 2), 2), 1).squeeze() + 1e-10) ** 0.5 - return torch.sum(ret) / size[0] - - def forward(self, input, input_origin): - """ - :param input: the matrix to do attention. [baz, senLen, h_dim] - :param inp: then token index include pad token( 0 ) [baz , senLen] - :return output1: the input matrix after attention operation [baz, multi-head , h_dim] - :return output2: the attention penalty term, a scalar [1] - """ - input = input.contiguous() - size = input.size() # [bsz, len, nhid] - - input_origin = input_origin.expand(self.attention_hops, -1, -1) # [hops,baz, len] - input_origin = input_origin.transpose(0, 1).contiguous() # [baz, hops,len] - - y1 = self.tanh(self.ws1(self.drop(input))) # [baz,len,dim] -->[bsz,len, attention-unit] - attention = self.ws2(y1).transpose(1, 2).contiguous() - # [bsz,len, attention-unit]--> [bsz, len, hop]--> [baz,hop,len] - - attention = attention + (-999999 * (input_origin == 0).float()) # remove the weight on padding token. - attention = F.softmax(attention, 2) # [baz ,hop, len] - return torch.bmm(attention, input), self.penalization(attention) # output1 --> [baz ,hop ,nhid] diff --git a/fastNLP/modules/decoder/MLP.py b/fastNLP/modules/decoder/MLP.py deleted file mode 100644 index c9198859..00000000 --- a/fastNLP/modules/decoder/MLP.py +++ /dev/null @@ -1,58 +0,0 @@ -import torch -import torch.nn as nn - -from fastNLP.modules.utils import initial_parameter - - -class MLP(nn.Module): - """Multilayer Perceptrons as a decoder - - :param list size_layer: list of int, define the size of MLP layers. - :param str activation: str or function, the activation function for hidden layers. - :param str initial_method: the name of initialization method. - :param float dropout: the probability of dropout. - - .. note:: - There is no activation function applying on output layer. - - """ - - def __init__(self, size_layer, activation='relu', initial_method=None, dropout=0.0): - super(MLP, self).__init__() - self.hiddens = nn.ModuleList() - self.output = None - for i in range(1, len(size_layer)): - if i + 1 == len(size_layer): - self.output = nn.Linear(size_layer[i-1], size_layer[i]) - else: - self.hiddens.append(nn.Linear(size_layer[i-1], size_layer[i])) - - self.dropout = nn.Dropout(p=dropout) - - actives = { - 'relu': nn.ReLU(), - 'tanh': nn.Tanh(), - } - if activation in actives: - self.hidden_active = actives[activation] - elif callable(activation): - self.hidden_active = activation - else: - raise ValueError("should set activation correctly: {}".format(activation)) - initial_parameter(self, initial_method) - - def forward(self, x): - for layer in self.hiddens: - x = self.dropout(self.hidden_active(layer(x))) - x = self.dropout(self.output(x)) - return x - - -if __name__ == '__main__': - net1 = MLP([5, 10, 5]) - net2 = MLP([5, 10, 5], 'tanh') - for net in [net1, net2]: - x = torch.randn(5, 5) - y = net(x) - print(x) - print(y) diff --git a/fastNLP/modules/decoder/__init__.py b/fastNLP/modules/decoder/__init__.py index a72b7cd0..664618b2 100644 --- a/fastNLP/modules/decoder/__init__.py +++ b/fastNLP/modules/decoder/__init__.py @@ -1,2 +1,11 @@ -from .CRF import ConditionalRandomField -from .MLP import MLP +__all__ = [ + "MLP", + "ConditionalRandomField", + "viterbi_decode", + "allowed_transitions" +] + +from .crf import ConditionalRandomField +from .mlp import MLP +from .utils import viterbi_decode +from .crf import allowed_transitions diff --git a/fastNLP/modules/decoder/CRF.py b/fastNLP/modules/decoder/crf.py similarity index 51% rename from fastNLP/modules/decoder/CRF.py rename to fastNLP/modules/decoder/crf.py index d7db3bf9..beb2b9be 100644 --- a/fastNLP/modules/decoder/CRF.py +++ b/fastNLP/modules/decoder/crf.py @@ -1,41 +1,37 @@ +__all__ = [ + "ConditionalRandomField", + "allowed_transitions" +] + import torch from torch import nn -from fastNLP.modules.utils import initial_parameter - - -def log_sum_exp(x, dim=-1): - max_value, _ = x.max(dim=dim, keepdim=True) - res = torch.log(torch.sum(torch.exp(x - max_value), dim=dim, keepdim=True)) + max_value - return res.squeeze(dim) - - -def seq_len_to_byte_mask(seq_lens): - # usually seq_lens: LongTensor, batch_size - # return value: ByteTensor, batch_size x max_len - batch_size = seq_lens.size(0) - max_len = seq_lens.max() - broadcast_arange = torch.arange(max_len).view(1, -1).repeat(batch_size, 1).to(seq_lens.device) - mask = broadcast_arange.float().lt(seq_lens.float().view(-1, 1)) - return mask +from ..utils import initial_parameter -def allowed_transitions(id2label, encoding_type='bio'): +def allowed_transitions(id2target, encoding_type='bio', include_start_end=True): """ + 别名::class:`fastNLP.modules.allowed_transitions` :class:`fastNLP.modules.decoder.crf.allowed_transitions` - :param dict id2label: key是label的indices,value是str类型的tag或tag-label。value可以是只有tag的, 比如"B", "M"; 也可以是 - "B-NN", "M-NN", tag和label之间一定要用"-"隔开。一般可以通过Vocabulary.get_id2word()id2label。 - :param encoding_type: str, 支持"bio", "bmes"。 - :return: List[Tuple(int, int)]], 内部的Tuple是(from_tag_id, to_tag_id)。 返回的结果考虑了start和end,比如"BIO"中,B、O可以 - 位于序列的开端,而I不行。所以返回的结果中会包含(start_idx, B_idx), (start_idx, O_idx), 但是不包含(start_idx, I_idx). - start_idx=len(id2label), end_idx=len(id2label)+1。 + 给定一个id到label的映射表,返回所有可以跳转的(from_tag_id, to_tag_id)列表。 + + :param dict id2target: key是label的indices,value是str类型的tag或tag-label。value可以是只有tag的, 比如"B", "M"; 也可以是 + "B-NN", "M-NN", tag和label之间一定要用"-"隔开。一般可以通过Vocabulary.idx2word得到id2label。 + :param str encoding_type: 支持"bio", "bmes", "bmeso"。 + :param bool include_start_end: 是否包含开始与结尾的转换。比如在bio中,b/o可以在开头,但是i不能在开头; + 为True,返回的结果中会包含(start_idx, b_idx), (start_idx, o_idx), 但是不包含(start_idx, i_idx); + start_idx=len(id2label), end_idx=len(id2label)+1。为False, 返回的结果中不含与开始结尾相关的内容 + :return: List[Tuple(int, int)]], 内部的Tuple是可以进行跳转的(from_tag_id, to_tag_id)。 """ - num_tags = len(id2label) + num_tags = len(id2target) start_idx = num_tags end_idx = num_tags + 1 encoding_type = encoding_type.lower() allowed_trans = [] - id_label_lst = list(id2label.items()) + [(start_idx, 'start'), (end_idx, 'end')] + id_label_lst = list(id2target.items()) + if include_start_end: + id_label_lst += [(start_idx, 'start'), (end_idx, 'end')] + def split_tag_label(from_label): from_label = from_label.lower() if from_label in ['start', 'end']: @@ -45,7 +41,7 @@ def allowed_transitions(id2label, encoding_type='bio'): from_tag = from_label[:1] from_label = from_label[2:] return from_tag, from_label - + for from_id, from_label in id_label_lst: if from_label in ['', '']: continue @@ -54,22 +50,22 @@ def allowed_transitions(id2label, encoding_type='bio'): if to_label in ['', '']: continue to_tag, to_label = split_tag_label(to_label) - if is_transition_allowed(encoding_type, from_tag, from_label, to_tag, to_label): + if _is_transition_allowed(encoding_type, from_tag, from_label, to_tag, to_label): allowed_trans.append((from_id, to_id)) return allowed_trans -def is_transition_allowed(encoding_type, from_tag, from_label, to_tag, to_label): +def _is_transition_allowed(encoding_type, from_tag, from_label, to_tag, to_label): """ - :param encoding_type: str, 支持"BIO", "BMES"。 - :param from_tag: str, 比如"B", "M"之类的标注tag. 还包括start, end等两种特殊tag - :param from_label: str, 比如"PER", "LOC"等label - :param to_tag: str, 比如"B", "M"之类的标注tag. 还包括start, end等两种特殊tag - :param to_label: str, 比如"PER", "LOC"等label + :param str encoding_type: 支持"BIO", "BMES", "BEMSO"。 + :param str from_tag: 比如"B", "M"之类的标注tag. 还包括start, end等两种特殊tag + :param str from_label: 比如"PER", "LOC"等label + :param str to_tag: 比如"B", "M"之类的标注tag. 还包括start, end等两种特殊tag + :param str to_label: 比如"PER", "LOC"等label :return: bool,能否跃迁 """ - if to_tag=='start' or from_tag=='end': + if to_tag == 'start' or from_tag == 'end': return False encoding_type = encoding_type.lower() if encoding_type == 'bio': @@ -92,12 +88,12 @@ def is_transition_allowed(encoding_type, from_tag, from_label, to_tag, to_label) if from_tag == 'start': return to_tag in ('b', 'o') elif from_tag in ['b', 'i']: - return any([to_tag in ['end', 'b', 'o'], to_tag=='i' and from_label==to_label]) + return any([to_tag in ['end', 'b', 'o'], to_tag == 'i' and from_label == to_label]) elif from_tag == 'o': return to_tag in ['end', 'b', 'o'] else: raise ValueError("Unexpect tag {}. Expect only 'B', 'I', 'O'.".format(from_tag)) - + elif encoding_type == 'bmes': """ 第一行是to_tag, 第一列是from_tag,y任意条件下可转,-只有在label相同时可转,n不可转 @@ -120,55 +116,68 @@ def is_transition_allowed(encoding_type, from_tag, from_label, to_tag, to_label) if from_tag == 'start': return to_tag in ['b', 's'] elif from_tag == 'b': - return to_tag in ['m', 'e'] and from_label==to_label + return to_tag in ['m', 'e'] and from_label == to_label elif from_tag == 'm': - return to_tag in ['m', 'e'] and from_label==to_label + return to_tag in ['m', 'e'] and from_label == to_label elif from_tag in ['e', 's']: return to_tag in ['b', 's', 'end'] else: raise ValueError("Unexpect tag type {}. Expect only 'B', 'M', 'E', 'S'.".format(from_tag)) + elif encoding_type == 'bmeso': + if from_tag == 'start': + return to_tag in ['b', 's', 'o'] + elif from_tag == 'b': + return to_tag in ['m', 'e'] and from_label == to_label + elif from_tag == 'm': + return to_tag in ['m', 'e'] and from_label == to_label + elif from_tag in ['e', 's', 'o']: + return to_tag in ['b', 's', 'end', 'o'] + else: + raise ValueError("Unexpect tag type {}. Expect only 'B', 'M', 'E', 'S', 'O'.".format(from_tag)) + else: - raise ValueError("Only support BIO, BMES encoding type, got {}.".format(encoding_type)) + raise ValueError("Only support BIO, BMES, BMESO encoding type, got {}.".format(encoding_type)) class ConditionalRandomField(nn.Module): """ + 别名::class:`fastNLP.modules.ConditionalRandomField` :class:`fastNLP.modules.decoder.crf.ConditionalRandomField` - :param int num_tags: 标签的数量。 - :param bool include_start_end_trans: 是否包含起始tag - :param list allowed_transitions: ``List[Tuple[from_tag_id(int), to_tag_id(int)]]``. 允许的跃迁,可以通过allowed_transitions()得到。 - 如果为None,则所有跃迁均为合法 - :param str initial_method: - """ + 条件随机场。 + 提供forward()以及viterbi_decode()两个方法,分别用于训练与inference。 - def __init__(self, num_tags, include_start_end_trans=False, allowed_transitions=None, initial_method=None): + :param int num_tags: 标签的数量 + :param bool include_start_end_trans: 是否考虑各个tag作为开始以及结尾的分数。 + :param List[Tuple[from_tag_id(int), to_tag_id(int)]] allowed_transitions: 内部的Tuple[from_tag_id(int), + to_tag_id(int)]视为允许发生的跃迁,其他没有包含的跃迁认为是禁止跃迁,可以通过 + allowed_transitions()函数得到;如果为None,则所有跃迁均为合法 + :param str initial_method: 初始化方法。见initial_parameter + """ + + def __init__(self, num_tags, include_start_end_trans=False, allowed_transitions=None, + initial_method=None): + super(ConditionalRandomField, self).__init__() - + self.include_start_end_trans = include_start_end_trans self.num_tags = num_tags - + # the meaning of entry in this matrix is (from_tag_id, to_tag_id) score self.trans_m = nn.Parameter(torch.randn(num_tags, num_tags)) if self.include_start_end_trans: self.start_scores = nn.Parameter(torch.randn(num_tags)) self.end_scores = nn.Parameter(torch.randn(num_tags)) - + if allowed_transitions is None: constrain = torch.zeros(num_tags + 2, num_tags + 2) else: - constrain = torch.ones(num_tags + 2, num_tags + 2) * -1000 + constrain = torch.full((num_tags + 2, num_tags + 2), fill_value=-10000.0, dtype=torch.float) for from_tag_id, to_tag_id in allowed_transitions: constrain[from_tag_id, to_tag_id] = 0 self._constrain = nn.Parameter(constrain, requires_grad=False) - - # self.reset_parameter() + initial_parameter(self, initial_method) - def reset_parameter(self): - nn.init.xavier_normal_(self.trans_m) - if self.include_start_end_trans: - nn.init.normal_(self.start_scores) - nn.init.normal_(self.end_scores) - + def _normalizer_likelihood(self, logits, mask): """Computes the (batch_size,) denominator term for the log-likelihood, which is the sum of the likelihoods across all possible state sequences. @@ -180,20 +189,23 @@ class ConditionalRandomField(nn.Module): seq_len, batch_size, n_tags = logits.size() alpha = logits[0] if self.include_start_end_trans: - alpha += self.start_scores.view(1, -1) - + alpha = alpha + self.start_scores.view(1, -1) + + flip_mask = mask.eq(0) + for i in range(1, seq_len): emit_score = logits[i].view(batch_size, 1, n_tags) trans_score = self.trans_m.view(1, n_tags, n_tags) tmp = alpha.view(batch_size, n_tags, 1) + emit_score + trans_score - alpha = log_sum_exp(tmp, 1) * mask[i].view(batch_size, 1) + alpha * (1 - mask[i]).view(batch_size, 1) - + alpha = torch.logsumexp(tmp, 1).masked_fill(flip_mask[i].view(batch_size, 1), 0) + \ + alpha.masked_fill(mask[i].byte().view(batch_size, 1), 0) + if self.include_start_end_trans: - alpha += self.end_scores.view(1, -1) - - return log_sum_exp(alpha, 1) - - def _glod_score(self, logits, tags, mask): + alpha = alpha + self.end_scores.view(1, -1) + + return torch.logsumexp(alpha, 1) + + def _gold_score(self, logits, tags, mask): """ Compute the score for the gold path. :param logits: FloatTensor, max_len x batch_size x num_tags @@ -204,98 +216,99 @@ class ConditionalRandomField(nn.Module): seq_len, batch_size, _ = logits.size() batch_idx = torch.arange(batch_size, dtype=torch.long, device=logits.device) seq_idx = torch.arange(seq_len, dtype=torch.long, device=logits.device) - + # trans_socre [L-1, B] - trans_score = self.trans_m[tags[:seq_len-1], tags[1:]] * mask[1:, :] + mask = mask.byte() + flip_mask = mask.eq(0) + trans_score = self.trans_m[tags[:seq_len - 1], tags[1:]].masked_fill(flip_mask[1:, :], 0) # emit_score [L, B] - emit_score = logits[seq_idx.view(-1,1), batch_idx.view(1,-1), tags] * mask + emit_score = logits[seq_idx.view(-1, 1), batch_idx.view(1, -1), tags].masked_fill(flip_mask, 0) # score [L-1, B] - score = trans_score + emit_score[:seq_len-1, :] - score = score.sum(0) + emit_score[-1] * mask[-1] + score = trans_score + emit_score[:seq_len - 1, :] + score = score.sum(0) + emit_score[-1].masked_fill(flip_mask[-1], 0) if self.include_start_end_trans: st_scores = self.start_scores.view(1, -1).repeat(batch_size, 1)[batch_idx, tags[0]] last_idx = mask.long().sum(0) - 1 ed_scores = self.end_scores.view(1, -1).repeat(batch_size, 1)[batch_idx, tags[last_idx, batch_idx]] - score += st_scores + ed_scores + score = score + st_scores + ed_scores # return [B,] return score - + def forward(self, feats, tags, mask): """ - Calculate the neg log likelihood - :param feats:FloatTensor, batch_size x max_len x num_tags - :param tags:LongTensor, batch_size x max_len - :param mask:ByteTensor batch_size x max_len - :return:FloatTensor, batch_size + 用于计算CRF的前向loss,返回值为一个batch_size的FloatTensor,可能需要mean()求得loss。 + + :param torch.FloatTensor feats: batch_size x max_len x num_tags,特征矩阵。 + :param torch.LongTensor tags: batch_size x max_len,标签矩阵。 + :param torch.ByteTensor mask: batch_size x max_len,为0的位置认为是padding。 + :return: torch.FloatTensor, (batch_size,) """ feats = feats.transpose(0, 1) tags = tags.transpose(0, 1).long() mask = mask.transpose(0, 1).float() all_path_score = self._normalizer_likelihood(feats, mask) - gold_path_score = self._glod_score(feats, tags, mask) - + gold_path_score = self._gold_score(feats, tags, mask) + return all_path_score - gold_path_score - - def viterbi_decode(self, data, mask, get_score=False, unpad=False): - """Given a feats matrix, return best decode path and best score. - - :param data:FloatTensor, batch_size x max_len x num_tags - :param mask:ByteTensor batch_size x max_len - :param get_score: bool, whether to output the decode score. - :param unpad: bool, 是否将结果unpad, - 如果False, 返回的是batch_size x max_len的tensor, - 如果True,返回的是List[List[int]], List[int]为每个sequence的label,已经unpadding了,即每个 - List[int]的长度是这个sample的有效长度 - :return: 如果get_score为False,返回结果根据unpadding变动 - 如果get_score为True, 返回 (paths, List[float], )。第一个仍然是解码后的路径(根据unpad变化),第二个List[Float] - 为每个seqence的解码分数。 + + def viterbi_decode(self, logits, mask, unpad=False): + """给定一个特征矩阵以及转移分数矩阵,计算出最佳的路径以及对应的分数 + + :param torch.FloatTensor logits: batch_size x max_len x num_tags,特征矩阵。 + :param torch.ByteTensor mask: batch_size x max_len, 为0的位置认为是pad;如果为None,则认为没有padding。 + :param bool unpad: 是否将结果删去padding。False, 返回的是batch_size x max_len的tensor; True,返回的是 + List[List[int]], 内部的List[int]为每个sequence的label,已经除去pad部分,即每个List[int]的长度是这 + 个sample的有效长度。 + :return: 返回 (paths, scores)。 + paths: 是解码后的路径, 其值参照unpad参数. + scores: torch.FloatTensor, size为(batch_size,), 对应每个最优路径的分数。 """ - batch_size, seq_len, n_tags = data.size() - data = data.transpose(0, 1).data # L, B, H - mask = mask.transpose(0, 1).data.float() # L, B - + batch_size, seq_len, n_tags = logits.size() + logits = logits.transpose(0, 1).data # L, B, H + mask = mask.transpose(0, 1).data.byte() # L, B + # dp - vpath = data.new_zeros((seq_len, batch_size, n_tags), dtype=torch.long) - vscore = data[0] + vpath = logits.new_zeros((seq_len, batch_size, n_tags), dtype=torch.long) + vscore = logits[0] transitions = self._constrain.data.clone() transitions[:n_tags, :n_tags] += self.trans_m.data if self.include_start_end_trans: transitions[n_tags, :n_tags] += self.start_scores.data - transitions[:n_tags, n_tags+1] += self.end_scores.data - + transitions[:n_tags, n_tags + 1] += self.end_scores.data + vscore += transitions[n_tags, :n_tags] trans_score = transitions[:n_tags, :n_tags].view(1, n_tags, n_tags).data for i in range(1, seq_len): prev_score = vscore.view(batch_size, n_tags, 1) - cur_score = data[i].view(batch_size, 1, n_tags) + cur_score = logits[i].view(batch_size, 1, n_tags) score = prev_score + trans_score + cur_score best_score, best_dst = score.max(1) vpath[i] = best_dst - vscore = best_score * mask[i].view(batch_size, 1) + vscore * (1 - mask[i]).view(batch_size, 1) - - vscore += transitions[:n_tags, n_tags+1].view(1, -1) - + vscore = best_score.masked_fill(mask[i].eq(0).view(batch_size, 1), 0) + \ + vscore.masked_fill(mask[i].view(batch_size, 1), 0) + + if self.include_start_end_trans: + vscore += transitions[:n_tags, n_tags + 1].view(1, -1) + # backtrace - batch_idx = torch.arange(batch_size, dtype=torch.long, device=data.device) - seq_idx = torch.arange(seq_len, dtype=torch.long, device=data.device) + batch_idx = torch.arange(batch_size, dtype=torch.long, device=logits.device) + seq_idx = torch.arange(seq_len, dtype=torch.long, device=logits.device) lens = (mask.long().sum(0) - 1) # idxes [L, B], batched idx from seq_len-1 to 0 - idxes = (lens.view(1,-1) - seq_idx.view(-1,1)) % seq_len - - ans = data.new_empty((seq_len, batch_size), dtype=torch.long) + idxes = (lens.view(1, -1) - seq_idx.view(-1, 1)) % seq_len + + ans = logits.new_empty((seq_len, batch_size), dtype=torch.long) ans_score, last_tags = vscore.max(1) ans[idxes[0], batch_idx] = last_tags for i in range(seq_len - 1): last_tags = vpath[idxes[i], batch_idx, last_tags] - ans[idxes[i+1], batch_idx] = last_tags + ans[idxes[i + 1], batch_idx] = last_tags ans = ans.transpose(0, 1) if unpad: paths = [] for idx, seq_len in enumerate(lens): - paths.append(ans[idx, :seq_len+1].tolist()) + paths.append(ans[idx, :seq_len + 1].tolist()) else: paths = ans - if get_score: - return paths, ans_score.tolist() - return paths + return paths, ans_score diff --git a/fastNLP/modules/decoder/mlp.py b/fastNLP/modules/decoder/mlp.py new file mode 100644 index 00000000..c1579224 --- /dev/null +++ b/fastNLP/modules/decoder/mlp.py @@ -0,0 +1,96 @@ +__all__ = [ + "MLP" +] + +import torch +import torch.nn as nn + +from ..utils import initial_parameter + + +class MLP(nn.Module): + """ + 别名::class:`fastNLP.modules.MLP` :class:`fastNLP.modules.decoder.mlp.MLP` + + 多层感知器 + + :param List[int] size_layer: 一个int的列表,用来定义MLP的层数,列表中的数字为每一层是hidden数目。MLP的层数为 len(size_layer) - 1 + :param Union[str,func,List[str]] activation: 一个字符串或者函数的列表,用来定义每一个隐层的激活函数,字符串包括relu,tanh和sigmoid,默认值为relu + :param Union[str,func] output_activation: 字符串或者函数,用来定义输出层的激活函数,默认值为None,表示输出层没有激活函数 + :param str initial_method: 参数初始化方式 + :param float dropout: dropout概率,默认值为0 + + .. note:: + 隐藏层的激活函数通过activation定义。一个str/function或者一个str/function的list可以被传入activation。 + 如果只传入了一个str/function,那么所有隐藏层的激活函数都由这个str/function定义; + 如果传入了一个str/function的list,那么每一个隐藏层的激活函数由这个list中对应的元素定义,其中list的长度为隐藏层数。 + 输出层的激活函数由output_activation定义,默认值为None,此时输出层没有激活函数。 + + Examples:: + + >>> net1 = MLP([5, 10, 5]) + >>> net2 = MLP([5, 10, 5], 'tanh') + >>> net3 = MLP([5, 6, 7, 8, 5], 'tanh') + >>> net4 = MLP([5, 6, 7, 8, 5], 'relu', output_activation='tanh') + >>> net5 = MLP([5, 6, 7, 8, 5], ['tanh', 'relu', 'tanh'], 'tanh') + >>> for net in [net1, net2, net3, net4, net5]: + >>> x = torch.randn(5, 5) + >>> y = net(x) + >>> print(x) + >>> print(y) + """ + + def __init__(self, size_layer, activation='relu', output_activation=None, initial_method=None, dropout=0.0): + super(MLP, self).__init__() + self.hiddens = nn.ModuleList() + self.output = None + self.output_activation = output_activation + for i in range(1, len(size_layer)): + if i + 1 == len(size_layer): + self.output = nn.Linear(size_layer[i - 1], size_layer[i]) + else: + self.hiddens.append(nn.Linear(size_layer[i - 1], size_layer[i])) + + self.dropout = nn.Dropout(p=dropout) + + actives = { + 'relu': nn.ReLU(), + 'tanh': nn.Tanh(), + 'sigmoid': nn.Sigmoid(), + } + if not isinstance(activation, list): + activation = [activation] * (len(size_layer) - 2) + elif len(activation) == len(size_layer) - 2: + pass + else: + raise ValueError( + f"the length of activation function list except {len(size_layer) - 2} but got {len(activation)}!") + self.hidden_active = [] + for func in activation: + if callable(activation): + self.hidden_active.append(activation) + elif func.lower() in actives: + self.hidden_active.append(actives[func]) + else: + raise ValueError("should set activation correctly: {}".format(activation)) + if self.output_activation is not None: + if callable(self.output_activation): + pass + elif self.output_activation.lower() in actives: + self.output_activation = actives[self.output_activation] + else: + raise ValueError("should set activation correctly: {}".format(activation)) + initial_parameter(self, initial_method) + + def forward(self, x): + """ + :param torch.Tensor x: MLP接受的输入 + :return: torch.Tensor : MLP的输出结果 + """ + for layer, func in zip(self.hiddens, self.hidden_active): + x = self.dropout(func(layer(x))) + x = self.output(x) + if self.output_activation is not None: + x = self.output_activation(x) + x = self.dropout(x) + return x diff --git a/fastNLP/modules/decoder/utils.py b/fastNLP/modules/decoder/utils.py new file mode 100644 index 00000000..249f3ff6 --- /dev/null +++ b/fastNLP/modules/decoder/utils.py @@ -0,0 +1,68 @@ +__all__ = [ + "viterbi_decode" +] +import torch + + +def viterbi_decode(logits, transitions, mask=None, unpad=False): + r""" + 别名::class:`fastNLP.modules.viterbi_decode` :class:`fastNLP.modules.decoder.utils.viterbi_decode` + + 给定一个特征矩阵以及转移分数矩阵,计算出最佳的路径以及对应的分数 + + :param torch.FloatTensor logits: batch_size x max_len x num_tags,特征矩阵。 + :param torch.FloatTensor transitions: n_tags x n_tags。[i, j]位置的值认为是从tag i到tag j的转换。 + :param torch.ByteTensor mask: batch_size x max_len, 为0的位置认为是pad;如果为None,则认为没有padding。 + :param bool unpad: 是否将结果删去padding。False, 返回的是batch_size x max_len的tensor; True,返回的是 + List[List[int]], 内部的List[int]为每个sequence的label,已经除去pad部分,即每个List[int]的长度是这 + 个sample的有效长度。 + :return: 返回 (paths, scores)。 + paths: 是解码后的路径, 其值参照unpad参数. + scores: torch.FloatTensor, size为(batch_size,), 对应每个最优路径的分数。 + + """ + batch_size, seq_len, n_tags = logits.size() + assert n_tags == transitions.size(0) and n_tags == transitions.size( + 1), "The shapes of transitions and feats are not " \ + "compatible." + logits = logits.transpose(0, 1).data # L, B, H + if mask is not None: + mask = mask.transpose(0, 1).data.byte() # L, B + else: + mask = logits.new_ones((seq_len, batch_size), dtype=torch.uint8) + + # dp + vpath = logits.new_zeros((seq_len, batch_size, n_tags), dtype=torch.long) + vscore = logits[0] + + trans_score = transitions.view(1, n_tags, n_tags).data + for i in range(1, seq_len): + prev_score = vscore.view(batch_size, n_tags, 1) + cur_score = logits[i].view(batch_size, 1, n_tags) + score = prev_score + trans_score + cur_score + best_score, best_dst = score.max(1) + vpath[i] = best_dst + vscore = best_score.masked_fill(mask[i].eq(0).view(batch_size, 1), 0) + \ + vscore.masked_fill(mask[i].view(batch_size, 1), 0) + + # backtrace + batch_idx = torch.arange(batch_size, dtype=torch.long, device=logits.device) + seq_idx = torch.arange(seq_len, dtype=torch.long, device=logits.device) + lens = (mask.long().sum(0) - 1) + # idxes [L, B], batched idx from seq_len-1 to 0 + idxes = (lens.view(1, -1) - seq_idx.view(-1, 1)) % seq_len + + ans = logits.new_empty((seq_len, batch_size), dtype=torch.long) + ans_score, last_tags = vscore.max(1) + ans[idxes[0], batch_idx] = last_tags + for i in range(seq_len - 1): + last_tags = vpath[idxes[i], batch_idx, last_tags] + ans[idxes[i + 1], batch_idx] = last_tags + ans = ans.transpose(0, 1) + if unpad: + paths = [] + for idx, seq_len in enumerate(lens): + paths.append(ans[idx, :seq_len + 1].tolist()) + else: + paths = ans + return paths, ans_score diff --git a/fastNLP/modules/dropout.py b/fastNLP/modules/dropout.py index 34cf9e90..1363165c 100644 --- a/fastNLP/modules/dropout.py +++ b/fastNLP/modules/dropout.py @@ -1,11 +1,16 @@ +__all__ = [] + import torch class TimestepDropout(torch.nn.Dropout): - """This module accepts a ``[batch_size, num_timesteps, embedding_dim)]`` and use a single - dropout mask of shape ``(batch_size, embedding_dim)`` to apply on every time step. """ + 别名::class:`fastNLP.modules.TimestepDropout` + 接受的参数shape为``[batch_size, num_timesteps, embedding_dim)]`` 使用同一个mask(shape为``(batch_size, embedding_dim)``) + 在每个timestamp上做dropout。 + """ + def forward(self, x): dropout_mask = x.new_ones(x.shape[0], x.shape[-1]) torch.nn.functional.dropout(dropout_mask, self.p, self.training, inplace=True) diff --git a/fastNLP/modules/encoder/__init__.py b/fastNLP/modules/encoder/__init__.py index b00a0ae9..bdc4cbf3 100644 --- a/fastNLP/modules/encoder/__init__.py +++ b/fastNLP/modules/encoder/__init__.py @@ -1,11 +1,28 @@ -from .conv import Conv +__all__ = [ + # "BertModel", + + "ConvolutionCharEncoder", + "LSTMCharEncoder", + + "ConvMaxpool", + + "Embedding", + + "LSTM", + + "StarTransformer", + + "TransformerEncoder", + + "VarRNN", + "VarLSTM", + "VarGRU" +] +from .bert import BertModel +from .char_encoder import ConvolutionCharEncoder, LSTMCharEncoder from .conv_maxpool import ConvMaxpool from .embedding import Embedding -from .linear import Linear from .lstm import LSTM - -__all__ = ["LSTM", - "Embedding", - "Linear", - "Conv", - "ConvMaxpool"] +from .star_transformer import StarTransformer +from .transformer import TransformerEncoder +from .variational_rnn import VarRNN, VarLSTM, VarGRU diff --git a/fastNLP/modules/encoder/bert.py b/fastNLP/modules/encoder/bert.py new file mode 100644 index 00000000..e123fda6 --- /dev/null +++ b/fastNLP/modules/encoder/bert.py @@ -0,0 +1,377 @@ +""" +bert.py is modified from huggingface/pytorch-pretrained-BERT, which is licensed under the Apache License 2.0. + +""" +import copy +import json +import math +import os + +import torch +from torch import nn + +CONFIG_FILE = 'bert_config.json' +MODEL_WEIGHTS = 'pytorch_model.bin' + + +def gelu(x): + return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0))) + + +def swish(x): + return x * torch.sigmoid(x) + + +ACT2FN = {"gelu": gelu, "relu": torch.nn.functional.relu, "swish": swish} + + +class BertLayerNorm(nn.Module): + def __init__(self, hidden_size, eps=1e-12): + super(BertLayerNorm, self).__init__() + self.weight = nn.Parameter(torch.ones(hidden_size)) + self.bias = nn.Parameter(torch.zeros(hidden_size)) + self.variance_epsilon = eps + + def forward(self, x): + u = x.mean(-1, keepdim=True) + s = (x - u).pow(2).mean(-1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.variance_epsilon) + return self.weight * x + self.bias + + +class BertEmbeddings(nn.Module): + def __init__(self, vocab_size, hidden_size, max_position_embeddings, type_vocab_size, hidden_dropout_prob): + super(BertEmbeddings, self).__init__() + self.word_embeddings = nn.Embedding(vocab_size, hidden_size) + self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size) + self.token_type_embeddings = nn.Embedding(type_vocab_size, hidden_size) + + # self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load + # any TensorFlow checkpoint file + self.LayerNorm = BertLayerNorm(hidden_size, eps=1e-12) + self.dropout = nn.Dropout(hidden_dropout_prob) + + def forward(self, input_ids, token_type_ids=None): + seq_length = input_ids.size(1) + position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device) + position_ids = position_ids.unsqueeze(0).expand_as(input_ids) + if token_type_ids is None: + token_type_ids = torch.zeros_like(input_ids) + + words_embeddings = self.word_embeddings(input_ids) + position_embeddings = self.position_embeddings(position_ids) + token_type_embeddings = self.token_type_embeddings(token_type_ids) + + embeddings = words_embeddings + position_embeddings + token_type_embeddings + embeddings = self.LayerNorm(embeddings) + embeddings = self.dropout(embeddings) + return embeddings + + +class BertSelfAttention(nn.Module): + def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob): + super(BertSelfAttention, self).__init__() + if hidden_size % num_attention_heads != 0: + raise ValueError( + "The hidden size (%d) is not a multiple of the number of attention " + "heads (%d)" % (hidden_size, num_attention_heads)) + self.num_attention_heads = num_attention_heads + self.attention_head_size = int(hidden_size / num_attention_heads) + self.all_head_size = self.num_attention_heads * self.attention_head_size + + self.query = nn.Linear(hidden_size, self.all_head_size) + self.key = nn.Linear(hidden_size, self.all_head_size) + self.value = nn.Linear(hidden_size, self.all_head_size) + + self.dropout = nn.Dropout(attention_probs_dropout_prob) + + def transpose_for_scores(self, x): + new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) + x = x.view(*new_x_shape) + return x.permute(0, 2, 1, 3) + + def forward(self, hidden_states, attention_mask): + mixed_query_layer = self.query(hidden_states) + mixed_key_layer = self.key(hidden_states) + mixed_value_layer = self.value(hidden_states) + + query_layer = self.transpose_for_scores(mixed_query_layer) + key_layer = self.transpose_for_scores(mixed_key_layer) + value_layer = self.transpose_for_scores(mixed_value_layer) + + # Take the dot product between "query" and "key" to get the raw attention scores. + attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) + attention_scores = attention_scores / math.sqrt(self.attention_head_size) + # Apply the attention mask is (precomputed for all layers in BertModel forward() function) + attention_scores = attention_scores + attention_mask + + # Normalize the attention scores to probabilities. + attention_probs = nn.Softmax(dim=-1)(attention_scores) + + # This is actually dropping out entire tokens to attend to, which might + # seem a bit unusual, but is taken from the original Transformer paper. + attention_probs = self.dropout(attention_probs) + + context_layer = torch.matmul(attention_probs, value_layer) + context_layer = context_layer.permute(0, 2, 1, 3).contiguous() + new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) + context_layer = context_layer.view(*new_context_layer_shape) + return context_layer + + +class BertSelfOutput(nn.Module): + def __init__(self, hidden_size, hidden_dropout_prob): + super(BertSelfOutput, self).__init__() + self.dense = nn.Linear(hidden_size, hidden_size) + self.LayerNorm = BertLayerNorm(hidden_size, eps=1e-12) + self.dropout = nn.Dropout(hidden_dropout_prob) + + def forward(self, hidden_states, input_tensor): + hidden_states = self.dense(hidden_states) + hidden_states = self.dropout(hidden_states) + hidden_states = self.LayerNorm(hidden_states + input_tensor) + return hidden_states + + +class BertAttention(nn.Module): + def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob): + super(BertAttention, self).__init__() + self.self = BertSelfAttention(hidden_size, num_attention_heads, attention_probs_dropout_prob) + self.output = BertSelfOutput(hidden_size, hidden_dropout_prob) + + def forward(self, input_tensor, attention_mask): + self_output = self.self(input_tensor, attention_mask) + attention_output = self.output(self_output, input_tensor) + return attention_output + + +class BertIntermediate(nn.Module): + def __init__(self, hidden_size, intermediate_size, hidden_act): + super(BertIntermediate, self).__init__() + self.dense = nn.Linear(hidden_size, intermediate_size) + self.intermediate_act_fn = ACT2FN[hidden_act] \ + if isinstance(hidden_act, str) else hidden_act + + def forward(self, hidden_states): + hidden_states = self.dense(hidden_states) + hidden_states = self.intermediate_act_fn(hidden_states) + return hidden_states + + +class BertOutput(nn.Module): + def __init__(self, hidden_size, intermediate_size, hidden_dropout_prob): + super(BertOutput, self).__init__() + self.dense = nn.Linear(intermediate_size, hidden_size) + self.LayerNorm = BertLayerNorm(hidden_size, eps=1e-12) + self.dropout = nn.Dropout(hidden_dropout_prob) + + def forward(self, hidden_states, input_tensor): + hidden_states = self.dense(hidden_states) + hidden_states = self.dropout(hidden_states) + hidden_states = self.LayerNorm(hidden_states + input_tensor) + return hidden_states + + +class BertLayer(nn.Module): + def __init__(self, hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob, + intermediate_size, hidden_act): + super(BertLayer, self).__init__() + self.attention = BertAttention(hidden_size, num_attention_heads, attention_probs_dropout_prob, + hidden_dropout_prob) + self.intermediate = BertIntermediate(hidden_size, intermediate_size, hidden_act) + self.output = BertOutput(hidden_size, intermediate_size, hidden_dropout_prob) + + def forward(self, hidden_states, attention_mask): + attention_output = self.attention(hidden_states, attention_mask) + intermediate_output = self.intermediate(attention_output) + layer_output = self.output(intermediate_output, attention_output) + return layer_output + + +class BertEncoder(nn.Module): + def __init__(self, num_hidden_layers, hidden_size, num_attention_heads, attention_probs_dropout_prob, + hidden_dropout_prob, + intermediate_size, hidden_act): + super(BertEncoder, self).__init__() + layer = BertLayer(hidden_size, num_attention_heads, attention_probs_dropout_prob, hidden_dropout_prob, + intermediate_size, hidden_act) + self.layer = nn.ModuleList([copy.deepcopy(layer) for _ in range(num_hidden_layers)]) + + def forward(self, hidden_states, attention_mask, output_all_encoded_layers=True): + all_encoder_layers = [] + for layer_module in self.layer: + hidden_states = layer_module(hidden_states, attention_mask) + if output_all_encoded_layers: + all_encoder_layers.append(hidden_states) + if not output_all_encoded_layers: + all_encoder_layers.append(hidden_states) + return all_encoder_layers + + +class BertPooler(nn.Module): + def __init__(self, hidden_size): + super(BertPooler, self).__init__() + self.dense = nn.Linear(hidden_size, hidden_size) + self.activation = nn.Tanh() + + def forward(self, hidden_states): + # We "pool" the model by simply taking the hidden state corresponding + # to the first token. + first_token_tensor = hidden_states[:, 0] + pooled_output = self.dense(first_token_tensor) + pooled_output = self.activation(pooled_output) + return pooled_output + + +class BertModel(nn.Module): + """BERT(Bidirectional Embedding Representations from Transformers). + + 如果你想使用预训练好的权重矩阵,请在以下网址下载. + sources:: + + 'bert-base-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased.tar.gz", + 'bert-large-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-uncased.tar.gz", + 'bert-base-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-cased.tar.gz", + 'bert-large-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-cased.tar.gz", + 'bert-base-multilingual-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-uncased.tar.gz", + 'bert-base-multilingual-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-cased.tar.gz", + 'bert-base-chinese': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese.tar.gz", + + + 用预训练权重矩阵来建立BERT模型:: + + model = BertModel.from_pretrained("path/to/weights/directory") + + 用随机初始化权重矩阵来建立BERT模型:: + + model = BertModel() + + :param int vocab_size: 词表大小,默认值为30522,为BERT English uncase版本的词表大小 + :param int hidden_size: 隐层大小,默认值为768,为BERT base的版本 + :param int num_hidden_layers: 隐藏层数,默认值为12,为BERT base的版本 + :param int num_attention_heads: 多头注意力头数,默认值为12,为BERT base的版本 + :param int intermediate_size: FFN隐藏层大小,默认值是3072,为BERT base的版本 + :param str hidden_act: FFN隐藏层激活函数,默认值为``gelu`` + :param float hidden_dropout_prob: FFN隐藏层dropout,默认值为0.1 + :param float attention_probs_dropout_prob: Attention层的dropout,默认值为0.1 + :param int max_position_embeddings: 最大的序列长度,默认值为512, + :param int type_vocab_size: 最大segment数量,默认值为2 + :param int initializer_range: 初始化权重范围,默认值为0.02 + """ + + def __init__(self, vocab_size=30522, + hidden_size=768, + num_hidden_layers=12, + num_attention_heads=12, + intermediate_size=3072, + hidden_act="gelu", + hidden_dropout_prob=0.1, + attention_probs_dropout_prob=0.1, + max_position_embeddings=512, + type_vocab_size=2, + initializer_range=0.02, **kwargs): + super(BertModel, self).__init__() + self.embeddings = BertEmbeddings(vocab_size, hidden_size, max_position_embeddings, + type_vocab_size, hidden_dropout_prob) + self.encoder = BertEncoder(num_hidden_layers, hidden_size, num_attention_heads, + attention_probs_dropout_prob, hidden_dropout_prob, intermediate_size, + hidden_act) + self.pooler = BertPooler(hidden_size) + self.initializer_range = initializer_range + + self.apply(self.init_bert_weights) + + def init_bert_weights(self, module): + if isinstance(module, (nn.Linear, nn.Embedding)): + # Slightly different from the TF version which uses truncated_normal for initialization + # cf https://github.com/pytorch/pytorch/pull/5617 + module.weight.data.normal_(mean=0.0, std=self.initializer_range) + elif isinstance(module, BertLayerNorm): + module.bias.data.zero_() + module.weight.data.fill_(1.0) + if isinstance(module, nn.Linear) and module.bias is not None: + module.bias.data.zero_() + + def forward(self, input_ids, token_type_ids=None, attention_mask=None, output_all_encoded_layers=True): + if attention_mask is None: + attention_mask = torch.ones_like(input_ids) + if token_type_ids is None: + token_type_ids = torch.zeros_like(input_ids) + + # We create a 3D attention mask from a 2D tensor mask. + # Sizes are [batch_size, 1, 1, to_seq_length] + # So we can broadcast to [batch_size, num_heads, from_seq_length, to_seq_length] + # this attention mask is more simple than the triangular masking of causal attention + # used in OpenAI GPT, we just need to prepare the broadcast dimension here. + extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) + + # Since attention_mask is 1.0 for positions we want to attend and 0.0 for + # masked positions, this operation will create a tensor which is 0.0 for + # positions we want to attend and -10000.0 for masked positions. + # Since we are adding it to the raw scores before the softmax, this is + # effectively the same as removing these entirely. + extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype) # fp16 compatibility + extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 + + embedding_output = self.embeddings(input_ids, token_type_ids) + encoded_layers = self.encoder(embedding_output, + extended_attention_mask, + output_all_encoded_layers=output_all_encoded_layers) + sequence_output = encoded_layers[-1] + pooled_output = self.pooler(sequence_output) + if not output_all_encoded_layers: + encoded_layers = encoded_layers[-1] + return encoded_layers, pooled_output + + @classmethod + def from_pretrained(cls, pretrained_model_dir, state_dict=None, *inputs, **kwargs): + # Load config + config_file = os.path.join(pretrained_model_dir, CONFIG_FILE) + config = json.load(open(config_file, "r")) + # config = BertConfig.from_json_file(config_file) + # logger.info("Model config {}".format(config)) + # Instantiate model. + model = cls(*inputs, **config, **kwargs) + if state_dict is None: + weights_path = os.path.join(pretrained_model_dir, MODEL_WEIGHTS) + state_dict = torch.load(weights_path) + + old_keys = [] + new_keys = [] + for key in state_dict.keys(): + new_key = None + if 'gamma' in key: + new_key = key.replace('gamma', 'weight') + if 'beta' in key: + new_key = key.replace('beta', 'bias') + if new_key: + old_keys.append(key) + new_keys.append(new_key) + for old_key, new_key in zip(old_keys, new_keys): + state_dict[new_key] = state_dict.pop(old_key) + + missing_keys = [] + unexpected_keys = [] + error_msgs = [] + # copy state_dict so _load_from_state_dict can modify it + metadata = getattr(state_dict, '_metadata', None) + state_dict = state_dict.copy() + if metadata is not None: + state_dict._metadata = metadata + + def load(module, prefix=''): + local_metadata = {} if metadata is None else metadata.get(prefix[:-1], {}) + module._load_from_state_dict( + state_dict, prefix, local_metadata, True, missing_keys, unexpected_keys, error_msgs) + for name, child in module._modules.items(): + if child is not None: + load(child, prefix + name + '.') + + load(model, prefix='' if hasattr(model, 'bert') else 'bert.') + if len(missing_keys) > 0: + print("Weights of {} not initialized from pretrained model: {}".format( + model.__class__.__name__, missing_keys)) + if len(unexpected_keys) > 0: + print("Weights from pretrained model not used in {}: {}".format( + model.__class__.__name__, unexpected_keys)) + return model diff --git a/fastNLP/modules/encoder/char_embedding.py b/fastNLP/modules/encoder/char_embedding.py deleted file mode 100644 index 057d080c..00000000 --- a/fastNLP/modules/encoder/char_embedding.py +++ /dev/null @@ -1,82 +0,0 @@ -import torch -from torch import nn - -from fastNLP.modules.utils import initial_parameter - - -# from torch.nn.init import xavier_uniform -class ConvCharEmbedding(nn.Module): - """Character-level Embedding with CNN. - - :param int char_emb_size: the size of character level embedding. Default: 50 - say 26 characters, each embedded to 50 dim vector, then the input_size is 50. - :param tuple feature_maps: tuple of int. The length of the tuple is the number of convolution operations - over characters. The i-th integer is the number of filters (dim of out channels) for the i-th - convolution. - :param tuple kernels: tuple of int. The width of each kernel. - """ - - def __init__(self, char_emb_size=50, feature_maps=(40, 30, 30), kernels=(3, 4, 5), initial_method=None): - super(ConvCharEmbedding, self).__init__() - self.convs = nn.ModuleList([ - nn.Conv2d(1, feature_maps[i], kernel_size=(char_emb_size, kernels[i]), bias=True, padding=(0, 4)) - for i in range(len(kernels))]) - - initial_parameter(self, initial_method) - - def forward(self, x): - """ - :param x: ``[batch_size * sent_length, word_length, char_emb_size]`` - :return: feature map of shape [batch_size * sent_length, sum(feature_maps), 1] - """ - x = x.contiguous().view(x.size(0), 1, x.size(1), x.size(2)) - # [batch_size*sent_length, channel, width, height] - x = x.transpose(2, 3) - # [batch_size*sent_length, channel, height, width] - return self.convolute(x).unsqueeze(2) - - def convolute(self, x): - feats = [] - for conv in self.convs: - y = conv(x) - # [batch_size*sent_length, feature_maps[i], 1, width - kernels[i] + 1] - y = torch.squeeze(y, 2) - # [batch_size*sent_length, feature_maps[i], width - kernels[i] + 1] - y = torch.tanh(y) - y, __ = torch.max(y, 2) - # [batch_size*sent_length, feature_maps[i]] - feats.append(y) - return torch.cat(feats, 1) # [batch_size*sent_length, sum(feature_maps)] - - -class LSTMCharEmbedding(nn.Module): - """Character-level Embedding with LSTM. - - :param int char_emb_size: the size of character level embedding. Default: 50 - say 26 characters, each embedded to 50 dim vector, then the input_size is 50. - :param int hidden_size: the number of hidden units. Default: equal to char_emb_size. - """ - def __init__(self, char_emb_size=50, hidden_size=None, initial_method=None): - super(LSTMCharEmbedding, self).__init__() - self.hidden_size = char_emb_size if hidden_size is None else hidden_size - - self.lstm = nn.LSTM(input_size=char_emb_size, - hidden_size=self.hidden_size, - num_layers=1, - bias=True, - batch_first=True) - initial_parameter(self, initial_method) - - def forward(self, x): - """ - :param x: ``[ n_batch*n_word, word_length, char_emb_size]`` - :return: [ n_batch*n_word, char_emb_size] - """ - batch_size = x.shape[0] - h0 = torch.empty(1, batch_size, self.hidden_size) - h0 = nn.init.orthogonal_(h0) - c0 = torch.empty(1, batch_size, self.hidden_size) - c0 = nn.init.orthogonal_(c0) - - _, hidden = self.lstm(x, (h0, c0)) - return hidden[0].squeeze().unsqueeze(2) diff --git a/fastNLP/modules/encoder/char_encoder.py b/fastNLP/modules/encoder/char_encoder.py new file mode 100644 index 00000000..481ad7ad --- /dev/null +++ b/fastNLP/modules/encoder/char_encoder.py @@ -0,0 +1,96 @@ +__all__ = [ + "ConvolutionCharEncoder", + "LSTMCharEncoder" +] +import torch +import torch.nn as nn + +from ..utils import initial_parameter + + +# from torch.nn.init import xavier_uniform +class ConvolutionCharEncoder(nn.Module): + """ + 别名::class:`fastNLP.modules.ConvolutionCharEncoder` :class:`fastNLP.modules.encoder.char_encoder.ConvolutionCharEncoder` + + char级别的卷积编码器. + + :param int char_emb_size: char级别embedding的维度. Default: 50 + :例: 有26个字符, 每一个的embedding是一个50维的向量, 所以输入的向量维度为50. + :param tuple feature_maps: 一个由int组成的tuple. tuple的长度是char级别卷积操作的数目, 第`i`个int表示第`i`个卷积操作的filter. + :param tuple kernels: 一个由int组成的tuple. tuple的长度是char级别卷积操作的数目, 第`i`个int表示第`i`个卷积操作的卷积核. + :param initial_method: 初始化参数的方式, 默认为`xavier normal` + """ + + def __init__(self, char_emb_size=50, feature_maps=(40, 30, 30), kernels=(3, 4, 5), initial_method=None): + super(ConvolutionCharEncoder, self).__init__() + self.convs = nn.ModuleList([ + nn.Conv2d(1, feature_maps[i], kernel_size=(char_emb_size, kernels[i]), bias=True, padding=(0, 4)) + for i in range(len(kernels))]) + + initial_parameter(self, initial_method) + + def forward(self, x): + """ + :param torch.Tensor x: ``[batch_size * sent_length, word_length, char_emb_size]`` 输入字符的embedding + :return: torch.Tensor : 卷积计算的结果, 维度为[batch_size * sent_length, sum(feature_maps), 1] + """ + x = x.contiguous().view(x.size(0), 1, x.size(1), x.size(2)) + # [batch_size*sent_length, channel, width, height] + x = x.transpose(2, 3) + # [batch_size*sent_length, channel, height, width] + return self._convolute(x).unsqueeze(2) + + def _convolute(self, x): + feats = [] + for conv in self.convs: + y = conv(x) + # [batch_size*sent_length, feature_maps[i], 1, width - kernels[i] + 1] + y = torch.squeeze(y, 2) + # [batch_size*sent_length, feature_maps[i], width - kernels[i] + 1] + y = torch.tanh(y) + y, __ = torch.max(y, 2) + # [batch_size*sent_length, feature_maps[i]] + feats.append(y) + return torch.cat(feats, 1) # [batch_size*sent_length, sum(feature_maps)] + + +class LSTMCharEncoder(nn.Module): + """ + 别名::class:`fastNLP.modules.LSTMCharEncoder` :class:`fastNLP.modules.encoder.char_encoder.LSTMCharEncoder` + + char级别基于LSTM的encoder. + + + """ + + def __init__(self, char_emb_size=50, hidden_size=None, initial_method=None): + """ + :param int char_emb_size: char级别embedding的维度. Default: 50 + 例: 有26个字符, 每一个的embedding是一个50维的向量, 所以输入的向量维度为50. + :param int hidden_size: LSTM隐层的大小, 默认为char的embedding维度 + :param initial_method: 初始化参数的方式, 默认为`xavier normal` + """ + super(LSTMCharEncoder, self).__init__() + self.hidden_size = char_emb_size if hidden_size is None else hidden_size + + self.lstm = nn.LSTM(input_size=char_emb_size, + hidden_size=self.hidden_size, + num_layers=1, + bias=True, + batch_first=True) + initial_parameter(self, initial_method) + + def forward(self, x): + """ + :param torch.Tensor x: ``[ n_batch*n_word, word_length, char_emb_size]`` 输入字符的embedding + :return: torch.Tensor : [ n_batch*n_word, char_emb_size]经过LSTM编码的结果 + """ + batch_size = x.shape[0] + h0 = torch.empty(1, batch_size, self.hidden_size) + h0 = nn.init.orthogonal_(h0) + c0 = torch.empty(1, batch_size, self.hidden_size) + c0 = nn.init.orthogonal_(c0) + + _, hidden = self.lstm(x, (h0, c0)) + return hidden[0].squeeze().unsqueeze(2) diff --git a/fastNLP/modules/encoder/conv.py b/fastNLP/modules/encoder/conv.py deleted file mode 100644 index 42254a8b..00000000 --- a/fastNLP/modules/encoder/conv.py +++ /dev/null @@ -1,58 +0,0 @@ -# python: 3.6 -# encoding: utf-8 - -import torch -import torch.nn as nn - -from fastNLP.modules.utils import initial_parameter - - -# import torch.nn.functional as F - - -class Conv(nn.Module): - """Basic 1-d convolution module, initialized with xavier_uniform. - - :param int in_channels: - :param int out_channels: - :param tuple kernel_size: - :param int stride: - :param int padding: - :param int dilation: - :param int groups: - :param bool bias: - :param str activation: - :param str initial_method: - """ - def __init__(self, in_channels, out_channels, kernel_size, - stride=1, padding=0, dilation=1, - groups=1, bias=True, activation='relu', initial_method=None): - super(Conv, self).__init__() - self.conv = nn.Conv1d( - in_channels=in_channels, - out_channels=out_channels, - kernel_size=kernel_size, - stride=stride, - padding=padding, - dilation=dilation, - groups=groups, - bias=bias) - # xavier_uniform_(self.conv.weight) - - activations = { - 'relu': nn.ReLU(), - 'tanh': nn.Tanh()} - if activation in activations: - self.activation = activations[activation] - else: - raise Exception( - 'Should choose activation function from: ' + - ', '.join([x for x in activations])) - initial_parameter(self, initial_method) - - def forward(self, x): - x = torch.transpose(x, 1, 2) # [N,L,C] -> [N,C,L] - x = self.conv(x) # [N,C_in,L] -> [N,C_out,L] - x = self.activation(x) - x = torch.transpose(x, 1, 2) # [N,C,L] -> [N,L,C] - return x diff --git a/fastNLP/modules/encoder/conv_maxpool.py b/fastNLP/modules/encoder/conv_maxpool.py index 8b035871..ae6bea04 100644 --- a/fastNLP/modules/encoder/conv_maxpool.py +++ b/fastNLP/modules/encoder/conv_maxpool.py @@ -1,38 +1,50 @@ -# python: 3.6 -# encoding: utf-8 - +__all__ = [ + "ConvMaxpool" +] import torch import torch.nn as nn import torch.nn.functional as F -from fastNLP.modules.utils import initial_parameter +from ..utils import initial_parameter class ConvMaxpool(nn.Module): - """Convolution and max-pooling module with multiple kernel sizes. + """ + 别名::class:`fastNLP.modules.ConvMaxpool` :class:`fastNLP.modules.encoder.conv_maxpool.ConvMaxpool` - :param int in_channels: - :param int out_channels: - :param tuple kernel_sizes: - :param int stride: - :param int padding: - :param int dilation: - :param int groups: - :param bool bias: - :param str activation: - :param str initial_method: + 集合了Convolution和Max-Pooling于一体的层。给定一个batch_size x max_len x input_size的输入,返回batch_size x + sum(output_channels) 大小的matrix。在内部,是先使用CNN给输入做卷积,然后经过activation激活层,在通过在长度(max_len) + 这一维进行max_pooling。最后得到每个sample的一个向量表示。 + + :param int in_channels: 输入channel的大小,一般是embedding的维度; 或encoder的output维度 + :param int,tuple(int) out_channels: 输出channel的数量。如果为list,则需要与kernel_sizes的数量保持一致 + :param int,tuple(int) kernel_sizes: 输出channel的kernel大小。 + :param int stride: 见pytorch Conv1D文档。所有kernel共享一个stride。 + :param int padding: 见pytorch Conv1D文档。所有kernel共享一个padding。 + :param int dilation: 见pytorch Conv1D文档。所有kernel共享一个dilation。 + :param int groups: 见pytorch Conv1D文档。所有kernel共享一个groups。 + :param bool bias: 见pytorch Conv1D文档。所有kernel共享一个bias。 + :param str activation: Convolution后的结果将通过该activation后再经过max-pooling。支持relu, sigmoid, tanh + :param str initial_method: str。 """ + def __init__(self, in_channels, out_channels, kernel_sizes, stride=1, padding=0, dilation=1, groups=1, bias=True, activation="relu", initial_method=None): super(ConvMaxpool, self).__init__() - + # convolution if isinstance(kernel_sizes, (list, tuple, int)): - if isinstance(kernel_sizes, int): + if isinstance(kernel_sizes, int) and isinstance(out_channels, int): out_channels = [out_channels] kernel_sizes = [kernel_sizes] - + elif isinstance(kernel_sizes, (tuple, list)) and isinstance(out_channels, (tuple, list)): + assert len(out_channels) == len( + kernel_sizes), "The number of out_channels should be equal to the number" \ + " of kernel_sizes." + else: + raise ValueError("The type of out_channels and kernel_sizes should be the same.") + self.convs = nn.ModuleList([nn.Conv1d( in_channels=in_channels, out_channels=oc, @@ -43,26 +55,39 @@ class ConvMaxpool(nn.Module): groups=groups, bias=bias) for oc, ks in zip(out_channels, kernel_sizes)]) - + else: raise Exception( 'Incorrect kernel sizes: should be list, tuple or int') - + # activation function if activation == 'relu': self.activation = F.relu + elif activation == 'sigmoid': + self.activation = F.sigmoid + elif activation == 'tanh': + self.activation = F.tanh else: raise Exception( - "Undefined activation function: choose from: relu") - + "Undefined activation function: choose from: relu, tanh, sigmoid") + initial_parameter(self, initial_method) + + def forward(self, x, mask=None): + """ - def forward(self, x): + :param torch.FloatTensor x: batch_size x max_len x input_size, 一般是经过embedding后的值 + :param mask: batch_size x max_len, pad的地方为0。不影响卷积运算,max-pool一定不会pool到pad为0的位置 + :return: + """ # [N,L,C] -> [N,C,L] x = torch.transpose(x, 1, 2) # convolution - xs = [self.activation(conv(x)) for conv in self.convs] # [[N,C,L]] + xs = [self.activation(conv(x)) for conv in self.convs] # [[N,C,L], ...] + if mask is not None: + mask = mask.unsqueeze(1) # B x 1 x L + xs = [x.masked_fill_(mask, float('-inf')) for x in xs] # max-pooling xs = [F.max_pool1d(input=i, kernel_size=i.size(2)).squeeze(2) - for i in xs] # [[N, C]] - return torch.cat(xs, dim=-1) # [N,C] + for i in xs] # [[N, C], ...] + return torch.cat(xs, dim=-1) # [N, C] diff --git a/fastNLP/modules/encoder/embedding.py b/fastNLP/modules/encoder/embedding.py index 7bcffb8e..f3c1f475 100644 --- a/fastNLP/modules/encoder/embedding.py +++ b/fastNLP/modules/encoder/embedding.py @@ -1,21 +1,43 @@ +__all__ = [ + "Embedding" +] import torch.nn as nn +from ..utils import get_embeddings -class Embedding(nn.Module): - """A simple lookup table. - - :param int nums: the size of the lookup table - :param int dims: the size of each vector - :param int padding_idx: pads the tensor with zeros whenever it encounters this index - :param bool sparse: If True, gradient matrix will be a sparse tensor. In this case, only optim.SGD(cuda and cpu) and optim.Adagrad(cpu) can be used +class Embedding(nn.Embedding): """ - def __init__(self, nums, dims, padding_idx=0, sparse=False, init_emb=None, dropout=0.0): - super(Embedding, self).__init__() - self.embed = nn.Embedding(nums, dims, padding_idx, sparse=sparse) - if init_emb is not None: - self.embed.weight = nn.Parameter(init_emb) - self.dropout = nn.Dropout(dropout) + 别名::class:`fastNLP.modules.Embedding` :class:`fastNLP.modules.encoder.embedding.Embedding` + Embedding组件. 可以通过self.num_embeddings获取词表大小; self.embedding_dim获取embedding的维度""" + + def __init__(self, init_embed, padding_idx=None, dropout=0.0, sparse=False, max_norm=None, norm_type=2, + scale_grad_by_freq=False): + """ + + :param tuple(int,int),torch.FloatTensor,nn.Embedding,numpy.ndarray init_embed: Embedding的大小(传入tuple(int, int), + 第一个int为vocab_zie, 第二个int为embed_dim); 如果为Tensor, Embedding, ndarray等则直接使用该值初始化Embedding + :param None,int padding_idx: 该index的Embedding将一直为0. + :param float dropout: 对Embedding的输出的dropout。 + :param bool sparse: 如果为True,则对Embedding的梯度将是sparse的,参考Pytorch Embedding获取更多信息。 + :param None,float max_norm: 每个vector最大的norm能为多大 + :param int norm_type: norm的类型 + :param bool scale_grad_by_freq: 如果为True,将会把梯度除以这个词出现的次数. + """ + embed = get_embeddings(init_embed) + num_embeddings, embedding_dim = embed.weight.size() + + super().__init__(num_embeddings, embedding_dim, padding_idx=padding_idx, + max_norm=max_norm, norm_type=norm_type, scale_grad_by_freq=scale_grad_by_freq, + sparse=sparse, _weight=embed.weight.data) + del embed + + self.dropout = nn.Dropout(dropout) + def forward(self, x): - x = self.embed(x) + """ + :param torch.LongTensor x: [batch, seq_len] + :return: torch.Tensor : [batch, seq_len, embed_dim] + """ + x = super().forward(x) return self.dropout(x) diff --git a/fastNLP/modules/encoder/linear.py b/fastNLP/modules/encoder/linear.py deleted file mode 100644 index 2dc31eea..00000000 --- a/fastNLP/modules/encoder/linear.py +++ /dev/null @@ -1,21 +0,0 @@ -import torch.nn as nn - -from fastNLP.modules.utils import initial_parameter - - -class Linear(nn.Module): - """ - - :param int input_size: input size - :param int output_size: output size - :param bool bias: - :param str initial_method: - """ - def __init__(self, input_size, output_size, bias=True, initial_method=None): - super(Linear, self).__init__() - self.linear = nn.Linear(input_size, output_size, bias) - initial_parameter(self, initial_method) - - def forward(self, x): - x = self.linear(x) - return x diff --git a/fastNLP/modules/encoder/lstm.py b/fastNLP/modules/encoder/lstm.py index 48c67a64..b4f960e7 100644 --- a/fastNLP/modules/encoder/lstm.py +++ b/fastNLP/modules/encoder/lstm.py @@ -1,39 +1,70 @@ +""" +轻量封装的 Pytorch LSTM 模块. +可在 forward 时传入序列的长度, 自动对padding做合适的处理. +""" +__all__ = [ + "LSTM" +] + +import torch import torch.nn as nn +import torch.nn.utils.rnn as rnn -from fastNLP.modules.utils import initial_parameter +from ..utils import initial_parameter class LSTM(nn.Module): - """Long Short Term Memory + """ + 别名::class:`fastNLP.modules.LSTM` :class:`fastNLP.modules.encoder.lstm.LSTM` + + LSTM 模块, 轻量封装的Pytorch LSTM - :param int input_size: - :param int hidden_size: - :param int num_layers: - :param float dropout: - :param bool batch_first: - :param bool bidirectional: - :param bool bias: - :param str initial_method: - :param bool get_hidden: + :param input_size: 输入 `x` 的特征维度 + :param hidden_size: 隐状态 `h` 的特征维度 + :param num_layers: rnn的层数. Default: 1 + :param dropout: 层间dropout概率. Default: 0 + :param bidirectional: 若为 ``True``, 使用双向的RNN. Default: ``False`` + :param batch_first: 若为 ``True``, 输入和输出 ``Tensor`` 形状为 + :(batch, seq, feature). Default: ``False`` + :param bias: 如果为 ``False``, 模型将不会使用bias. Default: ``True`` """ + def __init__(self, input_size, hidden_size=100, num_layers=1, dropout=0.0, batch_first=True, - bidirectional=False, bias=True, initial_method=None, get_hidden=False): + bidirectional=False, bias=True, initial_method=None): super(LSTM, self).__init__() + self.batch_first = batch_first self.lstm = nn.LSTM(input_size, hidden_size, num_layers, bias=bias, batch_first=batch_first, dropout=dropout, bidirectional=bidirectional) - self.get_hidden = get_hidden initial_parameter(self, initial_method) + + def forward(self, x, seq_len=None, h0=None, c0=None): + """ - def forward(self, x, h0=None, c0=None): + :param x: [batch, seq_len, input_size] 输入序列 + :param seq_len: [batch, ] 序列长度, 若为 ``None``, 所有输入看做一样长. Default: ``None`` + :param h0: [batch, hidden_size] 初始隐状态, 若为 ``None`` , 设为全1向量. Default: ``None`` + :param c0: [batch, hidden_size] 初始Cell状态, 若为 ``None`` , 设为全1向量. Default: ``None`` + :return (output, ht) 或 output: 若 ``get_hidden=True`` [batch, seq_len, hidden_size*num_direction] 输出序列 + 和 [batch, hidden_size*num_direction] 最后时刻隐状态. + """ if h0 is not None and c0 is not None: - x, (ht, ct) = self.lstm(x, (h0, c0)) + hx = (h0, c0) else: - x, (ht, ct) = self.lstm(x) - if self.get_hidden: - return x, (ht, ct) + hx = None + if seq_len is not None and not isinstance(x, rnn.PackedSequence): + sort_lens, sort_idx = torch.sort(seq_len, dim=0, descending=True) + if self.batch_first: + x = x[sort_idx] + else: + x = x[:, sort_idx] + x = rnn.pack_padded_sequence(x, sort_lens, batch_first=self.batch_first) + output, hx = self.lstm(x, hx) # -> [N,L,C] + output, _ = rnn.pad_packed_sequence(output, batch_first=self.batch_first) + _, unsort_idx = torch.sort(sort_idx, dim=0, descending=False) + if self.batch_first: + output = output[unsort_idx] + else: + output = output[:, unsort_idx] else: - return x - - -if __name__ == "__main__": - lstm = LSTM(10) + output, hx = self.lstm(x, hx) + return output, hx diff --git a/fastNLP/modules/encoder/masked_rnn.py b/fastNLP/modules/encoder/masked_rnn.py deleted file mode 100644 index 321546c4..00000000 --- a/fastNLP/modules/encoder/masked_rnn.py +++ /dev/null @@ -1,424 +0,0 @@ -__author__ = 'max' - -import torch -import torch.nn as nn -import torch.nn.functional as F - -from fastNLP.modules.utils import initial_parameter - - -def MaskedRecurrent(reverse=False): - def forward(input, hidden, cell, mask, train=True, dropout=0): - """ - :param input: - :param hidden: - :param cell: - :param mask: - :param dropout: step之间的dropout,对mask了的也会drop,应该是没问题的,反正没有gradient - :param train: 控制dropout的行为,在StackedRNN的forward中调用 - :return: - """ - output = [] - steps = range(input.size(0) - 1, -1, -1) if reverse else range(input.size(0)) - for i in steps: - if mask is None or mask[i].data.min() > 0.5: # 没有mask,都是1 - hidden = cell(input[i], hidden) - elif mask[i].data.max() > 0.5: # 有mask,但不全为0 - hidden_next = cell(input[i], hidden) # 一次喂入一个batch! - # hack to handle LSTM - if isinstance(hidden, tuple): # LSTM outputs a tuple of (hidden, cell), this is a common hack 😁 - mask = mask.float() - hx, cx = hidden - hp1, cp1 = hidden_next - hidden = ( - hx + (hp1 - hx) * mask[i].squeeze(), - cx + (cp1 - cx) * mask[i].squeeze()) # Why? 我知道了!!如果是mask就不用改变 - else: - hidden = hidden + (hidden_next - hidden) * mask[i] - - # if dropout != 0 and train: # warning, should i treat masked tensor differently? - # if isinstance(hidden, tuple): - # hidden = (F.dropout(hidden[0], p=dropout, training=train), - # F.dropout(hidden[1], p=dropout, training=train)) - # else: - # hidden = F.dropout(hidden, p=dropout, training=train) - - # hack to handle LSTM - output.append(hidden[0] if isinstance(hidden, tuple) else hidden) - - if reverse: - output.reverse() - output = torch.cat(output, 0).view(input.size(0), *output[0].size()) - - return hidden, output - - return forward - - -def StackedRNN(inners, num_layers, lstm=False, train=True, step_dropout=0, layer_dropout=0): - num_directions = len(inners) # rec_factory! - total_layers = num_layers * num_directions - - def forward(input, hidden, cells, mask): - assert (len(cells) == total_layers) - next_hidden = [] - - if lstm: - hidden = list(zip(*hidden)) - - for i in range(num_layers): - all_output = [] - for j, inner in enumerate(inners): - l = i * num_directions + j - hy, output = inner(input, hidden[l], cells[l], mask, step_dropout, train) - next_hidden.append(hy) - all_output.append(output) - - input = torch.cat(all_output, input.dim() - 1) # 下一层的输入 - - if layer_dropout != 0 and i < num_layers - 1: - input = F.dropout(input, p=layer_dropout, training=train, inplace=False) - - if lstm: - next_h, next_c = zip(*next_hidden) - next_hidden = ( - torch.cat(next_h, 0).view(total_layers, *next_h[0].size()), - torch.cat(next_c, 0).view(total_layers, *next_c[0].size()) - ) - else: - next_hidden = torch.cat(next_hidden, 0).view(total_layers, *next_hidden[0].size()) - - return next_hidden, input - - return forward - - -def AutogradMaskedRNN(num_layers=1, batch_first=False, train=True, layer_dropout=0, step_dropout=0, - bidirectional=False, lstm=False): - rec_factory = MaskedRecurrent - - if bidirectional: - layer = (rec_factory(), rec_factory(reverse=True)) - else: - layer = (rec_factory(),) # rec_factory 就是每层的结构啦!!在MaskedRecurrent中进行每层的计算!然后用StackedRNN接起来 - - func = StackedRNN(layer, - num_layers, - lstm=lstm, - layer_dropout=layer_dropout, step_dropout=step_dropout, - train=train) - - def forward(input, cells, hidden, mask): - if batch_first: - input = input.transpose(0, 1) - if mask is not None: - mask = mask.transpose(0, 1) - - nexth, output = func(input, hidden, cells, mask) - - if batch_first: - output = output.transpose(0, 1) - - return output, nexth - - return forward - - -def MaskedStep(): - def forward(input, hidden, cell, mask): - if mask is None or mask.data.min() > 0.5: - hidden = cell(input, hidden) - elif mask.data.max() > 0.5: - hidden_next = cell(input, hidden) - # hack to handle LSTM - if isinstance(hidden, tuple): - hx, cx = hidden - hp1, cp1 = hidden_next - hidden = (hx + (hp1 - hx) * mask, cx + (cp1 - cx) * mask) - else: - hidden = hidden + (hidden_next - hidden) * mask - # hack to handle LSTM - output = hidden[0] if isinstance(hidden, tuple) else hidden - - return hidden, output - - return forward - - -def StackedStep(layer, num_layers, lstm=False, dropout=0, train=True): - def forward(input, hidden, cells, mask): - assert (len(cells) == num_layers) - next_hidden = [] - - if lstm: - hidden = list(zip(*hidden)) - - for l in range(num_layers): - hy, output = layer(input, hidden[l], cells[l], mask) - next_hidden.append(hy) - input = output - - if dropout != 0 and l < num_layers - 1: - input = F.dropout(input, p=dropout, training=train, inplace=False) - - if lstm: - next_h, next_c = zip(*next_hidden) - next_hidden = ( - torch.cat(next_h, 0).view(num_layers, *next_h[0].size()), - torch.cat(next_c, 0).view(num_layers, *next_c[0].size()) - ) - else: - next_hidden = torch.cat(next_hidden, 0).view(num_layers, *next_hidden[0].size()) - - return next_hidden, input - - return forward - - -def AutogradMaskedStep(num_layers=1, dropout=0, train=True, lstm=False): - layer = MaskedStep() - - func = StackedStep(layer, - num_layers, - lstm=lstm, - dropout=dropout, - train=train) - - def forward(input, cells, hidden, mask): - nexth, output = func(input, hidden, cells, mask) - return output, nexth - - return forward - - -class MaskedRNNBase(nn.Module): - def __init__(self, Cell, input_size, hidden_size, - num_layers=1, bias=True, batch_first=False, - layer_dropout=0, step_dropout=0, bidirectional=False, initial_method = None , **kwargs): - """ - :param Cell: - :param input_size: - :param hidden_size: - :param num_layers: - :param bias: - :param batch_first: - :param layer_dropout: - :param step_dropout: - :param bidirectional: - :param kwargs: - """ - - super(MaskedRNNBase, self).__init__() - self.Cell = Cell - self.input_size = input_size - self.hidden_size = hidden_size - self.num_layers = num_layers - self.bias = bias - self.batch_first = batch_first - self.layer_dropout = layer_dropout - self.step_dropout = step_dropout - self.bidirectional = bidirectional - num_directions = 2 if bidirectional else 1 - - self.all_cells = [] - for layer in range(num_layers): # 初始化所有cell - for direction in range(num_directions): - layer_input_size = input_size if layer == 0 else hidden_size * num_directions - - cell = self.Cell(layer_input_size, hidden_size, self.bias, **kwargs) - self.all_cells.append(cell) - self.add_module('cell%d' % (layer * num_directions + direction), cell) # Max的代码写得真好看 - initial_parameter(self, initial_method) - def reset_parameters(self): - for cell in self.all_cells: - cell.reset_parameters() - - def forward(self, input, mask=None, hx=None): - batch_size = input.size(0) if self.batch_first else input.size(1) - lstm = self.Cell is nn.LSTMCell - if hx is None: - num_directions = 2 if self.bidirectional else 1 - hx = torch.autograd.Variable( - input.data.new(self.num_layers * num_directions, batch_size, self.hidden_size).zero_()) - if lstm: - hx = (hx, hx) - - func = AutogradMaskedRNN(num_layers=self.num_layers, - batch_first=self.batch_first, - step_dropout=self.step_dropout, - layer_dropout=self.layer_dropout, - train=self.training, - bidirectional=self.bidirectional, - lstm=lstm) # 传入all_cells,继续往底层封装走 - - output, hidden = func(input, self.all_cells, hx, - None if mask is None else mask.view(mask.size() + (1,))) # 这个+ (1, )是个什么操作? - return output, hidden - - def step(self, input, hx=None, mask=None): - """Execute one step forward (only for one-directional RNN). - - :param Tensor input: input tensor of this step. (batch, input_size) - :param Tensor hx: the hidden state of last step. (num_layers, batch, hidden_size) - :param Tensor mask: the mask tensor of this step. (batch, ) - :returns: - **output** (batch, hidden_size), tensor containing the output of this step from the last layer of RNN. - **hn** (num_layers, batch, hidden_size), tensor containing the hidden state of this step - - """ - assert not self.bidirectional, "step only cannot be applied to bidirectional RNN." # aha, typo! - batch_size = input.size(0) - lstm = self.Cell is nn.LSTMCell - if hx is None: - hx = torch.autograd.Variable(input.data.new(self.num_layers, batch_size, self.hidden_size).zero_()) - if lstm: - hx = (hx, hx) - - func = AutogradMaskedStep(num_layers=self.num_layers, - dropout=self.step_dropout, - train=self.training, - lstm=lstm) - - output, hidden = func(input, self.all_cells, hx, mask) - return output, hidden - - -class MaskedRNN(MaskedRNNBase): - r"""Applies a multi-layer Elman RNN with costomized non-linearity to an - input sequence. - For each element in the input sequence, each layer computes the following - function. :math:`h_t = \tanh(w_{ih} * x_t + b_{ih} + w_{hh} * h_{(t-1)} + b_{hh})` - - where :math:`h_t` is the hidden state at time `t`, and :math:`x_t` is - the hidden state of the previous layer at time `t` or :math:`input_t` - for the first layer. If nonlinearity='relu', then `ReLU` is used instead - of `tanh`. - - - :param int input_size: The number of expected features in the input x - :param int hidden_size: The number of features in the hidden state h - :param int num_layers: Number of recurrent layers. - :param str nonlinearity: The non-linearity to use ['tanh'|'relu']. Default: 'tanh' - :param bool bias: If False, then the layer does not use bias weights b_ih and b_hh. Default: True - :param bool batch_first: If True, then the input and output tensors are provided as (batch, seq, feature) - :param float dropout: If non-zero, introduces a dropout layer on the outputs of each RNN layer except the last layer - :param bool bidirectional: If True, becomes a bidirectional RNN. Default: False - - Inputs: input, mask, h_0 - - **input** (seq_len, batch, input_size): tensor containing the features - of the input sequence. - **mask** (seq_len, batch): 0-1 tensor containing the mask of the input sequence. - - **h_0** (num_layers * num_directions, batch, hidden_size): tensor - containing the initial hidden state for each element in the batch. - Outputs: output, h_n - - **output** (seq_len, batch, hidden_size * num_directions): tensor - containing the output features (h_k) from the last layer of the RNN, - for each k. If a :class:`torch.nn.utils.rnn.PackedSequence` has - been given as the input, the output will also be a packed sequence. - - **h_n** (num_layers * num_directions, batch, hidden_size): tensor - containing the hidden state for k=seq_len. - """ - - def __init__(self, *args, **kwargs): - super(MaskedRNN, self).__init__(nn.RNNCell, *args, **kwargs) - - -class MaskedLSTM(MaskedRNNBase): - r"""Applies a multi-layer long short-term memory (LSTM) RNN to an input - sequence. - For each element in the input sequence, each layer computes the following - function. - - .. math:: - - \begin{array}{ll} - i_t = \mathrm{sigmoid}(W_{ii} x_t + b_{ii} + W_{hi} h_{(t-1)} + b_{hi}) \\ - f_t = \mathrm{sigmoid}(W_{if} x_t + b_{if} + W_{hf} h_{(t-1)} + b_{hf}) \\ - g_t = \tanh(W_{ig} x_t + b_{ig} + W_{hc} h_{(t-1)} + b_{hg}) \\ - o_t = \mathrm{sigmoid}(W_{io} x_t + b_{io} + W_{ho} h_{(t-1)} + b_{ho}) \\ - c_t = f_t * c_{(t-1)} + i_t * g_t \\ - h_t = o_t * \tanh(c_t) - \end{array} - - where :math:`h_t` is the hidden state at time `t`, :math:`c_t` is the cell - state at time `t`, :math:`x_t` is the hidden state of the previous layer at - time `t` or :math:`input_t` for the first layer, and :math:`i_t`, - :math:`f_t`, :math:`g_t`, :math:`o_t` are the input, forget, cell, - and out gates, respectively. - - :param int input_size: The number of expected features in the input x - :param int hidden_size: The number of features in the hidden state h - :param int num_layers: Number of recurrent layers. - :param bool bias: If False, then the layer does not use bias weights b_ih and b_hh. Default: True - :param bool batch_first: If True, then the input and output tensors are provided as (batch, seq, feature) - :param bool dropout: If non-zero, introduces a dropout layer on the outputs of each RNN layer except the last layer - :param bool bidirectional: If True, becomes a bidirectional RNN. Default: False - - Inputs: input, mask, (h_0, c_0) - - **input** (seq_len, batch, input_size): tensor containing the features - of the input sequence. - **mask** (seq_len, batch): 0-1 tensor containing the mask of the input sequence. - - **h_0** (num_layers \* num_directions, batch, hidden_size): tensor - containing the initial hidden state for each element in the batch. - - **c_0** (num_layers \* num_directions, batch, hidden_size): tensor - containing the initial cell state for each element in the batch. - Outputs: output, (h_n, c_n) - - **output** (seq_len, batch, hidden_size * num_directions): tensor - containing the output features `(h_t)` from the last layer of the RNN, - for each t. If a :class:`torch.nn.utils.rnn.PackedSequence` has been - given as the input, the output will also be a packed sequence. - - **h_n** (num_layers * num_directions, batch, hidden_size): tensor - containing the hidden state for t=seq_len - - **c_n** (num_layers * num_directions, batch, hidden_size): tensor - containing the cell state for t=seq_len - """ - - def __init__(self, *args, **kwargs): - super(MaskedLSTM, self).__init__(nn.LSTMCell, *args, **kwargs) - - -class MaskedGRU(MaskedRNNBase): - r"""Applies a multi-layer gated recurrent unit (GRU) RNN to an input sequence. - For each element in the input sequence, each layer computes the following - function: - - .. math:: - - \begin{array}{ll} - r_t = \mathrm{sigmoid}(W_{ir} x_t + b_{ir} + W_{hr} h_{(t-1)} + b_{hr}) \\ - z_t = \mathrm{sigmoid}(W_{iz} x_t + b_{iz} + W_{hz} h_{(t-1)} + b_{hz}) \\ - n_t = \tanh(W_{in} x_t + b_{in} + r_t * (W_{hn} h_{(t-1)}+ b_{hn})) \\ - h_t = (1 - z_t) * n_t + z_t * h_{(t-1)} \\ - \end{array} - - where :math:`h_t` is the hidden state at time `t`, :math:`x_t` is the hidden - state of the previous layer at time `t` or :math:`input_t` for the first - layer, and :math:`r_t`, :math:`z_t`, :math:`n_t` are the reset, input, - and new gates, respectively. - - :param int input_size: The number of expected features in the input x - :param int hidden_size: The number of features in the hidden state h - :param int num_layers: Number of recurrent layers. - :param str nonlinearity: The non-linearity to use ['tanh'|'relu']. Default: 'tanh' - :param bool bias: If False, then the layer does not use bias weights b_ih and b_hh. Default: True - :param bool batch_first: If True, then the input and output tensors are provided as (batch, seq, feature) - :param bool dropout: If non-zero, introduces a dropout layer on the outputs of each RNN layer except the last layer - :param bool bidirectional: If True, becomes a bidirectional RNN. Default: False - - Inputs: input, mask, h_0 - - **input** (seq_len, batch, input_size): tensor containing the features - of the input sequence. - **mask** (seq_len, batch): 0-1 tensor containing the mask of the input sequence. - - **h_0** (num_layers * num_directions, batch, hidden_size): tensor - containing the initial hidden state for each element in the batch. - Outputs: output, h_n - - **output** (seq_len, batch, hidden_size * num_directions): tensor - containing the output features (h_k) from the last layer of the RNN, - for each k. If a :class:`torch.nn.utils.rnn.PackedSequence` has - been given as the input, the output will also be a packed sequence. - - **h_n** (num_layers * num_directions, batch, hidden_size): tensor - containing the hidden state for k=seq_len. - """ - - def __init__(self, *args, **kwargs): - super(MaskedGRU, self).__init__(nn.GRUCell, *args, **kwargs) diff --git a/fastNLP/modules/encoder/star_transformer.py b/fastNLP/modules/encoder/star_transformer.py index 1618c8ee..1eec7c13 100644 --- a/fastNLP/modules/encoder/star_transformer.py +++ b/fastNLP/modules/encoder/star_transformer.py @@ -1,59 +1,76 @@ +""" +Star-Transformer 的encoder部分的 Pytorch 实现 +""" +__all__ = [ + "StarTransformer" +] + +import numpy as NP import torch from torch import nn from torch.nn import functional as F -import numpy as NP class StarTransformer(nn.Module): - """Star-Transformer Encoder part。 + """ + 别名::class:`fastNLP.modules.StarTransformer` :class:`fastNLP.modules.encoder.star_transformer.StarTransformer` + + + Star-Transformer 的encoder部分。 输入3d的文本输入, 返回相同长度的文本编码 + paper: https://arxiv.org/abs/1902.09113 - :param hidden_size: int, 输入维度的大小。同时也是输出维度的大小。 - :param num_layers: int, star-transformer的层数 - :param num_head: int,head的数量。 - :param head_dim: int, 每个head的维度大小。 - :param dropout: float dropout 概率 - :param max_len: int or None, 如果为int,输入序列的最大长度, - 模型会为属于序列加上position embedding。 - 若为None,忽略加上position embedding的步骤 + + :param int hidden_size: 输入维度的大小。同时也是输出维度的大小。 + :param int num_layers: star-transformer的层数 + :param int num_head: head的数量。 + :param int head_dim: 每个head的维度大小。 + :param float dropout: dropout 概率. Default: 0.1 + :param int max_len: int or None, 如果为int,输入序列的最大长度, + 模型会为输入序列加上position embedding。 + 若为`None`,忽略加上position embedding的步骤. Default: `None` """ + def __init__(self, hidden_size, num_layers, num_head, head_dim, dropout=0.1, max_len=None): super(StarTransformer, self).__init__() self.iters = num_layers - + self.norm = nn.ModuleList([nn.LayerNorm(hidden_size) for _ in range(self.iters)]) self.ring_att = nn.ModuleList( - [MSA1(hidden_size, nhead=num_head, head_dim=head_dim, dropout=dropout) - for _ in range(self.iters)]) + [_MSA1(hidden_size, nhead=num_head, head_dim=head_dim, dropout=dropout) + for _ in range(self.iters)]) self.star_att = nn.ModuleList( - [MSA2(hidden_size, nhead=num_head, head_dim=head_dim, dropout=dropout) - for _ in range(self.iters)]) - + [_MSA2(hidden_size, nhead=num_head, head_dim=head_dim, dropout=dropout) + for _ in range(self.iters)]) + if max_len is not None: - self.pos_emb = self.pos_emb = nn.Embedding(max_len, hidden_size) + self.pos_emb = nn.Embedding(max_len, hidden_size) else: self.pos_emb = None - + def forward(self, data, mask): """ - :param FloatTensor data: [batch, length, hidden] the input sequence - :param ByteTensor mask: [batch, length] the padding mask for input, in which padding pos is 0 - :return: [batch, length, hidden] the output sequence - [batch, hidden] the global relay node + :param FloatTensor data: [batch, length, hidden] 输入的序列 + :param ByteTensor mask: [batch, length] 输入序列的padding mask, 在没有内容(padding 部分) 为 0, + 否则为 1 + :return: [batch, length, hidden] 编码后的输出序列 + + [batch, hidden] 全局 relay 节点, 详见论文 """ + def norm_func(f, x): # B, H, L, 1 return f(x.permute(0, 2, 3, 1)).permute(0, 3, 1, 2) - + B, L, H = data.size() - mask = (mask == 0) # flip the mask for masked_fill_ + mask = (mask == 0) # flip the mask for masked_fill_ smask = torch.cat([torch.zeros(B, 1, ).byte().to(mask), mask], 1) - - embs = data.permute(0, 2, 1)[:,:,:,None] # B H L 1 + + embs = data.permute(0, 2, 1)[:, :, :, None] # B H L 1 if self.pos_emb: - P = self.pos_emb(torch.arange(L, dtype=torch.long, device=embs.device)\ - .view(1, L)).permute(0, 2, 1).contiguous()[:, :, :, None] # 1 H L 1 + P = self.pos_emb(torch.arange(L, dtype=torch.long, device=embs.device) \ + .view(1, L)).permute(0, 2, 1).contiguous()[:, :, :, None] # 1 H L 1 embs = embs + P - + nodes = embs relay = embs.mean(2, keepdim=True) ex_mask = mask[:, None, :, None].expand(B, H, L, 1) @@ -62,78 +79,78 @@ class StarTransformer(nn.Module): ax = torch.cat([r_embs, relay.expand(B, H, 1, L)], 2) nodes = nodes + F.leaky_relu(self.ring_att[i](norm_func(self.norm[i], nodes), ax=ax)) relay = F.leaky_relu(self.star_att[i](relay, torch.cat([relay, nodes], 2), smask)) - + nodes = nodes.masked_fill_(ex_mask, 0) - + nodes = nodes.view(B, H, L).permute(0, 2, 1) - + return nodes, relay.view(B, H) -class MSA1(nn.Module): +class _MSA1(nn.Module): def __init__(self, nhid, nhead=10, head_dim=10, dropout=0.1): - super(MSA1, self).__init__() + super(_MSA1, self).__init__() # Multi-head Self Attention Case 1, doing self-attention for small regions # Due to the architecture of GPU, using hadamard production and summation are faster than dot production when unfold_size is very small self.WQ = nn.Conv2d(nhid, nhead * head_dim, 1) self.WK = nn.Conv2d(nhid, nhead * head_dim, 1) self.WV = nn.Conv2d(nhid, nhead * head_dim, 1) self.WO = nn.Conv2d(nhead * head_dim, nhid, 1) - + self.drop = nn.Dropout(dropout) - + # print('NUM_HEAD', nhead, 'DIM_HEAD', head_dim) self.nhid, self.nhead, self.head_dim, self.unfold_size = nhid, nhead, head_dim, 3 - + def forward(self, x, ax=None): # x: B, H, L, 1, ax : B, H, X, L append features nhid, nhead, head_dim, unfold_size = self.nhid, self.nhead, self.head_dim, self.unfold_size B, H, L, _ = x.shape - + q, k, v = self.WQ(x), self.WK(x), self.WV(x) # x: (B,H,L,1) - + if ax is not None: aL = ax.shape[2] ak = self.WK(ax).view(B, nhead, head_dim, aL, L) av = self.WV(ax).view(B, nhead, head_dim, aL, L) q = q.view(B, nhead, head_dim, 1, L) - k = F.unfold(k.view(B, nhead * head_dim, L, 1), (unfold_size, 1), padding=(unfold_size // 2, 0))\ - .view(B, nhead, head_dim, unfold_size, L) - v = F.unfold(v.view(B, nhead * head_dim, L, 1), (unfold_size, 1), padding=(unfold_size // 2, 0))\ - .view(B, nhead, head_dim, unfold_size, L) + k = F.unfold(k.view(B, nhead * head_dim, L, 1), (unfold_size, 1), padding=(unfold_size // 2, 0)) \ + .view(B, nhead, head_dim, unfold_size, L) + v = F.unfold(v.view(B, nhead * head_dim, L, 1), (unfold_size, 1), padding=(unfold_size // 2, 0)) \ + .view(B, nhead, head_dim, unfold_size, L) if ax is not None: k = torch.cat([k, ak], 3) v = torch.cat([v, av], 3) - + alphas = self.drop(F.softmax((q * k).sum(2, keepdim=True) / NP.sqrt(head_dim), 3)) # B N L 1 U att = (alphas * v).sum(3).view(B, nhead * head_dim, L, 1) - + ret = self.WO(att) - + return ret -class MSA2(nn.Module): +class _MSA2(nn.Module): def __init__(self, nhid, nhead=10, head_dim=10, dropout=0.1): # Multi-head Self Attention Case 2, a broadcastable query for a sequence key and value - super(MSA2, self).__init__() + super(_MSA2, self).__init__() self.WQ = nn.Conv2d(nhid, nhead * head_dim, 1) self.WK = nn.Conv2d(nhid, nhead * head_dim, 1) self.WV = nn.Conv2d(nhid, nhead * head_dim, 1) self.WO = nn.Conv2d(nhead * head_dim, nhid, 1) - + self.drop = nn.Dropout(dropout) - + # print('NUM_HEAD', nhead, 'DIM_HEAD', head_dim) self.nhid, self.nhead, self.head_dim, self.unfold_size = nhid, nhead, head_dim, 3 - + def forward(self, x, y, mask=None): # x: B, H, 1, 1, 1 y: B H L 1 nhid, nhead, head_dim, unfold_size = self.nhid, self.nhead, self.head_dim, self.unfold_size B, H, L, _ = y.shape - + q, k, v = self.WQ(x), self.WK(y), self.WV(y) - + q = q.view(B, nhead, 1, head_dim) # B, H, 1, 1 -> B, N, 1, h k = k.view(B, nhead, head_dim, L) # B, H, L, 1 -> B, N, h, L v = v.view(B, nhead, head_dim, L).permute(0, 1, 3, 2) # B, H, L, 1 -> B, N, L, h diff --git a/fastNLP/modules/encoder/transformer.py b/fastNLP/modules/encoder/transformer.py index d7b8c544..698ff95c 100644 --- a/fastNLP/modules/encoder/transformer.py +++ b/fastNLP/modules/encoder/transformer.py @@ -1,31 +1,39 @@ +__all__ = [ + "TransformerEncoder" +] from torch import nn -from ..aggregator.attention import MultiHeadAtte +from ..aggregator.attention import MultiHeadAttention from ..dropout import TimestepDropout class TransformerEncoder(nn.Module): - """transformer的encoder模块,不包含embedding层 + """ + 别名::class:`fastNLP.modules.TransformerEncoder` :class:`fastNLP.modules.encoder.transformer.TransformerEncoder` + + + transformer的encoder模块,不包含embedding层 - :param num_layers: int, transformer的层数 - :param model_size: int, 输入维度的大小。同时也是输出维度的大小。 - :param inner_size: int, FFN层的hidden大小 - :param key_size: int, 每个head的维度大小。 - :param value_size: int,每个head中value的维度。 - :param num_head: int,head的数量。 - :param dropout: float。 + :param int num_layers: transformer的层数 + :param int model_size: 输入维度的大小。同时也是输出维度的大小。 + :param int inner_size: FFN层的hidden大小 + :param int key_size: 每个head的维度大小。 + :param int value_size: 每个head中value的维度。 + :param int num_head: head的数量。 + :param float dropout: dropout概率. Default: 0.1 """ + class SubLayer(nn.Module): def __init__(self, model_size, inner_size, key_size, value_size, num_head, dropout=0.1): super(TransformerEncoder.SubLayer, self).__init__() - self.atte = MultiHeadAtte(model_size, key_size, value_size, num_head, dropout) + self.atte = MultiHeadAttention(model_size, key_size, value_size, num_head, dropout) self.norm1 = nn.LayerNorm(model_size) self.ffn = nn.Sequential(nn.Linear(model_size, inner_size), nn.ReLU(), nn.Linear(inner_size, model_size), - TimestepDropout(dropout),) + TimestepDropout(dropout), ) self.norm2 = nn.LayerNorm(model_size) - + def forward(self, input, seq_mask=None, atte_mask_out=None): """ @@ -40,23 +48,24 @@ class TransformerEncoder(nn.Module): output = self.norm2(output + norm_atte) output *= seq_mask return output - + def __init__(self, num_layers, **kargs): super(TransformerEncoder, self).__init__() self.layers = nn.ModuleList([self.SubLayer(**kargs) for _ in range(num_layers)]) - + def forward(self, x, seq_mask=None): """ :param x: [batch, seq_len, model_size] 输入序列 - :param seq_mask: [batch, seq_len] 输入序列的padding mask + :param seq_mask: [batch, seq_len] 输入序列的padding mask, 若为 ``None`` , 生成全1向量. + Default: ``None`` :return: [batch, seq_len, model_size] 输出序列 """ output = x if seq_mask is None: atte_mask_out = None else: - atte_mask_out = (seq_mask < 1)[:,None,:] - seq_mask = seq_mask[:,:,None] + atte_mask_out = (seq_mask < 1)[:, None, :] + seq_mask = seq_mask[:, :, None] for layer in self.layers: output = layer(output, seq_mask, atte_mask_out) return output diff --git a/fastNLP/modules/encoder/variational_rnn.py b/fastNLP/modules/encoder/variational_rnn.py index 0d58d67b..29b728e5 100644 --- a/fastNLP/modules/encoder/variational_rnn.py +++ b/fastNLP/modules/encoder/variational_rnn.py @@ -1,7 +1,15 @@ +""" +Variational RNN 的 Pytorch 实现 +""" +__all__ = [ + "VarRNN", + "VarLSTM", + "VarGRU" +] + import torch import torch.nn as nn from torch.nn.utils.rnn import PackedSequence, pack_padded_sequence, pad_packed_sequence -from fastNLP.modules.utils import initial_parameter try: from torch import flip @@ -9,37 +17,43 @@ except ImportError: def flip(x, dims): indices = [slice(None)] * x.dim() for dim in dims: - indices[dim] = torch.arange(x.size(dim) - 1, -1, -1, dtype=torch.long, device=x.device) + indices[dim] = torch.arange( + x.size(dim) - 1, -1, -1, dtype=torch.long, device=x.device) return x[tuple(indices)] +from ..utils import initial_parameter + class VarRnnCellWrapper(nn.Module): - """Wrapper for normal RNN Cells, make it support variational dropout """ - + Wrapper for normal RNN Cells, make it support variational dropout + """ + def __init__(self, cell, hidden_size, input_p, hidden_p): super(VarRnnCellWrapper, self).__init__() self.cell = cell self.hidden_size = hidden_size self.input_p = input_p self.hidden_p = hidden_p - + def forward(self, input_x, hidden, mask_x, mask_h, is_reversed=False): """ :param PackedSequence input_x: [seq_len, batch_size, input_size] :param hidden: for LSTM, tuple of (h_0, c_0), [batch_size, hidden_size] - for other RNN, h_0, [batch_size, hidden_size] + for other RNN, h_0, [batch_size, hidden_size] :param mask_x: [batch_size, input_size] dropout mask for input :param mask_h: [batch_size, hidden_size] dropout mask for hidden :return PackedSequence output: [seq_len, bacth_size, hidden_size] hidden: for LSTM, tuple of (h_n, c_n), [batch_size, hidden_size] for other RNN, h_n, [batch_size, hidden_size] """ + def get_hi(hi, h0, size): h0_size = size - hi.size(0) if h0_size > 0: return torch.cat([hi, h0[:h0_size]], dim=0) return hi[:size] + is_lstm = isinstance(hidden, tuple) input, batch_sizes = input_x.data, input_x.batch_sizes output = [] @@ -50,7 +64,7 @@ class VarRnnCellWrapper(nn.Module): else: batch_iter = batch_sizes idx = 0 - + if is_lstm: hn = (hidden[0].clone(), hidden[1].clone()) else: @@ -58,15 +72,16 @@ class VarRnnCellWrapper(nn.Module): hi = hidden for size in batch_iter: if is_reversed: - input_i = input[idx-size: idx] * mask_x[:size] + input_i = input[idx - size: idx] * mask_x[:size] idx -= size else: - input_i = input[idx: idx+size] * mask_x[:size] + input_i = input[idx: idx + size] * mask_x[:size] idx += size mask_hi = mask_h[:size] if is_lstm: hx, cx = hi - hi = (get_hi(hx, hidden[0], size) * mask_hi, get_hi(cx, hidden[1], size)) + hi = (get_hi(hx, hidden[0], size) * + mask_hi, get_hi(cx, hidden[1], size)) hi = cell(input_i, hi) hn[0][:size] = hi[0] hn[1][:size] = hi[1] @@ -76,7 +91,7 @@ class VarRnnCellWrapper(nn.Module): hi = cell(input_i, hi) hn[:size] = hi output.append(hi) - + if is_reversed: output = list(reversed(output)) output = torch.cat(output, dim=0) @@ -84,11 +99,25 @@ class VarRnnCellWrapper(nn.Module): class VarRNNBase(nn.Module): - """Implementation of Variational Dropout RNN network. - refer to `A Theoretically Grounded Application of Dropout in Recurrent Neural Networks (Yarin Gal and Zoubin Ghahramani, 2016) - https://arxiv.org/abs/1512.05287`. """ + Variational Dropout RNN 实现. + + 论文参考: `A Theoretically Grounded Application of Dropout in Recurrent Neural Networks (Yarin Gal and Zoubin Ghahramani, 2016) + https://arxiv.org/abs/1512.05287`. + :param mode: rnn 模式, (lstm or not) + :param Cell: rnn cell 类型, (lstm, gru, etc) + :param input_size: 输入 `x` 的特征维度 + :param hidden_size: 隐状态 `h` 的特征维度 + :param num_layers: rnn的层数. Default: 1 + :param bias: 如果为 ``False``, 模型将不会使用bias. Default: ``True`` + :param batch_first: 若为 ``True``, 输入和输出 ``Tensor`` 形状为 + (batch, seq, feature). Default: ``False`` + :param input_dropout: 对输入的dropout概率. Default: 0 + :param hidden_dropout: 对每个隐状态的dropout概率. Default: 0 + :param bidirectional: 若为 ``True``, 使用双向的RNN. Default: ``False`` + """ + def __init__(self, mode, Cell, input_size, hidden_size, num_layers=1, bias=True, batch_first=False, input_dropout=0, hidden_dropout=0, bidirectional=False): @@ -108,49 +137,65 @@ class VarRNNBase(nn.Module): for direction in range(self.num_directions): input_size = self.input_size if layer == 0 else self.hidden_size * self.num_directions cell = Cell(input_size, self.hidden_size, bias) - self._all_cells.append(VarRnnCellWrapper(cell, self.hidden_size, input_dropout, hidden_dropout)) + self._all_cells.append(VarRnnCellWrapper( + cell, self.hidden_size, input_dropout, hidden_dropout)) initial_parameter(self) self.is_lstm = (self.mode == "LSTM") - + def _forward_one(self, n_layer, n_direction, input, hx, mask_x, mask_h): is_lstm = self.is_lstm idx = self.num_directions * n_layer + n_direction cell = self._all_cells[idx] hi = (hx[0][idx], hx[1][idx]) if is_lstm else hx[idx] - output_x, hidden_x = cell(input, hi, mask_x, mask_h, is_reversed=(n_direction == 1)) + output_x, hidden_x = cell( + input, hi, mask_x, mask_h, is_reversed=(n_direction == 1)) return output_x, hidden_x + + def forward(self, x, hx=None): + """ - def forward(self, input, hx=None): + :param x: [batch, seq_len, input_size] 输入序列 + :param hx: [batch, hidden_size] 初始隐状态, 若为 ``None`` , 设为全1向量. Default: ``None`` + :return (output, ht): [batch, seq_len, hidden_size*num_direction] 输出序列 + 和 [batch, hidden_size*num_direction] 最后时刻隐状态 + """ is_lstm = self.is_lstm - is_packed = isinstance(input, PackedSequence) + is_packed = isinstance(x, PackedSequence) if not is_packed: - seq_len = input.size(1) if self.batch_first else input.size(0) - max_batch_size = input.size(0) if self.batch_first else input.size(1) - seq_lens = torch.LongTensor([seq_len for _ in range(max_batch_size)]) - input = pack_padded_sequence(input, seq_lens, batch_first=self.batch_first) + seq_len = x.size(1) if self.batch_first else x.size(0) + max_batch_size = x.size(0) if self.batch_first else x.size(1) + seq_lens = torch.LongTensor( + [seq_len for _ in range(max_batch_size)]) + x = pack_padded_sequence(x, seq_lens, batch_first=self.batch_first) else: - max_batch_size = int(input.batch_sizes[0]) - input, batch_sizes = input.data, input.batch_sizes - + max_batch_size = int(x.batch_sizes[0]) + x, batch_sizes = x.data, x.batch_sizes + if hx is None: - hx = input.new_zeros(self.num_layers * self.num_directions, - max_batch_size, self.hidden_size, requires_grad=True) + hx = x.new_zeros(self.num_layers * self.num_directions, + max_batch_size, self.hidden_size, requires_grad=True) if is_lstm: hx = (hx, hx.new_zeros(hx.size(), requires_grad=True)) - - mask_x = input.new_ones((max_batch_size, self.input_size)) - mask_out = input.new_ones((max_batch_size, self.hidden_size * self.num_directions)) - mask_h_ones = input.new_ones((max_batch_size, self.hidden_size)) - nn.functional.dropout(mask_x, p=self.input_dropout, training=self.training, inplace=True) - nn.functional.dropout(mask_out, p=self.hidden_dropout, training=self.training, inplace=True) - - hidden = input.new_zeros((self.num_layers*self.num_directions, max_batch_size, self.hidden_size)) + + mask_x = x.new_ones((max_batch_size, self.input_size)) + mask_out = x.new_ones( + (max_batch_size, self.hidden_size * self.num_directions)) + mask_h_ones = x.new_ones((max_batch_size, self.hidden_size)) + nn.functional.dropout(mask_x, p=self.input_dropout, + training=self.training, inplace=True) + nn.functional.dropout(mask_out, p=self.hidden_dropout, + training=self.training, inplace=True) + + hidden = x.new_zeros( + (self.num_layers * self.num_directions, max_batch_size, self.hidden_size)) if is_lstm: - cellstate = input.new_zeros((self.num_layers*self.num_directions, max_batch_size, self.hidden_size)) + cellstate = x.new_zeros( + (self.num_layers * self.num_directions, max_batch_size, self.hidden_size)) for layer in range(self.num_layers): output_list = [] - input_seq = PackedSequence(input, batch_sizes) - mask_h = nn.functional.dropout(mask_h_ones, p=self.hidden_dropout, training=self.training, inplace=False) + input_seq = PackedSequence(x, batch_sizes) + mask_h = nn.functional.dropout( + mask_h_ones, p=self.hidden_dropout, training=self.training, inplace=False) for direction in range(self.num_directions): output_x, hidden_x = self._forward_one(layer, direction, input_seq, hx, mask_x if layer == 0 else mask_out, mask_h) @@ -161,72 +206,90 @@ class VarRNNBase(nn.Module): cellstate[idx] = hidden_x[1] else: hidden[idx] = hidden_x - input = torch.cat(output_list, dim=-1) - + x = torch.cat(output_list, dim=-1) + if is_lstm: hidden = (hidden, cellstate) - + if is_packed: - output = PackedSequence(input, batch_sizes) + output = PackedSequence(x, batch_sizes) else: - input = PackedSequence(input, batch_sizes) - output, _ = pad_packed_sequence(input, batch_first=self.batch_first) - + x = PackedSequence(x, batch_sizes) + output, _ = pad_packed_sequence(x, batch_first=self.batch_first) + return output, hidden class VarLSTM(VarRNNBase): - """Variational Dropout LSTM. """ + 别名::class:`fastNLP.modules.VarLSTM` :class:`fastNLP.modules.encoder.variational_rnn.VarLSTM` + Variational Dropout LSTM. + + :param input_size: 输入 `x` 的特征维度 + :param hidden_size: 隐状态 `h` 的特征维度 + :param num_layers: rnn的层数. Default: 1 + :param bias: 如果为 ``False``, 模型将不会使用bias. Default: ``True`` + :param batch_first: 若为 ``True``, 输入和输出 ``Tensor`` 形状为 + (batch, seq, feature). Default: ``False`` + :param input_dropout: 对输入的dropout概率. Default: 0 + :param hidden_dropout: 对每个隐状态的dropout概率. Default: 0 + :param bidirectional: 若为 ``True``, 使用双向的LSTM. Default: ``False`` + """ + def __init__(self, *args, **kwargs): - super(VarLSTM, self).__init__(mode="LSTM", Cell=nn.LSTMCell, *args, **kwargs) + super(VarLSTM, self).__init__( + mode="LSTM", Cell=nn.LSTMCell, *args, **kwargs) + + def forward(self, x, hx=None): + return super(VarLSTM, self).forward(x, hx) class VarRNN(VarRNNBase): - """Variational Dropout RNN. """ + 别名::class:`fastNLP.modules.VarRNN` :class:`fastNLP.modules.encoder.variational_rnn.VarRNN` + + Variational Dropout RNN. + :param input_size: 输入 `x` 的特征维度 + :param hidden_size: 隐状态 `h` 的特征维度 + :param num_layers: rnn的层数. Default: 1 + :param bias: 如果为 ``False``, 模型将不会使用bias. Default: ``True`` + :param batch_first: 若为 ``True``, 输入和输出 ``Tensor`` 形状为 + (batch, seq, feature). Default: ``False`` + :param input_dropout: 对输入的dropout概率. Default: 0 + :param hidden_dropout: 对每个隐状态的dropout概率. Default: 0 + :param bidirectional: 若为 ``True``, 使用双向的RNN. Default: ``False`` + """ + def __init__(self, *args, **kwargs): - super(VarRNN, self).__init__(mode="RNN", Cell=nn.RNNCell, *args, **kwargs) + super(VarRNN, self).__init__( + mode="RNN", Cell=nn.RNNCell, *args, **kwargs) + + def forward(self, x, hx=None): + return super(VarRNN, self).forward(x, hx) class VarGRU(VarRNNBase): - """Variational Dropout GRU. """ + 别名::class:`fastNLP.modules.VarGRU` :class:`fastNLP.modules.encoder.variational_rnn.VarGRU` + + Variational Dropout GRU. + :param input_size: 输入 `x` 的特征维度 + :param hidden_size: 隐状态 `h` 的特征维度 + :param num_layers: rnn的层数. Default: 1 + :param bias: 如果为 ``False``, 模型将不会使用bias. Default: ``True`` + :param batch_first: 若为 ``True``, 输入和输出 ``Tensor`` 形状为 + (batch, seq, feature). Default: ``False`` + :param input_dropout: 对输入的dropout概率. Default: 0 + :param hidden_dropout: 对每个隐状态的dropout概率. Default: 0 + :param bidirectional: 若为 ``True``, 使用双向的GRU. Default: ``False`` + """ + def __init__(self, *args, **kwargs): - super(VarGRU, self).__init__(mode="GRU", Cell=nn.GRUCell, *args, **kwargs) - -# if __name__ == '__main__': -# x = torch.Tensor([[1,2,3], [4,5,0], [6,0,0]])[:,:,None] * 0.1 -# mask = (x != 0).float().view(3, -1) -# seq_lens = torch.LongTensor([3,2,1]) -# y = torch.Tensor([[0,1,1], [1,1,0], [0,0,0]]) -# # rev = _reverse_packed_sequence(pack) -# # # print(rev) -# lstm = VarLSTM(input_size=1, num_layers=2, hidden_size=2, -# batch_first=True, bidirectional=True, -# input_dropout=0.0, hidden_dropout=0.0,) -# # lstm = nn.LSTM(input_size=1, num_layers=2, hidden_size=2, -# # batch_first=True, bidirectional=True,) -# loss = nn.BCELoss() -# m = nn.Sigmoid() -# optim = torch.optim.SGD(lstm.parameters(), lr=1e-3) -# for i in range(2000): -# optim.zero_grad() -# pack = pack_padded_sequence(x, seq_lens, batch_first=True) -# out, hidden = lstm(pack) -# out, lens = pad_packed_sequence(out, batch_first=True) -# # print(lens) -# # print(out) -# # print(hidden[0]) -# # print(hidden[0].size()) -# # print(hidden[1]) -# out = out.sum(-1) -# out = m(out) * mask -# l = loss(out, y) -# l.backward() -# optim.step() -# if i % 50 == 0: -# print(out) + super(VarGRU, self).__init__( + mode="GRU", Cell=nn.GRUCell, *args, **kwargs) + + def forward(self, x, hx=None): + return super(VarGRU, self).forward(x, hx) diff --git a/fastNLP/modules/other_modules.py b/fastNLP/modules/other_modules.py deleted file mode 100644 index 92a08ba1..00000000 --- a/fastNLP/modules/other_modules.py +++ /dev/null @@ -1,186 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F -import torch.utils.data -from torch.nn import Parameter - - -class GroupNorm(nn.Module): - def __init__(self, num_features, num_groups=20, eps=1e-5): - super(GroupNorm, self).__init__() - self.weight = nn.Parameter(torch.ones(1, num_features, 1)) - self.bias = nn.Parameter(torch.zeros(1, num_features, 1)) - self.num_groups = num_groups - self.eps = eps - - def forward(self, x): - N, C, H = x.size() - G = self.num_groups - assert C % G == 0 - - x = x.view(N, G, -1) - mean = x.mean(-1, keepdim=True) - var = x.var(-1, keepdim=True) - - x = (x - mean) / (var + self.eps).sqrt() - x = x.view(N, C, H) - return x * self.weight + self.bias - - -class LayerNormalization(nn.Module): - """ - - :param int layer_size: - :param float eps: default=1e-3 - """ - def __init__(self, layer_size, eps=1e-3): - super(LayerNormalization, self).__init__() - - self.eps = eps - self.a_2 = nn.Parameter(torch.ones(1, layer_size, requires_grad=True)) - self.b_2 = nn.Parameter(torch.zeros(1, layer_size, requires_grad=True)) - - def forward(self, z): - if z.size(1) == 1: - return z - - mu = torch.mean(z, keepdim=True, dim=-1) - sigma = torch.std(z, keepdim=True, dim=-1) - ln_out = (z - mu) / (sigma + self.eps) - ln_out = ln_out * self.a_2 + self.b_2 - return ln_out - - -class BiLinear(nn.Module): - def __init__(self, n_left, n_right, n_out, bias=True): - """ - - :param int n_left: size of left input - :param int n_right: size of right input - :param int n_out: size of output - :param bool bias: If set to False, the layer will not learn an additive bias. Default: True - """ - super(BiLinear, self).__init__() - self.n_left = n_left - self.n_right = n_right - self.n_out = n_out - - self.U = Parameter(torch.Tensor(self.n_out, self.n_left, self.n_right)) - self.W_l = Parameter(torch.Tensor(self.n_out, self.n_left)) - self.W_r = Parameter(torch.Tensor(self.n_out, self.n_left)) - - if bias: - self.bias = Parameter(torch.Tensor(n_out)) - else: - self.register_parameter('bias', None) - - self.reset_parameters() - - def reset_parameters(self): - nn.init.xavier_uniform_(self.W_l) - nn.init.xavier_uniform_(self.W_r) - nn.init.constant_(self.bias, 0.) - nn.init.xavier_uniform_(self.U) - - def forward(self, input_left, input_right): - """ - :param Tensor input_left: the left input tensor with shape = [batch1, batch2, ..., left_features] - :param Tensor input_right: the right input tensor with shape = [batch1, batch2, ..., right_features] - - """ - left_size = input_left.size() - right_size = input_right.size() - assert left_size[:-1] == right_size[:-1], \ - "batch size of left and right inputs mis-match: (%s, %s)" % (left_size[:-1], right_size[:-1]) - batch = int(np.prod(left_size[:-1])) - - # convert left and right input to matrices [batch, left_features], [batch, right_features] - input_left = input_left.view(batch, self.n_left) - input_right = input_right.view(batch, self.n_right) - - # output [batch, out_features] - output = F.bilinear(input_left, input_right, self.U, self.bias) - output = output + \ - F.linear(input_left, self.W_l, None) + \ - F.linear(input_right, self.W_r, None) - # convert back to [batch1, batch2, ..., out_features] - return output.view(left_size[:-1] + (self.n_out,)) - - def __repr__(self): - return self.__class__.__name__ + ' (' \ - + 'in1_features=' + str(self.n_left) \ - + ', in2_features=' + str(self.n_right) \ - + ', out_features=' + str(self.n_out) + ')' - - -class BiAffine(nn.Module): - def __init__(self, n_enc, n_dec, n_labels, biaffine=True, **kwargs): - """ - - :param int n_enc: the dimension of the encoder input. - :param int n_dec: the dimension of the decoder input. - :param int n_labels: the number of labels of the crf layer - :param bool biaffine: if apply bi-affine parameter. - """ - super(BiAffine, self).__init__() - self.n_enc = n_enc - self.n_dec = n_dec - self.num_labels = n_labels - self.biaffine = biaffine - - self.W_d = Parameter(torch.Tensor(self.num_labels, self.n_dec)) - self.W_e = Parameter(torch.Tensor(self.num_labels, self.n_enc)) - self.b = Parameter(torch.Tensor(self.num_labels, 1, 1)) - if self.biaffine: - self.U = Parameter(torch.Tensor(self.num_labels, self.n_dec, self.n_enc)) - else: - self.register_parameter('U', None) - - self.reset_parameters() - - def reset_parameters(self): - nn.init.xavier_uniform_(self.W_d) - nn.init.xavier_uniform_(self.W_e) - nn.init.constant_(self.b, 0.) - if self.biaffine: - nn.init.xavier_uniform_(self.U) - - def forward(self, input_d, input_e, mask_d=None, mask_e=None): - """ - - :param Tensor input_d: the decoder input tensor with shape = [batch, length_decoder, input_size] - :param Tensor input_e: the child input tensor with shape = [batch, length_encoder, input_size] - :param mask_d: Tensor or None, the mask tensor for decoder with shape = [batch, length_decoder] - :param mask_e: Tensor or None, the mask tensor for encoder with shape = [batch, length_encoder] - :returns: Tensor, the energy tensor with shape = [batch, num_label, length, length] - """ - assert input_d.size(0) == input_e.size(0), 'batch sizes of encoder and decoder are requires to be equal.' - batch, length_decoder, _ = input_d.size() - _, length_encoder, _ = input_e.size() - - # compute decoder part: [num_label, input_size_decoder] * [batch, input_size_decoder, length_decoder] - # the output shape is [batch, num_label, length_decoder] - out_d = torch.matmul(self.W_d, input_d.transpose(1, 2)).unsqueeze(3) - # compute decoder part: [num_label, input_size_encoder] * [batch, input_size_encoder, length_encoder] - # the output shape is [batch, num_label, length_encoder] - out_e = torch.matmul(self.W_e, input_e.transpose(1, 2)).unsqueeze(2) - - # output shape [batch, num_label, length_decoder, length_encoder] - if self.biaffine: - # compute bi-affine part - # [batch, 1, length_decoder, input_size_decoder] * [num_labels, input_size_decoder, input_size_encoder] - # output shape [batch, num_label, length_decoder, input_size_encoder] - output = torch.matmul(input_d.unsqueeze(1), self.U) - # [batch, num_label, length_decoder, input_size_encoder] * [batch, 1, input_size_encoder, length_encoder] - # output shape [batch, num_label, length_decoder, length_encoder] - output = torch.matmul(output, input_e.unsqueeze(1).transpose(2, 3)) - - output = output + out_d + out_e + self.b - else: - output = out_d + out_d + self.b - - if mask_d is not None: - output = output * mask_d.unsqueeze(1).unsqueeze(3) * mask_e.unsqueeze(1).unsqueeze(2) - - return output diff --git a/fastNLP/modules/utils.py b/fastNLP/modules/utils.py index 5287bca4..c9a1f682 100644 --- a/fastNLP/modules/utils.py +++ b/fastNLP/modules/utils.py @@ -1,16 +1,11 @@ +from functools import reduce + +import numpy as np import torch import torch.nn as nn import torch.nn.init as init -def mask_softmax(matrix, mask): - if mask is None: - result = torch.nn.functional.softmax(matrix, dim=-1) - else: - raise NotImplementedError - return result - - def initial_parameter(net, initial_method=None): """A method used to initialize the weights of PyTorch models. @@ -60,7 +55,8 @@ def initial_parameter(net, initial_method=None): init_method(w.data) # weight else: init.normal_(w.data) # bias - elif hasattr(m, 'weight') and m.weight.requires_grad: + elif m is not None and hasattr(m, 'weight') and \ + hasattr(m.weight, "requires_grad"): init_method(m.weight.data) else: for w in m.parameters(): @@ -74,16 +70,63 @@ def initial_parameter(net, initial_method=None): net.apply(weights_init) -def seq_mask(seq_len, max_len): - """Create sequence mask. +def get_embeddings(init_embed): + """ + 根据输入的init_embed生成nn.Embedding对象。 + + :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 + embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, + 此时就以传入的对象作为embedding + :return nn.Embedding embeddings: + """ + if isinstance(init_embed, tuple): + res = nn.Embedding( + num_embeddings=init_embed[0], embedding_dim=init_embed[1]) + elif isinstance(init_embed, nn.Embedding): + res = init_embed + elif isinstance(init_embed, torch.Tensor): + res = nn.Embedding.from_pretrained(init_embed, freeze=False) + elif isinstance(init_embed, np.ndarray): + init_embed = torch.tensor(init_embed, dtype=torch.float32) + res = nn.Embedding.from_pretrained(init_embed, freeze=False) + else: + raise TypeError( + 'invalid init_embed type: {}'.format((type(init_embed)))) + return res + - :param seq_len: list or torch.Tensor, the lengths of sequences in a batch. - :param max_len: int, the maximum sequence length in a batch. - :return: mask, torch.LongTensor, [batch_size, max_len] +def summary(model: nn.Module): + """ + 得到模型的总参数量 + :params model: Pytorch 模型 + :return tuple: 包含总参数量,可训练参数量,不可训练参数量 """ - if not isinstance(seq_len, torch.Tensor): - seq_len = torch.LongTensor(seq_len) - seq_len = seq_len.view(-1, 1).long() # [batch_size, 1] - seq_range = torch.arange(start=0, end=max_len, dtype=torch.long, device=seq_len.device).view(1, -1) # [1, max_len] - return torch.gt(seq_len, seq_range) # [batch_size, max_len] + train = [] + nontrain = [] + + def layer_summary(module: nn.Module): + def count_size(sizes): + return reduce(lambda x, y: x*y, sizes) + + for p in module.parameters(recurse=False): + if p.requires_grad: + train.append(count_size(p.shape)) + else: + nontrain.append(count_size(p.shape)) + for subm in module.children(): + layer_summary(subm) + + layer_summary(model) + total_train = sum(train) + total_nontrain = sum(nontrain) + total = total_train + total_nontrain + strings = [] + strings.append('Total params: {:,}'.format(total)) + strings.append('Trainable params: {:,}'.format(total_train)) + strings.append('Non-trainable params: {:,}'.format(total_nontrain)) + max_len = len(max(strings, key=len)) + bar = '-'*(max_len + 3) + strings = [bar] + strings + [bar] + print('\n'.join(strings)) + return total, total_train, total_nontrain diff --git a/fastNLP/api/README.md b/legacy/api/README.md similarity index 100% rename from fastNLP/api/README.md rename to legacy/api/README.md diff --git a/legacy/api/__init__.py b/legacy/api/__init__.py new file mode 100644 index 00000000..5171d8c2 --- /dev/null +++ b/legacy/api/__init__.py @@ -0,0 +1,2 @@ +__all__ = ["CWS", "POS", "Parser"] +from .api import CWS, POS, Parser diff --git a/fastNLP/api/api.py b/legacy/api/api.py similarity index 75% rename from fastNLP/api/api.py rename to legacy/api/api.py index 53a80131..d5d1df6b 100644 --- a/fastNLP/api/api.py +++ b/legacy/api/api.py @@ -6,14 +6,13 @@ warnings.filterwarnings('ignore') import os from fastNLP.core.dataset import DataSet - -from fastNLP.api.utils import load_url -from fastNLP.api.processor import ModelProcessor -from fastNLP.io.dataset_loader import ConllCWSReader, ConllxDataLoader +from .utils import load_url +from .processor import ModelProcessor +from fastNLP.io.dataset_loader import _cut_long_sentence, ConllLoader from fastNLP.core.instance import Instance -from fastNLP.api.pipeline import Pipeline +from ..api.pipeline import Pipeline from fastNLP.core.metrics import SpanFPreRecMetric -from fastNLP.api.processor import IndexerProcessor +from .processor import IndexerProcessor # TODO add pretrain urls model_urls = { @@ -23,16 +22,98 @@ model_urls = { } +class ConllCWSReader(object): + """Deprecated. Use ConllLoader for all types of conll-format files.""" + + def __init__(self): + pass + + def load(self, path, cut_long_sent=False): + """ + 返回的DataSet只包含raw_sentence这个field,内容为str。 + 假定了输入为conll的格式,以空行隔开两个句子,每行共7列,即 + :: + + 1 编者按 编者按 NN O 11 nmod:topic + 2 : : PU O 11 punct + 3 7月 7月 NT DATE 4 compound:nn + 4 12日 12日 NT DATE 11 nmod:tmod + 5 , , PU O 11 punct + + 1 这 这 DT O 3 det + 2 款 款 M O 1 mark:clf + 3 飞行 飞行 NN O 8 nsubj + 4 从 从 P O 5 case + 5 外型 外型 NN O 8 nmod:prep + + """ + datalist = [] + with open(path, 'r', encoding='utf-8') as f: + sample = [] + for line in f: + if line.startswith('\n'): + datalist.append(sample) + sample = [] + elif line.startswith('#'): + continue + else: + sample.append(line.strip().split()) + if len(sample) > 0: + datalist.append(sample) + + ds = DataSet() + for sample in datalist: + # print(sample) + res = self.get_char_lst(sample) + if res is None: + continue + line = ' '.join(res) + if cut_long_sent: + sents = _cut_long_sentence(line) + else: + sents = [line] + for raw_sentence in sents: + ds.append(Instance(raw_sentence=raw_sentence)) + return ds + + def get_char_lst(self, sample): + if len(sample) == 0: + return None + text = [] + for w in sample: + t1, t2, t3, t4 = w[1], w[3], w[6], w[7] + if t3 == '_': + return None + text.append(t1) + return text + + +class ConllxDataLoader(ConllLoader): + """返回“词级别”的标签信息,包括词、词性、(句法)头依赖、(句法)边标签。跟``ZhConllPOSReader``完全不同。 + + Deprecated. Use ConllLoader for all types of conll-format files. + """ + + def __init__(self): + headers = [ + 'words', 'pos_tags', 'heads', 'labels', + ] + indexs = [ + 1, 3, 6, 7, + ] + super(ConllxDataLoader, self).__init__(headers=headers, indexes=indexs) + + class API: def __init__(self): self.pipeline = None self._dict = None - + def predict(self, *args, **kwargs): """Do prediction for the given input. """ raise NotImplementedError - + def test(self, file_path): """Test performance over the given data set. @@ -40,7 +121,7 @@ class API: :return: a dictionary of metric values """ raise NotImplementedError - + def load(self, path, device): if os.path.exists(os.path.expanduser(path)): _dict = torch.load(path, map_location='cpu') @@ -60,95 +141,98 @@ class POS(API): :param str device: device name such as "cpu" or "cuda:0". Use the same notation as PyTorch. """ - + def __init__(self, model_path=None, device='cpu'): super(POS, self).__init__() if model_path is None: model_path = model_urls['pos'] - + self.load(model_path, device) - + def predict(self, content): - """ - + """predict函数的介绍, + 函数介绍的第二句,这句话不会换行 + :param content: list of list of str. Each string is a token(word). :return answer: list of list of str. Each string is a tag. """ if not hasattr(self, "pipeline"): raise ValueError("You have to load model first.") - + sentence_list = content # 1. 检查sentence的类型 for sentence in sentence_list: if not all((type(obj) == str for obj in sentence)): raise ValueError("Input must be list of list of string.") - + # 2. 组建dataset dataset = DataSet() dataset.add_field("words", sentence_list) - + # 3. 使用pipeline self.pipeline(dataset) - + def merge_tag(words_list, tags_list): rtn = [] for words, tags in zip(words_list, tags_list): rtn.append([w + "/" + t for w, t in zip(words, tags)]) return rtn - + output = dataset.field_arrays["tag"].content if isinstance(content, str): return output[0] elif isinstance(content, list): return merge_tag(content, output) - + def test(self, file_path): test_data = ConllxDataLoader().load(file_path) - + save_dict = self._dict tag_vocab = save_dict["tag_vocab"] pipeline = save_dict["pipeline"] index_tag = IndexerProcessor(vocab=tag_vocab, field_name="tag", new_added_field_name="truth", is_input=False) pipeline.pipeline = [index_tag] + pipeline.pipeline - + test_data.rename_field("pos_tags", "tag") pipeline(test_data) test_data.set_target("truth") prediction = test_data.field_arrays["predict"].content truth = test_data.field_arrays["truth"].content seq_len = test_data.field_arrays["word_seq_origin_len"].content - + # padding by hand max_length = max([len(seq) for seq in prediction]) for idx in range(len(prediction)): prediction[idx] = list(prediction[idx]) + ([0] * (max_length - len(prediction[idx]))) truth[idx] = list(truth[idx]) + ([0] * (max_length - len(truth[idx]))) evaluator = SpanFPreRecMetric(tag_vocab=tag_vocab, pred="predict", target="truth", - seq_lens="word_seq_origin_len") + seq_len="word_seq_origin_len") evaluator({"predict": torch.Tensor(prediction), "word_seq_origin_len": torch.Tensor(seq_len)}, {"truth": torch.Tensor(truth)}) test_result = evaluator.get_metric() f1 = round(test_result['f'] * 100, 2) pre = round(test_result['pre'] * 100, 2) rec = round(test_result['rec'] * 100, 2) - + return {"F1": f1, "precision": pre, "recall": rec} class CWS(API): - def __init__(self, model_path=None, device='cpu'): - """ - 中文分词高级接口。 + """ + 中文分词高级接口。 - :param model_path: 当model_path为None,使用默认位置的model。如果默认位置不存在,则自动下载模型 - :param device: str,可以为'cpu', 'cuda'或'cuda:0'等。会将模型load到相应device进行推断。 - """ + :param model_path: 当model_path为None,使用默认位置的model。如果默认位置不存在,则自动下载模型 + :param device: str,可以为'cpu', 'cuda'或'cuda:0'等。会将模型load到相应device进行推断。 + """ + + def __init__(self, model_path=None, device='cpu'): + super(CWS, self).__init__() if model_path is None: model_path = model_urls['cws'] - + self.load(model_path, device) - + def predict(self, content): """ 分词接口。 @@ -159,42 +243,44 @@ class CWS(API): """ if not hasattr(self, 'pipeline'): raise ValueError("You have to load model first.") - + sentence_list = [] # 1. 检查sentence的类型 if isinstance(content, str): sentence_list.append(content) elif isinstance(content, list): sentence_list = content - + # 2. 组建dataset dataset = DataSet() dataset.add_field('raw_sentence', sentence_list) - + # 3. 使用pipeline self.pipeline(dataset) - + output = dataset.get_field('output').content if isinstance(content, str): return output[0] elif isinstance(content, list): return output - + def test(self, filepath): """ 传入一个分词文件路径,返回该数据集上分词f1, precision, recall。 - 分词文件应该为: + 分词文件应该为:: + 1 编者按 编者按 NN O 11 nmod:topic 2 : : PU O 11 punct 3 7月 7月 NT DATE 4 compound:nn 4 12日 12日 NT DATE 11 nmod:tmod 5 , , PU O 11 punct - + 1 这 这 DT O 3 det 2 款 款 M O 1 mark:clf 3 飞行 飞行 NN O 8 nsubj 4 从 从 P O 5 case 5 外型 外型 NN O 8 nmod:prep + 以空行分割两个句子,有内容的每行有7列。 :param filepath: str, 文件路径路径。 @@ -203,28 +289,28 @@ class CWS(API): tag_proc = self._dict['tag_proc'] cws_model = self.pipeline.pipeline[-2].model pipeline = self.pipeline.pipeline[:-2] - + pipeline.insert(1, tag_proc) pp = Pipeline(pipeline) - + reader = ConllCWSReader() - + # te_filename = '/home/hyan/ctb3/test.conllx' te_dataset = reader.load(filepath) pp(te_dataset) - - from fastNLP.core.tester import Tester - from fastNLP.core.metrics import BMESF1PreRecMetric - - tester = Tester(data=te_dataset, model=cws_model, metrics=BMESF1PreRecMetric(target='target'), batch_size=64, + + from ..core.tester import Tester + from ..core.metrics import SpanFPreRecMetric + + tester = Tester(data=te_dataset, model=cws_model, metrics=SpanFPreRecMetric(tag_proc.get_vocab()), batch_size=64, verbose=0) eval_res = tester.test() - - f1 = eval_res['BMESF1PreRecMetric']['f'] - pre = eval_res['BMESF1PreRecMetric']['pre'] - rec = eval_res['BMESF1PreRecMetric']['rec'] + + f1 = eval_res['SpanFPreRecMetric']['f'] + pre = eval_res['SpanFPreRecMetric']['pre'] + rec = eval_res['SpanFPreRecMetric']['rec'] # print("f1:{:.2f}, pre:{:.2f}, rec:{:.2f}".format(f1, pre, rec)) - + return {"F1": f1, "precision": pre, "recall": rec} @@ -233,25 +319,25 @@ class Parser(API): super(Parser, self).__init__() if model_path is None: model_path = model_urls['parser'] - + self.pos_tagger = POS(device=device) self.load(model_path, device) - + def predict(self, content): if not hasattr(self, 'pipeline'): raise ValueError("You have to load model first.") - + # 1. 利用POS得到分词和pos tagging结果 pos_out = self.pos_tagger.predict(content) # pos_out = ['这里/NN 是/VB 分词/NN 结果/NN'.split()] - + # 2. 组建dataset dataset = DataSet() dataset.add_field('wp', pos_out) dataset.apply(lambda x: [''] + [w.split('/')[0] for w in x['wp']], new_field_name='words') dataset.apply(lambda x: [''] + [w.split('/')[1] for w in x['wp']], new_field_name='pos') dataset.rename_field("words", "raw_words") - + # 3. 使用pipeline self.pipeline(dataset) dataset.apply(lambda x: [str(arc) for arc in x['arc_pred']], new_field_name='arc_pred') @@ -259,7 +345,7 @@ class Parser(API): zip(x['arc_pred'], x['label_pred_seq'])][1:], new_field_name='output') # output like: [['2/top', '0/root', '4/nn', '2/dep']] return dataset.field_arrays['output'].content - + def load_test_file(self, path): def get_one(sample): sample = list(map(list, zip(*sample))) @@ -271,7 +357,7 @@ class Parser(API): return None # return word_seq, pos_seq, head_seq, head_tag_seq return sample[1], sample[3], list(map(int, sample[6])), sample[7] - + datalist = [] with open(path, 'r', encoding='utf-8') as f: sample = [] @@ -285,14 +371,14 @@ class Parser(API): sample.append(line.split('\t')) if len(sample) > 0: datalist.append(sample) - + data = [get_one(sample) for sample in datalist] data_list = list(filter(lambda x: x is not None, data)) return data_list - + def test(self, filepath): data = self.load_test_file(filepath) - + def convert(data): BOS = '' dataset = DataSet() @@ -307,7 +393,7 @@ class Parser(API): arc_true=heads, tags=head_tags)) return dataset - + ds = convert(data) pp = self.pipeline for p in pp: @@ -328,23 +414,23 @@ class Parser(API): head_cor += 1 if head_pred[i] == head_gold[i] else 0 uas = head_cor / total # print('uas:{:.2f}'.format(uas)) - + for p in pp: if p.field_name == 'gold_words': p.field_name = 'word_list' elif p.field_name == 'gold_pos': p.field_name = 'pos_list' - + return {"USA": round(uas, 5)} class Analyzer: def __init__(self, device='cpu'): - + self.cws = CWS(device=device) self.pos = POS(device=device) self.parser = Parser(device=device) - + def predict(self, content, seg=False, pos=False, parser=False): if seg is False and pos is False and parser is False: seg = True @@ -358,9 +444,9 @@ class Analyzer: if parser: parser_output = self.parser.predict(content) output_dict['parser'] = parser_output - + return output_dict - + def test(self, filepath): output_dict = {} if self.cws: @@ -372,5 +458,5 @@ class Analyzer: if self.parser: parser_output = self.parser.test(filepath) output_dict['parser'] = parser_output - + return output_dict diff --git a/fastNLP/api/converter.py b/legacy/api/converter.py similarity index 100% rename from fastNLP/api/converter.py rename to legacy/api/converter.py diff --git a/fastNLP/api/examples.py b/legacy/api/examples.py similarity index 98% rename from fastNLP/api/examples.py rename to legacy/api/examples.py index a85e7c30..c1b2e155 100644 --- a/fastNLP/api/examples.py +++ b/legacy/api/examples.py @@ -3,7 +3,7 @@ api/example.py contains all API examples provided by fastNLP. It is used as a tutorial for API or a test script since it is difficult to test APIs in travis. """ -from fastNLP.api import CWS, POS, Parser +from . import CWS, POS, Parser text = ['编者按:7月12日,英国航空航天系统公司公布了该公司研制的第一款高科技隐形无人机雷电之神。', '这款飞行从外型上来看酷似电影中的太空飞行器,据英国方面介绍,可以实现洲际远程打击。', diff --git a/fastNLP/api/pipeline.py b/legacy/api/pipeline.py similarity index 95% rename from fastNLP/api/pipeline.py rename to legacy/api/pipeline.py index 0c567678..2cec16b3 100644 --- a/fastNLP/api/pipeline.py +++ b/legacy/api/pipeline.py @@ -1,4 +1,4 @@ -from fastNLP.api.processor import Processor +from ..api.processor import Processor class Pipeline: diff --git a/fastNLP/api/processor.py b/legacy/api/processor.py similarity index 99% rename from fastNLP/api/processor.py rename to legacy/api/processor.py index 0bba96c0..4c442ed2 100644 --- a/fastNLP/api/processor.py +++ b/legacy/api/processor.py @@ -257,10 +257,7 @@ class ModelProcessor(Processor): data_iterator = Batch(dataset, batch_size=self.batch_size, sampler=SequentialSampler()) batch_output = defaultdict(list) - if hasattr(self.model, "predict"): - predict_func = self.model.predict - else: - predict_func = self.model.forward + predict_func = self.model.forward with torch.no_grad(): for batch_x, _ in data_iterator: refined_batch_x = _build_args(predict_func, **batch_x) diff --git a/fastNLP/api/utils.py b/legacy/api/utils.py similarity index 97% rename from fastNLP/api/utils.py rename to legacy/api/utils.py index a54a53d9..184e5fe6 100644 --- a/fastNLP/api/utils.py +++ b/legacy/api/utils.py @@ -20,10 +20,10 @@ except ImportError: from urllib.request import urlopen from urllib.parse import urlparse try: - from tqdm import tqdm -except ImportError: - tqdm = None # defined below - + from tqdm.auto import tqdm +except: + from fastNLP.core.utils import _pseudo_tqdm as tqdm + # matches bfd8deac from resnet18-bfd8deac.pth HASH_REGEX = re.compile(r'-([a-f0-9]*)\.') diff --git a/legacy/automl/__init__.py b/legacy/automl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/legacy/automl/enas_controller.py b/legacy/automl/enas_controller.py new file mode 100644 index 00000000..6ddbb211 --- /dev/null +++ b/legacy/automl/enas_controller.py @@ -0,0 +1,223 @@ +# Code Modified from https://github.com/carpedm20/ENAS-pytorch +"""A module with NAS controller-related code.""" +import collections +import os + +import torch +import torch.nn.functional as F + +import fastNLP.automl.enas_utils as utils +from fastNLP.automl.enas_utils import Node + + +def _construct_dags(prev_nodes, activations, func_names, num_blocks): + """Constructs a set of DAGs based on the actions, i.e., previous nodes and + activation functions, sampled from the controller/policy pi. + + Args: + prev_nodes: Previous node actions from the policy. + activations: Activations sampled from the policy. + func_names: Mapping from activation function names to functions. + num_blocks: Number of blocks in the target RNN cell. + + Returns: + A list of DAGs defined by the inputs. + + RNN cell DAGs are represented in the following way: + + 1. Each element (node) in a DAG is a list of `Node`s. + + 2. The `Node`s in the list dag[i] correspond to the subsequent nodes + that take the output from node i as their own input. + + 3. dag[-1] is the node that takes input from x^{(t)} and h^{(t - 1)}. + dag[-1] always feeds dag[0]. + dag[-1] acts as if `w_xc`, `w_hc`, `w_xh` and `w_hh` are its + weights. + + 4. dag[N - 1] is the node that produces the hidden state passed to + the next timestep. dag[N - 1] is also always a leaf node, and therefore + is always averaged with the other leaf nodes and fed to the output + decoder. + """ + dags = [] + for nodes, func_ids in zip(prev_nodes, activations): + dag = collections.defaultdict(list) + + # add first node + dag[-1] = [Node(0, func_names[func_ids[0]])] + dag[-2] = [Node(0, func_names[func_ids[0]])] + + # add following nodes + for jdx, (idx, func_id) in enumerate(zip(nodes, func_ids[1:])): + dag[utils.to_item(idx)].append(Node(jdx + 1, func_names[func_id])) + + leaf_nodes = set(range(num_blocks)) - dag.keys() + + # merge with avg + for idx in leaf_nodes: + dag[idx] = [Node(num_blocks, 'avg')] + + # This is actually y^{(t)}. h^{(t)} is node N - 1 in + # the graph, where N Is the number of nodes. I.e., h^{(t)} takes + # only one other node as its input. + # last h[t] node + last_node = Node(num_blocks + 1, 'h[t]') + dag[num_blocks] = [last_node] + dags.append(dag) + + return dags + + +class Controller(torch.nn.Module): + """Based on + https://github.com/pytorch/examples/blob/master/word_language_model/model.py + + RL controllers do not necessarily have much to do with + language models. + + Base the controller RNN on the GRU from: + https://github.com/ikostrikov/pytorch-a2c-ppo-acktr/blob/master/model.py + """ + def __init__(self, num_blocks=4, controller_hid=100, cuda=False): + torch.nn.Module.__init__(self) + + # `num_tokens` here is just the activation function + # for every even step, + self.shared_rnn_activations = ['tanh', 'ReLU', 'identity', 'sigmoid'] + self.num_tokens = [len(self.shared_rnn_activations)] + self.controller_hid = controller_hid + self.use_cuda = cuda + self.num_blocks = num_blocks + for idx in range(num_blocks): + self.num_tokens += [idx + 1, len(self.shared_rnn_activations)] + self.func_names = self.shared_rnn_activations + + num_total_tokens = sum(self.num_tokens) + + self.encoder = torch.nn.Embedding(num_total_tokens, + controller_hid) + self.lstm = torch.nn.LSTMCell(controller_hid, controller_hid) + + # Perhaps these weights in the decoder should be + # shared? At least for the activation functions, which all have the + # same size. + self.decoders = [] + for idx, size in enumerate(self.num_tokens): + decoder = torch.nn.Linear(controller_hid, size) + self.decoders.append(decoder) + + self._decoders = torch.nn.ModuleList(self.decoders) + + self.reset_parameters() + self.static_init_hidden = utils.keydefaultdict(self.init_hidden) + + def _get_default_hidden(key): + return utils.get_variable( + torch.zeros(key, self.controller_hid), + self.use_cuda, + requires_grad=False) + + self.static_inputs = utils.keydefaultdict(_get_default_hidden) + + def reset_parameters(self): + init_range = 0.1 + for param in self.parameters(): + param.data.uniform_(-init_range, init_range) + for decoder in self.decoders: + decoder.bias.data.fill_(0) + + def forward(self, # pylint:disable=arguments-differ + inputs, + hidden, + block_idx, + is_embed): + if not is_embed: + embed = self.encoder(inputs) + else: + embed = inputs + + hx, cx = self.lstm(embed, hidden) + logits = self.decoders[block_idx](hx) + + logits /= 5.0 + + # # exploration + # if self.args.mode == 'train': + # logits = (2.5 * F.tanh(logits)) + + return logits, (hx, cx) + + def sample(self, batch_size=1, with_details=False, save_dir=None): + """Samples a set of `args.num_blocks` many computational nodes from the + controller, where each node is made up of an activation function, and + each node except the last also includes a previous node. + """ + if batch_size < 1: + raise Exception(f'Wrong batch_size: {batch_size} < 1') + + # [B, L, H] + inputs = self.static_inputs[batch_size] + hidden = self.static_init_hidden[batch_size] + + activations = [] + entropies = [] + log_probs = [] + prev_nodes = [] + # The RNN controller alternately outputs an activation, + # followed by a previous node, for each block except the last one, + # which only gets an activation function. The last node is the output + # node, and its previous node is the average of all leaf nodes. + for block_idx in range(2*(self.num_blocks - 1) + 1): + logits, hidden = self.forward(inputs, + hidden, + block_idx, + is_embed=(block_idx == 0)) + + probs = F.softmax(logits, dim=-1) + log_prob = F.log_softmax(logits, dim=-1) + # .mean() for entropy? + entropy = -(log_prob * probs).sum(1, keepdim=False) + + action = probs.multinomial(num_samples=1).data + selected_log_prob = log_prob.gather( + 1, utils.get_variable(action, requires_grad=False)) + + # why the [:, 0] here? Should it be .squeeze(), or + # .view()? Same below with `action`. + entropies.append(entropy) + log_probs.append(selected_log_prob[:, 0]) + + # 0: function, 1: previous node + mode = block_idx % 2 + inputs = utils.get_variable( + action[:, 0] + sum(self.num_tokens[:mode]), + requires_grad=False) + + if mode == 0: + activations.append(action[:, 0]) + elif mode == 1: + prev_nodes.append(action[:, 0]) + + prev_nodes = torch.stack(prev_nodes).transpose(0, 1) + activations = torch.stack(activations).transpose(0, 1) + + dags = _construct_dags(prev_nodes, + activations, + self.func_names, + self.num_blocks) + + if save_dir is not None: + for idx, dag in enumerate(dags): + utils.draw_network(dag, + os.path.join(save_dir, f'graph{idx}.png')) + + if with_details: + return dags, torch.cat(log_probs), torch.cat(entropies) + + return dags + + def init_hidden(self, batch_size): + zeros = torch.zeros(batch_size, self.controller_hid) + return (utils.get_variable(zeros, self.use_cuda, requires_grad=False), + utils.get_variable(zeros.clone(), self.use_cuda, requires_grad=False)) diff --git a/legacy/automl/enas_model.py b/legacy/automl/enas_model.py new file mode 100644 index 00000000..4f9fb449 --- /dev/null +++ b/legacy/automl/enas_model.py @@ -0,0 +1,388 @@ +# Code Modified from https://github.com/carpedm20/ENAS-pytorch + +"""Module containing the shared RNN model.""" +import collections + +import numpy as np +import torch +import torch.nn.functional as F +from torch import nn +from torch.autograd import Variable + +import fastNLP.automl.enas_utils as utils +from fastNLP.models.base_model import BaseModel + + +def _get_dropped_weights(w_raw, dropout_p, is_training): + """Drops out weights to implement DropConnect. + + Args: + w_raw: Full, pre-dropout, weights to be dropped out. + dropout_p: Proportion of weights to drop out. + is_training: True iff _shared_ model is training. + + Returns: + The dropped weights. + + Why does torch.nn.functional.dropout() return: + 1. `torch.autograd.Variable()` on the training loop + 2. `torch.nn.Parameter()` on the controller or eval loop, when + training = False... + + Even though the call to `_setweights` in the Smerity repo's + `weight_drop.py` does not have this behaviour, and `F.dropout` always + returns `torch.autograd.Variable` there, even when `training=False`? + + The above TODO is the reason for the hacky check for `torch.nn.Parameter`. + """ + dropped_w = F.dropout(w_raw, p=dropout_p, training=is_training) + + if isinstance(dropped_w, torch.nn.Parameter): + dropped_w = dropped_w.clone() + + return dropped_w + +class EmbeddingDropout(torch.nn.Embedding): + """Class for dropping out embeddings by zero'ing out parameters in the + embedding matrix. + + This is equivalent to dropping out particular words, e.g., in the sentence + 'the quick brown fox jumps over the lazy dog', dropping out 'the' would + lead to the sentence '### quick brown fox jumps over ### lazy dog' (in the + embedding vector space). + + See 'A Theoretically Grounded Application of Dropout in Recurrent Neural + Networks', (Gal and Ghahramani, 2016). + """ + def __init__(self, + num_embeddings, + embedding_dim, + max_norm=None, + norm_type=2, + scale_grad_by_freq=False, + sparse=False, + dropout=0.1, + scale=None): + """Embedding constructor. + + Args: + dropout: Dropout probability. + scale: Used to scale parameters of embedding weight matrix that are + not dropped out. Note that this is _in addition_ to the + `1/(1 - dropout)` scaling. + + See `torch.nn.Embedding` for remaining arguments. + """ + torch.nn.Embedding.__init__(self, + num_embeddings=num_embeddings, + embedding_dim=embedding_dim, + max_norm=max_norm, + norm_type=norm_type, + scale_grad_by_freq=scale_grad_by_freq, + sparse=sparse) + self.dropout = dropout + assert (dropout >= 0.0) and (dropout < 1.0), ('Dropout must be >= 0.0 ' + 'and < 1.0') + self.scale = scale + + def forward(self, inputs): # pylint:disable=arguments-differ + """Embeds `inputs` with the dropped out embedding weight matrix.""" + if self.training: + dropout = self.dropout + else: + dropout = 0 + + if dropout: + mask = self.weight.data.new(self.weight.size(0), 1) + mask.bernoulli_(1 - dropout) + mask = mask.expand_as(self.weight) + mask = mask / (1 - dropout) + masked_weight = self.weight * Variable(mask) + else: + masked_weight = self.weight + if self.scale and self.scale != 1: + masked_weight = masked_weight * self.scale + + return F.embedding(inputs, + masked_weight, + max_norm=self.max_norm, + norm_type=self.norm_type, + scale_grad_by_freq=self.scale_grad_by_freq, + sparse=self.sparse) + + +class LockedDropout(nn.Module): + # code from https://github.com/salesforce/awd-lstm-lm/blob/master/locked_dropout.py + def __init__(self): + super().__init__() + + def forward(self, x, dropout=0.5): + if not self.training or not dropout: + return x + m = x.data.new(1, x.size(1), x.size(2)).bernoulli_(1 - dropout) + mask = Variable(m, requires_grad=False) / (1 - dropout) + mask = mask.expand_as(x) + return mask * x + + +class ENASModel(BaseModel): + """Shared RNN model.""" + def __init__(self, embed_num, num_classes, num_blocks=4, cuda=False, shared_hid=1000, shared_embed=1000): + super(ENASModel, self).__init__() + + self.use_cuda = cuda + + self.shared_hid = shared_hid + self.num_blocks = num_blocks + self.decoder = nn.Linear(self.shared_hid, num_classes) + self.encoder = EmbeddingDropout(embed_num, + shared_embed, + dropout=0.1) + self.lockdrop = LockedDropout() + self.dag = None + + # Tie weights + # self.decoder.weight = self.encoder.weight + + # Since W^{x, c} and W^{h, c} are always summed, there + # is no point duplicating their bias offset parameter. Likewise for + # W^{x, h} and W^{h, h}. + self.w_xc = nn.Linear(shared_embed, self.shared_hid) + self.w_xh = nn.Linear(shared_embed, self.shared_hid) + + # The raw weights are stored here because the hidden-to-hidden weights + # are weight dropped on the forward pass. + self.w_hc_raw = torch.nn.Parameter( + torch.Tensor(self.shared_hid, self.shared_hid)) + self.w_hh_raw = torch.nn.Parameter( + torch.Tensor(self.shared_hid, self.shared_hid)) + self.w_hc = None + self.w_hh = None + + self.w_h = collections.defaultdict(dict) + self.w_c = collections.defaultdict(dict) + + for idx in range(self.num_blocks): + for jdx in range(idx + 1, self.num_blocks): + self.w_h[idx][jdx] = nn.Linear(self.shared_hid, + self.shared_hid, + bias=False) + self.w_c[idx][jdx] = nn.Linear(self.shared_hid, + self.shared_hid, + bias=False) + + self._w_h = nn.ModuleList([self.w_h[idx][jdx] + for idx in self.w_h + for jdx in self.w_h[idx]]) + self._w_c = nn.ModuleList([self.w_c[idx][jdx] + for idx in self.w_c + for jdx in self.w_c[idx]]) + + self.batch_norm = None + # if args.mode == 'train': + # self.batch_norm = nn.BatchNorm1d(self.shared_hid) + # else: + # self.batch_norm = None + + self.reset_parameters() + self.static_init_hidden = utils.keydefaultdict(self.init_hidden) + + def setDAG(self, dag): + if self.dag is None: + self.dag = dag + + def forward(self, word_seq, hidden=None): + inputs = torch.transpose(word_seq, 0, 1) + + time_steps = inputs.size(0) + batch_size = inputs.size(1) + + + self.w_hh = _get_dropped_weights(self.w_hh_raw, + 0.5, + self.training) + self.w_hc = _get_dropped_weights(self.w_hc_raw, + 0.5, + self.training) + + # hidden = self.static_init_hidden[batch_size] if hidden is None else hidden + hidden = self.static_init_hidden[batch_size] + + embed = self.encoder(inputs) + + embed = self.lockdrop(embed, 0.65 if self.training else 0) + + # The norm of hidden states are clipped here because + # otherwise ENAS is especially prone to exploding activations on the + # forward pass. This could probably be fixed in a more elegant way, but + # it might be exposing a weakness in the ENAS algorithm as currently + # proposed. + # + # For more details, see + # https://github.com/carpedm20/ENAS-pytorch/issues/6 + clipped_num = 0 + max_clipped_norm = 0 + h1tohT = [] + logits = [] + for step in range(time_steps): + x_t = embed[step] + logit, hidden = self.cell(x_t, hidden, self.dag) + + hidden_norms = hidden.norm(dim=-1) + max_norm = 25.0 + if hidden_norms.data.max() > max_norm: + # Just directly use the torch slice operations + # in PyTorch v0.4. + # + # This workaround for PyTorch v0.3.1 does everything in numpy, + # because the PyTorch slicing and slice assignment is too + # flaky. + hidden_norms = hidden_norms.data.cpu().numpy() + + clipped_num += 1 + if hidden_norms.max() > max_clipped_norm: + max_clipped_norm = hidden_norms.max() + + clip_select = hidden_norms > max_norm + clip_norms = hidden_norms[clip_select] + + mask = np.ones(hidden.size()) + normalizer = max_norm/clip_norms + normalizer = normalizer[:, np.newaxis] + + mask[clip_select] = normalizer + + if self.use_cuda: + hidden *= torch.autograd.Variable( + torch.FloatTensor(mask).cuda(), requires_grad=False) + else: + hidden *= torch.autograd.Variable( + torch.FloatTensor(mask), requires_grad=False) + logits.append(logit) + h1tohT.append(hidden) + + h1tohT = torch.stack(h1tohT) + output = torch.stack(logits) + raw_output = output + + output = self.lockdrop(output, 0.4 if self.training else 0) + + #Pooling + output = torch.mean(output, 0) + + decoded = self.decoder(output) + + extra_out = {'dropped': decoded, + 'hiddens': h1tohT, + 'raw': raw_output} + return {'pred': decoded, 'hidden': hidden, 'extra_out': extra_out} + + def cell(self, x, h_prev, dag): + """Computes a single pass through the discovered RNN cell.""" + c = {} + h = {} + f = {} + + f[0] = self.get_f(dag[-1][0].name) + c[0] = torch.sigmoid(self.w_xc(x) + F.linear(h_prev, self.w_hc, None)) + h[0] = (c[0]*f[0](self.w_xh(x) + F.linear(h_prev, self.w_hh, None)) + + (1 - c[0])*h_prev) + + leaf_node_ids = [] + q = collections.deque() + q.append(0) + + # Computes connections from the parent nodes `node_id` + # to their child nodes `next_id` recursively, skipping leaf nodes. A + # leaf node is a node whose id == `self.num_blocks`. + # + # Connections between parent i and child j should be computed as + # h_j = c_j*f_{ij}{(W^h_{ij}*h_i)} + (1 - c_j)*h_i, + # where c_j = \sigmoid{(W^c_{ij}*h_i)} + # + # See Training details from Section 3.1 of the paper. + # + # The following algorithm does a breadth-first (since `q.popleft()` is + # used) search over the nodes and computes all the hidden states. + while True: + if len(q) == 0: + break + + node_id = q.popleft() + nodes = dag[node_id] + + for next_node in nodes: + next_id = next_node.id + if next_id == self.num_blocks: + leaf_node_ids.append(node_id) + assert len(nodes) == 1, ('parent of leaf node should have ' + 'only one child') + continue + + w_h = self.w_h[node_id][next_id] + w_c = self.w_c[node_id][next_id] + + f[next_id] = self.get_f(next_node.name) + c[next_id] = torch.sigmoid(w_c(h[node_id])) + h[next_id] = (c[next_id]*f[next_id](w_h(h[node_id])) + + (1 - c[next_id])*h[node_id]) + + q.append(next_id) + + # Instead of averaging loose ends, perhaps there should + # be a set of separate unshared weights for each "loose" connection + # between each node in a cell and the output. + # + # As it stands, all weights W^h_{ij} are doing double duty by + # connecting both from i to j, as well as from i to the output. + + # average all the loose ends + leaf_nodes = [h[node_id] for node_id in leaf_node_ids] + output = torch.mean(torch.stack(leaf_nodes, 2), -1) + + # stabilizing the Updates of omega + if self.batch_norm is not None: + output = self.batch_norm(output) + + return output, h[self.num_blocks - 1] + + def init_hidden(self, batch_size): + zeros = torch.zeros(batch_size, self.shared_hid) + return utils.get_variable(zeros, self.use_cuda, requires_grad=False) + + def get_f(self, name): + name = name.lower() + if name == 'relu': + f = torch.relu + elif name == 'tanh': + f = torch.tanh + elif name == 'identity': + f = lambda x: x + elif name == 'sigmoid': + f = torch.sigmoid + return f + + + @property + def num_parameters(self): + def size(p): + return np.prod(p.size()) + return sum([size(param) for param in self.parameters()]) + + + def reset_parameters(self): + init_range = 0.025 + # init_range = 0.025 if self.args.mode == 'train' else 0.04 + for param in self.parameters(): + param.data.uniform_(-init_range, init_range) + self.decoder.bias.data.fill_(0) + + def predict(self, word_seq): + """ + + :param word_seq: torch.LongTensor, [batch_size, seq_len] + :return predict: dict of torch.LongTensor, [batch_size, seq_len] + """ + output = self(word_seq) + _, predict = output['pred'].max(dim=1) + return {'pred': predict} diff --git a/legacy/automl/enas_trainer.py b/legacy/automl/enas_trainer.py new file mode 100644 index 00000000..e3524aa9 --- /dev/null +++ b/legacy/automl/enas_trainer.py @@ -0,0 +1,383 @@ +# Code Modified from https://github.com/carpedm20/ENAS-pytorch + +import math +import time +from datetime import datetime +from datetime import timedelta + +import numpy as np +import torch + +try: + from tqdm.auto import tqdm +except: + from fastNLP.core.utils import _pseudo_tqdm as tqdm + +from fastNLP.core.batch import Batch +from fastNLP.core.callback import CallbackException +from fastNLP.core.dataset import DataSet +from fastNLP.core.utils import _move_dict_value_to_device +import fastNLP +from . import enas_utils as utils +from fastNLP.core.utils import _build_args + +from torch.optim import Adam + + +def _get_no_grad_ctx_mgr(): + """Returns a the `torch.no_grad` context manager for PyTorch version >= + 0.4, or a no-op context manager otherwise. + """ + return torch.no_grad() + + +class ENASTrainer(fastNLP.Trainer): + """A class to wrap training code.""" + def __init__(self, train_data, model, controller, **kwargs): + """Constructor for training algorithm. + :param DataSet train_data: the training data + :param torch.nn.modules.module model: a PyTorch model + :param torch.nn.modules.module controller: a PyTorch model + """ + self.final_epochs = kwargs['final_epochs'] + kwargs.pop('final_epochs') + super(ENASTrainer, self).__init__(train_data, model, **kwargs) + self.controller_step = 0 + self.shared_step = 0 + self.max_length = 35 + + self.shared = model + self.controller = controller + + self.shared_optim = Adam( + self.shared.parameters(), + lr=20.0, + weight_decay=1e-7) + + self.controller_optim = Adam( + self.controller.parameters(), + lr=3.5e-4) + + def train(self, load_best_model=True): + """ + :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效,如果True, trainer将在返回之前重新加载dev表现 + 最好的模型参数。 + :return results: 返回一个字典类型的数据, + 内含以下内容:: + + seconds: float, 表示训练时长 + 以下三个内容只有在提供了dev_data的情况下会有。 + best_eval: Dict of Dict, 表示evaluation的结果 + best_epoch: int,在第几个epoch取得的最佳值 + best_step: int, 在第几个step(batch)更新取得的最佳值 + + """ + results = {} + if self.n_epochs <= 0: + print(f"training epoch is {self.n_epochs}, nothing was done.") + results['seconds'] = 0. + return results + try: + if torch.cuda.is_available() and self.use_cuda: + self.model = self.model.cuda() + self._model_device = self.model.parameters().__next__().device + self._mode(self.model, is_test=False) + + self.start_time = str(datetime.now().strftime('%Y-%m-%d-%H-%M-%S')) + start_time = time.time() + print("training epochs started " + self.start_time, flush=True) + + try: + self.callback_manager.on_train_begin() + self._train() + self.callback_manager.on_train_end(self.model) + except (CallbackException, KeyboardInterrupt) as e: + self.callback_manager.on_exception(e, self.model) + + if self.dev_data is not None: + print("\nIn Epoch:{}/Step:{}, got best dev performance:".format(self.best_dev_epoch, self.best_dev_step) + + self.tester._format_eval_results(self.best_dev_perf),) + results['best_eval'] = self.best_dev_perf + results['best_epoch'] = self.best_dev_epoch + results['best_step'] = self.best_dev_step + if load_best_model: + model_name = "best_" + "_".join([self.model.__class__.__name__, self.metric_key, self.start_time]) + load_succeed = self._load_model(self.model, model_name) + if load_succeed: + print("Reloaded the best model.") + else: + print("Fail to reload best model.") + finally: + pass + results['seconds'] = round(time.time() - start_time, 2) + + return results + + def _train(self): + if not self.use_tqdm: + from fastNLP.core.utils import _pseudo_tqdm as inner_tqdm + else: + inner_tqdm = tqdm + self.step = 0 + start = time.time() + total_steps = (len(self.train_data) // self.batch_size + int( + len(self.train_data) % self.batch_size != 0)) * self.n_epochs + with inner_tqdm(total=total_steps, postfix='loss:{0:<6.5f}', leave=False, dynamic_ncols=True) as pbar: + avg_loss = 0 + data_iterator = Batch(self.train_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, + prefetch=self.prefetch) + for epoch in range(1, self.n_epochs+1): + pbar.set_description_str(desc="Epoch {}/{}".format(epoch, self.n_epochs)) + last_stage = (epoch > self.n_epochs + 1 - self.final_epochs) + if epoch == self.n_epochs + 1 - self.final_epochs: + print('Entering the final stage. (Only train the selected structure)') + # early stopping + self.callback_manager.on_epoch_begin(epoch, self.n_epochs) + + # 1. Training the shared parameters omega of the child models + self.train_shared(pbar) + + # 2. Training the controller parameters theta + if not last_stage: + self.train_controller() + + if ((self.validate_every > 0 and self.step % self.validate_every == 0) or + (self.validate_every < 0 and self.step % len(data_iterator) == 0)) \ + and self.dev_data is not None: + if not last_stage: + self.derive() + eval_res = self._do_validation(epoch=epoch, step=self.step) + eval_str = "Evaluation at Epoch {}/{}. Step:{}/{}. ".format(epoch, self.n_epochs, self.step, + total_steps) + \ + self.tester._format_eval_results(eval_res) + pbar.write(eval_str) + + # lr decay; early stopping + self.callback_manager.on_epoch_end(epoch, self.n_epochs, self.optimizer) + # =============== epochs end =================== # + pbar.close() + # ============ tqdm end ============== # + + + def get_loss(self, inputs, targets, hidden, dags): + """Computes the loss for the same batch for M models. + + This amounts to an estimate of the loss, which is turned into an + estimate for the gradients of the shared model. + """ + if not isinstance(dags, list): + dags = [dags] + + loss = 0 + for dag in dags: + self.shared.setDAG(dag) + inputs = _build_args(self.shared.forward, **inputs) + inputs['hidden'] = hidden + result = self.shared(**inputs) + output, hidden, extra_out = result['pred'], result['hidden'], result['extra_out'] + + self.callback_manager.on_loss_begin(targets, result) + sample_loss = self._compute_loss(result, targets) + loss += sample_loss + + assert len(dags) == 1, 'there are multiple `hidden` for multple `dags`' + return loss, hidden, extra_out + + def train_shared(self, pbar=None, max_step=None, dag=None): + """Train the language model for 400 steps of minibatches of 64 + examples. + + Args: + max_step: Used to run extra training steps as a warm-up. + dag: If not None, is used instead of calling sample(). + + BPTT is truncated at 35 timesteps. + + For each weight update, gradients are estimated by sampling M models + from the fixed controller policy, and averaging their gradients + computed on a batch of training data. + """ + model = self.shared + model.train() + self.controller.eval() + + hidden = self.shared.init_hidden(self.batch_size) + + abs_max_grad = 0 + abs_max_hidden_norm = 0 + step = 0 + raw_total_loss = 0 + total_loss = 0 + train_idx = 0 + avg_loss = 0 + data_iterator = Batch(self.train_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, + prefetch=self.prefetch) + + for batch_x, batch_y in data_iterator: + _move_dict_value_to_device(batch_x, batch_y, device=self._model_device) + indices = data_iterator.get_batch_indices() + # negative sampling; replace unknown; re-weight batch_y + self.callback_manager.on_batch_begin(batch_x, batch_y, indices) + # prediction = self._data_forward(self.model, batch_x) + + dags = self.controller.sample(1) + inputs, targets = batch_x, batch_y + # self.callback_manager.on_loss_begin(batch_y, prediction) + loss, hidden, extra_out = self.get_loss(inputs, + targets, + hidden, + dags) + hidden.detach_() + + avg_loss += loss.item() + + # Is loss NaN or inf? requires_grad = False + self.callback_manager.on_backward_begin(loss, self.model) + self._grad_backward(loss) + self.callback_manager.on_backward_end(self.model) + + self._update() + self.callback_manager.on_step_end(self.optimizer) + + if (self.step+1) % self.print_every == 0: + if self.use_tqdm: + print_output = "loss:{0:<6.5f}".format(avg_loss / self.print_every) + pbar.update(self.print_every) + else: + end = time.time() + diff = timedelta(seconds=round(end - start)) + print_output = "[epoch: {:>3} step: {:>4}] train loss: {:>4.6} time: {}".format( + epoch, self.step, avg_loss, diff) + pbar.set_postfix_str(print_output) + avg_loss = 0 + self.step += 1 + step += 1 + self.shared_step += 1 + self.callback_manager.on_batch_end() + # ================= mini-batch end ==================== # + + + def get_reward(self, dag, entropies, hidden, valid_idx=0): + """Computes the perplexity of a single sampled model on a minibatch of + validation data. + """ + if not isinstance(entropies, np.ndarray): + entropies = entropies.data.cpu().numpy() + + data_iterator = Batch(self.dev_data, batch_size=self.batch_size, sampler=self.sampler, as_numpy=False, + prefetch=self.prefetch) + + for inputs, targets in data_iterator: + valid_loss, hidden, _ = self.get_loss(inputs, targets, hidden, dag) + valid_loss = utils.to_item(valid_loss.data) + + valid_ppl = math.exp(valid_loss) + + R = 80 / valid_ppl + + rewards = R + 1e-4 * entropies + + return rewards, hidden + + def train_controller(self): + """Fixes the shared parameters and updates the controller parameters. + + The controller is updated with a score function gradient estimator + (i.e., REINFORCE), with the reward being c/valid_ppl, where valid_ppl + is computed on a minibatch of validation data. + + A moving average baseline is used. + + The controller is trained for 2000 steps per epoch (i.e., + first (Train Shared) phase -> second (Train Controller) phase). + """ + model = self.controller + model.train() + # Why can't we call shared.eval() here? Leads to loss + # being uniformly zero for the controller. + # self.shared.eval() + + avg_reward_base = None + baseline = None + adv_history = [] + entropy_history = [] + reward_history = [] + + hidden = self.shared.init_hidden(self.batch_size) + total_loss = 0 + valid_idx = 0 + for step in range(20): + # sample models + dags, log_probs, entropies = self.controller.sample( + with_details=True) + + # calculate reward + np_entropies = entropies.data.cpu().numpy() + # No gradients should be backpropagated to the + # shared model during controller training, obviously. + with _get_no_grad_ctx_mgr(): + rewards, hidden = self.get_reward(dags, + np_entropies, + hidden, + valid_idx) + + + reward_history.extend(rewards) + entropy_history.extend(np_entropies) + + # moving average baseline + if baseline is None: + baseline = rewards + else: + decay = 0.95 + baseline = decay * baseline + (1 - decay) * rewards + + adv = rewards - baseline + adv_history.extend(adv) + + # policy loss + loss = -log_probs*utils.get_variable(adv, + self.use_cuda, + requires_grad=False) + + loss = loss.sum() # or loss.mean() + + # update + self.controller_optim.zero_grad() + loss.backward() + + self.controller_optim.step() + + total_loss += utils.to_item(loss.data) + + if ((step % 50) == 0) and (step > 0): + reward_history, adv_history, entropy_history = [], [], [] + total_loss = 0 + + self.controller_step += 1 + # prev_valid_idx = valid_idx + # valid_idx = ((valid_idx + self.max_length) % + # (self.valid_data.size(0) - 1)) + # # Whenever we wrap around to the beginning of the + # # validation data, we reset the hidden states. + # if prev_valid_idx > valid_idx: + # hidden = self.shared.init_hidden(self.batch_size) + + def derive(self, sample_num=10, valid_idx=0): + """We are always deriving based on the very first batch + of validation data? This seems wrong... + """ + hidden = self.shared.init_hidden(self.batch_size) + + dags, _, entropies = self.controller.sample(sample_num, + with_details=True) + + max_R = 0 + best_dag = None + for dag in dags: + R, _ = self.get_reward(dag, entropies, hidden, valid_idx) + if R.max() > max_R: + max_R = R.max() + best_dag = dag + + self.model.setDAG(best_dag) diff --git a/legacy/automl/enas_utils.py b/legacy/automl/enas_utils.py new file mode 100644 index 00000000..7a53dd12 --- /dev/null +++ b/legacy/automl/enas_utils.py @@ -0,0 +1,53 @@ +# Code Modified from https://github.com/carpedm20/ENAS-pytorch + +from __future__ import print_function + +import collections +from collections import defaultdict + +import numpy as np +import torch +from torch.autograd import Variable + + +def detach(h): + if type(h) == Variable: + return Variable(h.data) + else: + return tuple(detach(v) for v in h) + +def get_variable(inputs, cuda=False, **kwargs): + if type(inputs) in [list, np.ndarray]: + inputs = torch.Tensor(inputs) + if cuda: + out = Variable(inputs.cuda(), **kwargs) + else: + out = Variable(inputs, **kwargs) + return out + +def update_lr(optimizer, lr): + for param_group in optimizer.param_groups: + param_group['lr'] = lr + +Node = collections.namedtuple('Node', ['id', 'name']) + + +class keydefaultdict(defaultdict): + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + else: + ret = self[key] = self.default_factory(key) + return ret + + +def to_item(x): + """Converts x, possibly scalar and possibly tensor, to a Python scalar.""" + if isinstance(x, (float, int)): + return x + + if float(torch.__version__[0:3]) < 0.4: + assert (x.dim() == 1) and (len(x) == 1) + return x[0] + + return x.item() diff --git a/legacy/component/__init__.py b/legacy/component/__init__.py new file mode 100644 index 00000000..c6784aef --- /dev/null +++ b/legacy/component/__init__.py @@ -0,0 +1 @@ +from .bert_tokenizer import BertTokenizer diff --git a/legacy/component/bert_tokenizer.py b/legacy/component/bert_tokenizer.py new file mode 100644 index 00000000..6354076d --- /dev/null +++ b/legacy/component/bert_tokenizer.py @@ -0,0 +1,378 @@ +""" +bert_tokenizer.py is modified from huggingface/pytorch-pretrained-BERT, which is licensed under the Apache License 2.0. +""" +import collections +import os +import unicodedata +from io import open + + +PRETRAINED_VOCAB_ARCHIVE_MAP = { + 'bert-base-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-uncased-vocab.txt", + 'bert-large-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-uncased-vocab.txt", + 'bert-base-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-cased-vocab.txt", + 'bert-large-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-large-cased-vocab.txt", + 'bert-base-multilingual-uncased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-uncased-vocab.txt", + 'bert-base-multilingual-cased': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-multilingual-cased-vocab.txt", + 'bert-base-chinese': "https://s3.amazonaws.com/models.huggingface.co/bert/bert-base-chinese-vocab.txt", +} +PRETRAINED_VOCAB_POSITIONAL_EMBEDDINGS_SIZE_MAP = { + 'bert-base-uncased': 512, + 'bert-large-uncased': 512, + 'bert-base-cased': 512, + 'bert-large-cased': 512, + 'bert-base-multilingual-uncased': 512, + 'bert-base-multilingual-cased': 512, + 'bert-base-chinese': 512, +} +VOCAB_NAME = 'vocab.txt' + + +def load_vocab(vocab_file): + """Loads a vocabulary file into a dictionary.""" + vocab = collections.OrderedDict() + index = 0 + with open(vocab_file, "r", encoding="utf-8") as reader: + while True: + token = reader.readline() + if not token: + break + token = token.strip() + vocab[token] = index + index += 1 + return vocab + + +def whitespace_tokenize(text): + """Runs basic whitespace cleaning and splitting on a piece of text.""" + text = text.strip() + if not text: + return [] + tokens = text.split() + return tokens + + +class BertTokenizer(object): + """Runs end-to-end tokenization: punctuation splitting + wordpiece""" + + def __init__(self, vocab_file, do_lower_case=True, max_len=None, do_basic_tokenize=True, + never_split=("[UNK]", "[SEP]", "[PAD]", "[CLS]", "[MASK]")): + """Constructs a BertTokenizer. + Args: + vocab_file: Path to a one-wordpiece-per-line vocabulary file + do_lower_case: Whether to lower case the input + Only has an effect when do_wordpiece_only=False + do_basic_tokenize: Whether to do basic tokenization before wordpiece. + max_len: An artificial maximum length to truncate tokenized sequences to; + Effective maximum length is always the minimum of this + value (if specified) and the underlying BERT model's + sequence length. + never_split: List of tokens which will never be split during tokenization. + Only has an effect when do_wordpiece_only=False + """ + if not os.path.isfile(vocab_file): + raise ValueError( + "Can't find a vocabulary file at path '{}'. To load the vocabulary from a Google pretrained " + "model use `tokenizer = BertTokenizer.from_pretrained(PRETRAINED_MODEL_NAME)`".format(vocab_file)) + self.vocab = load_vocab(vocab_file) + self.ids_to_tokens = collections.OrderedDict( + [(ids, tok) for tok, ids in self.vocab.items()]) + self.do_basic_tokenize = do_basic_tokenize + if do_basic_tokenize: + self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case, + never_split=never_split) + self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab) + self.max_len = max_len if max_len is not None else int(1e12) + + def tokenize(self, text): + split_tokens = [] + if self.do_basic_tokenize: + for token in self.basic_tokenizer.tokenize(text): + for sub_token in self.wordpiece_tokenizer.tokenize(token): + split_tokens.append(sub_token) + else: + split_tokens = self.wordpiece_tokenizer.tokenize(text) + return split_tokens + + def convert_tokens_to_ids(self, tokens): + """Converts a sequence of tokens into ids using the vocab.""" + ids = [] + for token in tokens: + ids.append(self.vocab[token]) + if len(ids) > self.max_len: + print( + "WARNING!\n\"" + "Token indices sequence length is longer than the specified maximum " + "sequence length for this BERT model ({} > {}). Running this" + " sequence through BERT will result in indexing errors".format(len(ids), self.max_len) + ) + return ids + + def convert_ids_to_tokens(self, ids): + """Converts a sequence of ids in wordpiece tokens using the vocab.""" + tokens = [] + for i in ids: + tokens.append(self.ids_to_tokens[i]) + return tokens + + def save_vocabulary(self, vocab_path): + """Save the tokenizer vocabulary to a directory or file.""" + index = 0 + if os.path.isdir(vocab_path): + vocab_file = os.path.join(vocab_path, VOCAB_NAME) + with open(vocab_file, "w", encoding="utf-8") as writer: + for token, token_index in sorted(self.vocab.items(), key=lambda kv: kv[1]): + if index != token_index: + print("Saving vocabulary to {}: vocabulary indices are not consecutive." + " Please check that the vocabulary is not corrupted!".format(vocab_file)) + index = token_index + writer.write(token + u'\n') + index += 1 + return vocab_file + + @classmethod + def from_pretrained(cls, pretrained_model_name_or_path, cache_dir=None, *inputs, **kwargs): + """ + Instantiate a PreTrainedBertModel from a pre-trained model file. + Download and cache the pre-trained model file if needed. + """ + if pretrained_model_name_or_path in PRETRAINED_VOCAB_ARCHIVE_MAP: + vocab_file = PRETRAINED_VOCAB_ARCHIVE_MAP[pretrained_model_name_or_path] + if '-cased' in pretrained_model_name_or_path and kwargs.get('do_lower_case', True): + print("The pre-trained model you are loading is a cased model but you have not set " + "`do_lower_case` to False. We are setting `do_lower_case=False` for you but " + "you may want to check this behavior.") + kwargs['do_lower_case'] = False + elif '-cased' not in pretrained_model_name_or_path and not kwargs.get('do_lower_case', True): + print("The pre-trained model you are loading is an uncased model but you have set " + "`do_lower_case` to False. We are setting `do_lower_case=True` for you " + "but you may want to check this behavior.") + kwargs['do_lower_case'] = True + else: + vocab_file = pretrained_model_name_or_path + if os.path.isdir(vocab_file): + vocab_file = os.path.join(vocab_file, VOCAB_NAME) + # redirect to the cache, if necessary + resolved_vocab_file = vocab_file + print("loading vocabulary file {}".format(vocab_file)) + if pretrained_model_name_or_path in PRETRAINED_VOCAB_POSITIONAL_EMBEDDINGS_SIZE_MAP: + # if we're using a pretrained model, ensure the tokenizer wont index sequences longer + # than the number of positional embeddings + max_len = PRETRAINED_VOCAB_POSITIONAL_EMBEDDINGS_SIZE_MAP[pretrained_model_name_or_path] + kwargs['max_len'] = min(kwargs.get('max_len', int(1e12)), max_len) + # Instantiate tokenizer. + tokenizer = cls(resolved_vocab_file, *inputs, **kwargs) + return tokenizer + + +class BasicTokenizer(object): + """Runs basic tokenization (punctuation splitting, lower casing, etc.).""" + + def __init__(self, + do_lower_case=True, + never_split=("[UNK]", "[SEP]", "[PAD]", "[CLS]", "[MASK]")): + """Constructs a BasicTokenizer. + Args: + do_lower_case: Whether to lower case the input. + """ + self.do_lower_case = do_lower_case + self.never_split = never_split + + def tokenize(self, text): + """Tokenizes a piece of 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 and token not in self.never_split: + 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.""" + if text in self.never_split: + return [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 tokenization.""" + + 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. + """ + + 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/reproduction/Biaffine_parser/run.py b/reproduction/Biaffine_parser/run.py index c226ce69..a69d3d58 100644 --- a/reproduction/Biaffine_parser/run.py +++ b/reproduction/Biaffine_parser/run.py @@ -155,7 +155,7 @@ print('test len {}'.format(len(test_data))) def train(path): # test saving pipeline save_pipe(path) - embed = EmbedLoader.fast_load_embedding(model_args['word_emb_dim'], emb_file_name, word_v) + embed = EmbedLoader.load_with_vocab(emb_file_name, word_v) embed = torch.tensor(embed, dtype=torch.float32) # embed = EmbedLoader.fast_load_embedding(emb_dim=model_args['word_emb_dim'], emb_file=emb_file_name, vocab=word_v) diff --git a/reproduction/Chinese_word_segmentation/models/cws_model.py b/reproduction/Chinese_word_segmentation/models/cws_model.py index daefc380..b41ad87d 100644 --- a/reproduction/Chinese_word_segmentation/models/cws_model.py +++ b/reproduction/Chinese_word_segmentation/models/cws_model.py @@ -3,7 +3,7 @@ import torch from torch import nn from fastNLP.models.base_model import BaseModel -from fastNLP.modules.decoder.MLP import MLP +from fastNLP.modules.decoder.mlp import MLP from reproduction.Chinese_word_segmentation.utils import seq_lens_to_mask @@ -120,8 +120,8 @@ class CWSBiLSTMSegApp(BaseModel): return {'pred_tags': pred_tags} -from fastNLP.modules.decoder.CRF import ConditionalRandomField -from fastNLP.modules.decoder.CRF import allowed_transitions +from fastNLP.modules.decoder.crf import ConditionalRandomField +from fastNLP.modules.decoder.crf import allowed_transitions class CWSBiLSTMCRF(BaseModel): def __init__(self, vocab_num, embed_dim=100, bigram_vocab_num=None, bigram_embed_dim=100, num_bigram_per_char=None, @@ -183,7 +183,7 @@ class CWSBiLSTMCRF(BaseModel): masks = seq_lens_to_mask(seq_lens) feats = self.encoder_model(chars, bigrams, seq_lens) feats = self.decoder_model(feats) - probs = self.crf.viterbi_decode(feats, masks, get_score=False) + paths, _ = self.crf.viterbi_decode(feats, masks) - return {'pred': probs, 'seq_lens':seq_lens} + return {'pred': paths, 'seq_lens':seq_lens} diff --git a/reproduction/Chinese_word_segmentation/models/cws_transformer.py b/reproduction/Chinese_word_segmentation/models/cws_transformer.py index 736edade..e8ae5ecc 100644 --- a/reproduction/Chinese_word_segmentation/models/cws_transformer.py +++ b/reproduction/Chinese_word_segmentation/models/cws_transformer.py @@ -8,9 +8,10 @@ from torch import nn import torch -from fastNLP.modules.encoder.transformer import TransformerEncoder -from fastNLP.modules.decoder.CRF import ConditionalRandomField,seq_len_to_byte_mask -from fastNLP.modules.decoder.CRF import allowed_transitions +# from fastNLP.modules.encoder.transformer import TransformerEncoder +from reproduction.Chinese_word_segmentation.models.transformer import TransformerEncoder +from fastNLP.modules.decoder.crf import ConditionalRandomField,seq_len_to_byte_mask +from fastNLP.modules.decoder.crf import allowed_transitions class TransformerCWS(nn.Module): def __init__(self, vocab_num, embed_dim=100, bigram_vocab_num=None, bigram_embed_dim=100, num_bigram_per_char=None, @@ -27,11 +28,12 @@ class TransformerCWS(nn.Module): self.fc1 = nn.Linear(input_size, hidden_size) - value_size = hidden_size//num_heads - self.transformer = TransformerEncoder(num_layers, model_size=hidden_size, inner_size=hidden_size, - key_size=value_size, - value_size=value_size, num_head=num_heads) - + # value_size = hidden_size//num_heads + # self.transformer = TransformerEncoder(num_layers, model_size=hidden_size, inner_size=hidden_size, + # key_size=value_size, + # value_size=value_size, num_head=num_heads) + self.transformer = TransformerEncoder(num_layers=num_layers, model_size=hidden_size, num_heads=num_heads, + hidden_size=hidden_size) self.fc2 = nn.Linear(hidden_size, tag_size) allowed_trans = allowed_transitions({0:'b', 1:'m', 2:'e', 3:'s'}, encoding_type='bmes') @@ -39,7 +41,7 @@ class TransformerCWS(nn.Module): allowed_transitions=allowed_trans) def forward(self, chars, target, seq_lens, bigrams=None): - masks = seq_len_to_byte_mask(seq_lens).float() + masks = seq_len_to_byte_mask(seq_lens) x = self.embedding(chars) batch_size = x.size(0) length = x.size(1) @@ -59,7 +61,7 @@ class TransformerCWS(nn.Module): return pred_dict def predict(self, chars, seq_lens, bigrams=None): - masks = seq_len_to_byte_mask(seq_lens).float() + masks = seq_len_to_byte_mask(seq_lens) x = self.embedding(chars) batch_size = x.size(0) @@ -77,6 +79,78 @@ class TransformerCWS(nn.Module): return {'pred': probs, 'seq_lens':seq_lens} +from reproduction.Chinese_word_segmentation.models.dilated_transformer import TransformerDilateEncoder + +class TransformerDilatedCWS(nn.Module): + def __init__(self, vocab_num, embed_dim=100, bigram_vocab_num=None, bigram_embed_dim=100, num_bigram_per_char=None, + embed_drop_p=0.3, hidden_size=200, kernel_size=3, dilate='none', + num_layers=1, num_heads=8, tag_size=4, + relative_pos_embed_dim=0): + super().__init__() + + self.embedding = nn.Embedding(vocab_num, embed_dim) + input_size = embed_dim + if bigram_vocab_num: + self.bigram_embedding = nn.Embedding(bigram_vocab_num, bigram_embed_dim) + input_size += num_bigram_per_char*bigram_embed_dim + + self.drop = nn.Dropout(embed_drop_p, inplace=True) + + self.fc1 = nn.Linear(input_size, hidden_size) + + # value_size = hidden_size//num_heads + # self.transformer = TransformerEncoder(num_layers, model_size=hidden_size, inner_size=hidden_size, + # key_size=value_size, + # value_size=value_size, num_head=num_heads) + self.transformer = TransformerDilateEncoder(num_layers=num_layers, model_size=hidden_size, num_heads=num_heads, + hidden_size=hidden_size, kernel_size=kernel_size, dilate=dilate, + relative_pos_embed_dim=relative_pos_embed_dim) + self.fc2 = nn.Linear(hidden_size, tag_size) + + allowed_trans = allowed_transitions({0:'b', 1:'m', 2:'e', 3:'s'}, encoding_type='bmes') + self.crf = ConditionalRandomField(num_tags=tag_size, include_start_end_trans=False, + allowed_transitions=allowed_trans) + + def forward(self, chars, target, seq_lens, bigrams=None): + masks = seq_len_to_byte_mask(seq_lens) + x = self.embedding(chars) + batch_size = x.size(0) + length = x.size(1) + if hasattr(self, 'bigram_embedding'): + bigrams = self.bigram_embedding(bigrams) # batch_size x seq_lens x per_char x embed_size + x = torch.cat([x, bigrams.view(batch_size, length, -1)], dim=-1) + self.drop(x) + x = self.fc1(x) + feats = self.transformer(x, masks) + feats = self.fc2(feats) + losses = self.crf(feats, target, masks.float()) + + pred_dict = {} + pred_dict['seq_lens'] = seq_lens + pred_dict['loss'] = torch.mean(losses) + + return pred_dict + + def predict(self, chars, seq_lens, bigrams=None): + masks = seq_len_to_byte_mask(seq_lens) + + x = self.embedding(chars) + batch_size = x.size(0) + length = x.size(1) + if hasattr(self, 'bigram_embedding'): + bigrams = self.bigram_embedding(bigrams) # batch_size x seq_lens x per_char x embed_size + x = torch.cat([x, bigrams.view(batch_size, length, -1)], dim=-1) + self.drop(x) + x = self.fc1(x) + feats = self.transformer(x, masks) + feats = self.fc2(feats) + + paths, _ = self.crf.viterbi_decode(feats, masks) + + return {'pred': paths, 'seq_lens':seq_lens} + + + class NoamOpt(torch.optim.Optimizer): "Optim wrapper that implements rate." @@ -107,10 +181,7 @@ class NoamOpt(torch.optim.Optimizer): (self.model_size ** (-0.5) * min(step ** (-0.5), step * self.warmup ** (-1.5))) - -if __name__ == '__main__': - - +def TransformerCWS_test(): transformer = TransformerCWS(10, embed_dim=100, bigram_vocab_num=10, bigram_embed_dim=100, num_bigram_per_char=8, hidden_size=200, embed_drop_p=0.3, num_layers=1, num_heads=8, tag_size=4) chars = torch.randint(10, size=(4, 7)).long() @@ -122,4 +193,7 @@ if __name__ == '__main__': optimizer = torch.optim.Adam(transformer.parameters()) - opt = NoamOpt(10 ,1, 400, optimizer) \ No newline at end of file + opt = NoamOpt(10 ,1, 400, optimizer) + +if __name__ == '__main__': + TransformerCWS_test() diff --git a/reproduction/LSTM+self_attention_sentiment_analysis/main.py b/reproduction/LSTM+self_attention_sentiment_analysis/main.py index ff2d7a67..871dc476 100644 --- a/reproduction/LSTM+self_attention_sentiment_analysis/main.py +++ b/reproduction/LSTM+self_attention_sentiment_analysis/main.py @@ -7,7 +7,7 @@ from fastNLP.io.config_io import ConfigSection from fastNLP.io.dataset_loader import DummyClassificationReader as Dataset_loader from fastNLP.models.base_model import BaseModel from fastNLP.modules.aggregator.self_attention import SelfAttention -from fastNLP.modules.decoder.MLP import MLP +from fastNLP.modules.decoder.mlp import MLP from fastNLP.modules.encoder.embedding import Embedding as Embedding from fastNLP.modules.encoder.lstm import LSTM @@ -42,7 +42,7 @@ train_data, dev_data = preprocess.run(train_data, dev_data) class SELF_ATTENTION_YELP_CLASSIFICATION(BaseModel): def __init__(self, args=None): super(SELF_ATTENTION_YELP_CLASSIFICATION,self).__init__() - self.embedding = Embedding(len(word2index) ,embeding_size , init_emb= None ) + self.embedding = Embedding((len(word2index) ,embeding_size)) self.lstm = LSTM(input_size=embeding_size, hidden_size=lstm_hidden_size, bidirectional=True) self.attention = SelfAttention(lstm_hidden_size * 2 ,dim =attention_unit ,num_vec=attention_hops) self.mlp = MLP(size_layer=[lstm_hidden_size * 2*attention_hops ,nfc ,class_num ]) diff --git a/reproduction/POS_tagging/train_pos_tag.py b/reproduction/POS_tagging/train_pos_tag.py index 06547701..ccf7aa1e 100644 --- a/reproduction/POS_tagging/train_pos_tag.py +++ b/reproduction/POS_tagging/train_pos_tag.py @@ -13,7 +13,7 @@ from fastNLP.api.processor import SeqLenProcessor, VocabIndexerProcessor, SetInp from fastNLP.core.metrics import SpanFPreRecMetric from fastNLP.core.trainer import Trainer from fastNLP.io.config_io import ConfigLoader, ConfigSection -from fastNLP.models.sequence_modeling import AdvSeqLabel +from fastNLP.models.sequence_labeling import AdvSeqLabel from fastNLP.io.dataset_loader import ConllxDataLoader from fastNLP.api.processor import ModelProcessor, Index2WordProcessor diff --git a/reproduction/README.md b/reproduction/README.md new file mode 100644 index 00000000..8d14d36d --- /dev/null +++ b/reproduction/README.md @@ -0,0 +1,44 @@ +# 模型复现 +这里复现了在fastNLP中实现的模型,旨在达到与论文中相符的性能。 + +复现的模型有: +- Star-Transformer +- ... + + +## Star-Transformer +[reference](https://arxiv.org/abs/1902.09113) +### Performance (still in progress) +|任务| 数据集 | SOTA | 模型表现 | +|------|------| ------| ------| +|Pos Tagging|CTB 9.0|-|ACC 92.31| +|Pos Tagging|CONLL 2012|-|ACC 96.51| +|Named Entity Recognition|CONLL 2012|-|F1 85.66| +|Text Classification|SST|-|49.18| +|Natural Language Inference|SNLI|-|83.76| + +### Usage +``` python +# for sequence labeling(ner, pos tagging, etc) +from fastNLP.models.star_transformer import STSeqLabel +model = STSeqLabel( + vocab_size=10000, num_cls=50, + emb_dim=300) + + +# for sequence classification +from fastNLP.models.star_transformer import STSeqCls +model = STSeqCls( + vocab_size=10000, num_cls=50, + emb_dim=300) + + +# for natural language inference +from fastNLP.models.star_transformer import STNLICls +model = STNLICls( + vocab_size=10000, num_cls=50, + emb_dim=300) + +``` + +## ... diff --git a/reproduction/Star_transformer/datasets.py b/reproduction/Star_transformer/datasets.py new file mode 100644 index 00000000..a9257fd4 --- /dev/null +++ b/reproduction/Star_transformer/datasets.py @@ -0,0 +1,157 @@ +import torch +import json +import os +from fastNLP import Vocabulary +from fastNLP.io.dataset_loader import ConllLoader, SSTLoader, SNLILoader +from fastNLP.core import Const as C +import numpy as np + +MAX_LEN = 128 + +def update_v(vocab, data, field): + data.apply(lambda x: vocab.add_word_lst(x[field]), new_field_name=None) + + +def to_index(vocab, data, field, name): + def func(x): + try: + return [vocab.to_index(w) for w in x[field]] + except ValueError: + return [vocab.padding_idx for _ in x[field]] + data.apply(func, new_field_name=name) + + +def load_seqtag(path, files, indexs): + word_h, tag_h = 'words', 'tags' + loader = ConllLoader(headers=[word_h, tag_h], indexes=indexs) + ds_list = [] + for fn in files: + ds_list.append(loader.load(os.path.join(path, fn))) + word_v = Vocabulary(min_freq=2) + tag_v = Vocabulary(unknown=None) + update_v(word_v, ds_list[0], word_h) + update_v(tag_v, ds_list[0], tag_h) + + def process_data(ds): + to_index(word_v, ds, word_h, C.INPUT) + to_index(tag_v, ds, tag_h, C.TARGET) + ds.apply(lambda x: x[C.INPUT][:MAX_LEN], new_field_name=C.INPUT) + ds.apply(lambda x: x[C.TARGET][:MAX_LEN], new_field_name=C.TARGET) + ds.apply(lambda x: len(x[word_h]), new_field_name=C.INPUT_LEN) + ds.set_input(C.INPUT, C.INPUT_LEN) + ds.set_target(C.TARGET, C.INPUT_LEN) + for i in range(len(ds_list)): + process_data(ds_list[i]) + return ds_list, word_v, tag_v + + +def load_sst(path, files): + loaders = [SSTLoader(subtree=sub, fine_grained=True) + for sub in [True, False, False]] + ds_list = [loader.load(os.path.join(path, fn)) + for fn, loader in zip(files, loaders)] + word_v = Vocabulary(min_freq=2) + tag_v = Vocabulary(unknown=None, padding=None) + for ds in ds_list: + ds.apply(lambda x: [w.lower() + for w in x['words']], new_field_name='words') + ds_list[0].drop(lambda x: len(x['words']) < 3) + update_v(word_v, ds_list[0], 'words') + ds_list[0].apply(lambda x: tag_v.add_word( + x['target']), new_field_name=None) + + def process_data(ds): + to_index(word_v, ds, 'words', C.INPUT) + ds.apply(lambda x: tag_v.to_index(x['target']), new_field_name=C.TARGET) + ds.apply(lambda x: x[C.INPUT][:MAX_LEN], new_field_name=C.INPUT) + ds.apply(lambda x: len(x['words']), new_field_name=C.INPUT_LEN) + ds.set_input(C.INPUT, C.INPUT_LEN) + ds.set_target(C.TARGET) + for i in range(len(ds_list)): + process_data(ds_list[i]) + return ds_list, word_v, tag_v + + +def load_snli(path, files): + loader = SNLILoader() + ds_list = [loader.load(os.path.join(path, f)) for f in files] + word_v = Vocabulary(min_freq=2) + tag_v = Vocabulary(unknown=None, padding=None) + for ds in ds_list: + ds.apply(lambda x: [w.lower() + for w in x['words1']], new_field_name='words1') + ds.apply(lambda x: [w.lower() + for w in x['words2']], new_field_name='words2') + update_v(word_v, ds_list[0], 'words1') + update_v(word_v, ds_list[0], 'words2') + ds_list[0].apply(lambda x: tag_v.add_word( + x['target']), new_field_name=None) + + def process_data(ds): + to_index(word_v, ds, 'words1', C.INPUTS(0)) + to_index(word_v, ds, 'words2', C.INPUTS(1)) + ds.apply(lambda x: tag_v.to_index(x['target']), new_field_name=C.TARGET) + ds.apply(lambda x: x[C.INPUTS(0)][:MAX_LEN], new_field_name=C.INPUTS(0)) + ds.apply(lambda x: x[C.INPUTS(1)][:MAX_LEN], new_field_name=C.INPUTS(1)) + ds.apply(lambda x: len(x[C.INPUTS(0)]), new_field_name=C.INPUT_LENS(0)) + ds.apply(lambda x: len(x[C.INPUTS(1)]), new_field_name=C.INPUT_LENS(1)) + ds.set_input(C.INPUTS(0), C.INPUTS(1), C.INPUT_LENS(0), C.INPUT_LENS(1)) + ds.set_target(C.TARGET) + for i in range(len(ds_list)): + process_data(ds_list[i]) + return ds_list, word_v, tag_v + + +class EmbedLoader: + @staticmethod + def parse_glove_line(line): + line = line.split() + if len(line) <= 2: + raise RuntimeError( + "something goes wrong in parsing glove embedding") + return line[0], line[1:] + + @staticmethod + def str_list_2_vec(line): + return torch.Tensor(list(map(float, line))) + + @staticmethod + def fast_load_embedding(emb_dim, emb_file, vocab): + """Fast load the pre-trained embedding and combine with the given dictionary. + This loading method uses line-by-line operation. + + :param int emb_dim: the dimension of the embedding. Should be the same as pre-trained embedding. + :param str emb_file: the pre-trained embedding file path. + :param Vocabulary vocab: a mapping from word to index, can be provided by user or built from pre-trained embedding + :return embedding_matrix: numpy.ndarray + + """ + if vocab is None: + raise RuntimeError("You must provide a vocabulary.") + embedding_matrix = np.zeros( + shape=(len(vocab), emb_dim), dtype=np.float32) + hit_flags = np.zeros(shape=(len(vocab),), dtype=int) + with open(emb_file, "r", encoding="utf-8") as f: + startline = f.readline() + if len(startline.split()) > 2: + f.seek(0) + for line in f: + word, vector = EmbedLoader.parse_glove_line(line) + try: + if word in vocab: + vector = EmbedLoader.str_list_2_vec(vector) + if emb_dim != vector.size(0): + continue + embedding_matrix[vocab[word]] = vector + hit_flags[vocab[word]] = 1 + except Exception: + continue + + if np.sum(hit_flags) < len(vocab): + # some words from vocab are missing in pre-trained embedding + # we normally sample each dimension + vocab_embed = embedding_matrix[np.where(hit_flags)] + sampled_vectors = np.random.normal(vocab_embed.mean(axis=0), vocab_embed.std(axis=0), + size=(len(vocab) - np.sum(hit_flags), emb_dim)) + embedding_matrix[np.where(1 - hit_flags)] = sampled_vectors + return embedding_matrix diff --git a/reproduction/Star_transformer/modules.py b/reproduction/Star_transformer/modules.py new file mode 100644 index 00000000..61a61d25 --- /dev/null +++ b/reproduction/Star_transformer/modules.py @@ -0,0 +1,56 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import numpy as np +from fastNLP.core.losses import LossBase + + +reduce_func = { + 'none': lambda x, mask: x*mask, + 'sum': lambda x, mask: (x*mask).sum(), + 'mean': lambda x, mask: (x*mask).sum() / mask.sum(), +} + + +class LabelSmoothCrossEntropy(nn.Module): + def __init__(self, smoothing=0.1, ignore_index=-100, reduction='mean'): + global reduce_func + super().__init__() + if smoothing < 0 or smoothing > 1: + raise ValueError('invalid smoothing value: {}'.format(smoothing)) + self.smoothing = smoothing + self.ignore_index = ignore_index + if reduction not in reduce_func: + raise ValueError('invalid reduce type: {}'.format(reduction)) + self.reduce_func = reduce_func[reduction] + + def forward(self, input, target): + input = F.log_softmax(input, dim=1) # [N, C, ...] + smooth_val = self.smoothing / input.size(1) # [N, C, ...] + target_logit = input.new_full(input.size(), fill_value=smooth_val) + target_logit.scatter_(1, target[:, None], 1 - self.smoothing) + result = -(target_logit * input).sum(1) # [N, ...] + mask = (target != self.ignore_index).float() + return self.reduce_func(result, mask) + + +class SmoothCE(LossBase): + def __init__(self, pred=None, target=None, **kwargs): + super().__init__() + self.loss_fn = LabelSmoothCrossEntropy(**kwargs) + self._init_param_map(pred=pred, target=target) + + def get_loss(self, pred, target): + return self.loss_fn(pred, target) + + +if __name__ == '__main__': + loss_fn = nn.CrossEntropyLoss(ignore_index=0) + sm_loss_fn = LabelSmoothCrossEntropy(smoothing=0, ignore_index=0) + predict = torch.tensor([[0, 0.2, 0.7, 0.1, 0], + [0, 0.9, 0.2, 0.1, 0], + [1, 0.2, 0.7, 0.1, 0]]) + target = torch.tensor([2, 1, 0]) + loss = loss_fn(predict, target) + sm_loss = sm_loss_fn(predict, target) + print(loss, sm_loss) diff --git a/reproduction/Star_transformer/run.sh b/reproduction/Star_transformer/run.sh new file mode 100644 index 00000000..0972c662 --- /dev/null +++ b/reproduction/Star_transformer/run.sh @@ -0,0 +1,5 @@ +#python -u train.py --task pos --ds conll --mode train --gpu 1 --lr 3e-4 --w_decay 2e-5 --lr_decay .95 --drop 0.3 --ep 25 --bsz 64 > conll_pos102.log 2>&1 & +#python -u train.py --task pos --ds ctb --mode train --gpu 1 --lr 3e-4 --w_decay 2e-5 --lr_decay .95 --drop 0.3 --ep 25 --bsz 64 > ctb_pos101.log 2>&1 & +#python -u train.py --task cls --ds sst --mode train --gpu 2 --lr 1e-4 --w_decay 1e-5 --lr_decay 0.9 --drop 0.5 --ep 50 --bsz 128 > sst_cls201.log & +#python -u train.py --task nli --ds snli --mode train --gpu 1 --lr 1e-4 --w_decay 1e-5 --lr_decay 0.9 --drop 0.4 --ep 120 --bsz 128 > snli_nli201.log & +python -u train.py --task ner --ds conll --mode train --gpu 0 --lr 1e-4 --w_decay 1e-5 --lr_decay 0.9 --drop 0.4 --ep 120 --bsz 64 > conll_ner201.log & diff --git a/reproduction/Star_transformer/train.py b/reproduction/Star_transformer/train.py new file mode 100644 index 00000000..dee85c38 --- /dev/null +++ b/reproduction/Star_transformer/train.py @@ -0,0 +1,214 @@ +from util import get_argparser, set_gpu, set_rng_seeds, add_model_args +from datasets import load_seqtag, load_sst, load_snli, EmbedLoader, MAX_LEN +import torch.nn as nn +import torch +import numpy as np +import fastNLP as FN +from fastNLP.models.star_transformer import STSeqLabel, STSeqCls, STNLICls +from fastNLP.core.const import Const as C +import sys +sys.path.append('/remote-home/yfshao/workdir/dev_fastnlp/') + + +g_model_select = { + 'pos': STSeqLabel, + 'ner': STSeqLabel, + 'cls': STSeqCls, + 'nli': STNLICls, +} + +g_emb_file_path = {'en': '/remote-home/yfshao/workdir/datasets/word_vector/glove.840B.300d.txt', + 'zh': '/remote-home/yfshao/workdir/datasets/word_vector/cc.zh.300.vec'} + +g_args = None +g_model_cfg = None + + +def get_ptb_pos(): + pos_dir = '/remote-home/yfshao/workdir/datasets/pos' + pos_files = ['train.pos', 'dev.pos', 'test.pos', ] + return load_seqtag(pos_dir, pos_files, [0, 1]) + + +def get_ctb_pos(): + ctb_dir = '/remote-home/yfshao/workdir/datasets/ctb9_hy' + files = ['train.conllx', 'dev.conllx', 'test.conllx'] + return load_seqtag(ctb_dir, files, [1, 4]) + + +def get_conll2012_pos(): + path = '/remote-home/yfshao/workdir/datasets/ontonotes/pos' + files = ['ontonotes-conll.train', + 'ontonotes-conll.dev', + 'ontonotes-conll.conll-2012-test'] + return load_seqtag(path, files, [0, 1]) + + +def get_conll2012_ner(): + path = '/remote-home/yfshao/workdir/datasets/ontonotes/ner' + files = ['bieso-ontonotes-conll-ner.train', + 'bieso-ontonotes-conll-ner.dev', + 'bieso-ontonotes-conll-ner.conll-2012-test'] + return load_seqtag(path, files, [0, 1]) + + +def get_sst(): + path = '/remote-home/yfshao/workdir/datasets/SST' + files = ['train.txt', 'dev.txt', 'test.txt'] + return load_sst(path, files) + + +def get_snli(): + path = '/remote-home/yfshao/workdir/datasets/nli-data/snli_1.0' + files = ['snli_1.0_train.jsonl', + 'snli_1.0_dev.jsonl', 'snli_1.0_test.jsonl'] + return load_snli(path, files) + + +g_datasets = { + 'ptb-pos': get_ptb_pos, + 'ctb-pos': get_ctb_pos, + 'conll-pos': get_conll2012_pos, + 'conll-ner': get_conll2012_ner, + 'sst-cls': get_sst, + 'snli-nli': get_snli, +} + + +def load_pretrain_emb(word_v, lang='en'): + print('loading pre-train embeddings') + emb = EmbedLoader.fast_load_embedding(300, g_emb_file_path[lang], word_v) + emb /= np.linalg.norm(emb, axis=1, keepdims=True) + emb = torch.tensor(emb, dtype=torch.float32) + print('embedding mean: {:.6}, std: {:.6}'.format(emb.mean(), emb.std())) + emb[word_v.padding_idx].fill_(0) + return emb + + +class MyCallback(FN.core.callback.Callback): + def on_train_begin(self): + super(MyCallback, self).on_train_begin() + self.init_lrs = [pg['lr'] for pg in self.optimizer.param_groups] + + def on_backward_end(self): + nn.utils.clip_grad.clip_grad_norm_(self.model.parameters(), 5.0) + + def on_step_end(self): + warm_steps = 6000 + # learning rate warm-up & decay + if self.step <= warm_steps: + for lr, pg in zip(self.init_lrs, self.optimizer.param_groups): + pg['lr'] = lr * (self.step / float(warm_steps)) + + elif self.step % 3000 == 0: + for pg in self.optimizer.param_groups: + cur_lr = pg['lr'] + pg['lr'] = max(1e-5, cur_lr*g_args.lr_decay) + + + +def train(): + seed = set_rng_seeds(1234) + print('RNG SEED {}'.format(seed)) + print('loading data') + ds_list, word_v, tag_v = g_datasets['{}-{}'.format( + g_args.ds, g_args.task)]() + print(ds_list[0][:2]) + embed = load_pretrain_emb(word_v, lang='zh' if g_args.ds == 'ctb' else 'en') + g_model_cfg['num_cls'] = len(tag_v) + print(g_model_cfg) + g_model_cfg['init_embed'] = embed + model = g_model_select[g_args.task.lower()](**g_model_cfg) + + def init_model(model): + for p in model.parameters(): + if p.size(0) != len(word_v): + nn.init.normal_(p, 0.0, 0.05) + init_model(model) + train_data = ds_list[0] + dev_data = ds_list[2] + test_data = ds_list[1] + print(tag_v.word2idx) + + if g_args.task in ['pos', 'ner']: + padding_idx = tag_v.padding_idx + else: + padding_idx = -100 + print('padding_idx ', padding_idx) + loss = FN.CrossEntropyLoss(padding_idx=padding_idx) + metrics = { + 'pos': (None, FN.AccuracyMetric()), + 'ner': ('f', FN.core.metrics.SpanFPreRecMetric( + tag_vocab=tag_v, encoding_type='bmeso', ignore_labels=[''], )), + 'cls': (None, FN.AccuracyMetric()), + 'nli': (None, FN.AccuracyMetric()), + } + metric_key, metric = metrics[g_args.task] + device = 'cuda' if torch.cuda.is_available() else 'cpu' + ex_param = [x for x in model.parameters( + ) if x.requires_grad and x.size(0) != len(word_v)] + optim_cfg = [{'params': model.enc.embedding.parameters(), 'lr': g_args.lr*0.1}, + {'params': ex_param, 'lr': g_args.lr, 'weight_decay': g_args.w_decay}, ] + trainer = FN.Trainer(model=model, train_data=train_data, dev_data=dev_data, + loss=loss, metrics=metric, metric_key=metric_key, + optimizer=torch.optim.Adam(optim_cfg), + n_epochs=g_args.ep, batch_size=g_args.bsz, print_every=10, validate_every=3000, + device=device, + use_tqdm=False, prefetch=False, + save_path=g_args.log, + callbacks=[MyCallback()]) + + trainer.train() + tester = FN.Tester(data=test_data, model=model, metrics=metric, + batch_size=128, device=device) + tester.test() + + +def test(): + pass + + +def infer(): + pass + + +run_select = { + 'train': train, + 'test': test, + 'infer': infer, +} + + +def main(): + global g_args, g_model_cfg + import signal + + def signal_handler(signal, frame): + raise KeyboardInterrupt + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + parser = get_argparser() + parser.add_argument('--task', choices=['pos', 'ner', 'cls', 'nli']) + parser.add_argument('--mode', choices=['train', 'test', 'infer']) + parser.add_argument('--ds', type=str) + add_model_args(parser) + g_args = parser.parse_args() + print(g_args.__dict__) + set_gpu(g_args.gpu) + g_model_cfg = { + 'init_embed': (None, 300), + 'num_cls': None, + 'hidden_size': g_args.hidden, + 'num_layers': 4, + 'num_head': g_args.nhead, + 'head_dim': g_args.hdim, + 'max_len': MAX_LEN, + 'cls_hidden_size': 600, + 'emb_dropout': 0.3, + 'dropout': g_args.drop, + } + run_select[g_args.mode.lower()]() + + +if __name__ == '__main__': + main() diff --git a/reproduction/Star_transformer/util.py b/reproduction/Star_transformer/util.py new file mode 100644 index 00000000..ecd1e18d --- /dev/null +++ b/reproduction/Star_transformer/util.py @@ -0,0 +1,112 @@ +import fastNLP as FN +import argparse +import os +import random +import numpy +import torch + + +def get_argparser(): + parser = argparse.ArgumentParser() + parser.add_argument('--lr', type=float, required=True) + parser.add_argument('--w_decay', type=float, required=True) + parser.add_argument('--lr_decay', type=float, required=True) + parser.add_argument('--bsz', type=int, required=True) + parser.add_argument('--ep', type=int, required=True) + parser.add_argument('--drop', type=float, required=True) + parser.add_argument('--gpu', type=str, required=True) + parser.add_argument('--log', type=str, default=None) + return parser + + +def add_model_args(parser): + parser.add_argument('--nhead', type=int, default=6) + parser.add_argument('--hdim', type=int, default=50) + parser.add_argument('--hidden', type=int, default=300) + return parser + + +def set_gpu(gpu_str): + os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" + os.environ['CUDA_VISIBLE_DEVICES'] = gpu_str + + +def set_rng_seeds(seed=None): + if seed is None: + seed = numpy.random.randint(0, 65536) + random.seed(seed) + numpy.random.seed(seed) + torch.random.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + # print('RNG_SEED {}'.format(seed)) + return seed + + +class TensorboardCallback(FN.Callback): + """ + 接受以下一个或多个字符串作为参数: + - "model" + - "loss" + - "metric" + """ + + def __init__(self, *options): + super(TensorboardCallback, self).__init__() + args = {"model", "loss", "metric"} + for opt in options: + if opt not in args: + raise ValueError( + "Unrecognized argument {}. Expect one of {}".format(opt, args)) + self.options = options + self._summary_writer = None + self.graph_added = False + + def on_train_begin(self): + save_dir = self.trainer.save_path + if save_dir is None: + path = os.path.join( + "./", 'tensorboard_logs_{}'.format(self.trainer.start_time)) + else: + path = os.path.join( + save_dir, 'tensorboard_logs_{}'.format(self.trainer.start_time)) + self._summary_writer = SummaryWriter(path) + + def on_batch_begin(self, batch_x, batch_y, indices): + if "model" in self.options and self.graph_added is False: + # tesorboardX 这里有大bug,暂时没法画模型图 + # from fastNLP.core.utils import _build_args + # inputs = _build_args(self.trainer.model, **batch_x) + # args = tuple([value for value in inputs.values()]) + # args = args[0] if len(args) == 1 else args + # self._summary_writer.add_graph(self.trainer.model, torch.zeros(32, 2)) + self.graph_added = True + + def on_backward_begin(self, loss): + if "loss" in self.options: + self._summary_writer.add_scalar( + "loss", loss.item(), global_step=self.trainer.step) + + if "model" in self.options: + for name, param in self.trainer.model.named_parameters(): + if param.requires_grad: + self._summary_writer.add_scalar( + name + "_mean", param.mean(), global_step=self.trainer.step) + # self._summary_writer.add_scalar(name + "_std", param.std(), global_step=self.trainer.step) + self._summary_writer.add_scalar(name + "_grad_mean", param.grad.mean(), + global_step=self.trainer.step) + + def on_valid_end(self, eval_result, metric_key): + if "metric" in self.options: + for name, metric in eval_result.items(): + for metric_key, metric_val in metric.items(): + self._summary_writer.add_scalar("valid_{}_{}".format(name, metric_key), metric_val, + global_step=self.trainer.step) + + def on_train_end(self): + self._summary_writer.close() + del self._summary_writer + + def on_exception(self, exception): + if hasattr(self, "_summary_writer"): + self._summary_writer.close() + del self._summary_writer diff --git a/requirements.txt b/requirements.txt index 45c84bc2..dfd2b16e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy>=1.14.2 +numpy torch>=0.4.0 -tensorboardX -tqdm>=4.28.1 \ No newline at end of file +tqdm +nltk \ No newline at end of file diff --git a/setup.py b/setup.py index a8b4834e..b7834d8d 100644 --- a/setup.py +++ b/setup.py @@ -13,12 +13,12 @@ with open('requirements.txt', encoding='utf-8') as f: setup( name='FastNLP', - version='0.1.1', + version='0.4.0', description='fastNLP: Deep Learning Toolkit for NLP, developed by Fudan FastNLP Team', long_description=readme, license=license, author='FudanNLP', - python_requires='>=3.5', + python_requires='>=3.6', packages=find_packages(), install_requires=reqs.strip().split('\n'), ) diff --git a/test/api/test_pipeline.py b/test/api/test_pipeline.py deleted file mode 100644 index c7094790..00000000 --- a/test/api/test_pipeline.py +++ /dev/null @@ -1,6 +0,0 @@ -import unittest - - -class TestPipeline(unittest.TestCase): - def test_case(self): - pass diff --git a/test/api/test_processor.py b/test/api/test_processor.py deleted file mode 100644 index d0c27c40..00000000 --- a/test/api/test_processor.py +++ /dev/null @@ -1,101 +0,0 @@ -import random -import unittest - -import numpy as np - -from fastNLP import Vocabulary, Instance -from fastNLP.api.processor import FullSpaceToHalfSpaceProcessor, PreAppendProcessor, SliceProcessor, Num2TagProcessor, \ - IndexerProcessor, VocabProcessor, SeqLenProcessor, ModelProcessor, Index2WordProcessor, SetTargetProcessor, \ - SetInputProcessor, VocabIndexerProcessor -from fastNLP.core.dataset import DataSet - - -class TestProcessor(unittest.TestCase): - def test_FullSpaceToHalfSpaceProcessor(self): - ds = DataSet({"word": ["00, u1, u), (u2, u2"]}) - proc = FullSpaceToHalfSpaceProcessor("word") - ds = proc(ds) - self.assertEqual(ds.field_arrays["word"].content, ["00, u1, u), (u2, u2"]) - - def test_PreAppendProcessor(self): - ds = DataSet({"word": [["1234", "3456"], ["8789", "3464"]]}) - proc = PreAppendProcessor(data="abc", field_name="word") - ds = proc(ds) - self.assertEqual(ds.field_arrays["word"].content, [["abc", "1234", "3456"], ["abc", "8789", "3464"]]) - - def test_SliceProcessor(self): - ds = DataSet({"xx": [[random.randint(0, 10) for _ in range(30)]] * 40}) - proc = SliceProcessor(10, 20, 2, "xx", new_added_field_name="yy") - ds = proc(ds) - self.assertEqual(len(ds.field_arrays["yy"].content[0]), 5) - - def test_Num2TagProcessor(self): - ds = DataSet({"num": [["99.9982", "2134.0"], ["0.002", "234"]]}) - proc = Num2TagProcessor("", "num") - ds = proc(ds) - for data in ds.field_arrays["num"].content: - for d in data: - self.assertEqual(d, "") - - def test_VocabProcessor_and_IndexerProcessor(self): - ds = DataSet({"xx": [[str(random.randint(0, 10)) for _ in range(30)]] * 40}) - vocab_proc = VocabProcessor("xx") - vocab_proc(ds) - vocab = vocab_proc.vocab - self.assertTrue(isinstance(vocab, Vocabulary)) - self.assertTrue(len(vocab) > 5) - - proc = IndexerProcessor(vocab, "xx", "yy") - ds = proc(ds) - for data in ds.field_arrays["yy"].content[0]: - self.assertTrue(isinstance(data, int)) - - def test_SeqLenProcessor(self): - ds = DataSet({"xx": [[str(random.randint(0, 10)) for _ in range(30)]] * 10}) - proc = SeqLenProcessor("xx", "len") - ds = proc(ds) - for data in ds.field_arrays["len"].content: - self.assertEqual(data, 30) - - def test_ModelProcessor(self): - from fastNLP.models.cnn_text_classification import CNNText - model = CNNText(100, 100, 5) - ins_list = [] - for _ in range(64): - seq_len = np.random.randint(5, 30) - ins_list.append(Instance(word_seq=[np.random.randint(0, 100) for _ in range(seq_len)], seq_lens=seq_len)) - data_set = DataSet(ins_list) - data_set.set_input("word_seq", "seq_lens") - proc = ModelProcessor(model) - data_set = proc(data_set) - self.assertTrue("pred" in data_set) - - def test_Index2WordProcessor(self): - vocab = Vocabulary() - vocab.add_word_lst(["a", "b", "c", "d", "e"]) - proc = Index2WordProcessor(vocab, "tag_id", "tag") - data_set = DataSet([Instance(tag_id=[np.random.randint(0, 7) for _ in range(32)])]) - data_set = proc(data_set) - self.assertTrue("tag" in data_set) - - def test_SetTargetProcessor(self): - proc = SetTargetProcessor("a", "b", "c") - data_set = DataSet({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]}) - data_set = proc(data_set) - self.assertTrue(data_set["a"].is_target) - self.assertTrue(data_set["b"].is_target) - self.assertTrue(data_set["c"].is_target) - - def test_SetInputProcessor(self): - proc = SetInputProcessor("a", "b", "c") - data_set = DataSet({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]}) - data_set = proc(data_set) - self.assertTrue(data_set["a"].is_input) - self.assertTrue(data_set["b"].is_input) - self.assertTrue(data_set["c"].is_input) - - def test_VocabIndexerProcessor(self): - proc = VocabIndexerProcessor("word_seq", "word_ids") - data_set = DataSet([Instance(word_seq=["a", "b", "c", "d", "e"])]) - data_set = proc(data_set) - self.assertTrue("word_ids" in data_set) diff --git a/test/core/test_batch.py b/test/core/test_batch.py index abc2b3e2..d1f93b9c 100644 --- a/test/core/test_batch.py +++ b/test/core/test_batch.py @@ -1,14 +1,12 @@ -import time import unittest import numpy as np import torch -from fastNLP.core.batch import Batch -from fastNLP.core.dataset import DataSet -from fastNLP.core.dataset import construct_dataset -from fastNLP.core.instance import Instance -from fastNLP.core.sampler import SequentialSampler +from fastNLP import Batch +from fastNLP import DataSet +from fastNLP import Instance +from fastNLP import SequentialSampler def generate_fake_dataset(num_samples=1000): @@ -17,11 +15,11 @@ def generate_fake_dataset(num_samples=1000): :param num_samples: sample的数量 :return: """ - + max_len = 50 min_len = 10 num_features = 4 - + data_dict = {} for i in range(num_features): data = [] @@ -29,9 +27,9 @@ def generate_fake_dataset(num_samples=1000): for length in lengths: data.append(np.random.randint(100, size=length)) data_dict[str(i)] = data - + dataset = DataSet(data_dict) - + for i in range(num_features): if np.random.randint(2) == 0: dataset.set_input(str(i)) @@ -39,18 +37,33 @@ def generate_fake_dataset(num_samples=1000): dataset.set_target(str(i)) return dataset + +def construct_dataset(sentences): + """Construct a data set from a list of sentences. + + :param sentences: list of list of str + :return dataset: a DataSet object + """ + dataset = DataSet() + for sentence in sentences: + instance = Instance() + instance['raw_sentence'] = sentence + dataset.append(instance) + return dataset + + class TestCase1(unittest.TestCase): def test_simple(self): dataset = construct_dataset( [["FastNLP", "is", "the", "most", "beautiful", "tool", "in", "the", "world"] for _ in range(40)]) dataset.set_target() batch = Batch(dataset, batch_size=4, sampler=SequentialSampler(), as_numpy=True) - + cnt = 0 for _, _ in batch: cnt += 1 self.assertEqual(cnt, 10) - + def test_dataset_batching(self): ds = DataSet({"x": [[1, 2, 3, 4]] * 40, "y": [[5, 6]] * 40}) ds.set_input("x") @@ -62,7 +75,7 @@ class TestCase1(unittest.TestCase): self.assertEqual(len(y["y"]), 4) self.assertListEqual(list(x["x"][-1]), [1, 2, 3, 4]) self.assertListEqual(list(y["y"][-1]), [5, 6]) - + def test_list_padding(self): ds = DataSet({"x": [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] * 10, "y": [[4, 3, 2, 1], [3, 2, 1], [2, 1], [1]] * 10}) @@ -72,7 +85,7 @@ class TestCase1(unittest.TestCase): for x, y in iter: self.assertEqual(x["x"].shape, (4, 4)) self.assertEqual(y["y"].shape, (4, 4)) - + def test_numpy_padding(self): ds = DataSet({"x": np.array([[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] * 10), "y": np.array([[4, 3, 2, 1], [3, 2, 1], [2, 1], [1]] * 10)}) @@ -82,7 +95,7 @@ class TestCase1(unittest.TestCase): for x, y in iter: self.assertEqual(x["x"].shape, (4, 4)) self.assertEqual(y["y"].shape, (4, 4)) - + def test_list_to_tensor(self): ds = DataSet({"x": [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] * 10, "y": [[4, 3, 2, 1], [3, 2, 1], [2, 1], [1]] * 10}) @@ -94,7 +107,7 @@ class TestCase1(unittest.TestCase): self.assertEqual(tuple(x["x"].shape), (4, 4)) self.assertTrue(isinstance(y["y"], torch.Tensor)) self.assertEqual(tuple(y["y"].shape), (4, 4)) - + def test_numpy_to_tensor(self): ds = DataSet({"x": np.array([[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] * 10), "y": np.array([[4, 3, 2, 1], [3, 2, 1], [2, 1], [1]] * 10)}) @@ -106,7 +119,7 @@ class TestCase1(unittest.TestCase): self.assertEqual(tuple(x["x"].shape), (4, 4)) self.assertTrue(isinstance(y["y"], torch.Tensor)) self.assertEqual(tuple(y["y"].shape), (4, 4)) - + def test_list_of_list_to_tensor(self): ds = DataSet([Instance(x=[1, 2], y=[3, 4]) for _ in range(2)] + [Instance(x=[1, 2, 3, 4], y=[3, 4, 5, 6]) for _ in range(2)]) @@ -118,7 +131,7 @@ class TestCase1(unittest.TestCase): self.assertEqual(tuple(x["x"].shape), (4, 4)) self.assertTrue(isinstance(y["y"], torch.Tensor)) self.assertEqual(tuple(y["y"].shape), (4, 4)) - + def test_list_of_numpy_to_tensor(self): ds = DataSet([Instance(x=np.array([1, 2]), y=np.array([3, 4])) for _ in range(2)] + [Instance(x=np.array([1, 2, 3, 4]), y=np.array([3, 4, 5, 6])) for _ in range(2)]) @@ -127,17 +140,16 @@ class TestCase1(unittest.TestCase): iter = Batch(ds, batch_size=4, sampler=SequentialSampler(), as_numpy=False) for x, y in iter: print(x, y) - + def test_sequential_batch(self): batch_size = 32 - pause_seconds = 0.01 num_samples = 1000 dataset = generate_fake_dataset(num_samples) - + batch = Batch(dataset, batch_size=batch_size, sampler=SequentialSampler()) for batch_x, batch_y in batch: - time.sleep(pause_seconds) - + pass + """ def test_multi_workers_batch(self): batch_size = 32 diff --git a/test/core/test_callbacks.py b/test/core/test_callbacks.py index 74ce4876..db640eb1 100644 --- a/test/core/test_callbacks.py +++ b/test/core/test_callbacks.py @@ -3,15 +3,14 @@ import unittest import numpy as np import torch -from fastNLP.core.callback import EchoCallback, EarlyStopCallback, GradientClipCallback, LRScheduler, ControlC, \ - LRFinder, \ - TensorboardCallback -from fastNLP.core.dataset import DataSet -from fastNLP.core.instance import Instance -from fastNLP.core.losses import BCELoss -from fastNLP.core.metrics import AccuracyMetric -from fastNLP.core.optimizer import SGD -from fastNLP.core.trainer import Trainer +from fastNLP.core.callback import EarlyStopCallback, GradientClipCallback, LRScheduler, ControlC, \ + LRFinder, TensorboardCallback +from fastNLP import DataSet +from fastNLP import Instance +from fastNLP import BCELoss +from fastNLP import AccuracyMetric +from fastNLP import SGD +from fastNLP import Trainer from fastNLP.models.base_model import NaiveClassifier @@ -20,15 +19,15 @@ def prepare_env(): mean = np.array([-3, -3]) cov = np.array([[1, 0], [0, 1]]) class_A = np.random.multivariate_normal(mean, cov, size=(1000,)) - + mean = np.array([3, 3]) cov = np.array([[1, 0], [0, 1]]) class_B = np.random.multivariate_normal(mean, cov, size=(1000,)) - + data_set = DataSet([Instance(x=[float(item[0]), float(item[1])], y=[0.0]) for item in class_A] + [Instance(x=[float(item[0]), float(item[1])], y=[1.0]) for item in class_B]) return data_set - + data_set = prepare_fake_dataset() data_set.set_input("x") data_set.set_target("y") @@ -37,19 +36,7 @@ def prepare_env(): class TestCallback(unittest.TestCase): - def test_echo_callback(self): - data_set, model = prepare_env() - trainer = Trainer(data_set, model, - loss=BCELoss(pred="predict", target="y"), - n_epochs=2, - batch_size=32, - print_every=50, - optimizer=SGD(lr=0.1), - check_code_level=2, - use_tqdm=False, - callbacks=[EchoCallback()]) - trainer.train() - + def test_gradient_clip(self): data_set, model = prepare_env() trainer = Trainer(data_set, model, @@ -64,7 +51,7 @@ class TestCallback(unittest.TestCase): metrics=AccuracyMetric(pred="predict", target="y"), callbacks=[GradientClipCallback(model.parameters(), clip_value=2)]) trainer.train() - + def test_early_stop(self): data_set, model = prepare_env() trainer = Trainer(data_set, model, @@ -79,7 +66,7 @@ class TestCallback(unittest.TestCase): metrics=AccuracyMetric(pred="predict", target="y"), callbacks=[EarlyStopCallback(5)]) trainer.train() - + def test_lr_scheduler(self): data_set, model = prepare_env() optimizer = torch.optim.SGD(model.parameters(), lr=0.01) @@ -95,7 +82,7 @@ class TestCallback(unittest.TestCase): metrics=AccuracyMetric(pred="predict", target="y"), callbacks=[LRScheduler(torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1))]) trainer.train() - + def test_KeyBoardInterrupt(self): data_set, model = prepare_env() trainer = Trainer(data_set, model, @@ -108,7 +95,7 @@ class TestCallback(unittest.TestCase): use_tqdm=False, callbacks=[ControlC(False)]) trainer.train() - + def test_LRFinder(self): data_set, model = prepare_env() trainer = Trainer(data_set, model, @@ -121,7 +108,7 @@ class TestCallback(unittest.TestCase): use_tqdm=False, callbacks=[LRFinder(len(data_set) // 32)]) trainer.train() - + def test_TensorboardCallback(self): data_set, model = prepare_env() trainer = Trainer(data_set, model, @@ -136,3 +123,33 @@ class TestCallback(unittest.TestCase): metrics=AccuracyMetric(pred="predict", target="y"), callbacks=[TensorboardCallback("loss", "metric")]) trainer.train() + + def test_readonly_property(self): + from fastNLP.core.callback import Callback + passed_epochs = [] + total_epochs = 5 + + class MyCallback(Callback): + def __init__(self): + super(MyCallback, self).__init__() + + def on_epoch_begin(self): + passed_epochs.append(self.epoch) + print(self.n_epochs, self.n_steps, self.batch_size) + print(self.model) + print(self.optimizer) + + data_set, model = prepare_env() + trainer = Trainer(data_set, model, + loss=BCELoss(pred="predict", target="y"), + n_epochs=total_epochs, + batch_size=32, + print_every=50, + optimizer=SGD(lr=0.1), + check_code_level=2, + use_tqdm=False, + dev_data=data_set, + metrics=AccuracyMetric(pred="predict", target="y"), + callbacks=[MyCallback()]) + trainer.train() + assert passed_epochs == list(range(1, total_epochs + 1)) diff --git a/test/core/test_dataset.py b/test/core/test_dataset.py index 72ced912..0228f207 100644 --- a/test/core/test_dataset.py +++ b/test/core/test_dataset.py @@ -1,9 +1,10 @@ import os import unittest -from fastNLP.core.dataset import DataSet -from fastNLP.core.fieldarray import FieldArray -from fastNLP.core.instance import Instance +from fastNLP import DataSet +from fastNLP import FieldArray +from fastNLP import Instance +from fastNLP.io import CSVLoader class TestDataSetInit(unittest.TestCase): @@ -52,7 +53,7 @@ class TestDataSetMethods(unittest.TestCase): self.assertEqual(dd.field_arrays["x"].content, [[1, 2, 3, 4]] * 3) self.assertEqual(dd.field_arrays["y"].content, [[5, 6]] * 3) - def test_add_append(self): + def test_add_field(self): dd = DataSet() dd.add_field("x", [[1, 2, 3]] * 10) dd.add_field("y", [[1, 2, 3, 4]] * 10) @@ -65,6 +66,11 @@ class TestDataSetMethods(unittest.TestCase): with self.assertRaises(RuntimeError): dd.add_field("??", [[1, 2]] * 40) + def test_add_field_ignore_type(self): + dd = DataSet() + dd.add_field("x", [(1, "1"), (2, "2"), (3, "3"), (4, "4")], ignore_type=True, is_target=True) + dd.add_field("y", [{1, "1"}, {2, "2"}, {3, "3"}, {4, "4"}], ignore_type=True, is_target=True) + def test_delete_field(self): dd = DataSet() dd.add_field("x", [[1, 2, 3]] * 10) @@ -115,9 +121,12 @@ class TestDataSetMethods(unittest.TestCase): self.assertTrue(isinstance(res, list) and len(res) > 0) self.assertTrue(res[0], 4) + ds.apply(lambda ins: (len(ins["x"]), "hahaha"), new_field_name="k", ignore_type=True) + # expect no exception raised + def test_drop(self): ds = DataSet({"x": [[1, 2, 3, 4]] * 40, "y": [[5, 6], [7, 8, 9, 0]] * 20}) - ds.drop(lambda ins: len(ins["y"]) < 3) + ds.drop(lambda ins: len(ins["y"]) < 3, inplace=True) self.assertEqual(len(ds), 20) def test_contains(self): @@ -155,17 +164,20 @@ class TestDataSetMethods(unittest.TestCase): ds = DataSet({"x": [[1, 2, 3, 4]] * 10, "y": [[5, 6]] * 10}) self.assertEqual(ds.get_target_name(), [_ for _ in ds.field_arrays if ds.field_arrays[_].is_target]) + def test_split(self): + ds = DataSet({"x": [[1, 2, 3, 4]] * 10, "y": [[5, 6]] * 10}) + d1, d2 = ds.split(0.1) + def test_apply2(self): def split_sent(ins): return ins['raw_sentence'].split() - - dataset = DataSet.read_csv('test/data_for_tests/tutorial_sample_dataset.csv', headers=('raw_sentence', 'label'), - sep='\t') - dataset.drop(lambda x: len(x['raw_sentence'].split()) == 0) + csv_loader = CSVLoader(headers=['raw_sentence', 'label'],sep='\t') + dataset = csv_loader.load('test/data_for_tests/tutorial_sample_dataset.csv') + dataset.drop(lambda x: len(x['raw_sentence'].split()) == 0, inplace=True) dataset.apply(split_sent, new_field_name='words', is_input=True) # print(dataset) - def test_add_field(self): + def test_add_field_v2(self): ds = DataSet({"x": [3, 4]}) ds.add_field('y', [['hello', 'world'], ['this', 'is', 'a', 'test']], is_input=True, is_target=True) # ds.apply(lambda x:[x['x']]*3, is_input=True, is_target=True, new_field_name='y') @@ -194,19 +206,11 @@ class TestDataSetMethods(unittest.TestCase): self.assertTrue(isinstance(ans, FieldArray)) self.assertEqual(ans.content, [[5, 6]] * 10) - def test_reader(self): - # 跑通即可 - ds = DataSet().read_naive("test/data_for_tests/tutorial_sample_dataset.csv") - self.assertTrue(isinstance(ds, DataSet)) - self.assertTrue(len(ds) > 0) - - ds = DataSet().read_rawdata("test/data_for_tests/people_daily_raw.txt") - self.assertTrue(isinstance(ds, DataSet)) - self.assertTrue(len(ds) > 0) - - ds = DataSet().read_pos("test/data_for_tests/people.txt") - self.assertTrue(isinstance(ds, DataSet)) - self.assertTrue(len(ds) > 0) + def test_add_null(self): + # TODO test failed because 'fastNLP\core\field.py:143: RuntimeError' + ds = DataSet() + with self.assertRaises(RuntimeError) as RE: + ds.add_field('test', []) class TestDataSetIter(unittest.TestCase): diff --git a/test/core/test_fieldarray.py b/test/core/test_field.py similarity index 90% rename from test/core/test_fieldarray.py rename to test/core/test_field.py index 151d9335..1f6580c1 100644 --- a/test/core/test_fieldarray.py +++ b/test/core/test_field.py @@ -2,7 +2,7 @@ import unittest import numpy as np -from fastNLP.core.fieldarray import FieldArray +from fastNLP import FieldArray class TestFieldArrayInit(unittest.TestCase): @@ -155,6 +155,13 @@ class TestFieldArray(unittest.TestCase): self.assertEqual(len(fa), 3) self.assertEqual(fa[2], [1.2, 2.3, 3.4, 4.5, 5.6]) + def test_ignore_type(self): + # 测试新添加的参数ignore_type,用来跳过类型检查 + fa = FieldArray("y", [[1.1, 2.2, "jin", {}, "hahah"], [int, 2, "$", 4, 5]], is_input=True, ignore_type=True) + fa.append([1.2, 2.3, str, 4.5, print]) + + fa = FieldArray("y", [(1, "1"), (2, "2"), (3, "3"), (4, "4")], is_target=True, ignore_type=True) + class TestPadder(unittest.TestCase): @@ -163,7 +170,7 @@ class TestPadder(unittest.TestCase): 测试AutoPadder能否正常工作 :return: """ - from fastNLP.core.fieldarray import AutoPadder + from fastNLP import AutoPadder padder = AutoPadder() content = ['This is a str', 'this is another str'] self.assertListEqual(content, padder(content, None, np.str).tolist()) @@ -187,7 +194,7 @@ class TestPadder(unittest.TestCase): 测试EngChar2DPadder能不能正确使用 :return: """ - from fastNLP.core.fieldarray import EngChar2DPadder + from fastNLP import EngChar2DPadder padder = EngChar2DPadder(pad_length=0) contents = [1, 2] @@ -215,4 +222,14 @@ class TestPadder(unittest.TestCase): [[[1, 2, 3, -100, -100], [4, 5, -100, -100, -100], [7, 8, 9, 10, -100]], [[1, -100, -100, -100, -100], [-100, -100, -100, -100, -100], [-100, -100, -100, -100, -100]]], padder(contents, None, np.int64).tolist() - ) \ No newline at end of file + ) + + def test_None_dtype(self): + from fastNLP import AutoPadder + padder = AutoPadder() + content = [ + [[1, 2, 3], [4, 5], [7, 8, 9, 10]], + [[1]] + ] + ans = padder(content, None, None).tolist() + self.assertListEqual(content, ans) diff --git a/test/core/test_instance.py b/test/core/test_instance.py index 1342ba2c..207b44e9 100644 --- a/test/core/test_instance.py +++ b/test/core/test_instance.py @@ -1,33 +1,33 @@ import unittest -from fastNLP.core.instance import Instance +from fastNLP import Instance class TestCase(unittest.TestCase): - + def test_init(self): fields = {"x": [1, 2, 3], "y": [4, 5, 6]} ins = Instance(x=[1, 2, 3], y=[4, 5, 6]) self.assertTrue(isinstance(ins.fields, dict)) self.assertEqual(ins.fields, fields) - + ins = Instance(**fields) self.assertEqual(ins.fields, fields) - + def test_add_field(self): fields = {"x": [1, 2, 3], "y": [4, 5, 6]} ins = Instance(**fields) ins.add_field("z", [1, 1, 1]) fields.update({"z": [1, 1, 1]}) self.assertEqual(ins.fields, fields) - + def test_get_item(self): fields = {"x": [1, 2, 3], "y": [4, 5, 6], "z": [1, 1, 1]} ins = Instance(**fields) self.assertEqual(ins["x"], [1, 2, 3]) self.assertEqual(ins["y"], [4, 5, 6]) self.assertEqual(ins["z"], [1, 1, 1]) - + def test_repr(self): fields = {"x": [1, 2, 3], "y": [4, 5, 6], "z": [1, 1, 1]} ins = Instance(**fields) diff --git a/test/core/test_loss.py b/test/core/test_loss.py index a6d542fa..8db54615 100644 --- a/test/core/test_loss.py +++ b/test/core/test_loss.py @@ -3,7 +3,7 @@ import unittest import torch import torch.nn.functional as F -import fastNLP.core.losses as loss +import fastNLP as loss from fastNLP.core.losses import squash, unpad @@ -14,21 +14,21 @@ class TestLoss(unittest.TestCase): b = torch.empty(3, dtype=torch.long).random_(5) ans = ce({"my_predict": a}, {"my_truth": b}) self.assertEqual(ans, torch.nn.functional.cross_entropy(a, b)) - + def test_BCELoss(self): bce = loss.BCELoss(pred="my_predict", target="my_truth") a = torch.sigmoid(torch.randn((3, 5), requires_grad=False)) b = torch.randn((3, 5), requires_grad=False) ans = bce({"my_predict": a}, {"my_truth": b}) self.assertEqual(ans, torch.nn.functional.binary_cross_entropy(a, b)) - + def test_L1Loss(self): l1 = loss.L1Loss(pred="my_predict", target="my_truth") a = torch.randn(3, 5, requires_grad=False) b = torch.randn(3, 5) ans = l1({"my_predict": a}, {"my_truth": b}) self.assertEqual(ans, torch.nn.functional.l1_loss(a, b)) - + def test_NLLLoss(self): l1 = loss.NLLLoss(pred="my_predict", target="my_truth") a = F.log_softmax(torch.randn(3, 5, requires_grad=False), dim=0) @@ -43,34 +43,34 @@ class TestLosserError(unittest.TestCase): pred_dict = {"pred": torch.zeros(4, 3)} target_dict = {'target': torch.zeros(4).long()} los = loss.CrossEntropyLoss() - + print(los(pred_dict=pred_dict, target_dict=target_dict)) - + # def test_losser2(self): # (2) with corrupted size pred_dict = {"pred": torch.zeros(16, 3)} target_dict = {'target': torch.zeros(16, 3).long()} los = loss.CrossEntropyLoss() - + with self.assertRaises(RuntimeError): print(los(pred_dict=pred_dict, target_dict=target_dict)) - + def test_losser3(self): # (2) with corrupted size pred_dict = {"pred": torch.zeros(16, 3), 'stop_fast_param': 0} target_dict = {'target': torch.zeros(16).long()} los = loss.CrossEntropyLoss() - + print(los(pred_dict=pred_dict, target_dict=target_dict)) - + def test_check_error(self): l1 = loss.NLLLoss(pred="my_predict", target="my_truth") a = F.log_softmax(torch.randn(3, 5, requires_grad=False), dim=0) b = torch.tensor([1, 0, 4]) with self.assertRaises(Exception): ans = l1({"wrong_predict": a, "my": b}, {"my_truth": b}) - + with self.assertRaises(Exception): ans = l1({"my_predict": a}, {"truth": b, "my": a}) @@ -80,7 +80,7 @@ class TestLossUtils(unittest.TestCase): a, b = squash(torch.randn(3, 5), torch.randn(3, 5)) self.assertEqual(tuple(a.size()), (3, 5)) self.assertEqual(tuple(b.size()), (15,)) - + def test_unpad(self): a, b = unpad(torch.randn(5, 8, 3), torch.randn(5, 8)) self.assertEqual(tuple(a.size()), (5, 8, 3)) diff --git a/test/core/test_metrics.py b/test/core/test_metrics.py index 80ed54e2..f3b0178c 100644 --- a/test/core/test_metrics.py +++ b/test/core/test_metrics.py @@ -3,10 +3,40 @@ import unittest import numpy as np import torch -from fastNLP.core.metrics import AccuracyMetric -from fastNLP.core.metrics import BMESF1PreRecMetric -from fastNLP.core.metrics import pred_topk, accuracy_topk - +from fastNLP import AccuracyMetric +from fastNLP.core.metrics import _pred_topk, _accuracy_topk +from fastNLP.core.vocabulary import Vocabulary +from collections import Counter +from fastNLP.core.metrics import SpanFPreRecMetric + + +def _generate_tags(encoding_type, number_labels=4): + vocab = {} + for i in range(number_labels): + label = str(i) + for tag in encoding_type: + if tag == 'O': + if tag not in vocab: + vocab['O'] = len(vocab) + 1 + continue + vocab['{}-{}'.format(tag, label)] = len(vocab) + 1 # 其实表达的是这个的count + return vocab + + +def _convert_res_to_fastnlp_res(metric_result): + allen_result = {} + key_map = {'f1-measure-overall': "f", "recall-overall": "rec", "precision-overall": "pre"} + for key, value in metric_result.items(): + if key in key_map: + key = key_map[key] + else: + label = key.split('-')[-1] + if key.startswith('f1'): + key = 'f-{}'.format(label) + else: + key = '{}-{}'.format(key[:3], label) + allen_result[key] = round(value, 6) + return allen_result class TestAccuracyMetric(unittest.TestCase): def test_AccuracyMetric1(self): @@ -14,24 +44,24 @@ class TestAccuracyMetric(unittest.TestCase): pred_dict = {"pred": torch.zeros(4, 3)} target_dict = {'target': torch.zeros(4)} metric = AccuracyMetric() - - metric(pred_dict=pred_dict, target_dict=target_dict, ) + + metric(pred_dict=pred_dict, target_dict=target_dict) print(metric.get_metric()) - + def test_AccuracyMetric2(self): # (2) with corrupted size try: pred_dict = {"pred": torch.zeros(4, 3, 2)} target_dict = {'target': torch.zeros(4)} metric = AccuracyMetric() - + metric(pred_dict=pred_dict, target_dict=target_dict, ) print(metric.get_metric()) except Exception as e: print(e) return - self.assertTrue(True, False), "No exception catches." - + print("No exception catches.") + def test_AccuracyMetric3(self): # (3) the second batch is corrupted size try: @@ -39,17 +69,17 @@ class TestAccuracyMetric(unittest.TestCase): pred_dict = {"pred": torch.zeros(4, 3, 2)} target_dict = {'target': torch.zeros(4, 3)} metric(pred_dict=pred_dict, target_dict=target_dict) - + pred_dict = {"pred": torch.zeros(4, 3, 2)} target_dict = {'target': torch.zeros(4)} metric(pred_dict=pred_dict, target_dict=target_dict) - + print(metric.get_metric()) except Exception as e: print(e) return self.assertTrue(True, False), "No exception catches." - + def test_AccuaryMetric4(self): # (5) check reset metric = AccuracyMetric() @@ -61,7 +91,7 @@ class TestAccuracyMetric(unittest.TestCase): self.assertTrue(isinstance(res, dict)) self.assertTrue("acc" in res) self.assertAlmostEqual(res["acc"], float(ans.float().mean()), places=3) - + def test_AccuaryMetric5(self): # (5) check reset metric = AccuracyMetric() @@ -71,7 +101,7 @@ class TestAccuracyMetric(unittest.TestCase): res = metric.get_metric(reset=False) ans = (torch.argmax(pred_dict["pred"], dim=2).float() == target_dict["target"]).float().mean() self.assertAlmostEqual(res["acc"], float(ans), places=4) - + def test_AccuaryMetric6(self): # (6) check numpy array is not acceptable try: @@ -83,7 +113,7 @@ class TestAccuracyMetric(unittest.TestCase): print(e) return self.assertTrue(True, False), "No exception catches." - + def test_AccuaryMetric7(self): # (7) check map, match metric = AccuracyMetric(pred='predictions', target='targets') @@ -93,12 +123,11 @@ class TestAccuracyMetric(unittest.TestCase): res = metric.get_metric() ans = (torch.argmax(pred_dict["predictions"], dim=2).float() == target_dict["targets"]).float().mean() self.assertAlmostEqual(res["acc"], float(ans), places=4) - + def test_AccuaryMetric8(self): - # (8) check map, does not match. use stop_fast_param to stop fast param map try: metric = AccuracyMetric(pred='predictions', target='targets') - pred_dict = {"prediction": torch.zeros(4, 3, 2), "stop_fast_param": 1} + pred_dict = {"prediction": torch.zeros(4, 3, 2)} target_dict = {'targets': torch.zeros(4, 3)} metric(pred_dict=pred_dict, target_dict=target_dict, ) self.assertDictEqual(metric.get_metric(), {'acc': 1}) @@ -106,7 +135,7 @@ class TestAccuracyMetric(unittest.TestCase): print(e) return self.assertTrue(True, False), "No exception catches." - + def test_AccuaryMetric9(self): # (9) check map, include unused try: @@ -119,12 +148,12 @@ class TestAccuracyMetric(unittest.TestCase): print(e) return self.assertTrue(True, False), "No exception catches." - + def test_AccuaryMetric10(self): # (10) check _fast_metric try: metric = AccuracyMetric() - pred_dict = {"predictions": torch.zeros(4, 3, 2), "masks": torch.zeros(4, 3)} + pred_dict = {"predictions": torch.zeros(4, 3, 2), "seq_len": torch.ones(3) * 3} target_dict = {'targets': torch.zeros(4, 3)} metric(pred_dict=pred_dict, target_dict=target_dict) self.assertDictEqual(metric.get_metric(), {'acc': 1}) @@ -132,242 +161,181 @@ class TestAccuracyMetric(unittest.TestCase): print(e) return self.assertTrue(True, False), "No exception catches." + + def test_seq_len(self): + N = 256 + seq_len = torch.zeros(N).long() + seq_len[0] = 2 + pred = {'pred': torch.ones(N, 2)} + target = {'target': torch.ones(N, 2), 'seq_len': seq_len} + metric = AccuracyMetric() + metric(pred_dict=pred, target_dict=target) + self.assertDictEqual(metric.get_metric(), {'acc': 1.}) + seq_len[1:] = 1 + metric(pred_dict=pred, target_dict=target) + self.assertDictEqual(metric.get_metric(), {'acc': 1.}) + class SpanF1PreRecMetric(unittest.TestCase): def test_case1(self): - from fastNLP.core.metrics import bmes_tag_to_spans - from fastNLP.core.metrics import bio_tag_to_spans - + from fastNLP.core.metrics import _bmes_tag_to_spans + from fastNLP.core.metrics import _bio_tag_to_spans + bmes_lst = ['M-8', 'S-2', 'S-0', 'B-9', 'B-6', 'E-5', 'B-7', 'S-2', 'E-7', 'S-8'] bio_lst = ['O-8', 'O-2', 'B-0', 'O-9', 'I-6', 'I-5', 'I-7', 'I-2', 'I-7', 'O-8'] expect_bmes_res = set() - expect_bmes_res.update([('8', (0, 0)), ('2', (1, 1)), ('0', (2, 2)), ('9', (3, 3)), ('6', (4, 4)), - ('5', (5, 5)), ('7', (6, 6)), ('2', (7, 7)), ('7', (8, 8)), ('8', (9, 9))]) + expect_bmes_res.update([('8', (0, 1)), ('2', (1, 2)), ('0', (2, 3)), ('9', (3, 4)), ('6', (4, 5)), + ('5', (5, 6)), ('7', (6, 7)), ('2', (7, 8)), ('7', (8, 9)), ('8', (9, 10))]) expect_bio_res = set() - expect_bio_res.update([('7', (8, 8)), ('0', (2, 2)), ('2', (7, 7)), ('5', (5, 5)), - ('6', (4, 4)), ('7', (6, 6))]) - self.assertSetEqual(expect_bmes_res,set(bmes_tag_to_spans(bmes_lst))) - self.assertSetEqual(expect_bio_res, set(bio_tag_to_spans(bio_lst))) - # 已与allennlp对应函数做过验证,但由于测试不能依赖allennlp,所以这里只是截取上面的例子做固定测试 - # from allennlp.data.dataset_readers.dataset_utils import bio_tags_to_spans as allen_bio_tags_to_spans - # from allennlp.data.dataset_readers.dataset_utils import bmes_tags_to_spans as allen_bmes_tags_to_spans - # for i in range(1000): - # strs = list(map(str, np.random.randint(100, size=1000))) - # bmes = list('bmes'.upper()) - # bmes_strs = [str_ + '-' + tag for tag, str_ in zip(strs, np.random.choice(bmes, size=len(strs)))] - # bio = list('bio'.upper()) - # bio_strs = [str_ + '-' + tag for tag, str_ in zip(strs, np.random.choice(bio, size=len(strs)))] - # self.assertSetEqual(set(allen_bmes_tags_to_spans(bmes_strs)),set(bmes_tag_to_spans(bmes_strs))) - # self.assertSetEqual(set(allen_bio_tags_to_spans(bio_strs)), set(bio_tag_to_spans(bio_strs))) + expect_bio_res.update([('7', (8, 9)), ('0', (2, 3)), ('2', (7, 8)), ('5', (5, 6)), + ('6', (4, 5)), ('7', (6, 7))]) + self.assertSetEqual(expect_bmes_res, set(_bmes_tag_to_spans(bmes_lst))) + self.assertSetEqual(expect_bio_res, set(_bio_tag_to_spans(bio_lst))) def test_case2(self): # 测试不带label的 - from fastNLP.core.metrics import bmes_tag_to_spans - from fastNLP.core.metrics import bio_tag_to_spans - + from fastNLP.core.metrics import _bmes_tag_to_spans + from fastNLP.core.metrics import _bio_tag_to_spans + bmes_lst = ['B', 'E', 'B', 'S', 'B', 'M', 'E', 'M', 'B', 'E'] bio_lst = ['I', 'B', 'O', 'O', 'I', 'O', 'I', 'B', 'O', 'O'] expect_bmes_res = set() - expect_bmes_res.update([('', (0, 1)), ('', (2, 2)), ('', (3, 3)), ('', (4, 6)), ('', (7, 7)), ('', (8, 9))]) + expect_bmes_res.update([('', (0, 2)), ('', (2, 3)), ('', (3, 4)), ('', (4, 7)), ('', (7, 8)), ('', (8, 10))]) expect_bio_res = set() - expect_bio_res.update([('', (7, 7)), ('', (6, 6)), ('', (4, 4)), ('', (0, 0)), ('', (1, 1))]) - self.assertSetEqual(expect_bmes_res,set(bmes_tag_to_spans(bmes_lst))) - self.assertSetEqual(expect_bio_res, set(bio_tag_to_spans(bio_lst))) - # 已与allennlp对应函数做过验证,但由于测试不能依赖allennlp,所以这里只是截取上面的例子做固定测试 - # from allennlp.data.dataset_readers.dataset_utils import bio_tags_to_spans as allen_bio_tags_to_spans - # from allennlp.data.dataset_readers.dataset_utils import bmes_tags_to_spans as allen_bmes_tags_to_spans - # for i in range(1000): - # bmes = list('bmes'.upper()) - # bmes_strs = np.random.choice(bmes, size=1000) - # bio = list('bio'.upper()) - # bio_strs = np.random.choice(bio, size=100) - # self.assertSetEqual(set(allen_bmes_tags_to_spans(bmes_strs)),set(bmes_tag_to_spans(bmes_strs))) - # self.assertSetEqual(set(allen_bio_tags_to_spans(bio_strs)), set(bio_tag_to_spans(bio_strs))) - - def tese_case3(self): - from fastNLP.core.vocabulary import Vocabulary - from collections import Counter - from fastNLP.core.metrics import SpanFPreRecMetric - # 与allennlp测试能否正确计算f metric - # - def generate_allen_tags(encoding_type, number_labels=4): - vocab = {} - for i in range(number_labels): - label = str(i) - for tag in encoding_type: - if tag == 'O': - if tag not in vocab: - vocab['O'] = len(vocab) + 1 - continue - vocab['{}-{}'.format(tag, label)] = len(vocab) + 1 # 其实表达的是这个的count - return vocab + expect_bio_res.update([('', (7, 8)), ('', (6, 7)), ('', (4, 5)), ('', (0, 1)), ('', (1, 2))]) + self.assertSetEqual(expect_bmes_res, set(_bmes_tag_to_spans(bmes_lst))) + self.assertSetEqual(expect_bio_res, set(_bio_tag_to_spans(bio_lst))) + def test_case3(self): number_labels = 4 # bio tag fastnlp_bio_vocab = Vocabulary(unknown=None, padding=None) - fastnlp_bio_vocab.word_count = Counter(generate_allen_tags('BIO', number_labels)) + fastnlp_bio_vocab.word_count = Counter(_generate_tags('BIO', number_labels)) fastnlp_bio_metric = SpanFPreRecMetric(tag_vocab=fastnlp_bio_vocab, only_gross=False) - bio_sequence = torch.FloatTensor( - [[[-0.9543, -1.4357, -0.2365, 0.2438, 1.0312, -1.4302, 0.3011, - 0.0470, 0.0971], - [-0.6638, -0.7116, -1.9804, 0.2787, -0.2732, -0.9501, -1.4523, - 0.7987, -0.3970], - [0.2939, 0.8132, -0.0903, -2.8296, 0.2080, -0.9823, -0.1898, - 0.6880, 1.4348], - [-0.1886, 0.0067, -0.6862, -0.4635, 2.2776, 0.0710, -1.6793, - -1.6876, -0.8917], - [-0.7663, 0.6377, 0.8669, 0.1237, 1.7628, 0.0313, -1.0824, - 1.4217, 0.2622]], + bio_sequence = torch.FloatTensor([[[-0.4424, -0.4579, -0.7376, 1.8129, 0.1316, 1.6566, -1.2169, + -0.3782, 0.8240], + [-1.2348, -0.1876, -0.1462, -0.4834, -0.6692, -0.9735, 1.1563, + -0.3562, -1.4116], + [ 1.6550, -0.9555, 0.3782, -1.3160, -1.5835, -0.3443, -1.7858, + 2.0023, 0.7075], + [-0.3772, -0.5447, -1.5631, 1.1614, 1.4598, -1.2764, 0.5186, + 0.3832, -0.1540], + [-0.1011, 0.0600, 1.1090, -0.3545, 0.1284, 1.1484, -1.0120, + -1.3508, -0.9513], + [ 1.8948, 0.8627, -2.1359, 1.3740, -0.7499, 1.5019, 0.6919, + -0.0842, -0.4294]], + + [[-0.2802, 0.6941, -0.4788, -0.3845, 1.7752, 1.2950, -1.9490, + -1.4138, -0.8853], + [-1.3752, -0.5457, -0.5305, 0.4018, 0.2934, 0.7931, 2.3845, + -1.0726, 0.0364], + [ 0.3621, 0.2609, 0.1269, -0.5950, 0.7212, 0.5959, 1.6264, + -0.8836, -0.9320], + [ 0.2003, -1.0758, -1.1560, -0.6472, -1.7549, 0.1264, 0.6044, + -1.6857, 1.1571], + [ 1.4277, -0.4915, 0.4496, 2.2027, 0.0730, -3.1792, -0.5125, + -0.5837, 1.0184], + [ 1.9495, 1.7145, -0.2143, -0.1230, -0.2205, 0.8250, 0.4943, + -0.9025, 0.0864]]]) + bio_target = torch.LongTensor([[3, 6, 0, 8, 2, 4], + [4, 1, 7, 0, 4, 7]]) + fastnlp_bio_metric({'pred': bio_sequence, 'seq_len': torch.LongTensor([6, 6])}, {'target': bio_target}) + expect_bio_res = {'pre-1': 0.333333, 'rec-1': 0.333333, 'f-1': 0.333333, 'pre-2': 0.5, 'rec-2': 0.5, + 'f-2': 0.5, 'pre-0': 0.0, 'rec-0': 0.0, 'f-0': 0.0, 'pre-3': 0.0, 'rec-3': 0.0, + 'f-3': 0.0, 'pre': 0.222222, 'rec': 0.181818, 'f': 0.2} - [[0.1529, 0.7474, -0.9037, 1.5287, 0.2771, 0.2223, 0.8136, - 1.3592, -0.8973], - [0.4515, -0.5235, 0.3265, -1.1947, 0.8308, 1.8754, -0.4887, - -0.4025, -0.3417], - [-0.7855, 0.1615, -0.1272, -1.9289, -0.5181, 1.9742, -0.9698, - 0.2861, -0.3966], - [-0.8291, -0.8823, -1.1496, 0.2164, 1.3390, -0.3964, -0.5275, - 0.0213, 1.4777], - [-1.1299, 0.0627, -0.1358, -1.5951, 0.4484, -0.6081, -1.9566, - 1.3024, 0.2001]]] - ) - bio_target = torch.LongTensor([[5., 0., 3., 3., 3.], - [5., 6., 8., 6., 0.]]) - fastnlp_bio_metric({'pred': bio_sequence, 'seq_lens': torch.LongTensor([5, 5])}, {'target': bio_target}) - expect_bio_res = {'pre-1': 0.24999999999999373, 'rec-1': 0.499999999999975, 'f-1': 0.33333333333327775, - 'pre-2': 0.0, 'rec-2': 0.0, 'f-2': 0.0, 'pre-3': 0.0, 'rec-3': 0.0, 'f-3': 0.0, 'pre-0': 0.0, - 'rec-0': 0.0, 'f-0': 0.0, 'pre': 0.12499999999999845, 'rec': 0.12499999999999845, - 'f': 0.12499999999994846} self.assertDictEqual(expect_bio_res, fastnlp_bio_metric.get_metric()) - #bmes tag - bmes_sequence = torch.FloatTensor( - [[[0.6536, -0.7179, 0.6579, 1.2503, 0.4176, 0.6696, 0.2352, - -0.4085, 0.4084, -0.4185, 1.4172, -0.9162, -0.2679, 0.3332, - -0.3505, -0.6002], - [0.3238, -1.2378, -1.3304, -0.4903, 1.4518, -0.1868, -0.7641, - 1.6199, -0.8877, 0.1449, 0.8995, -0.5810, 0.1041, 0.1002, - 0.4439, 0.2514], - [-0.8362, 2.9526, 0.8008, 0.1193, 1.0488, 0.6670, 1.1696, - -1.1006, -0.8540, -0.1600, -0.9519, -0.2749, -0.4948, -1.4753, - 0.5802, -0.0516], - [-0.8383, -1.7292, -1.4079, -1.5023, 0.5383, 0.6653, 0.3121, - 4.1249, -0.4173, -0.2043, 1.7755, 1.1110, -1.7069, -0.0390, - -0.9242, -0.0333], - [0.9088, -0.4955, -0.5076, 0.3732, 0.0283, -0.0263, -1.0393, - 0.7734, 1.0968, 0.4132, -1.3647, -0.5762, 0.6678, 0.8809, - -0.3779, -0.3195]], - - [[-0.4638, -0.5939, -0.1052, -0.5573, 0.4600, -1.3484, 0.1753, - 0.0685, 0.3663, -0.6789, 0.0097, 1.0327, -0.0212, -0.9957, - -0.1103, 0.4417], - [-0.2903, 0.9205, -1.5758, -1.0421, 0.2921, -0.2142, -0.3049, - -0.0879, -0.4412, -1.3195, -0.0657, -0.2986, 0.7214, 0.0631, - -0.6386, 0.2797], - [0.6440, -0.3748, 1.2912, -0.0170, 0.7447, 1.4075, -0.4947, - 0.4123, -0.8447, -0.5502, 0.3520, -0.2832, 0.5019, -0.1522, - 1.1237, -1.5385], - [0.2839, -0.7649, 0.9067, -0.1163, -1.3789, 0.2571, -1.3977, - -0.3680, -0.8902, -0.6983, -1.1583, 1.2779, 0.2197, 0.1376, - -0.0591, -0.2461], - [-0.2977, -1.8564, -0.5347, 1.0011, -1.1260, 0.4252, -2.0097, - 2.6973, -0.8308, -1.4939, 0.9865, -0.3935, 0.2743, 0.1142, - -0.7344, -1.2046]]] - ) - bmes_target = torch.LongTensor([[ 9., 6., 1., 9., 15.], - [ 6., 15., 6., 15., 5.]]) - - fastnlp_bmes_vocab = Vocabulary(unknown=None, padding=None) - fastnlp_bmes_vocab.word_count = Counter(generate_allen_tags('BMES', number_labels)) - fastnlp_bmes_metric = SpanFPreRecMetric(tag_vocab=fastnlp_bmes_vocab, only_gross=False, encoding_type='bmes') - fastnlp_bmes_metric({'pred': bmes_sequence, 'seq_lens': torch.LongTensor([20, 20])}, {'target': bmes_target}) - - expect_bmes_res = {'f-3': 0.6666666666665778, 'pre-3': 0.499999999999975, 'rec-3': 0.9999999999999001, - 'f-0': 0.0, 'pre-0': 0.0, 'rec-0': 0.0, 'f-1': 0.33333333333327775, - 'pre-1': 0.24999999999999373, 'rec-1': 0.499999999999975, 'f-2': 0.7499999999999314, - 'pre-2': 0.7499999999999812, 'rec-2': 0.7499999999999812, 'f': 0.49999999999994504, - 'pre': 0.499999999999995, 'rec': 0.499999999999995} - - self.assertDictEqual(fastnlp_bmes_metric.get_metric(), expect_bmes_res) - - # 已经和allennlp做过验证,但由于不能依赖allennlp,所以注释了以下代码 - # from allennlp.data.vocabulary import Vocabulary as allen_Vocabulary - # from allennlp.training.metrics import SpanBasedF1Measure - # allen_bio_vocab = allen_Vocabulary({"tags": generate_allen_tags('BIO', number_labels)}, - # non_padded_namespaces=['tags']) - # allen_bio_metric = SpanBasedF1Measure(allen_bio_vocab, 'tags') - # bio_sequence = torch.randn(size=(2, 20, 2 * number_labels + 1)) - # bio_target = torch.randint(2 * number_labels + 1, size=(2, 20)) - # allen_bio_metric(bio_sequence, bio_target, torch.ones(2, 20)) - # fastnlp_bio_vocab = Vocabulary(unknown=None, padding=None) - # fastnlp_bio_vocab.word_count = Counter(generate_allen_tags('BIO', number_labels)) - # fastnlp_bio_metric = SpanFPreRecMetric(tag_vocab=fastnlp_bio_vocab, only_gross=False) - # - # def convert_allen_res_to_fastnlp_res(metric_result): - # allen_result = {} - # key_map = {'f1-measure-overall': "f", "recall-overall": "rec", "precision-overall": "pre"} - # for key, value in metric_result.items(): - # if key in key_map: - # key = key_map[key] - # else: - # label = key.split('-')[-1] - # if key.startswith('f1'): - # key = 'f-{}'.format(label) - # else: - # key = '{}-{}'.format(key[:3], label) - # allen_result[key] = value - # return allen_result - # - # # print(convert_allen_res_to_fastnlp_res(allen_bio_metric.get_metric())) - # # print(fastnlp_bio_metric.get_metric()) - # self.assertDictEqual(convert_allen_res_to_fastnlp_res(allen_bio_metric.get_metric()), - # fastnlp_bio_metric.get_metric()) - # - # allen_bmes_vocab = allen_Vocabulary({"tags": generate_allen_tags('BMES', number_labels)}) - # allen_bmes_metric = SpanBasedF1Measure(allen_bmes_vocab, 'tags', label_encoding='BMES') - # fastnlp_bmes_vocab = Vocabulary(unknown=None, padding=None) - # fastnlp_bmes_vocab.word_count = Counter(generate_allen_tags('BMES', number_labels)) - # fastnlp_bmes_metric = SpanFPreRecMetric(tag_vocab=fastnlp_bmes_vocab, only_gross=False, encoding_type='bmes') - # bmes_sequence = torch.randn(size=(2, 20, 4 * number_labels)) - # bmes_target = torch.randint(4 * number_labels, size=(2, 20)) - # allen_bmes_metric(bmes_sequence, bmes_target, torch.ones(2, 20)) - # fastnlp_bmes_metric({'pred': bmes_sequence, 'seq_lens': torch.LongTensor([20, 20])}, {'target': bmes_target}) - # - # # print(convert_allen_res_to_fastnlp_res(allen_bmes_metric.get_metric())) - # # print(fastnlp_bmes_metric.get_metric()) - # self.assertDictEqual(convert_allen_res_to_fastnlp_res(allen_bmes_metric.get_metric()), - # fastnlp_bmes_metric.get_metric()) - -class TestBMESF1PreRecMetric(unittest.TestCase): - def test_case1(self): - seq_lens = torch.LongTensor([4, 2]) - pred = torch.randn(2, 4, 4) - target = torch.LongTensor([[0, 1, 2, 3], - [3, 3, 0, 0]]) - pred_dict = {'pred': pred} - target_dict = {'target': target, 'seq_lens': seq_lens} - - metric = BMESF1PreRecMetric() - metric(pred_dict, target_dict) - metric.get_metric() - - def test_case2(self): - # 测试相同两个seqence,应该给出{f1: 1, precision:1, recall:1} - seq_lens = torch.LongTensor([4, 2]) - target = torch.LongTensor([[0, 1, 2, 3], - [3, 3, 0, 0]]) - pred_dict = {'pred': target} - target_dict = {'target': target, 'seq_lens': seq_lens} - - metric = BMESF1PreRecMetric() - metric(pred_dict, target_dict) - self.assertDictEqual(metric.get_metric(), {'f': 1.0, 'pre': 1.0, 'rec': 1.0}) + def test_case4(self): + # bmes tag + def _generate_samples(): + target = [] + seq_len = [] + vocab = Vocabulary(unknown=None, padding=None) + for i in range(3): + target_i = [] + seq_len_i = 0 + for j in range(1, 10): + word_len = np.random.randint(1, 5) + seq_len_i += word_len + if word_len==1: + target_i.append('S') + else: + target_i.append('B') + target_i.extend(['M']*(word_len-2)) + target_i.append('E') + vocab.add_word_lst(target_i) + target.append(target_i) + seq_len.append(seq_len_i) + target_ = np.zeros((3, max(seq_len))) + for i in range(3): + target_i = [vocab.to_index(t) for t in target[i]] + target_[i, :seq_len[i]] = target_i + return target_, target, seq_len, vocab + def get_eval(raw_target, pred, vocab, seq_len): + pred = pred.argmax(dim=-1).tolist() + tp = 0 + gold = 0 + seg = 0 + pred_target = [] + for i in range(len(seq_len)): + tags = [vocab.to_word(p) for p in pred[i][:seq_len[i]]] + spans = [] + prev_bmes_tag = None + for idx, tag in enumerate(tags): + if tag in ('B', 'S'): + spans.append([idx, idx]) + elif tag in ('M', 'E') and prev_bmes_tag in ('B', 'M'): + spans[-1][1] = idx + else: + spans.append([idx, idx]) + prev_bmes_tag = tag + tmp = [] + for span in spans: + if span[1]-span[0]>0: + tmp.extend(['B'] + ['M']*(span[1]-span[0]-1) + ['E']) + else: + tmp.append('S') + pred_target.append(tmp) + for i in range(len(seq_len)): + raw_pred = pred_target[i] + start = 0 + for j in range(seq_len[i]): + if raw_target[i][j] in ('E', 'S'): + flag = True + for k in range(start, j+1): + if raw_target[i][k]!=raw_pred[k]: + flag = False + break + if flag: + tp += 1 + start = j + 1 + gold += 1 + if raw_pred[j] in ('E', 'S'): + seg += 1 + + pre = round(tp/seg, 6) + rec = round(tp/gold, 6) + return {'f': round(2*pre*rec/(pre+rec), 6), 'pre': pre, 'rec':rec} + + target, raw_target, seq_len, vocab = _generate_samples() + pred = torch.randn(3, max(seq_len), 4) + + expected_metric = get_eval(raw_target, pred, vocab, seq_len) + metric = SpanFPreRecMetric(vocab, encoding_type='bmes') + metric({'pred': pred, 'seq_len':torch.LongTensor(seq_len)}, {'target': torch.from_numpy(target)}) + # print(metric.get_metric(reset=False)) + # print(expected_metric) + metric_value = metric.get_metric() + for key, value in expected_metric.items(): + self.assertAlmostEqual(value, metric_value[key], places=5) class TestUsefulFunctions(unittest.TestCase): # 测试metrics.py中一些看上去挺有用的函数 def test_case_1(self): # multi-class - _ = accuracy_topk(np.random.randint(0, 3, size=(10, 1)), np.random.randint(0, 3, size=(10, 1)), k=3) - _ = pred_topk(np.random.randint(0, 3, size=(10, 1))) - + _ = _accuracy_topk(np.random.randint(0, 3, size=(10, 1)), np.random.randint(0, 3, size=(10, 1)), k=3) + _ = _pred_topk(np.random.randint(0, 3, size=(10, 1))) + # 跑通即可 diff --git a/test/core/test_optimizer.py b/test/core/test_optimizer.py index 83ed6000..b9a1c271 100644 --- a/test/core/test_optimizer.py +++ b/test/core/test_optimizer.py @@ -2,7 +2,7 @@ import unittest import torch -from fastNLP.core.optimizer import SGD, Adam +from fastNLP import SGD, Adam class TestOptim(unittest.TestCase): @@ -12,42 +12,42 @@ class TestOptim(unittest.TestCase): self.assertTrue("momentum" in optim.__dict__["settings"]) res = optim.construct_from_pytorch(torch.nn.Linear(10, 3).parameters()) self.assertTrue(isinstance(res, torch.optim.SGD)) - + optim = SGD(lr=0.001) self.assertEqual(optim.__dict__["settings"]["lr"], 0.001) res = optim.construct_from_pytorch(torch.nn.Linear(10, 3).parameters()) self.assertTrue(isinstance(res, torch.optim.SGD)) - + optim = SGD(lr=0.002, momentum=0.989) self.assertEqual(optim.__dict__["settings"]["lr"], 0.002) self.assertEqual(optim.__dict__["settings"]["momentum"], 0.989) - + optim = SGD(0.001) self.assertEqual(optim.__dict__["settings"]["lr"], 0.001) res = optim.construct_from_pytorch(torch.nn.Linear(10, 3).parameters()) self.assertTrue(isinstance(res, torch.optim.SGD)) - + with self.assertRaises(TypeError): _ = SGD("???") with self.assertRaises(TypeError): _ = SGD(0.001, lr=0.002) - + def test_Adam(self): optim = Adam(model_params=torch.nn.Linear(10, 3).parameters()) self.assertTrue("lr" in optim.__dict__["settings"]) self.assertTrue("weight_decay" in optim.__dict__["settings"]) res = optim.construct_from_pytorch(torch.nn.Linear(10, 3).parameters()) self.assertTrue(isinstance(res, torch.optim.Adam)) - + optim = Adam(lr=0.001) self.assertEqual(optim.__dict__["settings"]["lr"], 0.001) res = optim.construct_from_pytorch(torch.nn.Linear(10, 3).parameters()) self.assertTrue(isinstance(res, torch.optim.Adam)) - + optim = Adam(lr=0.002, weight_decay=0.989) self.assertEqual(optim.__dict__["settings"]["lr"], 0.002) self.assertEqual(optim.__dict__["settings"]["weight_decay"], 0.989) - + optim = Adam(0.001) self.assertEqual(optim.__dict__["settings"]["lr"], 0.001) res = optim.construct_from_pytorch(torch.nn.Linear(10, 3).parameters()) diff --git a/test/core/test_predictor.py b/test/core/test_predictor.py index c779e3ac..701353dc 100644 --- a/test/core/test_predictor.py +++ b/test/core/test_predictor.py @@ -7,7 +7,6 @@ import torch from fastNLP.core.dataset import DataSet from fastNLP.core.instance import Instance from fastNLP.core.predictor import Predictor -from fastNLP.modules.encoder.linear import Linear def prepare_fake_dataset(): @@ -27,7 +26,7 @@ def prepare_fake_dataset(): class LinearModel(torch.nn.Module): def __init__(self): super(LinearModel, self).__init__() - self.linear = Linear(2, 1) + self.linear = torch.nn.Linear(2, 1) def forward(self, x): return {"predict": self.linear(x)} diff --git a/test/core/test_sampler.py b/test/core/test_sampler.py index b23af470..703a9428 100644 --- a/test/core/test_sampler.py +++ b/test/core/test_sampler.py @@ -3,18 +3,12 @@ import unittest import torch -from fastNLP.core.dataset import DataSet -from fastNLP.core.sampler import convert_to_torch_tensor, SequentialSampler, RandomSampler, \ - k_means_1d, k_means_bucketing, simple_sort_bucketing, BucketSampler +from fastNLP import DataSet +from fastNLP import SequentialSampler, RandomSampler, BucketSampler +from fastNLP.core.sampler import k_means_1d, k_means_bucketing, simple_sort_bucketing class TestSampler(unittest.TestCase): - def test_convert_to_torch_tensor(self): - data = [[1, 2, 3, 4, 5], [5, 4, 3, 2, 1], [1, 3, 4, 5, 2]] - ans = convert_to_torch_tensor(data, False) - assert isinstance(ans, torch.Tensor) - assert tuple(ans.shape) == (3, 5) - def test_sequential_sampler(self): sampler = SequentialSampler() data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10] @@ -44,7 +38,7 @@ class TestSampler(unittest.TestCase): assert len(_) == 10 def test_BucketSampler(self): - sampler = BucketSampler(num_buckets=3, batch_size=16, seq_lens_field_name="seq_len") + sampler = BucketSampler(num_buckets=3, batch_size=16, seq_len_field_name="seq_len") data_set = DataSet({"x": [[0] * random.randint(1, 10)] * 10, "y": [[5, 6]] * 10}) data_set.apply(lambda ins: len(ins["x"]), new_field_name="seq_len") indices = sampler(data_set) diff --git a/test/core/test_tester.py b/test/core/test_tester.py index d606c0b8..d0267cce 100644 --- a/test/core/test_tester.py +++ b/test/core/test_tester.py @@ -1,32 +1,25 @@ import unittest +import numpy as np +from torch import nn +import time +from fastNLP import DataSet +from fastNLP import Instance +from fastNLP import AccuracyMetric +from fastNLP import Tester data_name = "pku_training.utf8" pickle_path = "data_for_tests" -import numpy as np -import torch.nn.functional as F -from torch import nn -import time -from fastNLP.core.utils import CheckError -from fastNLP.core.dataset import DataSet -from fastNLP.core.instance import Instance -from fastNLP.core.losses import BCELoss -from fastNLP.core.losses import CrossEntropyLoss -from fastNLP.core.metrics import AccuracyMetric -from fastNLP.core.optimizer import SGD -from fastNLP.core.tester import Tester -from fastNLP.models.base_model import NaiveClassifier - def prepare_fake_dataset(): mean = np.array([-3, -3]) cov = np.array([[1, 0], [0, 1]]) class_A = np.random.multivariate_normal(mean, cov, size=(1000,)) - + mean = np.array([3, 3]) cov = np.array([[1, 0], [0, 1]]) class_B = np.random.multivariate_normal(mean, cov, size=(1000,)) - + data_set = DataSet([Instance(x=[float(item[0]), float(item[1])], y=[0.0]) for item in class_A] + [Instance(x=[float(item[0]), float(item[1])], y=[1.0]) for item in class_B]) return data_set @@ -39,6 +32,7 @@ def prepare_fake_dataset2(*args, size=100): data[arg] = np.random.randn(size, 5) return DataSet(data=data) + class TestTester(unittest.TestCase): def test_case_1(self): # 检查报错提示能否正确提醒用户 @@ -46,10 +40,12 @@ class TestTester(unittest.TestCase): dataset.rename_field('x_unused', 'x2') dataset.set_input('x1', 'x2') dataset.set_target('y', 'x1') + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) + def forward(self, x1, x2): x1 = self.fc(x1) x2 = self.fc(x2) @@ -57,7 +53,7 @@ class TestTester(unittest.TestCase): time.sleep(0.1) # loss = F.cross_entropy(x, y) return {'preds': x} - + model = Model() with self.assertRaises(NameError): tester = Tester( diff --git a/test/core/test_trainer.py b/test/core/test_trainer.py index 36062ef7..f559eac5 100644 --- a/test/core/test_trainer.py +++ b/test/core/test_trainer.py @@ -5,25 +5,24 @@ import numpy as np import torch.nn.functional as F from torch import nn -from fastNLP.core.dataset import DataSet -from fastNLP.core.instance import Instance -from fastNLP.core.losses import BCELoss -from fastNLP.core.losses import CrossEntropyLoss -from fastNLP.core.metrics import AccuracyMetric -from fastNLP.core.optimizer import SGD -from fastNLP.core.trainer import Trainer +from fastNLP import DataSet +from fastNLP import Instance +from fastNLP import BCELoss +from fastNLP import CrossEntropyLoss +from fastNLP import AccuracyMetric +from fastNLP import SGD +from fastNLP import Trainer from fastNLP.models.base_model import NaiveClassifier - def prepare_fake_dataset(): mean = np.array([-3, -3]) cov = np.array([[1, 0], [0, 1]]) class_A = np.random.multivariate_normal(mean, cov, size=(1000,)) - + mean = np.array([3, 3]) cov = np.array([[1, 0], [0, 1]]) class_B = np.random.multivariate_normal(mean, cov, size=(1000,)) - + data_set = DataSet([Instance(x=[float(item[0]), float(item[1])], y=[0.0]) for item in class_A] + [Instance(x=[float(item[0]), float(item[1])], y=[1.0]) for item in class_B]) return data_set @@ -42,11 +41,11 @@ class TrainerTestGround(unittest.TestCase): data_set = prepare_fake_dataset() data_set.set_input("x", flag=True) data_set.set_target("y", flag=True) - + train_set, dev_set = data_set.split(0.3) - + model = NaiveClassifier(2, 1) - + trainer = Trainer(train_set, model, loss=BCELoss(pred="predict", target="y"), metrics=AccuracyMetric(pred="predict", target="y"), @@ -63,26 +62,26 @@ class TrainerTestGround(unittest.TestCase): """ # 应该正确运行 """ - + def test_trainer_suggestion1(self): # 检查报错提示能否正确提醒用户。 # 这里没有传入forward需要的数据。需要trainer提醒用户如何设置。 dataset = prepare_fake_dataset2('x') - + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) - + def forward(self, x1, x2, y): x1 = self.fc(x1) x2 = self.fc(x2) x = x1 + x2 loss = F.cross_entropy(x, y) return {'loss': loss} - + model = Model() - + with self.assertRaises(RuntimeError): trainer = Trainer( train_data=dataset, @@ -97,25 +96,25 @@ class TrainerTestGround(unittest.TestCase): (2). You need to provide ['x1', 'x2'] in DataSet and set it as input. """ - + def test_trainer_suggestion2(self): # 检查报错提示能否正确提醒用户 # 这里传入forward需要的数据,看是否可以运行 dataset = prepare_fake_dataset2('x1', 'x2') dataset.set_input('x1', 'x2', 'y', flag=True) - + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) - + def forward(self, x1, x2, y): x1 = self.fc(x1) x2 = self.fc(x2) x = x1 + x2 loss = F.cross_entropy(x, y) return {'loss': loss} - + model = Model() trainer = Trainer( train_data=dataset, @@ -127,25 +126,25 @@ class TrainerTestGround(unittest.TestCase): """ # 应该正确运行 """ - + def test_trainer_suggestion3(self): # 检查报错提示能否正确提醒用户 # 这里传入forward需要的数据,但是forward没有返回loss这个key dataset = prepare_fake_dataset2('x1', 'x2') dataset.set_input('x1', 'x2', 'y', flag=True) - + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) - + def forward(self, x1, x2, y): x1 = self.fc(x1) x2 = self.fc(x2) x = x1 + x2 loss = F.cross_entropy(x, y) return {'wrong_loss_key': loss} - + model = Model() with self.assertRaises(NameError): trainer = Trainer( @@ -155,23 +154,25 @@ class TrainerTestGround(unittest.TestCase): print_every=2 ) trainer.train() - + def test_trainer_suggestion4(self): # 检查报错提示能否正确提醒用户 # 这里传入forward需要的数据,是否可以正确提示unused dataset = prepare_fake_dataset2('x1', 'x2') dataset.set_input('x1', 'x2', 'y', flag=True) + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) + def forward(self, x1, x2, y): x1 = self.fc(x1) x2 = self.fc(x2) x = x1 + x2 loss = F.cross_entropy(x, y) return {'losses': loss} - + model = Model() with self.assertRaises(NameError): trainer = Trainer( @@ -180,7 +181,7 @@ class TrainerTestGround(unittest.TestCase): use_tqdm=False, print_every=2 ) - + def test_trainer_suggestion5(self): # 检查报错提示能否正确提醒用户 # 这里传入多余参数,让其duplicate, 但这里因为y不会被调用,所以其实不会报错 @@ -188,17 +189,19 @@ class TrainerTestGround(unittest.TestCase): dataset.rename_field('x_unused', 'x2') dataset.set_input('x1', 'x2', 'y') dataset.set_target('y') + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) + def forward(self, x1, x2, y): x1 = self.fc(x1) x2 = self.fc(x2) x = x1 + x2 loss = F.cross_entropy(x, y) return {'loss': loss} - + model = Model() trainer = Trainer( train_data=dataset, @@ -206,7 +209,7 @@ class TrainerTestGround(unittest.TestCase): use_tqdm=False, print_every=2 ) - + def test_trainer_suggestion6(self): # 检查报错提示能否正确提醒用户 # 这里传入多余参数,让其duplicate @@ -214,10 +217,12 @@ class TrainerTestGround(unittest.TestCase): dataset.rename_field('x_unused', 'x2') dataset.set_input('x1', 'x2') dataset.set_target('y', 'x1') + class Model(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(5, 4) + def forward(self, x1, x2): x1 = self.fc(x1) x2 = self.fc(x2) @@ -225,7 +230,7 @@ class TrainerTestGround(unittest.TestCase): time.sleep(0.1) # loss = F.cross_entropy(x, y) return {'preds': x} - + model = Model() with self.assertRaises(NameError): trainer = Trainer( @@ -236,7 +241,7 @@ class TrainerTestGround(unittest.TestCase): metrics=AccuracyMetric(), use_tqdm=False, print_every=2) - + """ def test_trainer_multiprocess(self): dataset = prepare_fake_dataset2('x1', 'x2') diff --git a/test/core/test_utils.py b/test/core/test_utils.py new file mode 100644 index 00000000..91b5d00f --- /dev/null +++ b/test/core/test_utils.py @@ -0,0 +1,252 @@ +import unittest +import _pickle +from fastNLP import cache_results +from fastNLP.io import EmbedLoader +from fastNLP import DataSet +from fastNLP import Instance +import time +import os +import torch +from torch import nn +from fastNLP.core.utils import _move_model_to_device, _get_model_device +import numpy as np +from fastNLP.core.utils import seq_len_to_mask + +class Model(nn.Module): + def __init__(self): + super().__init__() + self.param = nn.Parameter(torch.zeros(0)) + + +class TestMoveModelDeivce(unittest.TestCase): + def test_case1(self): + # 测试str + model = Model() + model = _move_model_to_device(model, 'cpu') + assert model.param.device == torch.device('cpu') + # 测试不存在的device报错 + with self.assertRaises(Exception): + _move_model_to_device(model, 'cpuu') + # 测试gpu + if torch.cuda.is_available(): + model = _move_model_to_device(model, 'cuda') + assert model.param.is_cuda + model = _move_model_to_device(model, 'cuda:0') + assert model.param.device == torch.device('cuda:0') + with self.assertRaises(Exception): + _move_model_to_device(model, 'cuda:1000') + # 测试None + model = _move_model_to_device(model, None) + + def test_case2(self): + # 测试使用int初始化 + model = Model() + if torch.cuda.is_available(): + model = _move_model_to_device(model, 0) + assert model.param.device == torch.device('cuda:0') + assert model.param.device == torch.device('cuda:0'), "The model should be in " + with self.assertRaises(Exception): + _move_model_to_device(model, 100) + with self.assertRaises(Exception): + _move_model_to_device(model, -1) + + def test_case3(self): + # 测试None + model = Model() + device = _get_model_device(model) + model = _move_model_to_device(model, None) + assert device == _get_model_device(model), "The device should not change." + if torch.cuda.is_available(): + model.cuda() + device = _get_model_device(model) + model = _move_model_to_device(model, None) + assert device == _get_model_device(model), "The device should not change." + + model = nn.DataParallel(model, device_ids=[0]) + _move_model_to_device(model, None) + with self.assertRaises(Exception): + _move_model_to_device(model, 'cpu') + + def test_case4(self): + # 测试传入list的内容 + model = Model() + device = ['cpu'] + with self.assertRaises(Exception): + _move_model_to_device(model, device) + if torch.cuda.is_available(): + device = [0] + _model = _move_model_to_device(model, device) + assert not isinstance(_model, nn.DataParallel) + device = [torch.device('cuda:0'), torch.device('cuda:0')] + with self.assertRaises(Exception): + _model = _move_model_to_device(model, device) + if torch.cuda.device_count() > 1: + device = [0, 1] + _model = _move_model_to_device(model, device) + assert isinstance(_model, nn.DataParallel) + device = ['cuda', 'cuda:1'] + with self.assertRaises(Exception): + _move_model_to_device(model, device) + + def test_case5(self): + if not torch.cuda.is_available(): + return + # torch.device() + device = torch.device('cpu') + model = Model() + _move_model_to_device(model, device) + device = torch.device('cuda') + model = _move_model_to_device(model, device) + assert model.param.device == torch.device('cuda:0') + with self.assertRaises(Exception): + _move_model_to_device(model, torch.device('cuda:100')) + + +@cache_results('test/demo1.pkl') +def process_data_1(embed_file, cws_train): + embed, vocab = EmbedLoader.load_without_vocab(embed_file) + time.sleep(1) # 测试是否通过读取cache获得结果 + with open(cws_train, 'r', encoding='utf-8') as f: + d = DataSet() + for line in f: + line = line.strip() + if len(line) > 0: + d.append(Instance(raw=line)) + return embed, vocab, d + + +class TestCache(unittest.TestCase): + def test_cache_save(self): + try: + start_time = time.time() + embed, vocab, d = process_data_1('test/data_for_tests/word2vec_test.txt', 'test/data_for_tests/cws_train') + end_time = time.time() + pre_time = end_time - start_time + with open('test/demo1.pkl', 'rb') as f: + _embed, _vocab, _d = _pickle.load(f) + self.assertEqual(embed.shape, _embed.shape) + for i in range(embed.shape[0]): + self.assertListEqual(embed[i].tolist(), _embed[i].tolist()) + start_time = time.time() + embed, vocab, d = process_data_1('test/data_for_tests/word2vec_test.txt', 'test/data_for_tests/cws_train') + end_time = time.time() + read_time = end_time - start_time + print("Read using {:.3f}, while prepare using:{:.3f}".format(read_time, pre_time)) + self.assertGreater(pre_time - 0.5, read_time) + finally: + os.remove('test/demo1.pkl') + + def test_cache_save_overwrite_path(self): + try: + start_time = time.time() + embed, vocab, d = process_data_1('test/data_for_tests/word2vec_test.txt', 'test/data_for_tests/cws_train', + _cache_fp='test/demo_overwrite.pkl') + end_time = time.time() + pre_time = end_time - start_time + with open('test/demo_overwrite.pkl', 'rb') as f: + _embed, _vocab, _d = _pickle.load(f) + self.assertEqual(embed.shape, _embed.shape) + for i in range(embed.shape[0]): + self.assertListEqual(embed[i].tolist(), _embed[i].tolist()) + start_time = time.time() + embed, vocab, d = process_data_1('test/data_for_tests/word2vec_test.txt', 'test/data_for_tests/cws_train', + _cache_fp='test/demo_overwrite.pkl') + end_time = time.time() + read_time = end_time - start_time + print("Read using {:.3f}, while prepare using:{:.3f}".format(read_time, pre_time)) + self.assertGreater(pre_time - 0.5, read_time) + finally: + os.remove('test/demo_overwrite.pkl') + + def test_cache_refresh(self): + try: + start_time = time.time() + embed, vocab, d = process_data_1('test/data_for_tests/word2vec_test.txt', 'test/data_for_tests/cws_train', + _refresh=True) + end_time = time.time() + pre_time = end_time - start_time + with open('test/demo1.pkl', 'rb') as f: + _embed, _vocab, _d = _pickle.load(f) + self.assertEqual(embed.shape, _embed.shape) + for i in range(embed.shape[0]): + self.assertListEqual(embed[i].tolist(), _embed[i].tolist()) + start_time = time.time() + embed, vocab, d = process_data_1('test/data_for_tests/word2vec_test.txt', 'test/data_for_tests/cws_train', + _refresh=True) + end_time = time.time() + read_time = end_time - start_time + print("Read using {:.3f}, while prepare using:{:.3f}".format(read_time, pre_time)) + self.assertGreater(0.1, pre_time - read_time) + finally: + os.remove('test/demo1.pkl') + + def test_duplicate_keyword(self): + with self.assertRaises(RuntimeError): + @cache_results(None) + def func_verbose(a, _verbose): + pass + + func_verbose(0, 1) + with self.assertRaises(RuntimeError): + @cache_results(None) + def func_cache(a, _cache_fp): + pass + + func_cache(1, 2) + with self.assertRaises(RuntimeError): + @cache_results(None) + def func_refresh(a, _refresh): + pass + + func_refresh(1, 2) + + def test_create_cache_dir(self): + @cache_results('test/demo1/demo.pkl') + def cache(): + return 1, 2 + + try: + results = cache() + print(results) + finally: + os.remove('test/demo1/demo.pkl') + os.rmdir('test/demo1') + + +class TestSeqLenToMask(unittest.TestCase): + + def evaluate_mask_seq_len(self, seq_len, mask): + max_len = int(max(seq_len)) + for i in range(len(seq_len)): + length = seq_len[i] + mask_i = mask[i] + for j in range(max_len): + self.assertEqual(mask_i[j], j 0 + + def test_SNLILoader(self): + ds = SNLILoader().load('test/data_for_tests/sample_snli.jsonl') + assert len(ds) == 3 + + def test_JsonLoader(self): + ds = JsonLoader().load('test/data_for_tests/sample_snli.jsonl') + assert len(ds) == 3 diff --git a/test/io/test_embed_loader.py b/test/io/test_embed_loader.py index 60e3710e..ff8ecfcf 100644 --- a/test/io/test_embed_loader.py +++ b/test/io/test_embed_loader.py @@ -1,12 +1,50 @@ import unittest +import numpy as np -from fastNLP.core.vocabulary import Vocabulary -from fastNLP.io.embed_loader import EmbedLoader +from fastNLP import Vocabulary +from fastNLP.io import EmbedLoader class TestEmbedLoader(unittest.TestCase): - def test_case(self): + def test_load_with_vocab(self): vocab = Vocabulary() - vocab.update(["the", "in", "I", "to", "of", "hahaha"]) - embedding = EmbedLoader().fast_load_embedding(50, "test/data_for_tests/glove.6B.50d_test.txt", vocab) - self.assertEqual(tuple(embedding.shape), (len(vocab), 50)) + glove = "test/data_for_tests/glove.6B.50d_test.txt" + word2vec = "test/data_for_tests/word2vec_test.txt" + vocab.add_word('the') + vocab.add_word('none') + g_m = EmbedLoader.load_with_vocab(glove, vocab) + self.assertEqual(g_m.shape, (4, 50)) + w_m = EmbedLoader.load_with_vocab(word2vec, vocab, normalize=True) + self.assertEqual(w_m.shape, (4, 50)) + self.assertAlmostEqual(np.linalg.norm(w_m, axis=1).sum(), 4) + + def test_load_without_vocab(self): + words = ['the', 'of', 'in', 'a', 'to', 'and'] + glove = "test/data_for_tests/glove.6B.50d_test.txt" + word2vec = "test/data_for_tests/word2vec_test.txt" + g_m, vocab = EmbedLoader.load_without_vocab(glove) + self.assertEqual(g_m.shape, (8, 50)) + for word in words: + self.assertIn(word, vocab) + w_m, vocab = EmbedLoader.load_without_vocab(word2vec, normalize=True) + self.assertEqual(w_m.shape, (8, 50)) + self.assertAlmostEqual(np.linalg.norm(w_m, axis=1).sum(), 8) + for word in words: + self.assertIn(word, vocab) + # no unk + w_m, vocab = EmbedLoader.load_without_vocab(word2vec, normalize=True, unknown=None) + self.assertEqual(w_m.shape, (7, 50)) + self.assertAlmostEqual(np.linalg.norm(w_m, axis=1).sum(), 7) + for word in words: + self.assertIn(word, vocab) + + def test_read_all_glove(self): + pass + # TODO + # 这是可以运行的,但是总数少于行数,应该是由于glove有重复的word + # path = '/where/to/read/full/glove' + # init_embed, vocab = EmbedLoader.load_without_vocab(path, error='strict') + # print(init_embed.shape) + # print(init_embed.mean()) + # print(np.isnan(init_embed).sum()) + # print(len(vocab)) diff --git a/test/models/__init__.py b/test/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/models/model_runner.py b/test/models/model_runner.py new file mode 100644 index 00000000..405aa7d6 --- /dev/null +++ b/test/models/model_runner.py @@ -0,0 +1,156 @@ +""" +此模块可以非常方便的测试模型。 +若你的模型属于:文本分类,序列标注,自然语言推理(NLI),可以直接使用此模块测试 +若模型不属于上述类别,也可以自己准备假数据,设定loss和metric进行测试 + +此模块的测试仅保证模型能使用fastNLP进行训练和测试,不测试模型实际性能 + +Example:: + + # import 全大写变量... + from model_runner import * + + # 测试一个文本分类模型 + init_emb = (VOCAB_SIZE, 50) + model = SomeModel(init_emb, num_cls=NUM_CLS) + RUNNER.run_model_with_task(TEXT_CLS, model) + + # 序列标注模型 + RUNNER.run_model_with_task(POS_TAGGING, model) + + # NLI模型 + RUNNER.run_model_with_task(NLI, model) + + # 自定义模型 + RUNNER.run_model(model, data=get_mydata(), + loss=Myloss(), metrics=Mymetric()) +""" +from fastNLP import Trainer, Tester, DataSet, Callback +from fastNLP import AccuracyMetric +from fastNLP import CrossEntropyLoss +from fastNLP.core.const import Const as C +from random import randrange + +VOCAB_SIZE = 100 +NUM_CLS = 100 +MAX_LEN = 10 +N_SAMPLES = 100 +N_EPOCHS = 1 +BATCH_SIZE = 5 + +TEXT_CLS = 'text_cls' +POS_TAGGING = 'pos_tagging' +NLI = 'nli' + +class ModelRunner(): + class Checker(Callback): + def on_backward_begin(self, loss): + assert loss.to('cpu').numpy().isfinate() + + def gen_seq(self, length, vocab_size): + """generate fake sequence indexes with given length""" + # reserve 0 for padding + return [randrange(1, vocab_size) for _ in range(length)] + + def gen_var_seq(self, max_len, vocab_size): + """generate fake sequence indexes in variant length""" + length = randrange(3, max_len) # at least 3 words in a seq + return self.gen_seq(length, vocab_size) + + def prepare_text_classification_data(self): + index = 'index' + ds = DataSet({index: list(range(N_SAMPLES))}) + ds.apply_field(lambda x: self.gen_var_seq(MAX_LEN, VOCAB_SIZE), + field_name=index, new_field_name=C.INPUT, + is_input=True) + ds.apply_field(lambda x: randrange(NUM_CLS), + field_name=index, new_field_name=C.TARGET, + is_target=True) + ds.apply_field(len, C.INPUT, C.INPUT_LEN, + is_input=True) + return ds + + def prepare_pos_tagging_data(self): + index = 'index' + ds = DataSet({index: list(range(N_SAMPLES))}) + ds.apply_field(lambda x: self.gen_var_seq(MAX_LEN, VOCAB_SIZE), + field_name=index, new_field_name=C.INPUT, + is_input=True) + ds.apply_field(lambda x: self.gen_seq(len(x), NUM_CLS), + field_name=C.INPUT, new_field_name=C.TARGET, + is_target=True) + ds.apply_field(len, C.INPUT, C.INPUT_LEN, + is_input=True, is_target=True) + return ds + + def prepare_nli_data(self): + index = 'index' + ds = DataSet({index: list(range(N_SAMPLES))}) + ds.apply_field(lambda x: self.gen_var_seq(MAX_LEN, VOCAB_SIZE), + field_name=index, new_field_name=C.INPUTS(0), + is_input=True) + ds.apply_field(lambda x: self.gen_var_seq(MAX_LEN, VOCAB_SIZE), + field_name=index, new_field_name=C.INPUTS(1), + is_input=True) + ds.apply_field(lambda x: randrange(NUM_CLS), + field_name=index, new_field_name=C.TARGET, + is_target=True) + ds.apply_field(len, C.INPUTS(0), C.INPUT_LENS(0), + is_input=True, is_target=True) + ds.apply_field(len, C.INPUTS(1), C.INPUT_LENS(1), + is_input = True, is_target = True) + ds.set_input(C.INPUTS(0), C.INPUTS(1)) + ds.set_target(C.TARGET) + return ds + + def run_text_classification(self, model, data=None): + if data is None: + data = self.prepare_text_classification_data() + loss = CrossEntropyLoss(pred=C.OUTPUT, target=C.TARGET) + metric = AccuracyMetric(pred=C.OUTPUT, target=C.TARGET) + self.run_model(model, data, loss, metric) + + def run_pos_tagging(self, model, data=None): + if data is None: + data = self.prepare_pos_tagging_data() + loss = CrossEntropyLoss(pred=C.OUTPUT, target=C.TARGET, padding_idx=0) + metric = AccuracyMetric(pred=C.OUTPUT, target=C.TARGET, seq_len=C.INPUT_LEN) + self.run_model(model, data, loss, metric) + + def run_nli(self, model, data=None): + if data is None: + data = self.prepare_nli_data() + loss = CrossEntropyLoss(pred=C.OUTPUT, target=C.TARGET) + metric = AccuracyMetric(pred=C.OUTPUT, target=C.TARGET) + self.run_model(model, data, loss, metric) + + def run_model(self, model, data, loss, metrics): + """run a model, test if it can run with fastNLP""" + print('testing model:', model.__class__.__name__) + tester = Tester(data=data, model=model, metrics=metrics, + batch_size=BATCH_SIZE, verbose=0) + before_train = tester.test() + trainer = Trainer(model=model, train_data=data, dev_data=None, + n_epochs=N_EPOCHS, batch_size=BATCH_SIZE, + loss=loss, + save_path=None, + use_tqdm=False) + trainer.train(load_best_model=False) + after_train = tester.test() + for metric_name, v1 in before_train.items(): + assert metric_name in after_train + # # at least we can sure model params changed, even if we don't know performance + # v2 = after_train[metric_name] + # assert v1 != v2 + + def run_model_with_task(self, task, model): + """run a model with certain task""" + TASKS = { + TEXT_CLS: self.run_text_classification, + POS_TAGGING: self.run_pos_tagging, + NLI: self.run_nli, + } + assert task in TASKS + TASKS[task](model) + +RUNNER = ModelRunner() diff --git a/test/models/test_biaffine_parser.py b/test/models/test_biaffine_parser.py index 88ba09b8..e6fca6a8 100644 --- a/test/models/test_biaffine_parser.py +++ b/test/models/test_biaffine_parser.py @@ -2,90 +2,47 @@ import unittest import fastNLP from fastNLP.models.biaffine_parser import BiaffineParser, ParserLoss, ParserMetric - -data_file = """ -1 The _ DET DT _ 3 det _ _ -2 new _ ADJ JJ _ 3 amod _ _ -3 rate _ NOUN NN _ 6 nsubj _ _ -4 will _ AUX MD _ 6 aux _ _ -5 be _ VERB VB _ 6 cop _ _ -6 payable _ ADJ JJ _ 0 root _ _ -7 mask _ ADJ JJ _ 6 punct _ _ -8 mask _ ADJ JJ _ 6 punct _ _ -9 cents _ NOUN NNS _ 4 nmod _ _ -10 from _ ADP IN _ 12 case _ _ -11 seven _ NUM CD _ 12 nummod _ _ -12 cents _ NOUN NNS _ 4 nmod _ _ -13 a _ DET DT _ 14 det _ _ -14 share _ NOUN NN _ 12 nmod:npmod _ _ -15 . _ PUNCT . _ 4 punct _ _ - -1 The _ DET DT _ 3 det _ _ -2 new _ ADJ JJ _ 3 amod _ _ -3 rate _ NOUN NN _ 6 nsubj _ _ -4 will _ AUX MD _ 6 aux _ _ -5 be _ VERB VB _ 6 cop _ _ -6 payable _ ADJ JJ _ 0 root _ _ -7 Feb. _ PROPN NNP _ 6 nmod:tmod _ _ -8 15 _ NUM CD _ 7 nummod _ _ -9 . _ PUNCT . _ 6 punct _ _ - -1 A _ DET DT _ 3 det _ _ -2 record _ NOUN NN _ 3 compound _ _ -3 date _ NOUN NN _ 7 nsubjpass _ _ -4 has _ AUX VBZ _ 7 aux _ _ -5 n't _ PART RB _ 7 neg _ _ -6 been _ AUX VBN _ 7 auxpass _ _ -7 set _ VERB VBN _ 0 root _ _ -8 . _ PUNCT . _ 7 punct _ _ - -""" - - -def init_data(): - ds = fastNLP.DataSet() - v = {'word_seq': fastNLP.Vocabulary(), - 'pos_seq': fastNLP.Vocabulary(), - 'label_true': fastNLP.Vocabulary()} - data = [] - for line in data_file.split('\n'): - line = line.split() - if len(line) == 0 and len(data) > 0: - data = list(zip(*data)) - ds.append(fastNLP.Instance(word_seq=data[1], - pos_seq=data[4], - arc_true=data[6], - label_true=data[7])) - data = [] - elif len(line) > 0: - data.append(line) - - for name in ['word_seq', 'pos_seq', 'label_true']: - ds.apply(lambda x: [''] + list(x[name]), new_field_name=name) - ds.apply(lambda x: v[name].add_word_lst(x[name])) - - for name in ['word_seq', 'pos_seq', 'label_true']: - ds.apply(lambda x: [v[name].to_index(w) for w in x[name]], new_field_name=name) - - ds.apply(lambda x: [0] + list(map(int, x['arc_true'])), new_field_name='arc_true') - ds.apply(lambda x: len(x['word_seq']), new_field_name='seq_lens') - ds.set_input('word_seq', 'pos_seq', 'seq_lens', flag=True) - ds.set_target('arc_true', 'label_true', 'seq_lens', flag=True) - return ds, v['word_seq'], v['pos_seq'], v['label_true'] +from .model_runner import * + + +def prepare_parser_data(): + index = 'index' + ds = DataSet({index: list(range(N_SAMPLES))}) + ds.apply_field(lambda x: RUNNER.gen_var_seq(MAX_LEN, VOCAB_SIZE), + field_name=index, new_field_name=C.INPUTS(0), + is_input=True) + ds.apply_field(lambda x: RUNNER.gen_seq(len(x), NUM_CLS), + field_name=C.INPUTS(0), new_field_name=C.INPUTS(1), + is_input=True) + # target1 is heads, should in range(0, len(words)) + ds.apply_field(lambda x: RUNNER.gen_seq(len(x), len(x)), + field_name=C.INPUTS(0), new_field_name=C.TARGETS(0), + is_target=True) + ds.apply_field(lambda x: RUNNER.gen_seq(len(x), NUM_CLS), + field_name=C.INPUTS(0), new_field_name=C.TARGETS(1), + is_target=True) + ds.apply_field(len, field_name=C.INPUTS(0), new_field_name=C.INPUT_LEN, + is_input=True, is_target=True) + return ds class TestBiaffineParser(unittest.TestCase): def test_train(self): - ds, v1, v2, v3 = init_data() - model = BiaffineParser(word_vocab_size=len(v1), word_emb_dim=30, - pos_vocab_size=len(v2), pos_emb_dim=30, - num_label=len(v3), encoder='var-lstm') - trainer = fastNLP.Trainer(model=model, train_data=ds, dev_data=ds, - loss=ParserLoss(), metrics=ParserMetric(), metric_key='UAS', - batch_size=1, validate_every=10, - n_epochs=10, use_cuda=False, use_tqdm=False) - trainer.train(load_best_model=False) - - -if __name__ == '__main__': - unittest.main() + model = BiaffineParser(init_embed=(VOCAB_SIZE, 10), + pos_vocab_size=VOCAB_SIZE, pos_emb_dim=10, + rnn_hidden_size=10, + arc_mlp_size=10, + label_mlp_size=10, + num_label=NUM_CLS, encoder='var-lstm') + ds = prepare_parser_data() + RUNNER.run_model(model, ds, loss=ParserLoss(), metrics=ParserMetric()) + + def test_train2(self): + model = BiaffineParser(init_embed=(VOCAB_SIZE, 10), + pos_vocab_size=VOCAB_SIZE, pos_emb_dim=10, + rnn_hidden_size=16, + arc_mlp_size=10, + label_mlp_size=10, + num_label=NUM_CLS, encoder='transformer') + ds = prepare_parser_data() + RUNNER.run_model(model, ds, loss=ParserLoss(), metrics=ParserMetric()) diff --git a/test/models/test_cnn_text_classification.py b/test/models/test_cnn_text_classification.py new file mode 100644 index 00000000..b83b7bad --- /dev/null +++ b/test/models/test_cnn_text_classification.py @@ -0,0 +1,18 @@ + +import unittest + +from .model_runner import * +from fastNLP.models.cnn_text_classification import CNNText + + +class TestCNNText(unittest.TestCase): + def test_case1(self): + # 测试能否正常运行CNN + init_emb = (VOCAB_SIZE, 30) + model = CNNText(init_emb, + NUM_CLS, + kernel_nums=(1, 3, 5), + kernel_sizes=(2, 2, 2), + padding=0, + dropout=0.5) + RUNNER.run_model_with_task(TEXT_CLS, model) diff --git a/test/models/test_enas.py b/test/models/test_enas.py deleted file mode 100644 index 07a43205..00000000 --- a/test/models/test_enas.py +++ /dev/null @@ -1,112 +0,0 @@ -import unittest - -from fastNLP import DataSet -from fastNLP import Instance -from fastNLP import Vocabulary -from fastNLP.core.losses import CrossEntropyLoss -from fastNLP.core.metrics import AccuracyMetric - - -class TestENAS(unittest.TestCase): - def testENAS(self): - # 从csv读取数据到DataSet - sample_path = "tutorials/sample_data/tutorial_sample_dataset.csv" - dataset = DataSet.read_csv(sample_path, headers=('raw_sentence', 'label'), - sep='\t') - print(len(dataset)) - print(dataset[0]) - print(dataset[-3]) - - dataset.append(Instance(raw_sentence='fake data', label='0')) - # 将所有数字转为小写 - dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='raw_sentence') - # label转int - dataset.apply(lambda x: int(x['label']), new_field_name='label') - - # 使用空格分割句子 - def split_sent(ins): - return ins['raw_sentence'].split() - - dataset.apply(split_sent, new_field_name='words') - - # 增加长度信息 - dataset.apply(lambda x: len(x['words']), new_field_name='seq_len') - print(len(dataset)) - print(dataset[0]) - - # DataSet.drop(func)筛除数据 - dataset.drop(lambda x: x['seq_len'] <= 3) - print(len(dataset)) - - # 设置DataSet中,哪些field要转为tensor - # set target,loss或evaluate中的golden,计算loss,模型评估时使用 - dataset.set_target("label") - # set input,模型forward时使用 - dataset.set_input("words", "seq_len") - - # 分出测试集、训练集 - test_data, train_data = dataset.split(0.5) - print(len(test_data)) - print(len(train_data)) - - # 构建词表, Vocabulary.add(word) - vocab = Vocabulary(min_freq=2) - train_data.apply(lambda x: [vocab.add(word) for word in x['words']]) - vocab.build_vocab() - - # index句子, Vocabulary.to_index(word) - train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='words') - test_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='words') - print(test_data[0]) - - # 如果你们需要做强化学习或者GAN之类的项目,你们也可以使用这些数据预处理的工具 - from fastNLP.core.batch import Batch - from fastNLP.core.sampler import RandomSampler - - batch_iterator = Batch(dataset=train_data, batch_size=2, sampler=RandomSampler()) - for batch_x, batch_y in batch_iterator: - print("batch_x has: ", batch_x) - print("batch_y has: ", batch_y) - break - - from fastNLP.models.enas_model import ENASModel - from fastNLP.models.enas_controller import Controller - model = ENASModel(embed_num=len(vocab), num_classes=5) - controller = Controller() - - from fastNLP.models.enas_trainer import ENASTrainer - from copy import deepcopy - - # 更改DataSet中对应field的名称,要以模型的forward等参数名一致 - train_data.rename_field('words', 'word_seq') # input field 与 forward 参数一致 - train_data.rename_field('label', 'label_seq') - test_data.rename_field('words', 'word_seq') - test_data.rename_field('label', 'label_seq') - - loss = CrossEntropyLoss(pred="output", target="label_seq") - metric = AccuracyMetric(pred="predict", target="label_seq") - - trainer = ENASTrainer(model=model, controller=controller, train_data=train_data, dev_data=test_data, - loss=CrossEntropyLoss(pred="output", target="label_seq"), - metrics=AccuracyMetric(pred="predict", target="label_seq"), - check_code_level=-1, - save_path=None, - batch_size=32, - print_every=1, - n_epochs=3, - final_epochs=1) - trainer.train() - print('Train finished!') - - # 调用Tester在test_data上评价效果 - from fastNLP import Tester - - tester = Tester(data=test_data, model=model, metrics=AccuracyMetric(pred="predict", target="label_seq"), - batch_size=4) - - acc = tester.test() - print(acc) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/test/models/test_sequence_labeling.py b/test/models/test_sequence_labeling.py new file mode 100644 index 00000000..3a70e381 --- /dev/null +++ b/test/models/test_sequence_labeling.py @@ -0,0 +1,36 @@ + + +import unittest + +from .model_runner import * +from fastNLP.models.sequence_labeling import SeqLabeling, AdvSeqLabel +from fastNLP.core.losses import LossInForward + +class TesSeqLabel(unittest.TestCase): + def test_case1(self): + # 测试能否正常运行CNN + init_emb = (VOCAB_SIZE, 30) + model = SeqLabeling(init_emb, + hidden_size=30, + num_classes=NUM_CLS) + + data = RUNNER.prepare_pos_tagging_data() + data.set_input('target') + loss = LossInForward() + metric = AccuracyMetric(pred=C.OUTPUT, target=C.TARGET, seq_len=C.INPUT_LEN) + RUNNER.run_model(model, data, loss, metric) + + +class TesAdvSeqLabel(unittest.TestCase): + def test_case1(self): + # 测试能否正常运行CNN + init_emb = (VOCAB_SIZE, 30) + model = AdvSeqLabel(init_emb, + hidden_size=30, + num_classes=NUM_CLS) + + data = RUNNER.prepare_pos_tagging_data() + data.set_input('target') + loss = LossInForward() + metric = AccuracyMetric(pred=C.OUTPUT, target=C.TARGET, seq_len=C.INPUT_LEN) + RUNNER.run_model(model, data, loss, metric) \ No newline at end of file diff --git a/test/models/test_star_trans.py b/test/models/test_star_trans.py new file mode 100644 index 00000000..eae19b24 --- /dev/null +++ b/test/models/test_star_trans.py @@ -0,0 +1,16 @@ +from .model_runner import * +from fastNLP.models.star_transformer import STNLICls, STSeqCls, STSeqLabel + + +# add star-transformer tests, for 3 kinds of tasks. +def test_cls(): + model = STSeqCls((VOCAB_SIZE, 10), NUM_CLS, dropout=0) + RUNNER.run_model_with_task(TEXT_CLS, model) + +def test_nli(): + model = STNLICls((VOCAB_SIZE, 10), NUM_CLS, dropout=0) + RUNNER.run_model_with_task(NLI, model) + +def test_seq_label(): + model = STSeqLabel((VOCAB_SIZE, 10), NUM_CLS, dropout=0) + RUNNER.run_model_with_task(POS_TAGGING, model) diff --git a/test/modules/decoder/test_CRF.py b/test/modules/decoder/test_CRF.py index 0fc331dc..5dec7d47 100644 --- a/test/modules/decoder/test_CRF.py +++ b/test/modules/decoder/test_CRF.py @@ -5,7 +5,7 @@ import unittest class TestCRF(unittest.TestCase): def test_case1(self): # 检查allowed_transitions()能否正确使用 - from fastNLP.modules.decoder.CRF import allowed_transitions + from fastNLP.modules.decoder.crf import allowed_transitions id2label = {0: 'B', 1: 'I', 2:'O'} expected_res = {(0, 0), (0, 1), (0, 2), (0, 4), (1, 0), (1, 1), (1, 2), (1, 4), (2, 0), (2, 2), @@ -43,7 +43,7 @@ class TestCRF(unittest.TestCase): # 测试CRF能否避免解码出非法跃迁, 使用allennlp做了验证。 pass # import torch - # from fastNLP.modules.decoder.CRF import seq_len_to_byte_mask + # from fastNLP.modules.decoder.crf import seq_len_to_byte_mask # # labels = ['O'] # for label in ['X', 'Y']: @@ -63,10 +63,10 @@ class TestCRF(unittest.TestCase): # mask = seq_len_to_byte_mask(seq_lens) # allen_res = allen_CRF.viterbi_tags(logits, mask) # - # from fastNLP.modules.decoder.CRF import ConditionalRandomField, allowed_transitions + # from fastNLP.modules.decoder.crf import ConditionalRandomField, allowed_transitions # fast_CRF = ConditionalRandomField(num_tags=num_tags, allowed_transitions=allowed_transitions(id2label)) # fast_CRF.trans_m = trans_m - # fast_res = fast_CRF.viterbi_decode(logits, mask, get_score=True) + # fast_res = fast_CRF.viterbi_decode(logits, mask, get_score=True, unpad=True) # # score equal # self.assertListEqual([score for _, score in allen_res], fast_res[1]) # # seq equal @@ -91,14 +91,38 @@ class TestCRF(unittest.TestCase): # mask = seq_len_to_byte_mask(seq_lens) # allen_res = allen_CRF.viterbi_tags(logits, mask) # - # from fastNLP.modules.decoder.CRF import ConditionalRandomField, allowed_transitions + # from fastNLP.modules.decoder.crf import ConditionalRandomField, allowed_transitions # fast_CRF = ConditionalRandomField(num_tags=num_tags, allowed_transitions=allowed_transitions(id2label, # encoding_type='BMES')) # fast_CRF.trans_m = trans_m - # fast_res = fast_CRF.viterbi_decode(logits, mask, get_score=True) + # fast_res = fast_CRF.viterbi_decode(logits, mask, get_score=True, unpad=True) # # score equal # self.assertListEqual([score for _, score in allen_res], fast_res[1]) # # seq equal # self.assertListEqual([_ for _, score in allen_res], fast_res[0]) + def test_case3(self): + # 测试crf的loss不会出现负数 + import torch + from fastNLP.modules.decoder.crf import ConditionalRandomField + from fastNLP.core.utils import seq_len_to_mask + from torch import optim + from torch import nn + num_tags, include_start_end_trans = 4, True + num_samples = 4 + lengths = torch.randint(3, 50, size=(num_samples, )).long() + max_len = lengths.max() + tags = torch.randint(num_tags, size=(num_samples, max_len)) + masks = seq_len_to_mask(lengths) + feats = nn.Parameter(torch.randn(num_samples, max_len, num_tags)) + crf = ConditionalRandomField(num_tags, include_start_end_trans) + optimizer = optim.SGD([param for param in crf.parameters() if param.requires_grad] + [feats], lr=0.1) + for _ in range(10): + loss = crf(feats, tags, masks).mean() + optimizer.zero_grad() + loss.backward() + optimizer.step() + if _%1000==0: + print(loss) + self.assertGreater(loss.item(), 0, "CRF loss cannot be less than 0.") diff --git a/test/modules/test_char_embedding.py b/test/modules/test_char_encoder.py similarity index 81% rename from test/modules/test_char_embedding.py rename to test/modules/test_char_encoder.py index 07def64a..cf3ec15e 100644 --- a/test/modules/test_char_embedding.py +++ b/test/modules/test_char_encoder.py @@ -2,7 +2,7 @@ import unittest import torch -from fastNLP.modules.encoder.char_embedding import ConvCharEmbedding, LSTMCharEmbedding +from fastNLP.modules.encoder.char_encoder import ConvolutionCharEncoder, LSTMCharEncoder class TestCharEmbed(unittest.TestCase): @@ -13,14 +13,14 @@ class TestCharEmbed(unittest.TestCase): x = torch.Tensor(batch_size, char_emb, word_length) x = x.transpose(1, 2) - cce = ConvCharEmbedding(char_emb) + cce = ConvolutionCharEncoder(char_emb) y = cce(x) self.assertEqual(tuple(x.shape), (batch_size, word_length, char_emb)) print("CNN Char Emb input: ", x.shape) self.assertEqual(tuple(y.shape), (batch_size, char_emb, 1)) print("CNN Char Emb output: ", y.shape) # [128, 100] - lce = LSTMCharEmbedding(char_emb) + lce = LSTMCharEncoder(char_emb) o = lce(x) self.assertEqual(tuple(x.shape), (batch_size, word_length, char_emb)) print("LSTM Char Emb input: ", x.shape) diff --git a/test/modules/test_masked_rnn.py b/test/modules/test_masked_rnn.py deleted file mode 100644 index 80f49f33..00000000 --- a/test/modules/test_masked_rnn.py +++ /dev/null @@ -1,27 +0,0 @@ - -import torch -import unittest - -from fastNLP.modules.encoder.masked_rnn import MaskedRNN - -class TestMaskedRnn(unittest.TestCase): - def test_case_1(self): - masked_rnn = MaskedRNN(input_size=1, hidden_size=1, bidirectional=True, batch_first=True) - x = torch.tensor([[[1.0], [2.0]]]) - print(x.size()) - y = masked_rnn(x) - mask = torch.tensor([[[1], [1]]]) - y = masked_rnn(x, mask=mask) - mask = torch.tensor([[[1], [0]]]) - y = masked_rnn(x, mask=mask) - - def test_case_2(self): - masked_rnn = MaskedRNN(input_size=1, hidden_size=1, bidirectional=False, batch_first=True) - x = torch.tensor([[[1.0], [2.0]]]) - print(x.size()) - y = masked_rnn(x) - mask = torch.tensor([[[1], [1]]]) - y = masked_rnn(x, mask=mask) - xx = torch.tensor([[[1.0]]]) - y = masked_rnn.step(xx) - y = masked_rnn.step(xx, mask=mask) \ No newline at end of file diff --git a/test/modules/test_other_modules.py b/test/modules/test_other_modules.py index 4e0fb838..c5462623 100644 --- a/test/modules/test_other_modules.py +++ b/test/modules/test_other_modules.py @@ -2,55 +2,9 @@ import unittest import torch -from fastNLP.modules.other_modules import GroupNorm, LayerNormalization, BiLinear, BiAffine from fastNLP.modules.encoder.star_transformer import StarTransformer -class TestGroupNorm(unittest.TestCase): - def test_case_1(self): - gn = GroupNorm(num_features=1, num_groups=10, eps=1.5e-5) - x = torch.randn((20, 50, 10)) - y = gn(x) - - -class TestLayerNormalization(unittest.TestCase): - def test_case_1(self): - ln = LayerNormalization(layer_size=5, eps=2e-3) - x = torch.randn((20, 50, 5)) - y = ln(x) - - -class TestBiLinear(unittest.TestCase): - def test_case_1(self): - bl = BiLinear(n_left=5, n_right=5, n_out=10, bias=True) - x_left = torch.randn((7, 10, 20, 5)) - x_right = torch.randn((7, 10, 20, 5)) - y = bl(x_left, x_right) - print(bl) - bl2 = BiLinear(n_left=15, n_right=15, n_out=10, bias=True) - - -class TestBiAffine(unittest.TestCase): - def test_case_1(self): - batch_size = 16 - encoder_length = 21 - decoder_length = 32 - layer = BiAffine(10, 10, 25, biaffine=True) - decoder_input = torch.randn((batch_size, encoder_length, 10)) - encoder_input = torch.randn((batch_size, decoder_length, 10)) - y = layer(decoder_input, encoder_input) - self.assertEqual(tuple(y.shape), (batch_size, 25, encoder_length, decoder_length)) - - def test_case_2(self): - batch_size = 16 - encoder_length = 21 - decoder_length = 32 - layer = BiAffine(10, 10, 25, biaffine=False) - decoder_input = torch.randn((batch_size, encoder_length, 10)) - encoder_input = torch.randn((batch_size, decoder_length, 10)) - y = layer(decoder_input, encoder_input) - self.assertEqual(tuple(y.shape), (batch_size, 25, encoder_length, 1)) - class TestStarTransformer(unittest.TestCase): def test_1(self): model = StarTransformer(num_layers=6, hidden_size=100, num_head=8, head_dim=20, max_len=100) diff --git a/test/modules/test_utils.py b/test/modules/test_utils.py deleted file mode 100644 index 1d3cfcac..00000000 --- a/test/modules/test_utils.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest - - -class TestUtils(unittest.TestCase): - def test_case_1(self): - pass - - def test_case_2(self): - pass diff --git a/test/test_tutorials.py b/test/test_tutorials.py index 68c874fa..128e4235 100644 --- a/test/test_tutorials.py +++ b/test/test_tutorials.py @@ -10,7 +10,7 @@ from fastNLP.core.metrics import AccuracyMetric class TestTutorial(unittest.TestCase): def test_fastnlp_10min_tutorial(self): # 从csv读取数据到DataSet - sample_path = "tutorials/sample_data/tutorial_sample_dataset.csv" + sample_path = "test/data_for_tests/tutorial_sample_dataset.csv" dataset = DataSet.read_csv(sample_path, headers=('raw_sentence', 'label'), sep='\t') print(len(dataset)) @@ -35,7 +35,7 @@ class TestTutorial(unittest.TestCase): print(dataset[0]) # DataSet.drop(func)筛除数据 - dataset.drop(lambda x: x['seq_len'] <= 3) + dataset.drop(lambda x: x['seq_len'] <= 3, inplace=True) print(len(dataset)) # 设置DataSet中,哪些field要转为tensor @@ -70,15 +70,13 @@ class TestTutorial(unittest.TestCase): break from fastNLP.models import CNNText - model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1) + model = CNNText((len(vocab), 50), num_classes=5, padding=2, dropout=0.1) from fastNLP import Trainer from copy import deepcopy # 更改DataSet中对应field的名称,要以模型的forward等参数名一致 - train_data.rename_field('words', 'word_seq') # input field 与 forward 参数一致 train_data.rename_field('label', 'label_seq') - test_data.rename_field('words', 'word_seq') test_data.rename_field('label', 'label_seq') loss = CrossEntropyLoss(pred="output", target="label_seq") @@ -115,14 +113,14 @@ class TestTutorial(unittest.TestCase): def test_fastnlp_1min_tutorial(self): # tutorials/fastnlp_1min_tutorial.ipynb - data_path = "tutorials/sample_data/tutorial_sample_dataset.csv" + data_path = "test/data_for_tests/tutorial_sample_dataset.csv" ds = DataSet.read_csv(data_path, headers=('raw_sentence', 'label'), sep='\t') print(ds[1]) # 将所有数字转为小写 ds.apply(lambda x: x['raw_sentence'].lower(), new_field_name='raw_sentence') # label转int - ds.apply(lambda x: int(x['label']), new_field_name='label_seq', is_target=True) + ds.apply(lambda x: int(x['label']), new_field_name='target', is_target=True) def split_sent(ins): return ins['raw_sentence'].split() @@ -139,294 +137,30 @@ class TestTutorial(unittest.TestCase): train_data.apply(lambda x: [vocab.add(word) for word in x['words']]) # index句子, Vocabulary.to_index(word) - train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', + train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='words', is_input=True) - dev_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', + dev_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='words', is_input=True) from fastNLP.models import CNNText - model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1) + model = CNNText((len(vocab), 50), num_classes=5, padding=2, dropout=0.1) + + from fastNLP import Trainer, CrossEntropyLoss, AccuracyMetric, Adam - from fastNLP import Trainer, CrossEntropyLoss, AccuracyMetric trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, loss=CrossEntropyLoss(), - metrics=AccuracyMetric() + optimizer= Adam(), + metrics=AccuracyMetric(target='target') ) trainer.train() print('Train finished!') - def test_fastnlp_advanced_tutorial(self): + def setUp(self): import os - os.chdir("tutorials/fastnlp_advanced_tutorial") - - from fastNLP import DataSet - from fastNLP import Instance - from fastNLP import Vocabulary - from fastNLP import Trainer - from fastNLP import Tester - - # ### Instance - # Instance表示一个样本,由一个或者多个field(域、属性、特征)组成,每个field具有自己的名字以及值 - # 在初始化Instance的时候可以定义它包含的field,使用"field_name=field_value"的写法 - - # In[2]: - - # 组织一个Instance,这个Instance由premise、hypothesis、label三个field组成 - instance = Instance(premise='an premise example .', hypothesis='an hypothesis example.', label=1) - instance - - # In[3]: - - data_set = DataSet([instance] * 5) - data_set.append(instance) - data_set[-2:] - - # In[4]: - - # 如果某一个field的类型与dataset对应的field类型不一样仍可被加入dataset中 - instance2 = Instance(premise='the second premise example .', hypothesis='the second hypothesis example.', - label='1') - try: - data_set.append(instance2) - except: - pass - data_set[-2:] - - # In[5]: - - # 如果某一个field的名字不对,则该instance不能被append到dataset中 - instance3 = Instance(premises='the third premise example .', hypothesis='the third hypothesis example.', - label=1) - try: - data_set.append(instance3) - except: - print('cannot append instance') - pass - data_set[-2:] - - # In[6]: - - # 除了文本以外,还可以将tensor作为其中一个field的value - import torch - tensor_ins = Instance(image=torch.randn(5, 5), label=0) - ds = DataSet() - ds.append(tensor_ins) - ds - - from fastNLP import DataSet - from fastNLP import Instance - - # 从csv读取数据到DataSet - # 类csv文件,即每一行为一个example的文件,都可以使用这种方法进行数据读取 - dataset = DataSet.read_csv('tutorial_sample_dataset.csv', headers=('raw_sentence', 'label'), sep='\t') - # 查看DataSet的大小 - len(dataset) - - # In[8]: - - # 使用数字索引[k],获取第k个样本 - dataset[0] - - # In[9]: - - # 获取的样本是一个Instance - type(dataset[0]) - - # In[10]: - - # 使用数字索引[a: b],获取第a到第b个样本 - dataset[0: 3] - - # In[11]: - - # 索引也可以是负数 - dataset[-1] - - data_path = ['premise', 'hypothesis', 'label'] - - # 读入文件 - with open(data_path[0]) as f: - premise = f.readlines() - - with open(data_path[1]) as f: - hypothesis = f.readlines() - - with open(data_path[2]) as f: - label = f.readlines() - - assert len(premise) == len(hypothesis) and len(hypothesis) == len(label) - - # 组织DataSet - data_set = DataSet() - for p, h, l in zip(premise, hypothesis, label): - p = p.strip() # 将行末空格去除 - h = h.strip() # 将行末空格去除 - data_set.append(Instance(premise=p, hypothesis=h, truth=l)) + self._init_wd = os.path.abspath(os.curdir) - data_set[0] - - # ### DataSet的其他操作 - # 在构建完毕DataSet后,仍然可以对DataSet的内容进行操作,函数接口为DataSet.apply() - - # In[13]: - - # 将premise域的所有文本转成小写 - data_set.apply(lambda x: x['premise'].lower(), new_field_name='premise') - data_set[-2:] - - # In[14]: - - # label转int - data_set.apply(lambda x: int(x['truth']), new_field_name='truth') - data_set[-2:] - - # In[15]: - - # 使用空格分割句子 - def split_sent(ins): - return ins['premise'].split() - - data_set.apply(split_sent, new_field_name='premise') - data_set.apply(lambda x: x['hypothesis'].split(), new_field_name='hypothesis') - data_set[-2:] - - # In[16]: - - # 筛选数据 - origin_data_set_len = len(data_set) - data_set.drop(lambda x: len(x['premise']) <= 6) - origin_data_set_len, len(data_set) - - # In[17]: - - # 增加长度信息 - data_set.apply(lambda x: [1] * len(x['premise']), new_field_name='premise_len') - data_set.apply(lambda x: [1] * len(x['hypothesis']), new_field_name='hypothesis_len') - data_set[-1] - - # In[18]: - - # 设定特征域、标签域 - data_set.set_input("premise", "premise_len", "hypothesis", "hypothesis_len") - data_set.set_target("truth") - - # In[19]: - - # 重命名field - data_set.rename_field('truth', 'label') - data_set[-1] - - # In[20]: - - # 切分训练、验证集、测试集 - train_data, vad_data = data_set.split(0.5) - dev_data, test_data = vad_data.split(0.4) - len(train_data), len(dev_data), len(test_data) - - # In[21]: - - # 深拷贝一个数据集 - import copy - train_data_2, dev_data_2 = copy.deepcopy(train_data), copy.deepcopy(dev_data) - del copy - - # 初始化词表,该词表最大的vocab_size为10000,词表中每个词出现的最低频率为2,''表示未知词语,''表示padding词语 - # Vocabulary默认初始化参数为max_size=None, min_freq=None, unknown='', padding='' - vocab = Vocabulary(max_size=10000, min_freq=2, unknown='', padding='') - - # 构建词表 - train_data.apply(lambda x: [vocab.add(word) for word in x['premise']]) - train_data.apply(lambda x: [vocab.add(word) for word in x['hypothesis']]) - vocab.build_vocab() - - # In[23]: - - # 根据词表index句子 - train_data.apply(lambda x: [vocab.to_index(word) for word in x['premise']], new_field_name='premise') - train_data.apply(lambda x: [vocab.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis') - dev_data.apply(lambda x: [vocab.to_index(word) for word in x['premise']], new_field_name='premise') - dev_data.apply(lambda x: [vocab.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis') - test_data.apply(lambda x: [vocab.to_index(word) for word in x['premise']], new_field_name='premise') - test_data.apply(lambda x: [vocab.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis') - train_data[-1], dev_data[-1], test_data[-1] - - # 读入vocab文件 - with open('vocab.txt') as f: - lines = f.readlines() - vocabs = [] - for line in lines: - vocabs.append(line.strip()) - - # 实例化Vocabulary - vocab_bert = Vocabulary(unknown=None, padding=None) - # 将vocabs列表加入Vocabulary - vocab_bert.add_word_lst(vocabs) - # 构建词表 - vocab_bert.build_vocab() - # 更新unknown与padding的token文本 - vocab_bert.unknown = '[UNK]' - vocab_bert.padding = '[PAD]' - - # In[25]: - - # 根据词表index句子 - train_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['premise']], new_field_name='premise') - train_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['hypothesis']], - new_field_name='hypothesis') - dev_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['premise']], new_field_name='premise') - dev_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis') - train_data_2[-1], dev_data_2[-1] - - # step 1:加载模型参数(非必选) - from fastNLP.io.config_io import ConfigSection, ConfigLoader - args = ConfigSection() - ConfigLoader().load_config("./data/config", {"esim_model": args}) - args["vocab_size"] = len(vocab) - args.data - - # In[27]: - - # step 2:加载ESIM模型 - from fastNLP.models import ESIM - model = ESIM(**args.data) - model - - # In[28]: - - # 另一个例子:加载CNN文本分类模型 - from fastNLP.models import CNNText - cnn_text_model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1) - cnn_text_model - - from fastNLP import CrossEntropyLoss - from fastNLP import Adam - from fastNLP import AccuracyMetric - trainer = Trainer( - train_data=train_data, - model=model, - loss=CrossEntropyLoss(pred='pred', target='label'), - metrics=AccuracyMetric(), - n_epochs=3, - batch_size=16, - print_every=-1, - validate_every=-1, - dev_data=dev_data, - use_cuda=False, - optimizer=Adam(lr=1e-3, weight_decay=0), - check_code_level=-1, - metric_key='acc', - use_tqdm=False, - ) - trainer.train() - - tester = Tester( - data=test_data, - model=model, - metrics=AccuracyMetric(), - batch_size=args["batch_size"], - ) - tester.test() - - os.chdir("../..") + def tearDown(self): + import os + os.chdir(self._init_wd) diff --git a/tutorials/README.md b/tutorials/README.md index 1de342e6..83df2bb9 100644 --- a/tutorials/README.md +++ b/tutorials/README.md @@ -1,12 +1,7 @@ # fastNLP 教程 ### 上手教程 Quick Start -- 一分钟上手:`fastnlp_1min_tutorial.ipynb` [Click Here](https://github.com/fastnlp/fastNLP/tree/master/tutorials/fastnlp_1min_tutorial.ipynb) -- 十分钟上手:`fastnlp_10min_tutorial.ipynb` [Click Here](https://github.com/fastnlp/fastNLP/tree/master/tutorials/fastnlp_10min_tutorial.ipynb) +`quickstart.ipynb` [Click Here](https://github.com/fastnlp/fastNLP/tree/master/tutorials/quickstart.ipynb) -### 进阶教程 Advanced Tutorial -- `fastnlp_advanced_tutorial/advance_tutorial.ipynb` [Click Here](https://github.com/fastnlp/fastNLP/tree/master/tutorials/fastnlp_advanced_tutorial/advance_tutorial.ipynb) - - -### 开发者指南 Developer Guide -- `tutorial_for_developer.md` [Click Here](https://github.com/fastnlp/fastNLP/tree/master/tutorials/tutorial_for_developer.md) +### 详细教程 Tutorial 1 +十分钟上手:`tutorial_1.ipynb` [Click Here](https://github.com/fastnlp/fastNLP/tree/master/tutorials/tutorial_1.ipynb) diff --git a/tutorials/fastNLP_padding_tutorial.ipynb b/tutorials/fastNLP_padding_tutorial.ipynb deleted file mode 100644 index 7dc50206..00000000 --- a/tutorials/fastNLP_padding_tutorial.ipynb +++ /dev/null @@ -1,370 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/yh/miniconda2/envs/python3/lib/python3.6/site-packages/tqdm/autonotebook/__init__.py:14: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)\n", - " \" (e.g. in jupyter console)\", TqdmExperimentalWarning)\n" - ] - }, - { - "data": { - "text/plain": [ - "DataSet({'raw_sent': this is a bad idea . type=str,\n", - "'label': 0 type=int,\n", - "'word_str_lst': ['this', 'is', 'a', 'bad', 'idea', '.'] type=list,\n", - "'words': [4, 2, 5, 6, 7, 3] type=list},\n", - "{'raw_sent': it is great . type=str,\n", - "'label': 1 type=int,\n", - "'word_str_lst': ['it', 'is', 'great', '.'] type=list,\n", - "'words': [8, 2, 9, 3] type=list})" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 假设有以下的DataSet, 这里只是为了举例所以只选择了两个sample\n", - "import sys\n", - "import os\n", - "sys.path.append('/Users/yh/Desktop/fastNLP/fastNLP')\n", - "\n", - "from fastNLP import DataSet\n", - "from fastNLP import Instance\n", - "from fastNLP import Vocabulary\n", - "\n", - "dataset = DataSet()\n", - "dataset.append(Instance(raw_sent='This is a bad idea .', label=0))\n", - "dataset.append(Instance(raw_sent='It is great .', label=1))\n", - "\n", - "# 按照fastNLP_10min_tutorial.ipynb的步骤,对数据进行一些处理。这里为了演示padding操作,把field的名称做了一些改变\n", - "dataset.apply(lambda x:x['raw_sent'].lower(), new_field_name='raw_sent')\n", - "dataset.apply(lambda x:x['raw_sent'].split(), new_field_name='word_str_lst')\n", - "\n", - "# 建立Vocabulary\n", - "word_vocab = Vocabulary()\n", - "dataset.apply(lambda x:word_vocab.update(x['word_str_lst']))\n", - "dataset.apply(lambda x:[word_vocab.to_index(word) for word in x['word_str_lst']], new_field_name='words')\n", - "\n", - "# 检查以下是否得到我们想要的结果了\n", - "dataset[:2]" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "batch_x has: {'word_str_lst': array([list(['this', 'is', 'a', 'bad', 'idea', '.']),\n", - " list(['it', 'is', 'great', '.'])], dtype=object), 'words': tensor([[4, 2, 5, 6, 7, 3],\n", - " [8, 2, 9, 3, 0, 0]])}\n", - "batch_y has: {'label': tensor([0, 1])}\n" - ] - }, - { - "data": { - "text/plain": [ - "'\"\\n结果中\\n Batch会对元素类型(元素即最内层的数据,raw_sent为str,word_str_lst为str,words为int, label为int)为int或者float的数据进行默认\\n padding,而非int或float的则不进行padding。但若每个Instance中该field为二维数据,也不进行padding。因为二维数据的padding涉及到\\n 两个维度的padding,不容易自动判断padding的形式。\\n'" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 将field设置为input或者target\n", - "dataset.set_input('word_str_lst')\n", - "dataset.set_input('words')\n", - "dataset.set_target('label')\n", - "\n", - "# 使用Batch取出batch数据\n", - "from fastNLP.core.batch import Batch\n", - "from fastNLP.core.sampler import RandomSampler\n", - "\n", - "batch_iterator = Batch(dataset=dataset, batch_size=2, sampler=RandomSampler())\n", - "for batch_x, batch_y in batch_iterator:\n", - " print(\"batch_x has: \", batch_x)\n", - " print(\"batch_y has: \", batch_y)\n", - "\"\"\"\"\n", - "结果中\n", - " Batch会对元素类型(元素即最内层的数据,raw_sent为str,word_str_lst为str,words为int, label为int)为int或者float的数据进行默认\n", - " padding,而非int或float的则不进行padding。但若每个Instance中该field为二维数据,也不进行padding。因为二维数据的padding涉及到\n", - " 两个维度的padding,不容易自动判断padding的形式。\n", - "\"\"\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "batch_x has: {'word_str_lst': array([list(['it', 'is', 'great', '.']),\n", - " list(['this', 'is', 'a', 'bad', 'idea', '.'])], dtype=object), 'words': tensor([[ 8, 2, 9, 3, -100, -100],\n", - " [ 4, 2, 5, 6, 7, 3]])}\n", - "batch_y has: {'label': tensor([1, 0])}\n" - ] - } - ], - "source": [ - "# 所有的pad_val都默认为0,如果需要修改某一个field的默认pad值,可以通过DataSet.set_pad_val(field_name, pad_val)进行修改\n", - "# 若需要将word的padding修改为-100\n", - "dataset.set_pad_val('words', pad_val=-100)\n", - "batch_iterator = Batch(dataset=dataset, batch_size=2, sampler=RandomSampler())\n", - "for batch_x, batch_y in batch_iterator:\n", - " print(\"batch_x has: \", batch_x)\n", - " print(\"batch_y has: \", batch_y)\n", - "# pad的值修改为-100了" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'raw_sent': this is a bad idea . type=str,\n", - "'label': 0 type=int,\n", - "'word_str_lst': ['this', 'is', 'a', 'bad', 'idea', '.'] type=list,\n", - "'words': [4, 2, 5, 6, 7, 3] type=list,\n", - "'char_str_lst': [['t', 'h', 'i', 's'], ['i', 's'], ['a'], ['b', 'a', 'd'], ['i', 'd', 'e', 'a'], ['.']] type=list,\n", - "'chars': [[4, 9, 2, 5], [2, 5], [3], [10, 3, 6], [2, 6, 7, 3], [8]] type=list},\n", - "{'raw_sent': it is great . type=str,\n", - "'label': 1 type=int,\n", - "'word_str_lst': ['it', 'is', 'great', '.'] type=list,\n", - "'words': [8, 2, 9, 3] type=list,\n", - "'char_str_lst': [['i', 't'], ['i', 's'], ['g', 'r', 'e', 'a', 't'], ['.']] type=list,\n", - "'chars': [[2, 4], [2, 5], [11, 12, 7, 3, 4], [8]] type=list})" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 若需要使用二维padding或指定padding方式,可以通过设置该field的padder实现,下面以英文的character padding为例。在某些场景下,可能想要\n", - "# 使用英文word的character作为特征,character的padding为二维padding,fastNLP默认只会进行一维padding。\n", - "\n", - "dataset.apply(lambda x: [[c for c in word] for word in x['word_str_lst']], new_field_name='char_str_lst')\n", - "char_vocab = Vocabulary()\n", - "dataset.apply(lambda x:[char_vocab.update(chars) for chars in x['char_str_lst']])\n", - "dataset.apply(lambda x:[[char_vocab.to_index(c) for c in chars] for chars in x['char_str_lst']],new_field_name='chars')\n", - "dataset[:2]" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "batch_x has: {'word_str_lst': array([list(['this', 'is', 'a', 'bad', 'idea', '.']),\n", - " list(['it', 'is', 'great', '.'])], dtype=object), 'words': tensor([[ 4, 2, 5, 6, 7, 3],\n", - " [ 8, 2, 9, 3, -100, -100]]), 'chars': array([list([[4, 9, 2, 5], [2, 5], [3], [10, 3, 6], [2, 6, 7, 3], [8]]),\n", - " list([[2, 4], [2, 5], [11, 12, 7, 3, 4], [8]])], dtype=object)}\n", - "batch_y has: {'label': tensor([0, 1])}\n" - ] - }, - { - "data": { - "text/plain": [ - "'\\n 其它field与之前的是相同的。chars因为存在两个维度需要padding,不能自动决定padding方式,所以直接输出了原始形式。\\n'" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 如果不针对二维的character指定padding方法\n", - "dataset.set_input('chars')\n", - "batch_iterator = Batch(dataset=dataset, batch_size=2, sampler=RandomSampler())\n", - "for batch_x, batch_y in batch_iterator:\n", - " print(\"batch_x has: \", batch_x)\n", - " print(\"batch_y has: \", batch_y)\n", - " \n", - "\"\"\"\n", - " 其它field与之前的是相同的。chars因为存在两个维度需要padding,不能自动决定padding方式,所以直接输出了原始形式。\n", - "\"\"\"" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "batch_x has: {'word_str_lst': array([list(['this', 'is', 'a', 'bad', 'idea', '.']),\n", - " list(['it', 'is', 'great', '.'])], dtype=object), 'words': tensor([[ 4, 2, 5, 6, 7, 3],\n", - " [ 8, 2, 9, 3, -100, -100]]), 'chars': tensor([[[ 4, 9, 2, 5],\n", - " [ 2, 5, 0, 0],\n", - " [ 3, 0, 0, 0],\n", - " [10, 3, 6, 0],\n", - " [ 2, 6, 7, 3],\n", - " [ 8, 0, 0, 0]],\n", - "\n", - " [[ 2, 4, 0, 0],\n", - " [ 2, 5, 0, 0],\n", - " [11, 12, 7, 3],\n", - " [ 8, 0, 0, 0],\n", - " [ 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0]]])}\n", - "batch_y has: {'label': tensor([0, 1])}\n" - ] - }, - { - "data": { - "text/plain": [ - "'\\n chars被正确padding了\\n'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 若要使用二维padding,需要手动设置padding方式\n", - "from fastNLP.core.fieldarray import EngChar2DPadder\n", - "dataset.set_padder('chars', EngChar2DPadder())\n", - "batch_iterator = Batch(dataset=dataset, batch_size=2, sampler=RandomSampler())\n", - "for batch_x, batch_y in batch_iterator:\n", - " print(\"batch_x has: \", batch_x)\n", - " print(\"batch_y has: \", batch_y)\n", - " \n", - "\"\"\"\n", - " chars被正确padding了\n", - "\"\"\"" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "batch_x has: {'raw_sent': ['this is a bad idea .', 'it is great . '], 'word_str_lst': array([list(['this', 'is', 'a', 'bad', 'idea', '.']),\n", - " list(['it', 'is', 'great', '.'])], dtype=object), 'words': tensor([[ 4, 2, 5, 6, 7, 3],\n", - " [ 8, 2, 9, 3, -100, -100]]), 'chars': tensor([[[ 4, 9, 2, 5],\n", - " [ 2, 5, 0, 0],\n", - " [ 3, 0, 0, 0],\n", - " [10, 3, 6, 0],\n", - " [ 2, 6, 7, 3],\n", - " [ 8, 0, 0, 0]],\n", - "\n", - " [[ 2, 4, 0, 0],\n", - " [ 2, 5, 0, 0],\n", - " [11, 12, 7, 3],\n", - " [ 8, 0, 0, 0],\n", - " [ 0, 0, 0, 0],\n", - " [ 0, 0, 0, 0]]])}\n", - "batch_y has: {'label': tensor([0, 1])}\n" - ] - }, - { - "data": { - "text/plain": [ - "'\\n raw_sent正确输出,对应内容也进行了pad。\\n'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 如果AutoPad与EngChar2DPadder不能满足需要,可以自己实现Padder对象。这里举一个例子,比如需要把raw_sentence pad到一样长\n", - "from fastNLP.core.fieldarray import PadderBase\n", - "\n", - "class PadStr(PadderBase):\n", - " def __init__(self, pad_val=' '):\n", - " super().__init__(pad_val=pad_val) #让父类管理pad_val的值,这样可以通过DataSet.set_pad_val()修改到该值\n", - " \n", - " def __call__(self, contents, field_name, field_ele_dtype):\n", - " \"\"\"\n", - " 如果以上面的例子举例,在raw_sent这个field进行pad时,传入的\n", - " contents:\n", - " [\n", - " 'This is a bad idea .',\n", - " 'It is great .'\n", - " ]\n", - " field_name: 'raw_sent',当前field的名称,主要用于帮助debug。\n", - " field_ele_dtype: np.str. 这个参数基本都用不上,是该field中内部元素的类型\n", - " \"\"\"\n", - " max_len = max([len(str_) for str_ in contents])\n", - " pad_strs = []\n", - " for content in contents:\n", - " pad_strs.append(content + (max_len-len(content))*self.pad_val)\n", - " return pad_strs\n", - "\n", - "dataset.set_input('raw_sent')\n", - "dataset.set_padder('raw_sent', PadStr())\n", - "batch_iterator = Batch(dataset=dataset, batch_size=2, sampler=RandomSampler())\n", - "for batch_x, batch_y in batch_iterator:\n", - " print(\"batch_x has: \", batch_x)\n", - " print(\"batch_y has: \", batch_y)\n", - "\n", - "\"\"\"\n", - " raw_sent正确输出,对应内容也进行了pad。\n", - "\"\"\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/fastnlp_10min_tutorial.ipynb b/tutorials/fastnlp_10min_tutorial.ipynb deleted file mode 100644 index 534c4e49..00000000 --- a/tutorials/fastnlp_10min_tutorial.ipynb +++ /dev/null @@ -1,762 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "fastNLP10 分钟上手教程\n", - "-------\n", - "\n", - "fastNLP提供方便的数据预处理,训练和测试模型的功能" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果您还没有通过pip安装fastNLP,可以执行下面的操作加载当前模块" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "sys.path.append(\"../\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "DataSet & Instance\n", - "------\n", - "\n", - "fastNLP用DataSet和Instance保存和处理数据。每个DataSet表示一个数据集,每个Instance表示一个数据样本。一个DataSet存有多个Instance,每个Instance可以自定义存哪些内容。\n", - "\n", - "有一些read_*方法,可以轻松从文件读取数据,存成DataSet。" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "77\n" - ] - } - ], - "source": [ - "from fastNLP import DataSet\n", - "from fastNLP import Instance\n", - "\n", - "# 从csv读取数据到DataSet\n", - "dataset = DataSet.read_csv('sample_data/tutorial_sample_dataset.csv', headers=('raw_sentence', 'label'), sep='\\t')\n", - "print(len(dataset))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=str}\n", - "{'raw_sentence': The plot is romantic comedy boilerplate from start to finish . type=str,\n", - "'label': 2 type=str}\n" - ] - } - ], - "source": [ - "# 使用数字索引[k],获取第k个样本\n", - "print(dataset[0])\n", - "\n", - "# 索引也可以是负数\n", - "print(dataset[-3])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Instance\n", - "Instance表示一个样本,由一个或多个field(域,属性,特征)组成,每个field有名字和值。\n", - "\n", - "在初始化Instance时即可定义它包含的域,使用 \"field_name=field_value\"的写法。" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'raw_sentence': fake data type=str,\n", - "'label': 0 type=str}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# DataSet.append(Instance)加入新数据\n", - "dataset.append(Instance(raw_sentence='fake data', label='0'))\n", - "dataset[-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## DataSet.apply方法\n", - "数据预处理利器" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'raw_sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=str}\n" - ] - } - ], - "source": [ - "# 将所有数字转为小写\n", - "dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='raw_sentence')\n", - "print(dataset[0])" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'raw_sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=int}\n" - ] - } - ], - "source": [ - "# label转int\n", - "dataset.apply(lambda x: int(x['label']), new_field_name='label')\n", - "print(dataset[0])" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'raw_sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=int,\n", - "'words': ['a', 'series', 'of', 'escapades', 'demonstrating', 'the', 'adage', 'that', 'what', 'is', 'good', 'for', 'the', 'goose', 'is', 'also', 'good', 'for', 'the', 'gander', ',', 'some', 'of', 'which', 'occasionally', 'amuses', 'but', 'none', 'of', 'which', 'amounts', 'to', 'much', 'of', 'a', 'story', '.'] type=list}\n" - ] - } - ], - "source": [ - "# 使用空格分割句子\n", - "def split_sent(ins):\n", - " return ins['raw_sentence'].split()\n", - "dataset.apply(split_sent, new_field_name='words')\n", - "print(dataset[0])" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'raw_sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=int,\n", - "'words': ['a', 'series', 'of', 'escapades', 'demonstrating', 'the', 'adage', 'that', 'what', 'is', 'good', 'for', 'the', 'goose', 'is', 'also', 'good', 'for', 'the', 'gander', ',', 'some', 'of', 'which', 'occasionally', 'amuses', 'but', 'none', 'of', 'which', 'amounts', 'to', 'much', 'of', 'a', 'story', '.'] type=list,\n", - "'seq_len': 37 type=int}\n" - ] - } - ], - "source": [ - "# 增加长度信息\n", - "dataset.apply(lambda x: len(x['words']), new_field_name='seq_len')\n", - "print(dataset[0])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## DataSet.drop\n", - "筛选数据" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "77\n" - ] - } - ], - "source": [ - "# 删除低于某个长度的词语\n", - "dataset.drop(lambda x: x['seq_len'] <= 3)\n", - "print(len(dataset))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 配置DataSet\n", - "1. 哪些域是特征,哪些域是标签\n", - "2. 切分训练集/验证集" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "# 设置DataSet中,哪些field要转为tensor\n", - "\n", - "# set target,loss或evaluate中的golden,计算loss,模型评估时使用\n", - "dataset.set_target(\"label\")\n", - "# set input,模型forward时使用\n", - "dataset.set_input(\"words\")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "54\n", - "23\n" - ] - } - ], - "source": [ - "# 分出测试集、训练集\n", - "\n", - "test_data, train_data = dataset.split(0.3)\n", - "print(len(test_data))\n", - "print(len(train_data))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Vocabulary\n", - "------\n", - "\n", - "fastNLP中的Vocabulary轻松构建词表,将词转成数字" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'raw_sentence': a welcome relief from baseball movies that try too hard to be mythic , this one is a sweet and modest and ultimately winning story . type=str,\n", - "'label': 3 type=int,\n", - "'words': [4, 1, 1, 18, 1, 1, 13, 1, 1, 1, 8, 26, 1, 5, 35, 1, 11, 4, 1, 10, 1, 10, 1, 1, 1, 2] type=list,\n", - "'seq_len': 26 type=int}\n" - ] - } - ], - "source": [ - "from fastNLP import Vocabulary\n", - "\n", - "# 构建词表, Vocabulary.add(word)\n", - "vocab = Vocabulary(min_freq=2)\n", - "train_data.apply(lambda x: [vocab.add(word) for word in x['words']])\n", - "vocab.build_vocab()\n", - "\n", - "# index句子, Vocabulary.to_index(word)\n", - "train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='words')\n", - "test_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='words')\n", - "\n", - "\n", - "print(test_data[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 如果你们需要做强化学习或者GAN之类的项目,你们也可以使用这些数据预处理的工具\n", - "from fastNLP.core.batch import Batch\n", - "from fastNLP.core.sampler import RandomSampler\n", - "\n", - "batch_iterator = Batch(dataset=train_data, batch_size=2, sampler=RandomSampler())\n", - "for batch_x, batch_y in batch_iterator:\n", - " print(\"batch_x has: \", batch_x)\n", - " print(\"batch_y has: \", batch_y)\n", - " break" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Model\n", - "定义一个PyTorch模型" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "CNNText(\n", - " (embed): Embedding(\n", - " (embed): Embedding(59, 50, padding_idx=0)\n", - " (dropout): Dropout(p=0.0)\n", - " )\n", - " (conv_pool): ConvMaxpool(\n", - " (convs): ModuleList(\n", - " (0): Conv1d(50, 3, kernel_size=(3,), stride=(1,), padding=(2,))\n", - " (1): Conv1d(50, 4, kernel_size=(4,), stride=(1,), padding=(2,))\n", - " (2): Conv1d(50, 5, kernel_size=(5,), stride=(1,), padding=(2,))\n", - " )\n", - " )\n", - " (dropout): Dropout(p=0.1)\n", - " (fc): Linear(\n", - " (linear): Linear(in_features=12, out_features=5, bias=True)\n", - " )\n", - ")" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from fastNLP.models import CNNText\n", - "model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1)\n", - "model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是上述模型的forward方法。如果你不知道什么是forward方法,请参考我们的PyTorch教程。\n", - "\n", - "注意两点:\n", - "1. forward参数名字叫**word_seq**,请记住。\n", - "2. forward的返回值是一个**dict**,其中有个key的名字叫**output**。\n", - "\n", - "```Python\n", - " def forward(self, word_seq):\n", - " \"\"\"\n", - "\n", - " :param word_seq: torch.LongTensor, [batch_size, seq_len]\n", - " :return output: dict of torch.LongTensor, [batch_size, num_classes]\n", - " \"\"\"\n", - " x = self.embed(word_seq) # [N,L] -> [N,L,C]\n", - " x = self.conv_pool(x) # [N,L,C] -> [N,C]\n", - " x = self.dropout(x)\n", - " x = self.fc(x) # [N,C] -> [N, N_class]\n", - " return {'output': x}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是上述模型的predict方法,是用来直接输出该任务的预测结果,与forward目的不同。\n", - "\n", - "注意两点:\n", - "1. predict参数名也叫**word_seq**。\n", - "2. predict的返回值是也一个**dict**,其中有个key的名字叫**predict**。\n", - "\n", - "```\n", - " def predict(self, word_seq):\n", - " \"\"\"\n", - "\n", - " :param word_seq: torch.LongTensor, [batch_size, seq_len]\n", - " :return predict: dict of torch.LongTensor, [batch_size, seq_len]\n", - " \"\"\"\n", - " output = self(word_seq)\n", - " _, predict = output['output'].max(dim=1)\n", - " return {'predict': predict}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Trainer & Tester\n", - "------\n", - "\n", - "使用fastNLP的Trainer训练模型" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "from fastNLP import Trainer\n", - "from copy import deepcopy\n", - "from fastNLP.core.losses import CrossEntropyLoss\n", - "from fastNLP.core.metrics import AccuracyMetric\n", - "\n", - "\n", - "# 更改DataSet中对应field的名称,与模型的forward的参数名一致\n", - "# 因为forward的参数叫word_seq, 所以要把原本叫words的field改名为word_seq\n", - "# 这里的演示是让你了解这种**命名规则**\n", - "train_data.rename_field('words', 'word_seq')\n", - "test_data.rename_field('words', 'word_seq')\n", - "\n", - "# 顺便把label换名为label_seq\n", - "train_data.rename_field('label', 'label_seq')\n", - "test_data.rename_field('label', 'label_seq')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### loss\n", - "训练模型需要提供一个损失函数\n", - "\n", - "下面提供了一个在分类问题中常用的交叉熵损失。注意它的**初始化参数**。\n", - "\n", - "pred参数对应的是模型的forward返回的dict的一个key的名字,这里是\"output\"。\n", - "\n", - "target参数对应的是dataset作为标签的field的名字,这里是\"label_seq\"。" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "loss = CrossEntropyLoss(pred=\"output\", target=\"label_seq\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Metric\n", - "定义评价指标\n", - "\n", - "这里使用准确率。参数的“命名规则”跟上面类似。\n", - "\n", - "pred参数对应的是模型的predict方法返回的dict的一个key的名字,这里是\"predict\"。\n", - "\n", - "target参数对应的是dataset作为标签的field的名字,这里是\"label_seq\"。" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "metric = AccuracyMetric(pred=\"predict\", target=\"label_seq\")" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "input fields after batch(if batch size is 2):\n", - "\tword_seq: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26]) \n", - "target fields after batch(if batch size is 2):\n", - "\tlabel_seq: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", - "\n", - "training epochs started 2019-01-12 17-07-51\n" - ] - }, - { - "data": { - "text/plain": [ - "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=10), HTML(value='')), layout=Layout(display='…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Evaluation at Epoch 1/5. Step:2/10. AccuracyMetric: acc=0.425926\n", - "Evaluation at Epoch 2/5. Step:4/10. AccuracyMetric: acc=0.425926\n", - "Evaluation at Epoch 3/5. Step:6/10. AccuracyMetric: acc=0.611111\n", - "Evaluation at Epoch 4/5. Step:8/10. AccuracyMetric: acc=0.648148\n", - "Evaluation at Epoch 5/5. Step:10/10. AccuracyMetric: acc=0.703704\n", - "\n", - "In Epoch:5/Step:10, got best dev performance:AccuracyMetric: acc=0.703704\n", - "Reloaded the best model.\n" - ] - }, - { - "data": { - "text/plain": [ - "{'best_eval': {'AccuracyMetric': {'acc': 0.703704}},\n", - " 'best_epoch': 5,\n", - " 'best_step': 10,\n", - " 'seconds': 0.62}" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 实例化Trainer,传入模型和数据,进行训练\n", - "# 先在test_data拟合(确保模型的实现是正确的)\n", - "copy_model = deepcopy(model)\n", - "overfit_trainer = Trainer(model=copy_model, train_data=test_data, dev_data=test_data,\n", - " loss=loss,\n", - " metrics=metric,\n", - " save_path=None,\n", - " batch_size=32,\n", - " n_epochs=5)\n", - "overfit_trainer.train()" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "input fields after batch(if batch size is 2):\n", - "\tword_seq: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 20]) \n", - "target fields after batch(if batch size is 2):\n", - "\tlabel_seq: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", - "\n", - "training epochs started 2019-01-12 17-09-05\n" - ] - }, - { - "data": { - "text/plain": [ - "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=5), HTML(value='')), layout=Layout(display='i…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Evaluation at Epoch 1/5. Step:1/5. AccuracyMetric: acc=0.37037\n", - "Evaluation at Epoch 2/5. Step:2/5. AccuracyMetric: acc=0.37037\n", - "Evaluation at Epoch 3/5. Step:3/5. AccuracyMetric: acc=0.462963\n", - "Evaluation at Epoch 4/5. Step:4/5. AccuracyMetric: acc=0.425926\n", - "Evaluation at Epoch 5/5. Step:5/5. AccuracyMetric: acc=0.481481\n", - "\n", - "In Epoch:5/Step:5, got best dev performance:AccuracyMetric: acc=0.481481\n", - "Reloaded the best model.\n", - "Train finished!\n" - ] - } - ], - "source": [ - "# 用train_data训练,在test_data验证\n", - "trainer = Trainer(model=model, train_data=train_data, dev_data=test_data,\n", - " loss=CrossEntropyLoss(pred=\"output\", target=\"label_seq\"),\n", - " metrics=AccuracyMetric(pred=\"predict\", target=\"label_seq\"),\n", - " save_path=None,\n", - " batch_size=32,\n", - " n_epochs=5)\n", - "trainer.train()\n", - "print('Train finished!')" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[tester] \n", - "AccuracyMetric: acc=0.481481\n", - "{'AccuracyMetric': {'acc': 0.481481}}\n" - ] - } - ], - "source": [ - "# 调用Tester在test_data上评价效果\n", - "from fastNLP import Tester\n", - "\n", - "tester = Tester(data=test_data, model=model, metrics=AccuracyMetric(pred=\"predict\", target=\"label_seq\"),\n", - " batch_size=4)\n", - "acc = tester.test()\n", - "print(acc)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# In summary\n", - "\n", - "## fastNLP Trainer的伪代码逻辑\n", - "### 1. 准备DataSet,假设DataSet中共有如下的fields\n", - " ['raw_sentence', 'word_seq1', 'word_seq2', 'raw_label','label']\n", - " 通过\n", - " DataSet.set_input('word_seq1', word_seq2', flag=True)将'word_seq1', 'word_seq2'设置为input\n", - " 通过\n", - " DataSet.set_target('label', flag=True)将'label'设置为target\n", - "### 2. 初始化模型\n", - " class Model(nn.Module):\n", - " def __init__(self):\n", - " xxx\n", - " def forward(self, word_seq1, word_seq2):\n", - " # (1) 这里使用的形参名必须和DataSet中的input field的名称对应。因为我们是通过形参名, 进行赋值的\n", - " # (2) input field的数量可以多于这里的形参数量。但是不能少于。\n", - " xxxx\n", - " # 输出必须是一个dict\n", - "### 3. Trainer的训练过程\n", - " (1) 从DataSet中按照batch_size取出一个batch,调用Model.forward\n", - " (2) 将 Model.forward的结果 与 标记为target的field 传入Losser当中。\n", - " 由于每个人写的Model.forward的output的dict可能key并不一样,比如有人是{'pred':xxx}, {'output': xxx}; \n", - " 另外每个人将target可能也会设置为不同的名称, 比如有人是label, 有人设置为target;\n", - " 为了解决以上的问题,我们的loss提供映射机制\n", - " 比如CrossEntropyLosser的需要的输入是(prediction, target)。但是forward的output是{'output': xxx}; 'label'是target\n", - " 那么初始化losser的时候写为CrossEntropyLosser(prediction='output', target='label')即可\n", - " (3) 对于Metric是同理的\n", - " Metric计算也是从 forward的结果中取值 与 设置target的field中取值。 也是可以通过映射找到对应的值 \n", - " \n", - " \n", - "\n", - "## 一些问题.\n", - "### 1. DataSet中为什么需要设置input和target\n", - " 只有被设置为input或者target的数据才会在train的过程中被取出来\n", - " (1.1) 我们只会在设置为input的field中寻找传递给Model.forward的参数。\n", - " (1.2) 我们在传递值给losser或者metric的时候会使用来自: \n", - " (a)Model.forward的output\n", - " (b)被设置为target的field\n", - " \n", - "\n", - "### 2. 我们是通过forwad中的形参名将DataSet中的field赋值给对应的参数\n", - " (1.1) 构建模型过程中,\n", - " 例如:\n", - " DataSet中x,seq_lens是input,那么forward就应该是\n", - " def forward(self, x, seq_lens):\n", - " pass\n", - " 我们是通过形参名称进行匹配的field的\n", - " \n", - "\n", - "\n", - "### 1. 加载数据到DataSet\n", - "### 2. 使用apply操作对DataSet进行预处理\n", - " (2.1) 处理过程中将某些field设置为input,某些field设置为target\n", - "### 3. 构建模型\n", - " (3.1) 构建模型过程中,需要注意forward函数的形参名需要和DataSet中设置为input的field名称是一致的。\n", - " 例如:\n", - " DataSet中x,seq_lens是input,那么forward就应该是\n", - " def forward(self, x, seq_lens):\n", - " pass\n", - " 我们是通过形参名称进行匹配的field的\n", - " (3.2) 模型的forward的output需要是dict类型的。\n", - " 建议将输出设置为{\"pred\": xx}.\n", - " \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/fastnlp_1min_tutorial.ipynb b/tutorials/fastnlp_1min_tutorial.ipynb deleted file mode 100644 index 7a35d992..00000000 --- a/tutorials/fastnlp_1min_tutorial.ipynb +++ /dev/null @@ -1,248 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "# fastNLP 1分钟上手教程" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## step 1\n", - "读取数据集" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\users\\zyfeng\\miniconda3\\envs\\fastnlp\\lib\\site-packages\\tqdm\\autonotebook\\__init__.py:14: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)\n", - " \" (e.g. in jupyter console)\", TqdmExperimentalWarning)\n" - ] - } - ], - "source": [ - "import sys\n", - "sys.path.append(\"../\")\n", - "\n", - "from fastNLP import DataSet\n", - "\n", - "data_path = \"./sample_data/tutorial_sample_dataset.csv\"\n", - "ds = DataSet.read_csv(data_path, headers=('raw_sentence', 'label'), sep='\\t')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'raw_sentence': This quiet , introspective and entertaining independent is worth seeking . type=str,\n", - "'label': 4 type=str}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ds[1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## step 2\n", - "数据预处理\n", - "1. 类型转换\n", - "2. 切分验证集\n", - "3. 构建词典" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# 将所有数字转为小写\n", - "ds.apply(lambda x: x['raw_sentence'].lower(), new_field_name='raw_sentence')\n", - "# label转int\n", - "ds.apply(lambda x: int(x['label']), new_field_name='label_seq', is_target=True)\n", - "\n", - "def split_sent(ins):\n", - " return ins['raw_sentence'].split()\n", - "ds.apply(split_sent, new_field_name='words', is_input=True)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train size: 54\n", - "Test size: 23\n" - ] - } - ], - "source": [ - "# 分割训练集/验证集\n", - "train_data, dev_data = ds.split(0.3)\n", - "print(\"Train size: \", len(train_data))\n", - "print(\"Test size: \", len(dev_data))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "from fastNLP import Vocabulary\n", - "vocab = Vocabulary(min_freq=2)\n", - "train_data.apply(lambda x: [vocab.add(word) for word in x['words']])\n", - "\n", - "# index句子, Vocabulary.to_index(word)\n", - "train_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', is_input=True)\n", - "dev_data.apply(lambda x: [vocab.to_index(word) for word in x['words']], new_field_name='word_seq', is_input=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## step 3\n", - " 定义模型" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from fastNLP.models import CNNText\n", - "model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## step 4\n", - "开始训练" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "input fields after batch(if batch size is 2):\n", - "\twords: (1)type:numpy.ndarray (2)dtype:object, (3)shape:(2,) \n", - "\tword_seq: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 25]) \n", - "target fields after batch(if batch size is 2):\n", - "\tlabel_seq: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", - "\n", - "training epochs started 2019-01-12 17-00-48\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "23979df0f63e446fbb0406b919b91dd3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=6), HTML(value='')), layout=Layout(display='i…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Evaluation at Epoch 1/3. Step:2/6. AccuracyMetric: acc=0.173913\n", - "Evaluation at Epoch 2/3. Step:4/6. AccuracyMetric: acc=0.26087\n", - "Evaluation at Epoch 3/3. Step:6/6. AccuracyMetric: acc=0.304348\n", - "\n", - "In Epoch:3/Step:6, got best dev performance:AccuracyMetric: acc=0.304348\n", - "Reloaded the best model.\n", - "Train finished!\n" - ] - } - ], - "source": [ - "from fastNLP import Trainer, CrossEntropyLoss, AccuracyMetric\n", - "trainer = Trainer(model=model, \n", - " train_data=train_data, \n", - " dev_data=dev_data,\n", - " loss=CrossEntropyLoss(),\n", - " metrics=AccuracyMetric()\n", - " )\n", - "trainer.train()\n", - "print('Train finished!')\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 本教程结束。更多操作请参考进阶教程。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/tutorials/fastnlp_advanced_tutorial/advance_tutorial.ipynb b/tutorials/fastnlp_advanced_tutorial/advance_tutorial.ipynb deleted file mode 100644 index a787eeaf..00000000 --- a/tutorials/fastnlp_advanced_tutorial/advance_tutorial.ipynb +++ /dev/null @@ -1,1169 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# fastNLP开发进阶教程\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 组织数据部分\n", - "## DataSet & Instance\n", - "fastNLP用DataSet和Instance保存和处理数据。每个DataSet表示一个数据集,每个Instance表示一个数据样本。一个DataSet存有多个Instance,每个Instance可以自定义存哪些内容。" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/remote-home/ygxu/anaconda3/envs/no-fastnlp/lib/python3.7/site-packages/tqdm/autonotebook/__init__.py:14: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)\n", - " \" (e.g. in jupyter console)\", TqdmExperimentalWarning)\n" - ] - } - ], - "source": [ - "# 声明部件\n", - "import torch\n", - "import fastNLP\n", - "from fastNLP import DataSet\n", - "from fastNLP import Instance\n", - "from fastNLP import Vocabulary\n", - "from fastNLP import Trainer\n", - "from fastNLP import Tester" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Instance\n", - "Instance表示一个样本,由一个或者多个field(域、属性、特征)组成,每个field具有自己的名字以及值\n", - "在初始化Instance的时候可以定义它包含的field,使用\"field_name=field_value\"的写法" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'premise': an premise example . type=str,\n", - "'hypothesis': an hypothesis example. type=str,\n", - "'label': 1 type=int}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 组织一个Instance,这个Instance由premise、hypothesis、label三个field组成\n", - "instance = Instance(premise='an premise example .', hypothesis='an hypothesis example.', label=1)\n", - "instance" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'premise': an premise example . type=str,\n", - "'hypothesis': an hypothesis example. type=str,\n", - "'label': 1 type=int},\n", - "{'premise': an premise example . type=str,\n", - "'hypothesis': an hypothesis example. type=str,\n", - "'label': 1 type=int})" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "data_set = DataSet([instance] * 5)\n", - "data_set.append(instance)\n", - "data_set[-2: ]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'premise': an premise example . type=str,\n", - "'hypothesis': an hypothesis example. type=str,\n", - "'label': 1 type=int},\n", - "{'premise': the second premise example . type=str,\n", - "'hypothesis': the second hypothesis example. type=str,\n", - "'label': 1 type=str})" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 如果某一个field的类型与dataset对应的field类型不一样仍可被加入dataset中\n", - "instance2 = Instance(premise='the second premise example .', hypothesis='the second hypothesis example.', label='1')\n", - "try:\n", - " data_set.append(instance2)\n", - "except:\n", - " pass\n", - "data_set[-2: ]" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cannot append instance\n" - ] - }, - { - "data": { - "text/plain": [ - "DataSet({'premise': an premise example . type=str,\n", - "'hypothesis': an hypothesis example. type=str,\n", - "'label': 1 type=int},\n", - "{'premise': the second premise example . type=str,\n", - "'hypothesis': the second hypothesis example. type=str,\n", - "'label': 1 type=str})" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 如果某一个field的名字不对,则该instance不能被append到dataset中\n", - "instance3 = Instance(premises='the third premise example .', hypothesis='the third hypothesis example.', label=1)\n", - "try:\n", - " data_set.append(instance3)\n", - "except:\n", - " print('cannot append instance')\n", - " pass\n", - "data_set[-2: ]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'image': tensor([[ 2.1747, -1.0147, -1.3853, 0.0216, -0.4957],\n", - " [ 0.8138, -0.2933, -0.1217, -0.6027, 0.3932],\n", - " [ 0.6750, -1.1136, -1.3371, -0.0185, -0.3206],\n", - " [-0.5076, -0.3822, 0.1719, -0.6447, -0.5702],\n", - " [ 0.3804, 0.0889, 0.8027, -0.7121, -0.7320]]) type=torch.Tensor,\n", - "'label': 0 type=int})" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 除了文本以外,还可以将tensor作为其中一个field的value\n", - "import torch\n", - "tensor_ins = Instance(image=torch.randn(5, 5), label=0)\n", - "ds = DataSet()\n", - "ds.append(tensor_ins)\n", - "ds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### DataSet\n", - "### 使用现有代码读取并组织DataSet\n", - "在DataSet类当中有一些read_* 方法,可以从文件中读取数据并组织DataSet" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "77" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import fastNLP\n", - "from fastNLP import DataSet\n", - "from fastNLP import Instance\n", - "\n", - "# 从csv读取数据到DataSet\n", - "# 类csv文件,即每一行为一个example的文件,都可以使用这种方法进行数据读取\n", - "dataset = DataSet.read_csv('tutorial_sample_dataset.csv', headers=('raw_sentence', 'label'), sep='\\t')\n", - "# 查看DataSet的大小\n", - "len(dataset)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=str}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 使用数字索引[k],获取第k个样本\n", - "dataset[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "fastNLP.core.instance.Instance" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 获取的样本是一个Instance\n", - "type(dataset[0])" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", - "'label': 1 type=str},\n", - "{'raw_sentence': This quiet , introspective and entertaining independent is worth seeking . type=str,\n", - "'label': 4 type=str},\n", - "{'raw_sentence': Even fans of Ismail Merchant 's work , I suspect , would have a hard time sitting through this one . type=str,\n", - "'label': 1 type=str})" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 使用数字索引[a: b],获取第a到第b个样本\n", - "dataset[0: 3]" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'raw_sentence': A film that clearly means to preach exclusively to the converted . type=str,\n", - "'label': 2 type=str}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 索引也可以是负数\n", - "dataset[-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 自行读取并组织DataSet\n", - "以SNLI数据集为例,\n", - "SNLI数据集的训练、验证、测试集分别三个文件组成:第一个文件每一行是一句话,代表一个example当中的premise;第二个文件每一行也是一句话,代表一个example当中的hypothesis;第三个文件每一行是一个label" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'premise': A person on a horse jumps over a broken down airplane . type=str,\n", - "'hypothesis': A person is training his horse for a competition . type=str,\n", - "'truth': 1\n", - " type=str}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "data_path = ['premise', 'hypothesis', 'label']\n", - "\n", - "# 读入文件\n", - "with open(data_path[0]) as f:\n", - " premise = f.readlines()\n", - "\n", - "with open(data_path[1]) as f:\n", - " hypothesis = f.readlines()\n", - "\n", - "with open(data_path[2]) as f:\n", - " label = f.readlines()\n", - "\n", - "assert len(premise) == len(hypothesis) and len(hypothesis) == len(label)\n", - "\n", - "# 组织DataSet\n", - "data_set = DataSet()\n", - "for p, h, l in zip(premise, hypothesis, label):\n", - " p = p.strip() # 将行末空格去除\n", - " h = h.strip() # 将行末空格去除\n", - " data_set.append(Instance(premise=p, hypothesis=h, truth=l))\n", - "\n", - "data_set[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### DataSet的其他操作\n", - "在构建完毕DataSet后,仍然可以对DataSet的内容进行操作,函数接口为DataSet.apply()" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'premise': a woman is walking across the street eating a banana , while a man is following with his briefcase . type=str,\n", - "'hypothesis': An actress and her favorite assistant talk a walk in the city . type=str,\n", - "'truth': 1\n", - " type=str},\n", - "{'premise': a woman is walking across the street eating a banana , while a man is following with his briefcase . type=str,\n", - "'hypothesis': a woman eating a banana crosses a street type=str,\n", - "'truth': 0\n", - " type=str})" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 将premise域的所有文本转成小写\n", - "data_set.apply(lambda x: x['premise'].lower(), new_field_name='premise')\n", - "data_set[-2: ]" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'premise': a woman is walking across the street eating a banana , while a man is following with his briefcase . type=str,\n", - "'hypothesis': An actress and her favorite assistant talk a walk in the city . type=str,\n", - "'truth': 1 type=int},\n", - "{'premise': a woman is walking across the street eating a banana , while a man is following with his briefcase . type=str,\n", - "'hypothesis': a woman eating a banana crosses a street type=str,\n", - "'truth': 0 type=int})" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# label转int\n", - "data_set.apply(lambda x: int(x['truth']), new_field_name='truth')\n", - "data_set[-2: ]" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "DataSet({'premise': ['a', 'woman', 'is', 'walking', 'across', 'the', 'street', 'eating', 'a', 'banana', ',', 'while', 'a', 'man', 'is', 'following', 'with', 'his', 'briefcase', '.'] type=list,\n", - "'hypothesis': ['An', 'actress', 'and', 'her', 'favorite', 'assistant', 'talk', 'a', 'walk', 'in', 'the', 'city', '.'] type=list,\n", - "'truth': 1 type=int},\n", - "{'premise': ['a', 'woman', 'is', 'walking', 'across', 'the', 'street', 'eating', 'a', 'banana', ',', 'while', 'a', 'man', 'is', 'following', 'with', 'his', 'briefcase', '.'] type=list,\n", - "'hypothesis': ['a', 'woman', 'eating', 'a', 'banana', 'crosses', 'a', 'street'] type=list,\n", - "'truth': 0 type=int})" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 使用空格分割句子\n", - "def split_sent(ins):\n", - " return ins['premise'].split()\n", - "data_set.apply(split_sent, new_field_name='premise')\n", - "data_set.apply(lambda x: x['hypothesis'].split(), new_field_name='hypothesis')\n", - "data_set[-2:]" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(100, 97)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 筛选数据\n", - "origin_data_set_len = len(data_set)\n", - "data_set.drop(lambda x: len(x['premise']) <= 6)\n", - "origin_data_set_len, len(data_set)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'premise': ['a', 'woman', 'is', 'walking', 'across', 'the', 'street', 'eating', 'a', 'banana', ',', 'while', 'a', 'man', 'is', 'following', 'with', 'his', 'briefcase', '.'] type=list,\n", - "'hypothesis': ['a', 'woman', 'eating', 'a', 'banana', 'crosses', 'a', 'street'] type=list,\n", - "'truth': 0 type=int,\n", - "'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - "'hypothesis_len': [1, 1, 1, 1, 1, 1, 1, 1] type=list}" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 增加长度信息\n", - "data_set.apply(lambda x: [1] * len(x['premise']), new_field_name='premise_len')\n", - "data_set.apply(lambda x: [1] * len(x['hypothesis']), new_field_name='hypothesis_len')\n", - "data_set[-1]" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "# 设定特征域、标签域\n", - "data_set.set_input(\"premise\", \"premise_len\", \"hypothesis\", \"hypothesis_len\")\n", - "data_set.set_target(\"truth\")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'premise': ['a', 'woman', 'is', 'walking', 'across', 'the', 'street', 'eating', 'a', 'banana', ',', 'while', 'a', 'man', 'is', 'following', 'with', 'his', 'briefcase', '.'] type=list,\n", - "'hypothesis': ['a', 'woman', 'eating', 'a', 'banana', 'crosses', 'a', 'street'] type=list,\n", - "'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - "'hypothesis_len': [1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - "'label': 0 type=int}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 重命名field\n", - "data_set.rename_field('truth', 'label')\n", - "data_set[-1]" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(49, 29, 19)" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 切分训练、验证集、测试集\n", - "train_data, vad_data = data_set.split(0.5)\n", - "dev_data, test_data = vad_data.split(0.4)\n", - "len(train_data), len(dev_data), len(test_data)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "# 深拷贝一个数据集\n", - "import copy\n", - "train_data_2, dev_data_2 = copy.deepcopy(train_data), copy.deepcopy(dev_data)\n", - "del copy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### DataSet的总结:\n", - "将DataSet的ield设置为input和target后,这些field在接下来的代码中将被使用。其中被设置为input的field会被传递给Model.forward,这个过程是通过键匹配的方式进行的。举例如下: \n", - "假设DataSet中有'x1', 'x2', 'x3'被设置为了input,而 \n", - "   (1)函数是Model.forward(self, x1, x3), 那么DataSet中'x1', 'x3'会被传递给forward函数。多余的'x2'会被忽略 \n", - "   (2)函数是Model.forward(self, x1, x4), 这里多需要了一个'x4', 但是DataSet的input field中没有这个field,会报错。 \n", - "   (3)函数是Model.forward(self, x1, kwargs), 会把'x1', 'x2', 'x3'都传入。但如果是Model.forward(self, x4, kwargs)就会发生报错,因为没有'x4'。 \n", - "   (4)对于设置为target的field的名称,我们建议取名为'target'(如果只有一个需要predict的值),但是不强制。如果这个名称不是target,那么在加载loss函数的时候需要手动添加名称转换map" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Vocabulary\n", - "fastNLP中的Vocabulary轻松构建词表,并将词转成数字。构建词表有两种方式:根据数据集构建词表;载入现有词表\n", - "### 根据数据集构建词表" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "# 初始化词表,该词表最大的vocab_size为10000,词表中每个词出现的最低频率为2,''表示未知词语,''表示padding词语\n", - "# Vocabulary默认初始化参数为max_size=None, min_freq=None, unknown='', padding=''\n", - "vocab = Vocabulary(max_size=10000, min_freq=2, unknown='', padding='')\n", - "\n", - "# 构建词表\n", - "train_data.apply(lambda x: [vocab.add(word) for word in x['premise']])\n", - "train_data.apply(lambda x: [vocab.add(word) for word in x['hypothesis']])\n", - "vocab.build_vocab()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "({'premise': [2, 145, 146, 80, 147, 26, 148, 2, 104, 149, 150, 2, 151, 5, 55, 152, 105, 3] type=list,\n", - " 'hypothesis': [22, 80, 8, 1, 1, 20, 1, 3] type=list,\n", - " 'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'hypothesis_len': [1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'label': 2 type=int},\n", - " {'premise': [11, 5, 18, 5, 24, 6, 2, 10, 59, 52, 14, 9, 2, 53, 29, 60, 54, 45, 6, 46, 5, 7, 61, 3] type=list,\n", - " 'hypothesis': [22, 11, 1, 45, 3] type=list,\n", - " 'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'hypothesis_len': [1, 1, 1, 1, 1] type=list,\n", - " 'label': 1 type=int},\n", - " {'premise': [2, 11, 8, 14, 16, 7, 15, 50, 2, 66, 4, 76, 2, 10, 8, 98, 9, 58, 67, 3] type=list,\n", - " 'hypothesis': [22, 27, 50, 3] type=list,\n", - " 'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'hypothesis_len': [1, 1, 1, 1] type=list,\n", - " 'label': 0 type=int})" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 根据词表index句子\n", - "train_data.apply(lambda x: [vocab.to_index(word) for word in x['premise']], new_field_name='premise')\n", - "train_data.apply(lambda x: [vocab.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis')\n", - "dev_data.apply(lambda x: [vocab.to_index(word) for word in x['premise']], new_field_name='premise')\n", - "dev_data.apply(lambda x: [vocab.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis')\n", - "test_data.apply(lambda x: [vocab.to_index(word) for word in x['premise']], new_field_name='premise')\n", - "test_data.apply(lambda x: [vocab.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis')\n", - "train_data[-1], dev_data[-1], test_data[-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 载入现有词表\n", - "以BERT pretrained model为例,词表由一个vocab.txt文件来保存\n", - "用以下方法可以载入现有词表,并保证词表顺序不变" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "# 读入vocab文件\n", - "with open('vocab.txt') as f:\n", - " lines = f.readlines()\n", - "vocabs = []\n", - "for line in lines:\n", - " vocabs.append(line.strip())\n", - "\n", - "# 实例化Vocabulary\n", - "vocab_bert = Vocabulary(unknown=None, padding=None)\n", - "# 将vocabs列表加入Vocabulary\n", - "vocab_bert.add_word_lst(vocabs)\n", - "# 构建词表\n", - "vocab_bert.build_vocab()\n", - "# 更新unknown与padding的token文本\n", - "vocab_bert.unknown = '[UNK]'\n", - "vocab_bert.padding = '[PAD]'" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "({'premise': [1037, 2210, 2223, 2136, 5363, 2000, 4608, 1037, 5479, 8058, 2046, 1037, 2918, 1999, 2019, 5027, 2208, 1012] type=list,\n", - " 'hypothesis': [100, 2136, 2003, 2652, 3598, 2006, 100, 1012] type=list,\n", - " 'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'hypothesis_len': [1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'label': 2 type=int},\n", - " {'premise': [2450, 1999, 2317, 1999, 100, 1998, 1037, 2158, 3621, 2369, 3788, 2007, 1037, 3696, 2005, 2198, 100, 10733, 1998, 100, 1999, 1996, 4281, 1012] type=list,\n", - " 'hypothesis': [100, 2450, 13063, 10733, 1012] type=list,\n", - " 'premise_len': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] type=list,\n", - " 'hypothesis_len': [1, 1, 1, 1, 1] type=list,\n", - " 'label': 1 type=int})" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 根据词表index句子\n", - "train_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['premise']], new_field_name='premise')\n", - "train_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis')\n", - "dev_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['premise']], new_field_name='premise')\n", - "dev_data_2.apply(lambda x: [vocab_bert.to_index(word) for word in x['hypothesis']], new_field_name='hypothesis')\n", - "train_data_2[-1], dev_data_2[-1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 模型部分\n", - "## Model\n", - "模型部分fastNLP提供两种使用方式:调用fastNLP现有模型;开发者自行搭建模型\n", - "### 调用fastNLP现有模型" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'embed_dim': 300,\n", - " 'hidden_size': 300,\n", - " 'batch_first': True,\n", - " 'dropout': 0.3,\n", - " 'num_classes': 3,\n", - " 'gpu': True,\n", - " 'batch_size': 32,\n", - " 'vocab_size': 165}" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# step 1:加载模型参数(非必选)\n", - "from fastNLP.io.config_io import ConfigSection, ConfigLoader\n", - "args = ConfigSection()\n", - "ConfigLoader().load_config(\"./data/config\", {\"esim_model\": args})\n", - "args[\"vocab_size\"] = len(vocab)\n", - "args.data" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ESIM(\n", - " (drop): Dropout(p=0.3)\n", - " (embedding): Embedding(\n", - " (embed): Embedding(165, 300, padding_idx=0)\n", - " (dropout): Dropout(p=0.3)\n", - " )\n", - " (embedding_layer): Linear(\n", - " (linear): Linear(in_features=300, out_features=300, bias=True)\n", - " )\n", - " (encoder): LSTM(\n", - " (lstm): LSTM(300, 300, batch_first=True, bidirectional=True)\n", - " )\n", - " (bi_attention): Bi_Attention()\n", - " (mean_pooling): MeanPoolWithMask()\n", - " (max_pooling): MaxPoolWithMask()\n", - " (inference_layer): Linear(\n", - " (linear): Linear(in_features=1200, out_features=300, bias=True)\n", - " )\n", - " (decoder): LSTM(\n", - " (lstm): LSTM(300, 300, batch_first=True, bidirectional=True)\n", - " )\n", - " (output): MLP(\n", - " (hiddens): ModuleList(\n", - " (0): Linear(in_features=1200, out_features=300, bias=True)\n", - " )\n", - " (output): Linear(in_features=300, out_features=3, bias=True)\n", - " (dropout): Dropout(p=0.3)\n", - " (hidden_active): Tanh()\n", - " )\n", - ")" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# step 2:加载ESIM模型\n", - "from fastNLP.models import ESIM\n", - "model = ESIM(**args.data)\n", - "model" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "CNNText(\n", - " (embed): Embedding(\n", - " (embed): Embedding(165, 50, padding_idx=0)\n", - " (dropout): Dropout(p=0.0)\n", - " )\n", - " (conv_pool): ConvMaxpool(\n", - " (convs): ModuleList(\n", - " (0): Conv1d(50, 3, kernel_size=(3,), stride=(1,), padding=(2,))\n", - " (1): Conv1d(50, 4, kernel_size=(4,), stride=(1,), padding=(2,))\n", - " (2): Conv1d(50, 5, kernel_size=(5,), stride=(1,), padding=(2,))\n", - " )\n", - " )\n", - " (dropout): Dropout(p=0.1)\n", - " (fc): Linear(\n", - " (linear): Linear(in_features=12, out_features=5, bias=True)\n", - " )\n", - ")" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# 另一个例子:加载CNN文本分类模型\n", - "from fastNLP.models import CNNText\n", - "cnn_text_model = CNNText(embed_num=len(vocab), embed_dim=50, num_classes=5, padding=2, dropout=0.1)\n", - "cnn_text_model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是上述模型的forward方法。如果你不知道什么是forward方法,请参考我们的PyTorch教程。\n", - "\n", - "注意两点:\n", - "1. forward参数名字叫**word_seq**,请记住。\n", - "2. forward的返回值是一个**dict**,其中有个key的名字叫**pred**。\n", - "\n", - "```Python\n", - " def forward(self, word_seq):\n", - " \"\"\"\n", - "\n", - " :param word_seq: torch.LongTensor, [batch_size, seq_len]\n", - " :return output: dict of torch.LongTensor, [batch_size, num_classes]\n", - " \"\"\"\n", - " x = self.embed(word_seq) # [N,L] -> [N,L,C]\n", - " x = self.conv_pool(x) # [N,L,C] -> [N,C]\n", - " x = self.dropout(x)\n", - " x = self.fc(x) # [N,C] -> [N, N_class]\n", - " return {'pred': x}\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 自行搭载模型\n", - "自行搭载的模型必须是nn.Module的子类, \n", - "(1)必须实现forward方法,并且forward方法不能出现**\\*arg**这种参数,例如\n", - "```Python\n", - " def forword(self, word_seq, *args): # 这是不允许的\n", - " xxx\n", - "```\n", - "forward函数的返回值必须是一个**dict**。 \n", - "dict当中模型预测的值所对应的key建议用**'pred'**,这里不做强制限制,但是如果不是pred的话,在加载loss函数的时候需要手动添加名称转换map \n", - "(2)如果实现了predict方法,在做evaluation的时候将调用predict方法而不是forward。如果没有predict方法,则在evaluation时调用forward方法。predict方法也不能使用\\*args这种参数形式,同时结果也必须返回一个dict,同样推荐key为'pred'。 \n", - "(3)forward函数可以计算loss并返回结果,在dict中的key为'loss',如: \n", - "```Python\n", - " def forword(self, word_seq, *args): \n", - " xxx\n", - " return {'pred': pred, 'loss': loss}\n", - "```\n", - "当loss函数没有在trainer里被定义的时候,trainer将会根据forward函数返回的dict中key为'loss'的值来进行反向传播,具体见loss部分" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 训练测试部分\n", - "## Loss\n", - "### 键映射\n", - "根据上文DataSet与Model部分可以知道,fastNLP并不限制Model.forward()的返回值,也不限制DataSet中target field的key。因此在计算loss的时候,需要通过键映射的方式来完成取值。 \n", - "这里以CrossEntropyLoss为例,我们的交叉熵函数部分如下:\n", - "```Python\n", - " def get_loss(self, pred, target):\n", - " return F.cross_entropy(input=pred, target=target,\n", - " ignore_index=self.padding_idx)\n", - "```\n", - "这里接收的两个参数名字分别为pred和target,其中pred是从model的forward函数返回值中取得,target是从DataSet的is_target的field当中取得。在没有设置键映射的基础上,pred从model的forward函数返回的dict中取'pred'键得到;target从DataSet的'target'field中得到。\n", - "。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 修改键映射\n", - "在初始化CrossEntropyLoss的时候,可以传入两个参数(pred=None, target=None), 这两个参数接受的类型是str,假设(pred='output', target='label'),那么CrossEntropyLoss会使用'output'这个key在forward的output与batch_y中寻找值;'label'也是在forward的output与d ataset的is target field中寻找值。注意这里pred或target的来源并不一定非要来自于model.forward与dataset的is target field,也可以只来自于forward的结果" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 创建一个自己的loss\n", - "    (1)采用fastNLP.LossInForward,在model.forward()的结果中包含一个'loss'的key,具体见**自行搭载模型**部分 \n", - "    (2)自己定义一个继承于fastNLP.core.loss.LossBase的class。重写get_loss方法。 \n", - "      (2.1)在初始化自己的loss class的时候,需要初始化需要映射的值 \n", - "      (2.2)在get_loss函数中,参数的名字需要与初始化时的映射是一致的 \n", - "以L1Loss为例子:\n", - "```Python\n", - "class L1Loss(LossBase):\n", - " def __init__(self, pred=None, target=None):\n", - " super(L1Loss, self).__init__()\n", - " \"\"\"\n", - " 这里传入_init_param_map以使得pred和target被正确注册,但这一步不是必须的, 建议调用。传入_init_param_map的是用于\n", - " \"键映射\"的键值对。假设初始化__init__(pred=None, target=None, threshold=0.1)中threshold是用于控制loss计算的,\n", - " 则\\不要将threshold传入_init_param_map.\n", - " \"\"\"\n", - " self._init_param_map(pred=pred, target=target)\n", - "\n", - " def get_loss(self, pred, target):\n", - " # 这里'pred', 'target'必须和初始化的映射是一致的。\n", - " return F.l1_loss(input=pred, target=target)\n", - "```\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Trainer\n", - "trainer的作用是训练模型,是一个对训练过程的封装。trainer当中比较重要的函数是trainer.train(),train函数的主要步骤包括: \n", - "(1)创建batch \n", - "```Python\n", - "batch = Batch(dataset, batch_size, sampler=sampler)\n", - "```\n", - "(2)for batch_x, batch_y in batch: (batch_x, batch_y的内容分别为dataset中is input和is target的部分,这两个dict的key就是DataSet中的key,value会根据情况做好padding及tensor) \n", - "  (2.1)将batch_x, batch_y中的tensor移动到model所在的device \n", - "  (2.2)根据model.forward的参数列表,从batch_x中取出需要传递给forward的数据 \n", - "  (2.3)获取model.forward返回的dict,并与batch_y一起传递给loss函数,求得loss \n", - "  (2.4)对loss进行反向梯度传播并更新参数 \n", - "(3)如果有验证集,则进行验证 \n", - "(4)如果验证集的结果是当前最佳结果,则保存模型" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "除了以上的内容, Trainer中还提供了\"预跑\"的功能。该功能通过check_code_level管理,如果check_code_level为-1,则不进行\"预跑\"。 check_code_level=0,1,2代表不同的提醒级别。目前不同提醒级别对应的是对DataSet中设置为input或target但又没有使用的field的提醒级别。 0是忽略(默认);1是会warning发生了未使用field的情况;2是出现了unused会直接报错并退出运行 \"预跑\"的主要目的有两个: \n", - "(1)防止train完了之后进行evaluation的时候出现错误。之前的train就白费了 \n", - "(2)由于存在\"键映射\",直接运行导致的报错可能不太容易debug,通过\"预跑\"过程的报错会有一些debug提示 \"预跑\"会进行以下的操作: \n", - "  (i) 使用很小的batch_size, 检查batch_x中是否包含Model.forward所需要的参数。只会运行两个循环。 \n", - "  (ii)将Model.foward的输出pred_dict与batch_y输入到loss中,并尝试backward。不会更新参数,而且grad会被清零。如果传入了dev_data,还将进行metric的测试 \n", - "  (iii)创建Tester,并传入少量数据,检测是否可以正常运行 \n", - "\"预跑\"操作是在Trainer初始化的时候执行的。正常情况下,应该不需要改动\"预跑\"的代码。但如果遇到bug或者有什么好的建议,欢迎在开发群或者github提交issue。" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "training epochs started 2019-01-09 00-08-17\n", - "[tester] \n", - "AccuracyMetric: acc=0.206897\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/remote-home/ygxu/anaconda3/envs/no-fastnlp/lib/python3.7/site-packages/torch/nn/functional.py:1320: UserWarning: nn.functional.tanh is deprecated. Use torch.tanh instead.\n", - " warnings.warn(\"nn.functional.tanh is deprecated. Use torch.tanh instead.\")\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[tester] \n", - "AccuracyMetric: acc=0.206897\n", - "[tester] \n", - "AccuracyMetric: acc=0.206897\n", - "[tester] \n", - "AccuracyMetric: acc=0.206897\n", - "[tester] \n", - "AccuracyMetric: acc=0.206897\n", - "\n", - "In Epoch:1/Step:4, got best dev performance:AccuracyMetric: acc=0.206897\n", - "Reloaded the best model.\n" - ] - }, - { - "data": { - "text/plain": [ - "{'best_eval': {'AccuracyMetric': {'acc': 0.206897}},\n", - " 'best_epoch': 1,\n", - " 'best_step': 4,\n", - " 'seconds': 0.79}" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from fastNLP import CrossEntropyLoss\n", - "from fastNLP import Adam\n", - "from fastNLP import AccuracyMetric\n", - "trainer = Trainer(\n", - " train_data=train_data,\n", - " model=model,\n", - " loss=CrossEntropyLoss(pred='pred', target='label'),\n", - " metrics=AccuracyMetric(),\n", - " n_epochs=5,\n", - " batch_size=16,\n", - " print_every=-1,\n", - " validate_every=-1,\n", - " dev_data=dev_data,\n", - " use_cuda=True,\n", - " optimizer=Adam(lr=1e-3, weight_decay=0),\n", - " check_code_level=-1,\n", - " metric_key='acc',\n", - " use_tqdm=False,\n", - ")\n", - "trainer.train()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Tester\n", - "tester的作用是训练模型,是一个对训练过程的封装。tester当中比较重要的函数是tester.test(),test函数的主要步骤包括:\n", - "(1)创建batch\n", - "```Python\n", - "batch = Batch(dataset, batch_size, sampler=sampler)\n", - "```\n", - "(2)for batch_x, batch_y in batch: (batch_x, batch_y的内容分别为dataset中is input和is target的部分,这两个dict的key就是DataSet中的key,value会根据情况做好padding及tensor) \n", - "  (2.1)同步数据与model将batch_x, batch_y中的tensor移动到model所在的device \n", - "  (2.2)根据predict_func的参数列表,从batch_x中取出需要传递给predict_func的数据,得到结果pred_dict \n", - "  (2.3)调用metric(pred_dict, batch_y) \n", - "  (2.4)当所有batch都运行完毕,会调用metric的get_metric方法,并且以返回的值作为evaluation的结果 " - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[tester] \n", - "AccuracyMetric: acc=0.263158\n" - ] - }, - { - "data": { - "text/plain": [ - "{'AccuracyMetric': {'acc': 0.263158}}" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tester = Tester(\n", - " data=test_data,\n", - " model=model,\n", - " metrics=AccuracyMetric(),\n", - " batch_size=args[\"batch_size\"],\n", - ")\n", - "tester.test()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/fastnlp_advanced_tutorial/data/config b/tutorials/fastnlp_advanced_tutorial/data/config deleted file mode 100644 index 87348b72..00000000 --- a/tutorials/fastnlp_advanced_tutorial/data/config +++ /dev/null @@ -1,8 +0,0 @@ -[esim_model] -embed_dim = 300 -hidden_size = 300 -batch_first = true -dropout = 0.3 -num_classes = 3 -gpu = true -batch_size = 32 diff --git a/tutorials/fastnlp_advanced_tutorial/hypothesis b/tutorials/fastnlp_advanced_tutorial/hypothesis deleted file mode 100644 index 07aec3c9..00000000 --- a/tutorials/fastnlp_advanced_tutorial/hypothesis +++ /dev/null @@ -1,100 +0,0 @@ -A person is training his horse for a competition . -A person is at a diner , ordering an omelette . -A person is outdoors , on a horse . -They are smiling at their parents -There are children present -The kids are frowning -The boy skates down the sidewalk . -The boy does a skateboarding trick . -The boy is wearing safety equipment . -An older man drinks his juice as he waits for his daughter to get off work . -A boy flips a burger . -An elderly man sits in a small shop . -Some women are hugging on vacation . -The women are sleeping . -There are women showing affection . -The people are eating omelettes . -The people are sitting at desks in school . -The diners are at a restaurant . -A man is drinking juice . -Two women are at a restaurant drinking wine . -A man in a restaurant is waiting for his meal to arrive . -A blond man getting a drink of water from a fountain in the park . -A blond man wearing a brown shirt is reading a book on a bench in the park -A blond man drinking water from a fountain . -The friends scowl at each other over a full dinner table . -There are two woman in this picture . -The friends have just met for the first time in 20 years , and have had a great time catching up . -The two sisters saw each other across the crowded diner and shared a hug , both clutching their doggie bags . -Two groups of rival gang members flipped each other off . -Two women hug each other . -A team is trying to score the games winning out . -A team is trying to tag a runner out . -A team is playing baseball on Saturn . -A school hosts a basketball game . -A high school is hosting an event . -A school is hosting an event . -The women do not care what clothes they wear . -Women are waiting by a tram . -The women enjoy having a good fashion sense . -A child with mom and dad , on summer vacation at the beach . -A family of three is at the beach . -A family of three is at the mall shopping . -The people waiting on the train are sitting . -There are people just getting on a train -There are people waiting on a train . -A couple are playing with a young child outside . -A couple are playing frisbee with a young child at the beach . -A couple watch a little girl play by herself on the beach . -The family is sitting down for dinner . -The family is outside . -The family is on vacation . -The people are standing still on the curb . -Near a couple of restaurants , two people walk across the street . -The couple are walking across the street together . -The woman is nake . -The woman is cold . -The woman is wearing green . -The man with the sign is caucasian . -They are protesting outside the capital . -A woman in white . -A man is advertising for a restaurant . -The woman is wearing black . -A man and a woman walk down a crowded city street . -The woman is wearing white . -They are working for John 's Pizza . -Olympic swimming . -A man and a soman are eating together at John 's Pizza and Gyro . -They are walking with a sign . -The woman is waiting for a friend . -The man is sitting down while he has a sign for John 's Pizza and Gyro in his arms . -The woman and man are outdoors . -A woman ordering pizza . -The people are related . -Two adults run across the street to get away from a red shirted person chasing them . -The adults are both male and female . -Two people walk home after a tasty steak dinner . -Two adults swimming in water -Two adults walk across a street . -Two people ride bicycles into a tunnel . -Two people walk away from a restaurant across a street . -Two adults walking across a road near the convicted prisoner dressed in red -Two friends cross a street . -Some people board a train . -Two adults walk across the street . -Two adults walking across a road -There are no women in the picture . -Two adults walk across the street to get away from a red shirted person who is chasing them . -A married couple is sleeping . -A female is next to a man . -A married couple is walking next to each other . -Nobody has food . -A woman eats a banana and walks across a street , and there is a man trailing behind her . -The woman and man are playing baseball together . -two coworkers cross pathes on a street -A woman eats ice cream walking down the sidewalk , and there is another woman in front of her with a purse . -The mans briefcase is for work . -A person eating . -A person that is hungry . -An actress and her favorite assistant talk a walk in the city . -a woman eating a banana crosses a street diff --git a/tutorials/fastnlp_advanced_tutorial/label b/tutorials/fastnlp_advanced_tutorial/label deleted file mode 100644 index e28836df..00000000 --- a/tutorials/fastnlp_advanced_tutorial/label +++ /dev/null @@ -1,100 +0,0 @@ -1 -2 -0 -1 -0 -2 -2 -0 -1 -1 -2 -1 -1 -2 -0 -1 -2 -0 -0 -2 -1 -1 -2 -0 -2 -0 -1 -1 -2 -0 -1 -0 -2 -2 -1 -0 -2 -0 -1 -1 -0 -2 -1 -0 -0 -0 -1 -2 -2 -0 -1 -2 -0 -1 -2 -1 -0 -1 -2 -0 -0 -2 -1 -0 -1 -2 -2 -0 -1 -2 -0 -1 -1 -2 -0 -1 -2 -0 -2 -0 -1 -1 -2 -0 -0 -2 -1 -2 -0 -1 -2 -0 -2 -1 -2 -1 -0 -1 -1 -0 diff --git a/tutorials/fastnlp_advanced_tutorial/premise b/tutorials/fastnlp_advanced_tutorial/premise deleted file mode 100644 index 0c9af30e..00000000 --- a/tutorials/fastnlp_advanced_tutorial/premise +++ /dev/null @@ -1,100 +0,0 @@ -A person on a horse jumps over a broken down airplane . -A person on a horse jumps over a broken down airplane . -A person on a horse jumps over a broken down airplane . -Children smiling and waving at camera -Children smiling and waving at camera -Children smiling and waving at camera -A boy is jumping on skateboard in the middle of a red bridge . -A boy is jumping on skateboard in the middle of a red bridge . -A boy is jumping on skateboard in the middle of a red bridge . -An older man sits with his orange juice at a small table in a coffee shop while employees in bright colored shirts smile in the background . -An older man sits with his orange juice at a small table in a coffee shop while employees in bright colored shirts smile in the background . -An older man sits with his orange juice at a small table in a coffee shop while employees in bright colored shirts smile in the background . -Two blond women are hugging one another . -Two blond women are hugging one another . -Two blond women are hugging one another . -A few people in a restaurant setting , one of them is drinking orange juice . -A few people in a restaurant setting , one of them is drinking orange juice . -A few people in a restaurant setting , one of them is drinking orange juice . -An older man is drinking orange juice at a restaurant . -An older man is drinking orange juice at a restaurant . -An older man is drinking orange juice at a restaurant . -A man with blond-hair , and a brown shirt drinking out of a public water fountain . -A man with blond-hair , and a brown shirt drinking out of a public water fountain . -A man with blond-hair , and a brown shirt drinking out of a public water fountain . -Two women who just had lunch hugging and saying goodbye . -Two women who just had lunch hugging and saying goodbye . -Two women who just had lunch hugging and saying goodbye . -Two women , holding food carryout containers , hug . -Two women , holding food carryout containers , hug . -Two women , holding food carryout containers , hug . -A Little League team tries to catch a runner sliding into a base in an afternoon game . -A Little League team tries to catch a runner sliding into a base in an afternoon game . -A Little League team tries to catch a runner sliding into a base in an afternoon game . -The school is having a special event in order to show the american culture on how other cultures are dealt with in parties . -The school is having a special event in order to show the american culture on how other cultures are dealt with in parties . -The school is having a special event in order to show the american culture on how other cultures are dealt with in parties . -High fashion ladies wait outside a tram beside a crowd of people in the city . -High fashion ladies wait outside a tram beside a crowd of people in the city . -High fashion ladies wait outside a tram beside a crowd of people in the city . -A man , woman , and child enjoying themselves on a beach . -A man , woman , and child enjoying themselves on a beach . -A man , woman , and child enjoying themselves on a beach . -People waiting to get on a train or just getting off . -People waiting to get on a train or just getting off . -People waiting to get on a train or just getting off . -A couple playing with a little boy on the beach . -A couple playing with a little boy on the beach . -A couple playing with a little boy on the beach . -A couple play in the tide with their young son . -A couple play in the tide with their young son . -A couple play in the tide with their young son . -A man and a woman cross the street in front of a pizza and gyro restaurant . -A man and a woman cross the street in front of a pizza and gyro restaurant . -A man and a woman cross the street in front of a pizza and gyro restaurant . -A woman in a green jacket and hood over her head looking towards a valley . -A woman in a green jacket and hood over her head looking towards a valley . -A woman in a green jacket and hood over her head looking towards a valley . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Woman in white in foreground and a man slightly behind walking with a sign for John 's Pizza and Gyro in the background . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -Two adults , one female in white , with shades and one male , gray clothes , walking across a street , away from a eatery with a blurred image of a dark colored red shirted person in the foreground . -A woman wearing all white and eating , walks next to a man holding a briefcase . -A woman wearing all white and eating , walks next to a man holding a briefcase . -A woman wearing all white and eating , walks next to a man holding a briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . -A woman is walking across the street eating a banana , while a man is following with his briefcase . diff --git a/tutorials/fastnlp_advanced_tutorial/tutorial_sample_dataset.csv b/tutorials/fastnlp_advanced_tutorial/tutorial_sample_dataset.csv deleted file mode 100644 index e5c0a74f..00000000 --- a/tutorials/fastnlp_advanced_tutorial/tutorial_sample_dataset.csv +++ /dev/null @@ -1,77 +0,0 @@ -A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . 1 -This quiet , introspective and entertaining independent is worth seeking . 4 -Even fans of Ismail Merchant 's work , I suspect , would have a hard time sitting through this one . 1 -A positively thrilling combination of ethnography and all the intrigue , betrayal , deceit and murder of a Shakespearean tragedy or a juicy soap opera . 3 -Aggressive self-glorification and a manipulative whitewash . 1 -A comedy-drama of nearly epic proportions rooted in a sincere performance by the title character undergoing midlife crisis . 4 -Narratively , Trouble Every Day is a plodding mess . 1 -The Importance of Being Earnest , so thick with wit it plays like a reading from Bartlett 's Familiar Quotations 3 -But it does n't leave you with much . 1 -You could hate it for the same reason . 1 -There 's little to recommend Snow Dogs , unless one considers cliched dialogue and perverse escapism a source of high hilarity . 1 -Kung Pow is Oedekerk 's realization of his childhood dream to be in a martial-arts flick , and proves that sometimes the dreams of youth should remain just that . 1 -The performances are an absolute joy . 4 -Fresnadillo has something serious to say about the ways in which extravagant chance can distort our perspective and throw us off the path of good sense . 3 -I still like Moonlight Mile , better judgment be damned . 3 -A welcome relief from baseball movies that try too hard to be mythic , this one is a sweet and modest and ultimately winning story . 3 -a bilingual charmer , just like the woman who inspired it 3 -Like a less dizzily gorgeous companion to Mr. Wong 's In the Mood for Love -- very much a Hong Kong movie despite its mainland setting . 2 -As inept as big-screen remakes of The Avengers and The Wild Wild West . 1 -It 's everything you 'd expect -- but nothing more . 2 -Best indie of the year , so far . 4 -Hatfield and Hicks make the oddest of couples , and in this sense the movie becomes a study of the gambles of the publishing world , offering a case study that exists apart from all the movie 's political ramifications . 3 -It 's like going to a house party and watching the host defend himself against a frothing ex-girlfriend . 1 -That the Chuck Norris `` grenade gag '' occurs about 7 times during Windtalkers is a good indication of how serious-minded the film is . 2 -The plot is romantic comedy boilerplate from start to finish . 2 -It arrives with an impeccable pedigree , mongrel pep , and almost indecipherable plot complications . 2 -A film that clearly means to preach exclusively to the converted . 2 -While The Importance of Being Earnest offers opportunities for occasional smiles and chuckles , it does n't give us a reason to be in the theater beyond Wilde 's wit and the actors ' performances . 1 -The latest vapid actor 's exercise to appropriate the structure of Arthur Schnitzler 's Reigen . 1 -More vaudeville show than well-constructed narrative , but on those terms it 's inoffensive and actually rather sweet . 2 -Nothing more than a run-of-the-mill action flick . 2 -Hampered -- no , paralyzed -- by a self-indulgent script ... that aims for poetry and ends up sounding like satire . 0 -Ice Age is the first computer-generated feature cartoon to feel like other movies , and that makes for some glacial pacing early on . 2 -There 's very little sense to what 's going on here , but the makers serve up the cliches with considerable dash . 2 -Cattaneo should have followed the runaway success of his first film , The Full Monty , with something different . 2 -They 're the unnamed , easily substitutable forces that serve as whatever terror the heroes of horror movies try to avoid . 1 -It almost feels as if the movie is more interested in entertaining itself than in amusing us . 1 -The movie 's progression into rambling incoherence gives new meaning to the phrase ` fatal script error . ' 0 -I still like Moonlight Mile , better judgment be damned . 3 -A welcome relief from baseball movies that try too hard to be mythic , this one is a sweet and modest and ultimately winning story . 3 -a bilingual charmer , just like the woman who inspired it 3 -Like a less dizzily gorgeous companion to Mr. Wong 's In the Mood for Love -- very much a Hong Kong movie despite its mainland setting . 2 -As inept as big-screen remakes of The Avengers and The Wild Wild West . 1 -It 's everything you 'd expect -- but nothing more . 2 -Best indie of the year , so far . 4 -Hatfield and Hicks make the oddest of couples , and in this sense the movie becomes a study of the gambles of the publishing world , offering a case study that exists apart from all the movie 's political ramifications . 3 -It 's like going to a house party and watching the host defend himself against a frothing ex-girlfriend . 1 -That the Chuck Norris `` grenade gag '' occurs about 7 times during Windtalkers is a good indication of how serious-minded the film is . 2 -The plot is romantic comedy boilerplate from start to finish . 2 -It arrives with an impeccable pedigree , mongrel pep , and almost indecipherable plot complications . 2 -A film that clearly means to preach exclusively to the converted . 2 -I still like Moonlight Mile , better judgment be damned . 3 -A welcome relief from baseball movies that try too hard to be mythic , this one is a sweet and modest and ultimately winning story . 3 -a bilingual charmer , just like the woman who inspired it 3 -Like a less dizzily gorgeous companion to Mr. Wong 's In the Mood for Love -- very much a Hong Kong movie despite its mainland setting . 2 -As inept as big-screen remakes of The Avengers and The Wild Wild West . 1 -It 's everything you 'd expect -- but nothing more . 2 -Best indie of the year , so far . 4 -Hatfield and Hicks make the oddest of couples , and in this sense the movie becomes a study of the gambles of the publishing world , offering a case study that exists apart from all the movie 's political ramifications . 3 -It 's like going to a house party and watching the host defend himself against a frothing ex-girlfriend . 1 -That the Chuck Norris `` grenade gag '' occurs about 7 times during Windtalkers is a good indication of how serious-minded the film is . 2 -The plot is romantic comedy boilerplate from start to finish . 2 -It arrives with an impeccable pedigree , mongrel pep , and almost indecipherable plot complications . 2 -A film that clearly means to preach exclusively to the converted . 2 -I still like Moonlight Mile , better judgment be damned . 3 -A welcome relief from baseball movies that try too hard to be mythic , this one is a sweet and modest and ultimately winning story . 3 -a bilingual charmer , just like the woman who inspired it 3 -Like a less dizzily gorgeous companion to Mr. Wong 's In the Mood for Love -- very much a Hong Kong movie despite its mainland setting . 2 -As inept as big-screen remakes of The Avengers and The Wild Wild West . 1 -It 's everything you 'd expect -- but nothing more . 2 -Best indie of the year , so far . 4 -Hatfield and Hicks make the oddest of couples , and in this sense the movie becomes a study of the gambles of the publishing world , offering a case study that exists apart from all the movie 's political ramifications . 3 -It 's like going to a house party and watching the host defend himself against a frothing ex-girlfriend . 1 -That the Chuck Norris `` grenade gag '' occurs about 7 times during Windtalkers is a good indication of how serious-minded the film is . 2 -The plot is romantic comedy boilerplate from start to finish . 2 -It arrives with an impeccable pedigree , mongrel pep , and almost indecipherable plot complications . 2 -A film that clearly means to preach exclusively to the converted . 2 \ No newline at end of file diff --git a/tutorials/fastnlp_advanced_tutorial/vocab.txt b/tutorials/fastnlp_advanced_tutorial/vocab.txt deleted file mode 100644 index fb140275..00000000 --- a/tutorials/fastnlp_advanced_tutorial/vocab.txt +++ /dev/null @@ -1,30522 +0,0 @@ -[PAD] -[unused0] -[unused1] -[unused2] -[unused3] -[unused4] -[unused5] -[unused6] -[unused7] -[unused8] -[unused9] -[unused10] -[unused11] -[unused12] -[unused13] -[unused14] -[unused15] -[unused16] -[unused17] -[unused18] -[unused19] -[unused20] -[unused21] -[unused22] -[unused23] -[unused24] -[unused25] -[unused26] -[unused27] -[unused28] -[unused29] -[unused30] -[unused31] -[unused32] -[unused33] -[unused34] -[unused35] -[unused36] -[unused37] -[unused38] -[unused39] -[unused40] -[unused41] -[unused42] -[unused43] -[unused44] -[unused45] -[unused46] -[unused47] -[unused48] -[unused49] -[unused50] -[unused51] -[unused52] -[unused53] -[unused54] -[unused55] -[unused56] -[unused57] -[unused58] -[unused59] -[unused60] -[unused61] -[unused62] -[unused63] -[unused64] -[unused65] -[unused66] -[unused67] -[unused68] -[unused69] -[unused70] -[unused71] -[unused72] -[unused73] -[unused74] -[unused75] -[unused76] -[unused77] -[unused78] -[unused79] -[unused80] -[unused81] -[unused82] -[unused83] -[unused84] -[unused85] -[unused86] -[unused87] -[unused88] -[unused89] -[unused90] -[unused91] -[unused92] -[unused93] -[unused94] -[unused95] -[unused96] -[unused97] -[unused98] -[UNK] -[CLS] -[SEP] -[MASK] -[unused99] -[unused100] -[unused101] -[unused102] -[unused103] -[unused104] -[unused105] -[unused106] -[unused107] -[unused108] -[unused109] -[unused110] -[unused111] -[unused112] -[unused113] -[unused114] -[unused115] -[unused116] -[unused117] -[unused118] -[unused119] -[unused120] -[unused121] -[unused122] -[unused123] -[unused124] -[unused125] -[unused126] -[unused127] -[unused128] -[unused129] -[unused130] -[unused131] -[unused132] -[unused133] -[unused134] -[unused135] -[unused136] -[unused137] -[unused138] -[unused139] -[unused140] -[unused141] -[unused142] -[unused143] -[unused144] -[unused145] -[unused146] -[unused147] -[unused148] -[unused149] -[unused150] -[unused151] -[unused152] -[unused153] -[unused154] -[unused155] -[unused156] -[unused157] -[unused158] -[unused159] -[unused160] -[unused161] -[unused162] -[unused163] -[unused164] -[unused165] -[unused166] -[unused167] -[unused168] -[unused169] -[unused170] -[unused171] -[unused172] -[unused173] -[unused174] -[unused175] -[unused176] -[unused177] -[unused178] -[unused179] -[unused180] -[unused181] -[unused182] -[unused183] -[unused184] -[unused185] -[unused186] -[unused187] -[unused188] -[unused189] -[unused190] -[unused191] -[unused192] -[unused193] -[unused194] -[unused195] -[unused196] -[unused197] -[unused198] -[unused199] -[unused200] -[unused201] -[unused202] -[unused203] -[unused204] -[unused205] -[unused206] -[unused207] -[unused208] -[unused209] -[unused210] -[unused211] -[unused212] -[unused213] -[unused214] -[unused215] -[unused216] -[unused217] -[unused218] -[unused219] -[unused220] -[unused221] -[unused222] -[unused223] -[unused224] -[unused225] -[unused226] -[unused227] -[unused228] -[unused229] -[unused230] -[unused231] -[unused232] -[unused233] -[unused234] -[unused235] -[unused236] -[unused237] -[unused238] -[unused239] -[unused240] -[unused241] -[unused242] -[unused243] -[unused244] -[unused245] -[unused246] -[unused247] -[unused248] -[unused249] -[unused250] -[unused251] -[unused252] -[unused253] -[unused254] -[unused255] -[unused256] -[unused257] -[unused258] -[unused259] -[unused260] -[unused261] -[unused262] -[unused263] -[unused264] -[unused265] -[unused266] -[unused267] -[unused268] -[unused269] -[unused270] -[unused271] -[unused272] -[unused273] -[unused274] -[unused275] -[unused276] -[unused277] -[unused278] -[unused279] -[unused280] -[unused281] -[unused282] -[unused283] -[unused284] -[unused285] -[unused286] -[unused287] -[unused288] -[unused289] -[unused290] -[unused291] -[unused292] -[unused293] -[unused294] -[unused295] -[unused296] -[unused297] -[unused298] -[unused299] -[unused300] -[unused301] -[unused302] -[unused303] -[unused304] -[unused305] -[unused306] -[unused307] -[unused308] -[unused309] -[unused310] -[unused311] -[unused312] -[unused313] -[unused314] -[unused315] -[unused316] -[unused317] -[unused318] -[unused319] -[unused320] -[unused321] -[unused322] -[unused323] -[unused324] -[unused325] -[unused326] -[unused327] -[unused328] -[unused329] -[unused330] -[unused331] -[unused332] -[unused333] -[unused334] -[unused335] -[unused336] -[unused337] -[unused338] -[unused339] -[unused340] -[unused341] -[unused342] -[unused343] -[unused344] -[unused345] -[unused346] -[unused347] -[unused348] -[unused349] -[unused350] -[unused351] -[unused352] -[unused353] -[unused354] -[unused355] -[unused356] -[unused357] -[unused358] -[unused359] -[unused360] -[unused361] -[unused362] -[unused363] -[unused364] -[unused365] -[unused366] -[unused367] -[unused368] -[unused369] -[unused370] -[unused371] -[unused372] -[unused373] -[unused374] -[unused375] -[unused376] -[unused377] -[unused378] -[unused379] -[unused380] -[unused381] -[unused382] -[unused383] -[unused384] -[unused385] -[unused386] -[unused387] -[unused388] -[unused389] -[unused390] -[unused391] -[unused392] -[unused393] -[unused394] -[unused395] -[unused396] -[unused397] -[unused398] -[unused399] -[unused400] -[unused401] -[unused402] -[unused403] -[unused404] -[unused405] -[unused406] -[unused407] -[unused408] -[unused409] -[unused410] -[unused411] -[unused412] -[unused413] -[unused414] -[unused415] -[unused416] -[unused417] -[unused418] -[unused419] -[unused420] -[unused421] -[unused422] -[unused423] -[unused424] -[unused425] -[unused426] -[unused427] -[unused428] -[unused429] -[unused430] -[unused431] -[unused432] -[unused433] -[unused434] -[unused435] -[unused436] -[unused437] -[unused438] -[unused439] -[unused440] -[unused441] -[unused442] -[unused443] -[unused444] -[unused445] -[unused446] -[unused447] -[unused448] -[unused449] -[unused450] -[unused451] -[unused452] -[unused453] -[unused454] -[unused455] -[unused456] -[unused457] -[unused458] -[unused459] -[unused460] -[unused461] -[unused462] -[unused463] -[unused464] -[unused465] -[unused466] -[unused467] -[unused468] -[unused469] -[unused470] -[unused471] -[unused472] -[unused473] -[unused474] -[unused475] -[unused476] -[unused477] -[unused478] -[unused479] -[unused480] -[unused481] -[unused482] -[unused483] -[unused484] -[unused485] -[unused486] -[unused487] -[unused488] -[unused489] -[unused490] -[unused491] -[unused492] -[unused493] -[unused494] -[unused495] -[unused496] -[unused497] -[unused498] -[unused499] -[unused500] -[unused501] -[unused502] -[unused503] -[unused504] -[unused505] -[unused506] -[unused507] -[unused508] -[unused509] -[unused510] -[unused511] -[unused512] -[unused513] -[unused514] -[unused515] -[unused516] -[unused517] -[unused518] -[unused519] -[unused520] -[unused521] -[unused522] -[unused523] -[unused524] -[unused525] -[unused526] -[unused527] -[unused528] -[unused529] -[unused530] -[unused531] -[unused532] -[unused533] -[unused534] -[unused535] -[unused536] -[unused537] -[unused538] -[unused539] -[unused540] -[unused541] -[unused542] -[unused543] -[unused544] -[unused545] -[unused546] -[unused547] -[unused548] -[unused549] -[unused550] -[unused551] -[unused552] -[unused553] -[unused554] -[unused555] -[unused556] -[unused557] -[unused558] -[unused559] -[unused560] -[unused561] -[unused562] -[unused563] -[unused564] -[unused565] -[unused566] -[unused567] -[unused568] -[unused569] -[unused570] -[unused571] -[unused572] -[unused573] -[unused574] -[unused575] -[unused576] -[unused577] -[unused578] -[unused579] -[unused580] -[unused581] -[unused582] -[unused583] -[unused584] -[unused585] -[unused586] -[unused587] -[unused588] -[unused589] -[unused590] -[unused591] -[unused592] -[unused593] -[unused594] -[unused595] -[unused596] -[unused597] -[unused598] -[unused599] -[unused600] -[unused601] -[unused602] -[unused603] -[unused604] -[unused605] -[unused606] -[unused607] -[unused608] -[unused609] -[unused610] -[unused611] -[unused612] -[unused613] -[unused614] -[unused615] -[unused616] -[unused617] -[unused618] -[unused619] -[unused620] -[unused621] -[unused622] -[unused623] -[unused624] -[unused625] -[unused626] -[unused627] -[unused628] -[unused629] -[unused630] -[unused631] -[unused632] -[unused633] -[unused634] -[unused635] -[unused636] -[unused637] -[unused638] -[unused639] -[unused640] -[unused641] -[unused642] -[unused643] -[unused644] -[unused645] -[unused646] -[unused647] -[unused648] -[unused649] -[unused650] -[unused651] -[unused652] -[unused653] -[unused654] -[unused655] -[unused656] -[unused657] -[unused658] -[unused659] -[unused660] -[unused661] -[unused662] -[unused663] -[unused664] -[unused665] -[unused666] -[unused667] -[unused668] -[unused669] -[unused670] -[unused671] -[unused672] -[unused673] -[unused674] -[unused675] -[unused676] -[unused677] -[unused678] -[unused679] -[unused680] -[unused681] -[unused682] -[unused683] -[unused684] -[unused685] -[unused686] -[unused687] -[unused688] -[unused689] -[unused690] -[unused691] -[unused692] -[unused693] -[unused694] -[unused695] -[unused696] -[unused697] -[unused698] -[unused699] -[unused700] -[unused701] -[unused702] -[unused703] -[unused704] -[unused705] -[unused706] -[unused707] -[unused708] -[unused709] -[unused710] -[unused711] -[unused712] -[unused713] -[unused714] -[unused715] -[unused716] -[unused717] -[unused718] -[unused719] -[unused720] -[unused721] -[unused722] -[unused723] -[unused724] -[unused725] -[unused726] -[unused727] -[unused728] -[unused729] -[unused730] -[unused731] -[unused732] -[unused733] -[unused734] -[unused735] -[unused736] -[unused737] -[unused738] -[unused739] -[unused740] -[unused741] -[unused742] -[unused743] -[unused744] -[unused745] -[unused746] -[unused747] -[unused748] -[unused749] -[unused750] -[unused751] -[unused752] -[unused753] -[unused754] -[unused755] -[unused756] -[unused757] -[unused758] -[unused759] -[unused760] -[unused761] -[unused762] -[unused763] -[unused764] -[unused765] -[unused766] -[unused767] -[unused768] -[unused769] -[unused770] -[unused771] -[unused772] -[unused773] -[unused774] -[unused775] -[unused776] -[unused777] -[unused778] -[unused779] -[unused780] -[unused781] -[unused782] -[unused783] -[unused784] -[unused785] -[unused786] -[unused787] -[unused788] -[unused789] -[unused790] -[unused791] -[unused792] -[unused793] -[unused794] -[unused795] -[unused796] -[unused797] -[unused798] -[unused799] -[unused800] -[unused801] -[unused802] -[unused803] -[unused804] -[unused805] -[unused806] -[unused807] -[unused808] -[unused809] -[unused810] -[unused811] -[unused812] -[unused813] -[unused814] -[unused815] -[unused816] -[unused817] -[unused818] -[unused819] -[unused820] -[unused821] -[unused822] -[unused823] -[unused824] -[unused825] -[unused826] -[unused827] -[unused828] -[unused829] -[unused830] -[unused831] -[unused832] -[unused833] -[unused834] -[unused835] -[unused836] -[unused837] -[unused838] -[unused839] -[unused840] -[unused841] -[unused842] -[unused843] -[unused844] -[unused845] -[unused846] -[unused847] -[unused848] -[unused849] -[unused850] -[unused851] -[unused852] -[unused853] -[unused854] -[unused855] -[unused856] -[unused857] -[unused858] -[unused859] -[unused860] -[unused861] -[unused862] -[unused863] -[unused864] -[unused865] -[unused866] -[unused867] -[unused868] -[unused869] -[unused870] -[unused871] -[unused872] -[unused873] -[unused874] -[unused875] -[unused876] -[unused877] -[unused878] -[unused879] -[unused880] -[unused881] -[unused882] -[unused883] -[unused884] -[unused885] -[unused886] -[unused887] -[unused888] -[unused889] -[unused890] -[unused891] -[unused892] -[unused893] -[unused894] -[unused895] -[unused896] -[unused897] -[unused898] -[unused899] -[unused900] -[unused901] -[unused902] -[unused903] -[unused904] -[unused905] -[unused906] -[unused907] -[unused908] -[unused909] -[unused910] -[unused911] -[unused912] -[unused913] -[unused914] -[unused915] -[unused916] -[unused917] -[unused918] -[unused919] -[unused920] -[unused921] -[unused922] -[unused923] -[unused924] -[unused925] -[unused926] -[unused927] -[unused928] -[unused929] -[unused930] -[unused931] -[unused932] -[unused933] -[unused934] -[unused935] -[unused936] -[unused937] -[unused938] -[unused939] -[unused940] -[unused941] -[unused942] -[unused943] -[unused944] -[unused945] -[unused946] -[unused947] -[unused948] -[unused949] -[unused950] -[unused951] -[unused952] -[unused953] -[unused954] -[unused955] -[unused956] -[unused957] -[unused958] -[unused959] -[unused960] -[unused961] -[unused962] -[unused963] -[unused964] -[unused965] -[unused966] -[unused967] -[unused968] -[unused969] -[unused970] -[unused971] -[unused972] -[unused973] -[unused974] -[unused975] -[unused976] -[unused977] -[unused978] -[unused979] -[unused980] -[unused981] -[unused982] -[unused983] -[unused984] -[unused985] -[unused986] -[unused987] -[unused988] -[unused989] -[unused990] -[unused991] -[unused992] -[unused993] -! -" -# -$ -% -& -' -( -) -* -+ -, -- -. -/ -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -: -; -< -= -> -? -@ -[ -\ -] -^ -_ -` -a -b -c -d -e -f -g -h -i -j -k -l -m -n -o -p -q -r -s -t -u -v -w -x -y -z -{ -| -} -~ -¡ -¢ -£ -¤ -¥ -¦ -§ -¨ -© -ª -« -¬ -® -° -± -² -³ -´ -µ -¶ -· -¹ -º -» -¼ -½ -¾ -¿ -× -ß -æ -ð -÷ -ø -þ -đ -ħ -ı -ł -ŋ -œ -ƒ -ɐ -ɑ -ɒ -ɔ -ɕ -ə -ɛ -ɡ -ɣ -ɨ -ɪ -ɫ -ɬ -ɯ -ɲ -ɴ -ɹ -ɾ -ʀ -ʁ -ʂ -ʃ -ʉ -ʊ -ʋ -ʌ -ʎ -ʐ -ʑ -ʒ -ʔ -ʰ -ʲ -ʳ -ʷ -ʸ -ʻ -ʼ -ʾ -ʿ -ˈ -ː -ˡ -ˢ -ˣ -ˤ -α -β -γ -δ -ε -ζ -η -θ -ι -κ -λ -μ -ν -ξ -ο -π -ρ -ς -σ -τ -υ -φ -χ -ψ -ω -а -б -в -г -д -е -ж -з -и -к -л -м -н -о -п -р -с -т -у -ф -х -ц -ч -ш -щ -ъ -ы -ь -э -ю -я -ђ -є -і -ј -љ -њ -ћ -ӏ -ա -բ -գ -դ -ե -թ -ի -լ -կ -հ -մ -յ -ն -ո -պ -ս -վ -տ -ր -ւ -ք -־ -א -ב -ג -ד -ה -ו -ז -ח -ט -י -ך -כ -ל -ם -מ -ן -נ -ס -ע -ף -פ -ץ -צ -ק -ר -ש -ת -، -ء -ا -ب -ة -ت -ث -ج -ح -خ -د -ذ -ر -ز -س -ش -ص -ض -ط -ظ -ع -غ -ـ -ف -ق -ك -ل -م -ن -ه -و -ى -ي -ٹ -پ -چ -ک -گ -ں -ھ -ہ -ی -ے -अ -आ -उ -ए -क -ख -ग -च -ज -ट -ड -ण -त -थ -द -ध -न -प -ब -भ -म -य -र -ल -व -श -ष -स -ह -ा -ि -ी -ो -। -॥ -ং -অ -আ -ই -উ -এ -ও -ক -খ -গ -চ -ছ -জ -ট -ড -ণ -ত -থ -দ -ধ -ন -প -ব -ভ -ম -য -র -ল -শ -ষ -স -হ -া -ি -ী -ে -க -ச -ட -த -ந -ன -ப -ம -ய -ர -ல -ள -வ -ா -ி -ு -ே -ை -ನ -ರ -ಾ -ක -ය -ර -ල -ව -ා -ก -ง -ต -ท -น -พ -ม -ย -ร -ล -ว -ส -อ -า -เ -་ -། -ག -ང -ད -ན -པ -བ -མ -འ -ར -ལ -ས -မ -ა -ბ -გ -დ -ე -ვ -თ -ი -კ -ლ -მ -ნ -ო -რ -ს -ტ -უ -ᄀ -ᄂ -ᄃ -ᄅ -ᄆ -ᄇ -ᄉ -ᄊ -ᄋ -ᄌ -ᄎ -ᄏ -ᄐ -ᄑ -ᄒ -ᅡ -ᅢ -ᅥ -ᅦ -ᅧ -ᅩ -ᅪ -ᅭ -ᅮ -ᅯ -ᅲ -ᅳ -ᅴ -ᅵ -ᆨ -ᆫ -ᆯ -ᆷ -ᆸ -ᆼ -ᴬ -ᴮ -ᴰ -ᴵ -ᴺ -ᵀ -ᵃ -ᵇ -ᵈ -ᵉ -ᵍ -ᵏ -ᵐ -ᵒ -ᵖ -ᵗ -ᵘ -ᵢ -ᵣ -ᵤ -ᵥ -ᶜ -ᶠ -‐ -‑ -‒ -– -— -― -‖ -‘ -’ -‚ -“ -” -„ -† -‡ -• -… -‰ -′ -″ -› -‿ -⁄ -⁰ -ⁱ -⁴ -⁵ -⁶ -⁷ -⁸ -⁹ -⁺ -⁻ -ⁿ -₀ -₁ -₂ -₃ -₄ -₅ -₆ -₇ -₈ -₉ -₊ -₍ -₎ -ₐ -ₑ -ₒ -ₓ -ₕ -ₖ -ₗ -ₘ -ₙ -ₚ -ₛ -ₜ -₤ -₩ -€ -₱ -₹ -ℓ -№ -ℝ -™ -⅓ -⅔ -← -↑ -→ -↓ -↔ -↦ -⇄ -⇌ -⇒ -∂ -∅ -∆ -∇ -∈ -− -∗ -∘ -√ -∞ -∧ -∨ -∩ -∪ -≈ -≡ -≤ -≥ -⊂ -⊆ -⊕ -⊗ -⋅ -─ -│ -■ -▪ -● -★ -☆ -☉ -♠ -♣ -♥ -♦ -♭ -♯ -⟨ -⟩ -ⱼ -⺩ -⺼ -⽥ -、 -。 -〈 -〉 -《 -》 -「 -」 -『 -』 -〜 -あ -い -う -え -お -か -き -く -け -こ -さ -し -す -せ -そ -た -ち -っ -つ -て -と -な -に -ぬ -ね -の -は -ひ -ふ -へ -ほ -ま -み -む -め -も -や -ゆ -よ -ら -り -る -れ -ろ -を -ん -ァ -ア -ィ -イ -ウ -ェ -エ -オ -カ -キ -ク -ケ -コ -サ -シ -ス -セ -タ -チ -ッ -ツ -テ -ト -ナ -ニ -ノ -ハ -ヒ -フ -ヘ -ホ -マ -ミ -ム -メ -モ -ャ -ュ -ョ -ラ -リ -ル -レ -ロ -ワ -ン -・ -ー -一 -三 -上 -下 -不 -世 -中 -主 -久 -之 -也 -事 -二 -五 -井 -京 -人 -亻 -仁 -介 -代 -仮 -伊 -会 -佐 -侍 -保 -信 -健 -元 -光 -八 -公 -内 -出 -分 -前 -劉 -力 -加 -勝 -北 -区 -十 -千 -南 -博 -原 -口 -古 -史 -司 -合 -吉 -同 -名 -和 -囗 -四 -国 -國 -土 -地 -坂 -城 -堂 -場 -士 -夏 -外 -大 -天 -太 -夫 -奈 -女 -子 -学 -宀 -宇 -安 -宗 -定 -宣 -宮 -家 -宿 -寺 -將 -小 -尚 -山 -岡 -島 -崎 -川 -州 -巿 -帝 -平 -年 -幸 -广 -弘 -張 -彳 -後 -御 -德 -心 -忄 -志 -忠 -愛 -成 -我 -戦 -戸 -手 -扌 -政 -文 -新 -方 -日 -明 -星 -春 -昭 -智 -曲 -書 -月 -有 -朝 -木 -本 -李 -村 -東 -松 -林 -森 -楊 -樹 -橋 -歌 -止 -正 -武 -比 -氏 -民 -水 -氵 -氷 -永 -江 -沢 -河 -治 -法 -海 -清 -漢 -瀬 -火 -版 -犬 -王 -生 -田 -男 -疒 -発 -白 -的 -皇 -目 -相 -省 -真 -石 -示 -社 -神 -福 -禾 -秀 -秋 -空 -立 -章 -竹 -糹 -美 -義 -耳 -良 -艹 -花 -英 -華 -葉 -藤 -行 -街 -西 -見 -訁 -語 -谷 -貝 -貴 -車 -軍 -辶 -道 -郎 -郡 -部 -都 -里 -野 -金 -鈴 -镇 -長 -門 -間 -阝 -阿 -陳 -陽 -雄 -青 -面 -風 -食 -香 -馬 -高 -龍 -龸 -fi -fl -! -( -) -, -- -. -/ -: -? -~ -the -of -and -in -to -was -he -is -as -for -on -with -that -it -his -by -at -from -her -##s -she -you -had -an -were -but -be -this -are -not -my -they -one -which -or -have -him -me -first -all -also -their -has -up -who -out -been -when -after -there -into -new -two -its -##a -time -would -no -what -about -said -we -over -then -other -so -more -##e -can -if -like -back -them -only -some -could -##i -where -just -##ing -during -before -##n -do -##o -made -school -through -than -now -years -most -world -may -between -down -well -three -##d -year -while -will -##ed -##r -##y -later -##t -city -under -around -did -such -being -used -state -people -part -know -against -your -many -second -university -both -national -##er -these -don -known -off -way -until -re -how -even -get -head -... -didn -##ly -team -american -because -de -##l -born -united -film -since -still -long -work -south -us -became -any -high -again -day -family -see -right -man -eyes -house -season -war -states -including -took -life -north -same -each -called -name -much -place -however -go -four -group -another -found -won -area -here -going -10 -away -series -left -home -music -best -make -hand -number -company -several -never -last -john -000 -very -album -take -end -good -too -following -released -game -played -little -began -district -##m -old -want -those -side -held -own -early -county -ll -league -use -west -##u -face -think -##es -2010 -government -##h -march -came -small -general -town -june -##on -line -based -something -##k -september -thought -looked -along -international -2011 -air -july -club -went -january -october -our -august -april -york -12 -few -2012 -2008 -east -show -member -college -2009 -father -public -##us -come -men -five -set -station -church -##c -next -former -november -room -party -located -december -2013 -age -got -2007 -##g -system -let -love -2006 -though -every -2014 -look -song -water -century -without -body -black -night -within -great -women -single -ve -building -large -population -river -named -band -white -started -##an -once -15 -20 -should -18 -2015 -service -top -built -british -open -death -king -moved -local -times -children -february -book -why -11 -door -need -president -order -final -road -wasn -although -due -major -died -village -third -knew -2016 -asked -turned -st -wanted -say -##p -together -received -main -son -served -different -##en -behind -himself -felt -members -power -football -law -voice -play -##in -near -park -history -30 -having -2005 -16 -##man -saw -mother -##al -army -point -front -help -english -street -art -late -hands -games -award -##ia -young -14 -put -published -country -division -across -told -13 -often -ever -french -london -center -six -red -2017 -led -days -include -light -25 -find -tell -among -species -really -according -central -half -2004 -form -original -gave -office -making -enough -lost -full -opened -must -included -live -given -german -player -run -business -woman -community -cup -might -million -land -2000 -court -development -17 -short -round -ii -km -seen -class -story -always -become -sure -research -almost -director -council -la -##2 -career -things -using -island -##z -couldn -car -##is -24 -close -force -##1 -better -free -support -control -field -students -2003 -education -married -##b -nothing -worked -others -record -big -inside -level -anything -continued -give -james -##3 -military -established -non -returned -feel -does -title -written -thing -feet -william -far -co -association -hard -already -2002 -##ra -championship -human -western -100 -##na -department -hall -role -various -production -21 -19 -heart -2001 -living -fire -version -##ers -##f -television -royal -##4 -produced -working -act -case -society -region -present -radio -period -looking -least -total -keep -england -wife -program -per -brother -mind -special -22 -##le -am -works -soon -##6 -political -george -services -taken -created -##7 -further -able -reached -david -union -joined -upon -done -important -social -information -either -##ic -##x -appeared -position -ground -lead -rock -dark -election -23 -board -france -hair -course -arms -site -police -girl -instead -real -sound -##v -words -moment -##te -someone -##8 -summer -project -announced -san -less -wrote -past -followed -##5 -blue -founded -al -finally -india -taking -records -america -##ne -1999 -design -considered -northern -god -stop -battle -toward -european -outside -described -track -today -playing -language -28 -call -26 -heard -professional -low -australia -miles -california -win -yet -green -##ie -trying -blood -##ton -southern -science -maybe -everything -match -square -27 -mouth -video -race -recorded -leave -above -##9 -daughter -points -space -1998 -museum -change -middle -common -##0 -move -tv -post -##ta -lake -seven -tried -elected -closed -ten -paul -minister -##th -months -start -chief -return -canada -person -sea -release -similar -modern -brought -rest -hit -formed -mr -##la -1997 -floor -event -doing -thomas -1996 -robert -care -killed -training -star -week -needed -turn -finished -railway -rather -news -health -sent -example -ran -term -michael -coming -currently -yes -forces -despite -gold -areas -50 -stage -fact -29 -dead -says -popular -2018 -originally -germany -probably -developed -result -pulled -friend -stood -money -running -mi -signed -word -songs -child -eventually -met -tour -average -teams -minutes -festival -current -deep -kind -1995 -decided -usually -eastern -seemed -##ness -episode -bed -added -table -indian -private -charles -route -available -idea -throughout -centre -addition -appointed -style -1994 -books -eight -construction -press -mean -wall -friends -remained -schools -study -##ch -##um -institute -oh -chinese -sometimes -events -possible -1992 -australian -type -brown -forward -talk -process -food -debut -seat -performance -committee -features -character -arts -herself -else -lot -strong -russian -range -hours -peter -arm -##da -morning -dr -sold -##ry -quickly -directed -1993 -guitar -china -##w -31 -list -##ma -performed -media -uk -players -smile -##rs -myself -40 -placed -coach -province -towards -wouldn -leading -whole -boy -official -designed -grand -census -##el -europe -attack -japanese -henry -1991 -##re -##os -cross -getting -alone -action -lower -network -wide -washington -japan -1990 -hospital -believe -changed -sister -##ar -hold -gone -sir -hadn -ship -##ka -studies -academy -shot -rights -below -base -bad -involved -kept -largest -##ist -bank -future -especially -beginning -mark -movement -section -female -magazine -plan -professor -lord -longer -##ian -sat -walked -hill -actually -civil -energy -model -families -size -thus -aircraft -completed -includes -data -captain -##or -fight -vocals -featured -richard -bridge -fourth -1989 -officer -stone -hear -##ism -means -medical -groups -management -self -lips -competition -entire -lived -technology -leaving -federal -tournament -bit -passed -hot -independent -awards -kingdom -mary -spent -fine -doesn -reported -##ling -jack -fall -raised -itself -stay -true -studio -1988 -sports -replaced -paris -systems -saint -leader -theatre -whose -market -capital -parents -spanish -canadian -earth -##ity -cut -degree -writing -bay -christian -awarded -natural -higher -bill -##as -coast -provided -previous -senior -ft -valley -organization -stopped -onto -countries -parts -conference -queen -security -interest -saying -allowed -master -earlier -phone -matter -smith -winning -try -happened -moving -campaign -los -##ley -breath -nearly -mid -1987 -certain -girls -date -italian -african -standing -fell -artist -##ted -shows -deal -mine -industry -1986 -##ng -everyone -republic -provide -collection -library -student -##ville -primary -owned -older -via -heavy -1st -makes -##able -attention -anyone -africa -##ri -stated -length -ended -fingers -command -staff -skin -foreign -opening -governor -okay -medal -kill -sun -cover -job -1985 -introduced -chest -hell -feeling -##ies -success -meet -reason -standard -meeting -novel -1984 -trade -source -buildings -##land -rose -guy -goal -##ur -chapter -native -husband -previously -unit -limited -entered -weeks -producer -operations -mountain -takes -covered -forced -related -roman -complete -successful -key -texas -cold -##ya -channel -1980 -traditional -films -dance -clear -approximately -500 -nine -van -prince -question -active -tracks -ireland -regional -silver -author -personal -sense -operation -##ine -economic -1983 -holding -twenty -isbn -additional -speed -hour -edition -regular -historic -places -whom -shook -movie -km² -secretary -prior -report -chicago -read -foundation -view -engine -scored -1982 -units -ask -airport -property -ready -immediately -lady -month -listed -contract -##de -manager -themselves -lines -##ki -navy -writer -meant -##ts -runs -##ro -practice -championships -singer -glass -commission -required -forest -starting -culture -generally -giving -access -attended -test -couple -stand -catholic -martin -caught -executive -##less -eye -##ey -thinking -chair -quite -shoulder -1979 -hope -decision -plays -defeated -municipality -whether -structure -offered -slowly -pain -ice -direction -##ion -paper -mission -1981 -mostly -200 -noted -individual -managed -nature -lives -plant -##ha -helped -except -studied -computer -figure -relationship -issue -significant -loss -die -smiled -gun -ago -highest -1972 -##am -male -bring -goals -mexico -problem -distance -commercial -completely -location -annual -famous -drive -1976 -neck -1978 -surface -caused -italy -understand -greek -highway -wrong -hotel -comes -appearance -joseph -double -issues -musical -companies -castle -income -review -assembly -bass -initially -parliament -artists -experience -1974 -particular -walk -foot -engineering -talking -window -dropped -##ter -miss -baby -boys -break -1975 -stars -edge -remember -policy -carried -train -stadium -bar -sex -angeles -evidence -##ge -becoming -assistant -soviet -1977 -upper -step -wing -1970 -youth -financial -reach -##ll -actor -numerous -##se -##st -nodded -arrived -##ation -minute -##nt -believed -sorry -complex -beautiful -victory -associated -temple -1968 -1973 -chance -perhaps -metal -##son -1945 -bishop -##et -lee -launched -particularly -tree -le -retired -subject -prize -contains -yeah -theory -empire -##ce -suddenly -waiting -trust -recording -##to -happy -terms -camp -champion -1971 -religious -pass -zealand -names -2nd -port -ancient -tom -corner -represented -watch -legal -anti -justice -cause -watched -brothers -45 -material -changes -simply -response -louis -fast -##ting -answer -60 -historical -1969 -stories -straight -create -feature -increased -rate -administration -virginia -el -activities -cultural -overall -winner -programs -basketball -legs -guard -beyond -cast -doctor -mm -flight -results -remains -cost -effect -winter -##ble -larger -islands -problems -chairman -grew -commander -isn -1967 -pay -failed -selected -hurt -fort -box -regiment -majority -journal -35 -edward -plans -##ke -##ni -shown -pretty -irish -characters -directly -scene -likely -operated -allow -spring -##j -junior -matches -looks -mike -houses -fellow -##tion -beach -marriage -##ham -##ive -rules -oil -65 -florida -expected -nearby -congress -sam -peace -recent -iii -wait -subsequently -cell -##do -variety -serving -agreed -please -poor -joe -pacific -attempt -wood -democratic -piece -prime -##ca -rural -mile -touch -appears -township -1964 -1966 -soldiers -##men -##ized -1965 -pennsylvania -closer -fighting -claimed -score -jones -physical -editor -##ous -filled -genus -specific -sitting -super -mom -##va -therefore -supported -status -fear -cases -store -meaning -wales -minor -spain -tower -focus -vice -frank -follow -parish -separate -golden -horse -fifth -remaining -branch -32 -presented -stared -##id -uses -secret -forms -##co -baseball -exactly -##ck -choice -note -discovered -travel -composed -truth -russia -ball -color -kiss -dad -wind -continue -ring -referred -numbers -digital -greater -##ns -metres -slightly -direct -increase -1960 -responsible -crew -rule -trees -troops -##no -broke -goes -individuals -hundred -weight -creek -sleep -memory -defense -provides -ordered -code -value -jewish -windows -1944 -safe -judge -whatever -corps -realized -growing -pre -##ga -cities -alexander -gaze -lies -spread -scott -letter -showed -situation -mayor -transport -watching -workers -extended -##li -expression -normal -##ment -chart -multiple -border -##ba -host -##ner -daily -mrs -walls -piano -##ko -heat -cannot -##ate -earned -products -drama -era -authority -seasons -join -grade -##io -sign -difficult -machine -1963 -territory -mainly -##wood -stations -squadron -1962 -stepped -iron -19th -##led -serve -appear -sky -speak -broken -charge -knowledge -kilometres -removed -ships -article -campus -simple -##ty -pushed -britain -##ve -leaves -recently -cd -soft -boston -latter -easy -acquired -poland -##sa -quality -officers -presence -planned -nations -mass -broadcast -jean -share -image -influence -wild -offer -emperor -electric -reading -headed -ability -promoted -yellow -ministry -1942 -throat -smaller -politician -##by -latin -spoke -cars -williams -males -lack -pop -80 -##ier -acting -seeing -consists -##ti -estate -1961 -pressure -johnson -newspaper -jr -chris -olympics -online -conditions -beat -elements -walking -vote -##field -needs -carolina -text -featuring -global -block -shirt -levels -francisco -purpose -females -et -dutch -duke -ahead -gas -twice -safety -serious -turning -highly -lieutenant -firm -maria -amount -mixed -daniel -proposed -perfect -agreement -affairs -3rd -seconds -contemporary -paid -1943 -prison -save -kitchen -label -administrative -intended -constructed -academic -nice -teacher -races -1956 -formerly -corporation -ben -nation -issued -shut -1958 -drums -housing -victoria -seems -opera -1959 -graduated -function -von -mentioned -picked -build -recognized -shortly -protection -picture -notable -exchange -elections -1980s -loved -percent -racing -fish -elizabeth -garden -volume -hockey -1941 -beside -settled -##ford -1940 -competed -replied -drew -1948 -actress -marine -scotland -steel -glanced -farm -steve -1957 -risk -tonight -positive -magic -singles -effects -gray -screen -dog -##ja -residents -bus -sides -none -secondary -literature -polish -destroyed -flying -founder -households -1939 -lay -reserve -usa -gallery -##ler -1946 -industrial -younger -approach -appearances -urban -ones -1950 -finish -avenue -powerful -fully -growth -page -honor -jersey -projects -advanced -revealed -basic -90 -infantry -pair -equipment -visit -33 -evening -search -grant -effort -solo -treatment -buried -republican -primarily -bottom -owner -1970s -israel -gives -jim -dream -bob -remain -spot -70 -notes -produce -champions -contact -ed -soul -accepted -ways -del -##ally -losing -split -price -capacity -basis -trial -questions -##ina -1955 -20th -guess -officially -memorial -naval -initial -##ization -whispered -median -engineer -##ful -sydney -##go -columbia -strength -300 -1952 -tears -senate -00 -card -asian -agent -1947 -software -44 -draw -warm -supposed -com -pro -##il -transferred -leaned -##at -candidate -escape -mountains -asia -potential -activity -entertainment -seem -traffic -jackson -murder -36 -slow -product -orchestra -haven -agency -bbc -taught -website -comedy -unable -storm -planning -albums -rugby -environment -scientific -grabbed -protect -##hi -boat -typically -1954 -1953 -damage -principal -divided -dedicated -mount -ohio -##berg -pick -fought -driver -##der -empty -shoulders -sort -thank -berlin -prominent -account -freedom -necessary -efforts -alex -headquarters -follows -alongside -des -simon -andrew -suggested -operating -learning -steps -1949 -sweet -technical -begin -easily -34 -teeth -speaking -settlement -scale -##sh -renamed -ray -max -enemy -semi -joint -compared -##rd -scottish -leadership -analysis -offers -georgia -pieces -captured -animal -deputy -guest -organized -##lin -tony -combined -method -challenge -1960s -huge -wants -battalion -sons -rise -crime -types -facilities -telling -path -1951 -platform -sit -1990s -##lo -tells -assigned -rich -pull -##ot -commonly -alive -##za -letters -concept -conducted -wearing -happen -bought -becomes -holy -gets -ocean -defeat -languages -purchased -coffee -occurred -titled -##q -declared -applied -sciences -concert -sounds -jazz -brain -##me -painting -fleet -tax -nick -##ius -michigan -count -animals -leaders -episodes -##line -content -##den -birth -##it -clubs -64 -palace -critical -refused -fair -leg -laughed -returning -surrounding -participated -formation -lifted -pointed -connected -rome -medicine -laid -taylor -santa -powers -adam -tall -shared -focused -knowing -yards -entrance -falls -##wa -calling -##ad -sources -chosen -beneath -resources -yard -##ite -nominated -silence -zone -defined -##que -gained -thirty -38 -bodies -moon -##ard -adopted -christmas -widely -register -apart -iran -premier -serves -du -unknown -parties -##les -generation -##ff -continues -quick -fields -brigade -quiet -teaching -clothes -impact -weapons -partner -flat -theater -supreme -1938 -37 -relations -##tor -plants -suffered -1936 -wilson -kids -begins -##age -1918 -seats -armed -internet -models -worth -laws -400 -communities -classes -background -knows -thanks -quarter -reaching -humans -carry -killing -format -kong -hong -setting -75 -architecture -disease -railroad -inc -possibly -wish -arthur -thoughts -harry -doors -density -##di -crowd -illinois -stomach -tone -unique -reports -anyway -##ir -liberal -der -vehicle -thick -dry -drug -faced -largely -facility -theme -holds -creation -strange -colonel -##mi -revolution -bell -politics -turns -silent -rail -relief -independence -combat -shape -write -determined -sales -learned -4th -finger -oxford -providing -1937 -heritage -fiction -situated -designated -allowing -distribution -hosted -##est -sight -interview -estimated -reduced -##ria -toronto -footballer -keeping -guys -damn -claim -motion -sport -sixth -stayed -##ze -en -rear -receive -handed -twelve -dress -audience -granted -brazil -##well -spirit -##ated -noticed -etc -olympic -representative -eric -tight -trouble -reviews -drink -vampire -missing -roles -ranked -newly -household -finals -wave -critics -##ee -phase -massachusetts -pilot -unlike -philadelphia -bright -guns -crown -organizations -roof -42 -respectively -clearly -tongue -marked -circle -fox -korea -bronze -brian -expanded -sexual -supply -yourself -inspired -labour -fc -##ah -reference -vision -draft -connection -brand -reasons -1935 -classic -driving -trip -jesus -cells -entry -1920 -neither -trail -claims -atlantic -orders -labor -nose -afraid -identified -intelligence -calls -cancer -attacked -passing -stephen -positions -imperial -grey -jason -39 -sunday -48 -swedish -avoid -extra -uncle -message -covers -allows -surprise -materials -fame -hunter -##ji -1930 -citizens -figures -davis -environmental -confirmed -shit -titles -di -performing -difference -acts -attacks -##ov -existing -votes -opportunity -nor -shop -entirely -trains -opposite -pakistan -##pa -develop -resulted -representatives -actions -reality -pressed -##ish -barely -wine -conversation -faculty -northwest -ends -documentary -nuclear -stock -grace -sets -eat -alternative -##ps -bag -resulting -creating -surprised -cemetery -1919 -drop -finding -sarah -cricket -streets -tradition -ride -1933 -exhibition -target -ear -explained -rain -composer -injury -apartment -municipal -educational -occupied -netherlands -clean -billion -constitution -learn -1914 -maximum -classical -francis -lose -opposition -jose -ontario -bear -core -hills -rolled -ending -drawn -permanent -fun -##tes -##lla -lewis -sites -chamber -ryan -##way -scoring -height -1934 -##house -lyrics -staring -55 -officials -1917 -snow -oldest -##tic -orange -##ger -qualified -interior -apparently -succeeded -thousand -dinner -lights -existence -fans -heavily -41 -greatest -conservative -send -bowl -plus -enter -catch -##un -economy -duty -1929 -speech -authorities -princess -performances -versions -shall -graduate -pictures -effective -remembered -poetry -desk -crossed -starring -starts -passenger -sharp -##ant -acres -ass -weather -falling -rank -fund -supporting -check -adult -publishing -heads -cm -southeast -lane -##burg -application -bc -##ura -les -condition -transfer -prevent -display -ex -regions -earl -federation -cool -relatively -answered -besides -1928 -obtained -portion -##town -mix -##ding -reaction -liked -dean -express -peak -1932 -##tte -counter -religion -chain -rare -miller -convention -aid -lie -vehicles -mobile -perform -squad -wonder -lying -crazy -sword -##ping -attempted -centuries -weren -philosophy -category -##ize -anna -interested -47 -sweden -wolf -frequently -abandoned -kg -literary -alliance -task -entitled -##ay -threw -promotion -factory -tiny -soccer -visited -matt -fm -achieved -52 -defence -internal -persian -43 -methods -##ging -arrested -otherwise -cambridge -programming -villages -elementary -districts -rooms -criminal -conflict -worry -trained -1931 -attempts -waited -signal -bird -truck -subsequent -programme -##ol -ad -49 -communist -details -faith -sector -patrick -carrying -laugh -##ss -controlled -korean -showing -origin -fuel -evil -1927 -##ent -brief -identity -darkness -address -pool -missed -publication -web -planet -ian -anne -wings -invited -##tt -briefly -standards -kissed -##be -ideas -climate -causing -walter -worse -albert -articles -winners -desire -aged -northeast -dangerous -gate -doubt -1922 -wooden -multi -##ky -poet -rising -funding -46 -communications -communication -violence -copies -prepared -ford -investigation -skills -1924 -pulling -electronic -##ak -##ial -##han -containing -ultimately -offices -singing -understanding -restaurant -tomorrow -fashion -christ -ward -da -pope -stands -5th -flow -studios -aired -commissioned -contained -exist -fresh -americans -##per -wrestling -approved -kid -employed -respect -suit -1925 -angel -asking -increasing -frame -angry -selling -1950s -thin -finds -##nd -temperature -statement -ali -explain -inhabitants -towns -extensive -narrow -51 -jane -flowers -images -promise -somewhere -object -fly -closely -##ls -1912 -bureau -cape -1926 -weekly -presidential -legislative -1921 -##ai -##au -launch -founding -##ny -978 -##ring -artillery -strike -un -institutions -roll -writers -landing -chose -kevin -anymore -pp -##ut -attorney -fit -dan -billboard -receiving -agricultural -breaking -sought -dave -admitted -lands -mexican -##bury -charlie -specifically -hole -iv -howard -credit -moscow -roads -accident -1923 -proved -wear -struck -hey -guards -stuff -slid -expansion -1915 -cat -anthony -##kin -melbourne -opposed -sub -southwest -architect -failure -plane -1916 -##ron -map -camera -tank -listen -regarding -wet -introduction -metropolitan -link -ep -fighter -inch -grown -gene -anger -fixed -buy -dvd -khan -domestic -worldwide -chapel -mill -functions -examples -##head -developing -1910 -turkey -hits -pocket -antonio -papers -grow -unless -circuit -18th -concerned -attached -journalist -selection -journey -converted -provincial -painted -hearing -aren -bands -negative -aside -wondered -knight -lap -survey -ma -##ow -noise -billy -##ium -shooting -guide -bedroom -priest -resistance -motor -homes -sounded -giant -##mer -150 -scenes -equal -comic -patients -hidden -solid -actual -bringing -afternoon -touched -funds -wedding -consisted -marie -canal -sr -kim -treaty -turkish -recognition -residence -cathedral -broad -knees -incident -shaped -fired -norwegian -handle -cheek -contest -represent -##pe -representing -beauty -##sen -birds -advantage -emergency -wrapped -drawing -notice -pink -broadcasting -##ong -somehow -bachelor -seventh -collected -registered -establishment -alan -assumed -chemical -personnel -roger -retirement -jeff -portuguese -wore -tied -device -threat -progress -advance -##ised -banks -hired -manchester -nfl -teachers -structures -forever -##bo -tennis -helping -saturday -sale -applications -junction -hip -incorporated -neighborhood -dressed -ceremony -##ds -influenced -hers -visual -stairs -decades -inner -kansas -hung -hoped -gain -scheduled -downtown -engaged -austria -clock -norway -certainly -pale -protected -1913 -victor -employees -plate -putting -surrounded -##ists -finishing -blues -tropical -##ries -minnesota -consider -philippines -accept -54 -retrieved -1900 -concern -anderson -properties -institution -gordon -successfully -vietnam -##dy -backing -outstanding -muslim -crossing -folk -producing -usual -demand -occurs -observed -lawyer -educated -##ana -kelly -string -pleasure -budget -items -quietly -colorado -philip -typical -##worth -derived -600 -survived -asks -mental -##ide -56 -jake -jews -distinguished -ltd -1911 -sri -extremely -53 -athletic -loud -thousands -worried -shadow -transportation -horses -weapon -arena -importance -users -tim -objects -contributed -dragon -douglas -aware -senator -johnny -jordan -sisters -engines -flag -investment -samuel -shock -capable -clark -row -wheel -refers -session -familiar -biggest -wins -hate -maintained -drove -hamilton -request -expressed -injured -underground -churches -walker -wars -tunnel -passes -stupid -agriculture -softly -cabinet -regarded -joining -indiana -##ea -##ms -push -dates -spend -behavior -woods -protein -gently -chase -morgan -mention -burning -wake -combination -occur -mirror -leads -jimmy -indeed -impossible -singapore -paintings -covering -##nes -soldier -locations -attendance -sell -historian -wisconsin -invasion -argued -painter -diego -changing -egypt -##don -experienced -inches -##ku -missouri -vol -grounds -spoken -switzerland -##gan -reform -rolling -ha -forget -massive -resigned -burned -allen -tennessee -locked -values -improved -##mo -wounded -universe -sick -dating -facing -pack -purchase -user -##pur -moments -##ul -merged -anniversary -1908 -coal -brick -understood -causes -dynasty -queensland -establish -stores -crisis -promote -hoping -views -cards -referee -extension -##si -raise -arizona -improve -colonial -formal -charged -##rt -palm -lucky -hide -rescue -faces -95 -feelings -candidates -juan -##ell -goods -6th -courses -weekend -59 -luke -cash -fallen -##om -delivered -affected -installed -carefully -tries -swiss -hollywood -costs -lincoln -responsibility -##he -shore -file -proper -normally -maryland -assistance -jump -constant -offering -friendly -waters -persons -realize -contain -trophy -800 -partnership -factor -58 -musicians -cry -bound -oregon -indicated -hero -houston -medium -##ure -consisting -somewhat -##ara -57 -cycle -##che -beer -moore -frederick -gotten -eleven -worst -weak -approached -arranged -chin -loan -universal -bond -fifteen -pattern -disappeared -##ney -translated -##zed -lip -arab -capture -interests -insurance -##chi -shifted -cave -prix -warning -sections -courts -coat -plot -smell -feed -golf -favorite -maintain -knife -vs -voted -degrees -finance -quebec -opinion -translation -manner -ruled -operate -productions -choose -musician -discovery -confused -tired -separated -stream -techniques -committed -attend -ranking -kings -throw -passengers -measure -horror -fan -mining -sand -danger -salt -calm -decade -dam -require -runner -##ik -rush -associate -greece -##ker -rivers -consecutive -matthew -##ski -sighed -sq -documents -steam -edited -closing -tie -accused -1905 -##ini -islamic -distributed -directors -organisation -bruce -7th -breathing -mad -lit -arrival -concrete -taste -08 -composition -shaking -faster -amateur -adjacent -stating -1906 -twin -flew -##ran -tokyo -publications -##tone -obviously -ridge -storage -1907 -carl -pages -concluded -desert -driven -universities -ages -terminal -sequence -borough -250 -constituency -creative -cousin -economics -dreams -margaret -notably -reduce -montreal -mode -17th -ears -saved -jan -vocal -##ica -1909 -andy -##jo -riding -roughly -threatened -##ise -meters -meanwhile -landed -compete -repeated -grass -czech -regularly -charges -tea -sudden -appeal -##ung -solution -describes -pierre -classification -glad -parking -##ning -belt -physics -99 -rachel -add -hungarian -participate -expedition -damaged -gift -childhood -85 -fifty -##red -mathematics -jumped -letting -defensive -mph -##ux -##gh -testing -##hip -hundreds -shoot -owners -matters -smoke -israeli -kentucky -dancing -mounted -grandfather -emma -designs -profit -argentina -##gs -truly -li -lawrence -cole -begun -detroit -willing -branches -smiling -decide -miami -enjoyed -recordings -##dale -poverty -ethnic -gay -##bi -gary -arabic -09 -accompanied -##one -##ons -fishing -determine -residential -acid -##ary -alice -returns -starred -mail -##ang -jonathan -strategy -##ue -net -forty -cook -businesses -equivalent -commonwealth -distinct -ill -##cy -seriously -##ors -##ped -shift -harris -replace -rio -imagine -formula -ensure -##ber -additionally -scheme -conservation -occasionally -purposes -feels -favor -##and -##ore -1930s -contrast -hanging -hunt -movies -1904 -instruments -victims -danish -christopher -busy -demon -sugar -earliest -colony -studying -balance -duties -##ks -belgium -slipped -carter -05 -visible -stages -iraq -fifa -##im -commune -forming -zero -07 -continuing -talked -counties -legend -bathroom -option -tail -clay -daughters -afterwards -severe -jaw -visitors -##ded -devices -aviation -russell -kate -##vi -entering -subjects -##ino -temporary -swimming -forth -smooth -ghost -audio -bush -operates -rocks -movements -signs -eddie -##tz -ann -voices -honorary -06 -memories -dallas -pure -measures -racial -promised -66 -harvard -ceo -16th -parliamentary -indicate -benefit -flesh -dublin -louisiana -1902 -1901 -patient -sleeping -1903 -membership -coastal -medieval -wanting -element -scholars -rice -62 -limit -survive -makeup -rating -definitely -collaboration -obvious -##tan -boss -ms -baron -birthday -linked -soil -diocese -##lan -ncaa -##mann -offensive -shell -shouldn -waist -##tus -plain -ross -organ -resolution -manufacturing -adding -relative -kennedy -98 -whilst -moth -marketing -gardens -crash -72 -heading -partners -credited -carlos -moves -cable -##zi -marshall -##out -depending -bottle -represents -rejected -responded -existed -04 -jobs -denmark -lock -##ating -treated -graham -routes -talent -commissioner -drugs -secure -tests -reign -restored -photography -##gi -contributions -oklahoma -designer -disc -grin -seattle -robin -paused -atlanta -unusual -##gate -praised -las -laughing -satellite -hungary -visiting -##sky -interesting -factors -deck -poems -norman -##water -stuck -speaker -rifle -domain -premiered -##her -dc -comics -actors -01 -reputation -eliminated -8th -ceiling -prisoners -script -##nce -leather -austin -mississippi -rapidly -admiral -parallel -charlotte -guilty -tools -gender -divisions -fruit -##bs -laboratory -nelson -fantasy -marry -rapid -aunt -tribe -requirements -aspects -suicide -amongst -adams -bone -ukraine -abc -kick -sees -edinburgh -clothing -column -rough -gods -hunting -broadway -gathered -concerns -##ek -spending -ty -12th -snapped -requires -solar -bones -cavalry -##tta -iowa -drinking -waste -index -franklin -charity -thompson -stewart -tip -flash -landscape -friday -enjoy -singh -poem -listening -##back -eighth -fred -differences -adapted -bomb -ukrainian -surgery -corporate -masters -anywhere -##more -waves -odd -sean -portugal -orleans -dick -debate -kent -eating -puerto -cleared -96 -expect -cinema -97 -guitarist -blocks -electrical -agree -involving -depth -dying -panel -struggle -##ged -peninsula -adults -novels -emerged -vienna -metro -debuted -shoes -tamil -songwriter -meets -prove -beating -instance -heaven -scared -sending -marks -artistic -passage -superior -03 -significantly -shopping -##tive -retained -##izing -malaysia -technique -cheeks -##ola -warren -maintenance -destroy -extreme -allied -120 -appearing -##yn -fill -advice -alabama -qualifying -policies -cleveland -hat -battery -smart -authors -10th -soundtrack -acted -dated -lb -glance -equipped -coalition -funny -outer -ambassador -roy -possibility -couples -campbell -dna -loose -ethan -supplies -1898 -gonna -88 -monster -##res -shake -agents -frequency -springs -dogs -practices -61 -gang -plastic -easier -suggests -gulf -blade -exposed -colors -industries -markets -pan -nervous -electoral -charts -legislation -ownership -##idae -mac -appointment -shield -copy -assault -socialist -abbey -monument -license -throne -employment -jay -93 -replacement -charter -cloud -powered -suffering -accounts -oak -connecticut -strongly -wright -colour -crystal -13th -context -welsh -networks -voiced -gabriel -jerry -##cing -forehead -mp -##ens -manage -schedule -totally -remix -##ii -forests -occupation -print -nicholas -brazilian -strategic -vampires -engineers -76 -roots -seek -correct -instrumental -und -alfred -backed -hop -##des -stanley -robinson -traveled -wayne -welcome -austrian -achieve -67 -exit -rates -1899 -strip -whereas -##cs -sing -deeply -adventure -bobby -rick -jamie -careful -components -cap -useful -personality -knee -##shi -pushing -hosts -02 -protest -ca -ottoman -symphony -##sis -63 -boundary -1890 -processes -considering -considerable -tons -##work -##ft -##nia -cooper -trading -dear -conduct -91 -illegal -apple -revolutionary -holiday -definition -harder -##van -jacob -circumstances -destruction -##lle -popularity -grip -classified -liverpool -donald -baltimore -flows -seeking -honour -approval -92 -mechanical -till -happening -statue -critic -increasingly -immediate -describe -commerce -stare -##ster -indonesia -meat -rounds -boats -baker -orthodox -depression -formally -worn -naked -claire -muttered -sentence -11th -emily -document -77 -criticism -wished -vessel -spiritual -bent -virgin -parker -minimum -murray -lunch -danny -printed -compilation -keyboards -false -blow -belonged -68 -raising -78 -cutting -##board -pittsburgh -##up -9th -shadows -81 -hated -indigenous -jon -15th -barry -scholar -ah -##zer -oliver -##gy -stick -susan -meetings -attracted -spell -romantic -##ver -ye -1895 -photo -demanded -customers -##ac -1896 -logan -revival -keys -modified -commanded -jeans -##ious -upset -raw -phil -detective -hiding -resident -vincent -##bly -experiences -diamond -defeating -coverage -lucas -external -parks -franchise -helen -bible -successor -percussion -celebrated -il -lift -profile -clan -romania -##ied -mills -##su -nobody -achievement -shrugged -fault -1897 -rhythm -initiative -breakfast -carbon -700 -69 -lasted -violent -74 -wound -ken -killer -gradually -filmed -°c -dollars -processing -94 -remove -criticized -guests -sang -chemistry -##vin -legislature -disney -##bridge -uniform -escaped -integrated -proposal -purple -denied -liquid -karl -influential -morris -nights -stones -intense -experimental -twisted -71 -84 -##ld -pace -nazi -mitchell -ny -blind -reporter -newspapers -14th -centers -burn -basin -forgotten -surviving -filed -collections -monastery -losses -manual -couch -description -appropriate -merely -tag -missions -sebastian -restoration -replacing -triple -73 -elder -julia -warriors -benjamin -julian -convinced -stronger -amazing -declined -versus -merchant -happens -output -finland -bare -barbara -absence -ignored -dawn -injuries -##port -producers -##ram -82 -luis -##ities -kw -admit -expensive -electricity -nba -exception -symbol -##ving -ladies -shower -sheriff -characteristics -##je -aimed -button -ratio -effectively -summit -angle -jury -bears -foster -vessels -pants -executed -evans -dozen -advertising -kicked -patrol -1889 -competitions -lifetime -principles -athletics -##logy -birmingham -sponsored -89 -rob -nomination -1893 -acoustic -##sm -creature -longest -##tra -credits -harbor -dust -josh -##so -territories -milk -infrastructure -completion -thailand -indians -leon -archbishop -##sy -assist -pitch -blake -arrangement -girlfriend -serbian -operational -hence -sad -scent -fur -dj -sessions -hp -refer -rarely -##ora -exists -1892 -##ten -scientists -dirty -penalty -burst -portrait -seed -79 -pole -limits -rival -1894 -stable -alpha -grave -constitutional -alcohol -arrest -flower -mystery -devil -architectural -relationships -greatly -habitat -##istic -larry -progressive -remote -cotton -##ics -##ok -preserved -reaches -##ming -cited -86 -vast -scholarship -decisions -cbs -joy -teach -1885 -editions -knocked -eve -searching -partly -participation -gap -animated -fate -excellent -##ett -na -87 -alternate -saints -youngest -##ily -climbed -##ita -##tors -suggest -##ct -discussion -staying -choir -lakes -jacket -revenue -nevertheless -peaked -instrument -wondering -annually -managing -neil -1891 -signing -terry -##ice -apply -clinical -brooklyn -aim -catherine -fuck -farmers -figured -ninth -pride -hugh -evolution -ordinary -involvement -comfortable -shouted -tech -encouraged -taiwan -representation -sharing -##lia -##em -panic -exact -cargo -competing -fat -cried -83 -1920s -occasions -pa -cabin -borders -utah -marcus -##isation -badly -muscles -##ance -victorian -transition -warner -bet -permission -##rin -slave -terrible -similarly -shares -seth -uefa -possession -medals -benefits -colleges -lowered -perfectly -mall -transit -##ye -##kar -publisher -##ened -harrison -deaths -elevation -##ae -asleep -machines -sigh -ash -hardly -argument -occasion -parent -leo -decline -1888 -contribution -##ua -concentration -1000 -opportunities -hispanic -guardian -extent -emotions -hips -mason -volumes -bloody -controversy -diameter -steady -mistake -phoenix -identify -violin -##sk -departure -richmond -spin -funeral -enemies -1864 -gear -literally -connor -random -sergeant -grab -confusion -1865 -transmission -informed -op -leaning -sacred -suspended -thinks -gates -portland -luck -agencies -yours -hull -expert -muscle -layer -practical -sculpture -jerusalem -latest -lloyd -statistics -deeper -recommended -warrior -arkansas -mess -supports -greg -eagle -1880 -recovered -rated -concerts -rushed -##ano -stops -eggs -files -premiere -keith -##vo -delhi -turner -pit -affair -belief -paint -##zing -mate -##ach -##ev -victim -##ology -withdrew -bonus -styles -fled -##ud -glasgow -technologies -funded -nbc -adaptation -##ata -portrayed -cooperation -supporters -judges -bernard -justin -hallway -ralph -##ick -graduating -controversial -distant -continental -spider -bite -##ho -recognize -intention -mixing -##ese -egyptian -bow -tourism -suppose -claiming -tiger -dominated -participants -vi -##ru -nurse -partially -tape -##rum -psychology -##rn -essential -touring -duo -voting -civilian -emotional -channels -##king -apparent -hebrew -1887 -tommy -carrier -intersection -beast -hudson -##gar -##zo -lab -nova -bench -discuss -costa -##ered -detailed -behalf -drivers -unfortunately -obtain -##lis -rocky -##dae -siege -friendship -honey -##rian -1861 -amy -hang -posted -governments -collins -respond -wildlife -preferred -operator -##po -laura -pregnant -videos -dennis -suspected -boots -instantly -weird -automatic -businessman -alleged -placing -throwing -ph -mood -1862 -perry -venue -jet -remainder -##lli -##ci -passion -biological -boyfriend -1863 -dirt -buffalo -ron -segment -fa -abuse -##era -genre -thrown -stroke -colored -stress -exercise -displayed -##gen -struggled -##tti -abroad -dramatic -wonderful -thereafter -madrid -component -widespread -##sed -tale -citizen -todd -monday -1886 -vancouver -overseas -forcing -crying -descent -##ris -discussed -substantial -ranks -regime -1870 -provinces -switch -drum -zane -ted -tribes -proof -lp -cream -researchers -volunteer -manor -silk -milan -donated -allies -venture -principle -delivery -enterprise -##ves -##ans -bars -traditionally -witch -reminded -copper -##uk -pete -inter -links -colin -grinned -elsewhere -competitive -frequent -##oy -scream -##hu -tension -texts -submarine -finnish -defending -defend -pat -detail -1884 -affiliated -stuart -themes -villa -periods -tool -belgian -ruling -crimes -answers -folded -licensed -resort -demolished -hans -lucy -1881 -lion -traded -photographs -writes -craig -##fa -trials -generated -beth -noble -debt -percentage -yorkshire -erected -ss -viewed -grades -confidence -ceased -islam -telephone -retail -##ible -chile -m² -roberts -sixteen -##ich -commented -hampshire -innocent -dual -pounds -checked -regulations -afghanistan -sung -rico -liberty -assets -bigger -options -angels -relegated -tribute -wells -attending -leaf -##yan -butler -romanian -forum -monthly -lisa -patterns -gmina -##tory -madison -hurricane -rev -##ians -bristol -##ula -elite -valuable -disaster -democracy -awareness -germans -freyja -##ins -loop -absolutely -paying -populations -maine -sole -prayer -spencer -releases -doorway -bull -##ani -lover -midnight -conclusion -##sson -thirteen -lily -mediterranean -##lt -nhl -proud -sample -##hill -drummer -guinea -##ova -murphy -climb -##ston -instant -attributed -horn -ain -railways -steven -##ao -autumn -ferry -opponent -root -traveling -secured -corridor -stretched -tales -sheet -trinity -cattle -helps -indicates -manhattan -murdered -fitted -1882 -gentle -grandmother -mines -shocked -vegas -produces -##light -caribbean -##ou -belong -continuous -desperate -drunk -historically -trio -waved -raf -dealing -nathan -bat -murmured -interrupted -residing -scientist -pioneer -harold -aaron -##net -delta -attempting -minority -mini -believes -chorus -tend -lots -eyed -indoor -load -shots -updated -jail -##llo -concerning -connecting -wealth -##ved -slaves -arrive -rangers -sufficient -rebuilt -##wick -cardinal -flood -muhammad -whenever -relation -runners -moral -repair -viewers -arriving -revenge -punk -assisted -bath -fairly -breathe -lists -innings -illustrated -whisper -nearest -voters -clinton -ties -ultimate -screamed -beijing -lions -andre -fictional -gathering -comfort -radar -suitable -dismissed -hms -ban -pine -wrist -atmosphere -voivodeship -bid -timber -##ned -##nan -giants -##ane -cameron -recovery -uss -identical -categories -switched -serbia -laughter -noah -ensemble -therapy -peoples -touching -##off -locally -pearl -platforms -everywhere -ballet -tables -lanka -herbert -outdoor -toured -derek -1883 -spaces -contested -swept -1878 -exclusive -slight -connections -##dra -winds -prisoner -collective -bangladesh -tube -publicly -wealthy -thai -##ys -isolated -select -##ric -insisted -pen -fortune -ticket -spotted -reportedly -animation -enforcement -tanks -110 -decides -wider -lowest -owen -##time -nod -hitting -##hn -gregory -furthermore -magazines -fighters -solutions -##ery -pointing -requested -peru -reed -chancellor -knights -mask -worker -eldest -flames -reduction -1860 -volunteers -##tis -reporting -##hl -wire -advisory -endemic -origins -settlers -pursue -knock -consumer -1876 -eu -compound -creatures -mansion -sentenced -ivan -deployed -guitars -frowned -involves -mechanism -kilometers -perspective -shops -maps -terminus -duncan -alien -fist -bridges -##pers -heroes -fed -derby -swallowed -##ros -patent -sara -illness -characterized -adventures -slide -hawaii -jurisdiction -##op -organised -##side -adelaide -walks -biology -se -##ties -rogers -swing -tightly -boundaries -##rie -prepare -implementation -stolen -##sha -certified -colombia -edwards -garage -##mm -recalled -##ball -rage -harm -nigeria -breast -##ren -furniture -pupils -settle -##lus -cuba -balls -client -alaska -21st -linear -thrust -celebration -latino -genetic -terror -##cia -##ening -lightning -fee -witness -lodge -establishing -skull -##ique -earning -hood -##ei -rebellion -wang -sporting -warned -missile -devoted -activist -porch -worship -fourteen -package -1871 -decorated -##shire -housed -##ock -chess -sailed -doctors -oscar -joan -treat -garcia -harbour -jeremy -##ire -traditions -dominant -jacques -##gon -##wan -relocated -1879 -amendment -sized -companion -simultaneously -volleyball -spun -acre -increases -stopping -loves -belongs -affect -drafted -tossed -scout -battles -1875 -filming -shoved -munich -tenure -vertical -romance -pc -##cher -argue -##ical -craft -ranging -www -opens -honest -tyler -yesterday -virtual -##let -muslims -reveal -snake -immigrants -radical -screaming -speakers -firing -saving -belonging -ease -lighting -prefecture -blame -farmer -hungry -grows -rubbed -beam -sur -subsidiary -##cha -armenian -sao -dropping -conventional -##fer -microsoft -reply -qualify -spots -1867 -sweat -festivals -##ken -immigration -physician -discover -exposure -sandy -explanation -isaac -implemented -##fish -hart -initiated -connect -stakes -presents -heights -householder -pleased -tourist -regardless -slip -closest -##ction -surely -sultan -brings -riley -preparation -aboard -slammed -baptist -experiment -ongoing -interstate -organic -playoffs -##ika -1877 -130 -##tar -hindu -error -tours -tier -plenty -arrangements -talks -trapped -excited -sank -ho -athens -1872 -denver -welfare -suburb -athletes -trick -diverse -belly -exclusively -yelled -1868 -##med -conversion -##ette -1874 -internationally -computers -conductor -abilities -sensitive -hello -dispute -measured -globe -rocket -prices -amsterdam -flights -tigers -inn -municipalities -emotion -references -3d -##mus -explains -airlines -manufactured -pm -archaeological -1873 -interpretation -devon -comment -##ites -settlements -kissing -absolute -improvement -suite -impressed -barcelona -sullivan -jefferson -towers -jesse -julie -##tin -##lu -grandson -hi -gauge -regard -rings -interviews -trace -raymond -thumb -departments -burns -serial -bulgarian -scores -demonstrated -##ix -1866 -kyle -alberta -underneath -romanized -##ward -relieved -acquisition -phrase -cliff -reveals -han -cuts -merger -custom -##dar -nee -gilbert -graduation -##nts -assessment -cafe -difficulty -demands -swung -democrat -jennifer -commons -1940s -grove -##yo -completing -focuses -sum -substitute -bearing -stretch -reception -##py -reflected -essentially -destination -pairs -##ched -survival -resource -##bach -promoting -doubles -messages -tear -##down -##fully -parade -florence -harvey -incumbent -partial -framework -900 -pedro -frozen -procedure -olivia -controls -##mic -shelter -personally -temperatures -##od -brisbane -tested -sits -marble -comprehensive -oxygen -leonard -##kov -inaugural -iranian -referring -quarters -attitude -##ivity -mainstream -lined -mars -dakota -norfolk -unsuccessful -##° -explosion -helicopter -congressional -##sing -inspector -bitch -seal -departed -divine -##ters -coaching -examination -punishment -manufacturer -sink -columns -unincorporated -signals -nevada -squeezed -dylan -dining -photos -martial -manuel -eighteen -elevator -brushed -plates -ministers -ivy -congregation -##len -slept -specialized -taxes -curve -restricted -negotiations -likes -statistical -arnold -inspiration -execution -bold -intermediate -significance -margin -ruler -wheels -gothic -intellectual -dependent -listened -eligible -buses -widow -syria -earn -cincinnati -collapsed -recipient -secrets -accessible -philippine -maritime -goddess -clerk -surrender -breaks -playoff -database -##ified -##lon -ideal -beetle -aspect -soap -regulation -strings -expand -anglo -shorter -crosses -retreat -tough -coins -wallace -directions -pressing -##oon -shipping -locomotives -comparison -topics -nephew -##mes -distinction -honors -travelled -sierra -ibn -##over -fortress -sa -recognised -carved -1869 -clients -##dan -intent -##mar -coaches -describing -bread -##ington -beaten -northwestern -##ona -merit -youtube -collapse -challenges -em -historians -objective -submitted -virus -attacking -drake -assume -##ere -diseases -marc -stem -leeds -##cus -##ab -farming -glasses -##lock -visits -nowhere -fellowship -relevant -carries -restaurants -experiments -101 -constantly -bases -targets -shah -tenth -opponents -verse -territorial -##ira -writings -corruption -##hs -instruction -inherited -reverse -emphasis -##vic -employee -arch -keeps -rabbi -watson -payment -uh -##ala -nancy -##tre -venice -fastest -sexy -banned -adrian -properly -ruth -touchdown -dollar -boards -metre -circles -edges -favour -comments -ok -travels -liberation -scattered -firmly -##ular -holland -permitted -diesel -kenya -den -originated -##ral -demons -resumed -dragged -rider -##rus -servant -blinked -extend -torn -##ias -##sey -input -meal -everybody -cylinder -kinds -camps -##fe -bullet -logic -##wn -croatian -evolved -healthy -fool -chocolate -wise -preserve -pradesh -##ess -respective -1850 -##ew -chicken -artificial -gross -corresponding -convicted -cage -caroline -dialogue -##dor -narrative -stranger -mario -br -christianity -failing -trent -commanding -buddhist -1848 -maurice -focusing -yale -bike -altitude -##ering -mouse -revised -##sley -veteran -##ig -pulls -theology -crashed -campaigns -legion -##ability -drag -excellence -customer -cancelled -intensity -excuse -##lar -liga -participating -contributing -printing -##burn -variable -##rk -curious -bin -legacy -renaissance -##my -symptoms -binding -vocalist -dancer -##nie -grammar -gospel -democrats -ya -enters -sc -diplomatic -hitler -##ser -clouds -mathematical -quit -defended -oriented -##heim -fundamental -hardware -impressive -equally -convince -confederate -guilt -chuck -sliding -##ware -magnetic -narrowed -petersburg -bulgaria -otto -phd -skill -##ama -reader -hopes -pitcher -reservoir -hearts -automatically -expecting -mysterious -bennett -extensively -imagined -seeds -monitor -fix -##ative -journalism -struggling -signature -ranch -encounter -photographer -observation -protests -##pin -influences -##hr -calendar -##all -cruz -croatia -locomotive -hughes -naturally -shakespeare -basement -hook -uncredited -faded -theories -approaches -dare -phillips -filling -fury -obama -##ain -efficient -arc -deliver -min -raid -breeding -inducted -leagues -efficiency -axis -montana -eagles -##ked -supplied -instructions -karen -picking -indicating -trap -anchor -practically -christians -tomb -vary -occasional -electronics -lords -readers -newcastle -faint -innovation -collect -situations -engagement -160 -claude -mixture -##feld -peer -tissue -logo -lean -##ration -°f -floors -##ven -architects -reducing -##our -##ments -rope -1859 -ottawa -##har -samples -banking -declaration -proteins -resignation -francois -saudi -advocate -exhibited -armor -twins -divorce -##ras -abraham -reviewed -jo -temporarily -matrix -physically -pulse -curled -##ena -difficulties -bengal -usage -##ban -annie -riders -certificate -##pi -holes -warsaw -distinctive -jessica -##mon -mutual -1857 -customs -circular -eugene -removal -loaded -mere -vulnerable -depicted -generations -dame -heir -enormous -lightly -climbing -pitched -lessons -pilots -nepal -ram -google -preparing -brad -louise -renowned -##₂ -liam -##ably -plaza -shaw -sophie -brilliant -bills -##bar -##nik -fucking -mainland -server -pleasant -seized -veterans -jerked -fail -beta -brush -radiation -stored -warmth -southeastern -nate -sin -raced -berkeley -joke -athlete -designation -trunk -##low -roland -qualification -archives -heels -artwork -receives -judicial -reserves -##bed -woke -installation -abu -floating -fake -lesser -excitement -interface -concentrated -addressed -characteristic -amanda -saxophone -monk -auto -##bus -releasing -egg -dies -interaction -defender -ce -outbreak -glory -loving -##bert -sequel -consciousness -http -awake -ski -enrolled -##ress -handling -rookie -brow -somebody -biography -warfare -amounts -contracts -presentation -fabric -dissolved -challenged -meter -psychological -lt -elevated -rally -accurate -##tha -hospitals -undergraduate -specialist -venezuela -exhibit -shed -nursing -protestant -fluid -structural -footage -jared -consistent -prey -##ska -succession -reflect -exile -lebanon -wiped -suspect -shanghai -resting -integration -preservation -marvel -variant -pirates -sheep -rounded -capita -sailing -colonies -manuscript -deemed -variations -clarke -functional -emerging -boxing -relaxed -curse -azerbaijan -heavyweight -nickname -editorial -rang -grid -tightened -earthquake -flashed -miguel -rushing -##ches -improvements -boxes -brooks -180 -consumption -molecular -felix -societies -repeatedly -variation -aids -civic -graphics -professionals -realm -autonomous -receiver -delayed -workshop -militia -chairs -trump -canyon -##point -harsh -extending -lovely -happiness -##jan -stake -eyebrows -embassy -wellington -hannah -##ella -sony -corners -bishops -swear -cloth -contents -xi -namely -commenced -1854 -stanford -nashville -courage -graphic -commitment -garrison -##bin -hamlet -clearing -rebels -attraction -literacy -cooking -ruins -temples -jenny -humanity -celebrate -hasn -freight -sixty -rebel -bastard -##art -newton -##ada -deer -##ges -##ching -smiles -delaware -singers -##ets -approaching -assists -flame -##ph -boulevard -barrel -planted -##ome -pursuit -##sia -consequences -posts -shallow -invitation -rode -depot -ernest -kane -rod -concepts -preston -topic -chambers -striking -blast -arrives -descendants -montgomery -ranges -worlds -##lay -##ari -span -chaos -praise -##ag -fewer -1855 -sanctuary -mud -fbi -##ions -programmes -maintaining -unity -harper -bore -handsome -closure -tournaments -thunder -nebraska -linda -facade -puts -satisfied -argentine -dale -cork -dome -panama -##yl -1858 -tasks -experts -##ates -feeding -equation -##las -##ida -##tu -engage -bryan -##ax -um -quartet -melody -disbanded -sheffield -blocked -gasped -delay -kisses -maggie -connects -##non -sts -poured -creator -publishers -##we -guided -ellis -extinct -hug -gaining -##ord -complicated -##bility -poll -clenched -investigate -##use -thereby -quantum -spine -cdp -humor -kills -administered -semifinals -##du -encountered -ignore -##bu -commentary -##maker -bother -roosevelt -140 -plains -halfway -flowing -cultures -crack -imprisoned -neighboring -airline -##ses -##view -##mate -##ec -gather -wolves -marathon -transformed -##ill -cruise -organisations -carol -punch -exhibitions -numbered -alarm -ratings -daddy -silently -##stein -queens -colours -impression -guidance -liu -tactical -##rat -marshal -della -arrow -##ings -rested -feared -tender -owns -bitter -advisor -escort -##ides -spare -farms -grants -##ene -dragons -encourage -colleagues -cameras -##und -sucked -pile -spirits -prague -statements -suspension -landmark -fence -torture -recreation -bags -permanently -survivors -pond -spy -predecessor -bombing -coup -##og -protecting -transformation -glow -##lands -##book -dug -priests -andrea -feat -barn -jumping -##chen -##ologist -##con -casualties -stern -auckland -pipe -serie -revealing -ba -##bel -trevor -mercy -spectrum -yang -consist -governing -collaborated -possessed -epic -comprises -blew -shane -##ack -lopez -honored -magical -sacrifice -judgment -perceived -hammer -mtv -baronet -tune -das -missionary -sheets -350 -neutral -oral -threatening -attractive -shade -aims -seminary -##master -estates -1856 -michel -wounds -refugees -manufacturers -##nic -mercury -syndrome -porter -##iya -##din -hamburg -identification -upstairs -purse -widened -pause -cared -breathed -affiliate -santiago -prevented -celtic -fisher -125 -recruited -byzantine -reconstruction -farther -##mp -diet -sake -au -spite -sensation -##ert -blank -separation -105 -##hon -vladimir -armies -anime -##lie -accommodate -orbit -cult -sofia -archive -##ify -##box -founders -sustained -disorder -honours -northeastern -mia -crops -violet -threats -blanket -fires -canton -followers -southwestern -prototype -voyage -assignment -altered -moderate -protocol -pistol -##eo -questioned -brass -lifting -1852 -math -authored -##ual -doug -dimensional -dynamic -##san -1851 -pronounced -grateful -quest -uncomfortable -boom -presidency -stevens -relating -politicians -chen -barrier -quinn -diana -mosque -tribal -cheese -palmer -portions -sometime -chester -treasure -wu -bend -download -millions -reforms -registration -##osa -consequently -monitoring -ate -preliminary -brandon -invented -ps -eaten -exterior -intervention -ports -documented -log -displays -lecture -sally -favourite -##itz -vermont -lo -invisible -isle -breed -##ator -journalists -relay -speaks -backward -explore -midfielder -actively -stefan -procedures -cannon -blond -kenneth -centered -servants -chains -libraries -malcolm -essex -henri -slavery -##hal -facts -fairy -coached -cassie -cats -washed -cop -##fi -announcement -item -2000s -vinyl -activated -marco -frontier -growled -curriculum -##das -loyal -accomplished -leslie -ritual -kenny -##00 -vii -napoleon -hollow -hybrid -jungle -stationed -friedrich -counted -##ulated -platinum -theatrical -seated -col -rubber -glen -1840 -diversity -healing -extends -id -provisions -administrator -columbus -##oe -tributary -te -assured -org -##uous -prestigious -examined -lectures -grammy -ronald -associations -bailey -allan -essays -flute -believing -consultant -proceedings -travelling -1853 -kit -kerala -yugoslavia -buddy -methodist -##ith -burial -centres -batman -##nda -discontinued -bo -dock -stockholm -lungs -severely -##nk -citing -manga -##ugh -steal -mumbai -iraqi -robot -celebrity -bride -broadcasts -abolished -pot -joel -overhead -franz -packed -reconnaissance -johann -acknowledged -introduce -handled -doctorate -developments -drinks -alley -palestine -##nis -##aki -proceeded -recover -bradley -grain -patch -afford -infection -nationalist -legendary -##ath -interchange -virtually -gen -gravity -exploration -amber -vital -wishes -powell -doctrine -elbow -screenplay -##bird -contribute -indonesian -pet -creates -##com -enzyme -kylie -discipline -drops -manila -hunger -##ien -layers -suffer -fever -bits -monica -keyboard -manages -##hood -searched -appeals -##bad -testament -grande -reid -##war -beliefs -congo -##ification -##dia -si -requiring -##via -casey -1849 -regret -streak -rape -depends -syrian -sprint -pound -tourists -upcoming -pub -##xi -tense -##els -practiced -echo -nationwide -guild -motorcycle -liz -##zar -chiefs -desired -elena -bye -precious -absorbed -relatives -booth -pianist -##mal -citizenship -exhausted -wilhelm -##ceae -##hed -noting -quarterback -urge -hectares -##gue -ace -holly -##tal -blonde -davies -parked -sustainable -stepping -twentieth -airfield -galaxy -nest -chip -##nell -tan -shaft -paulo -requirement -##zy -paradise -tobacco -trans -renewed -vietnamese -##cker -##ju -suggesting -catching -holmes -enjoying -md -trips -colt -holder -butterfly -nerve -reformed -cherry -bowling -trailer -carriage -goodbye -appreciate -toy -joshua -interactive -enabled -involve -##kan -collar -determination -bunch -facebook -recall -shorts -superintendent -episcopal -frustration -giovanni -nineteenth -laser -privately -array -circulation -##ovic -armstrong -deals -painful -permit -discrimination -##wi -aires -retiring -cottage -ni -##sta -horizon -ellen -jamaica -ripped -fernando -chapters -playstation -patron -lecturer -navigation -behaviour -genes -georgian -export -solomon -rivals -swift -seventeen -rodriguez -princeton -independently -sox -1847 -arguing -entity -casting -hank -criteria -oakland -geographic -milwaukee -reflection -expanding -conquest -dubbed -##tv -halt -brave -brunswick -doi -arched -curtis -divorced -predominantly -somerset -streams -ugly -zoo -horrible -curved -buenos -fierce -dictionary -vector -theological -unions -handful -stability -chan -punjab -segments -##lly -altar -ignoring -gesture -monsters -pastor -##stone -thighs -unexpected -operators -abruptly -coin -compiled -associates -improving -migration -pin -##ose -compact -collegiate -reserved -##urs -quarterfinals -roster -restore -assembled -hurry -oval -##cies -1846 -flags -martha -##del -victories -sharply -##rated -argues -deadly -neo -drawings -symbols -performer -##iel -griffin -restrictions -editing -andrews -java -journals -arabia -compositions -dee -pierce -removing -hindi -casino -runway -civilians -minds -nasa -hotels -##zation -refuge -rent -retain -potentially -conferences -suburban -conducting -##tto -##tions -##tle -descended -massacre -##cal -ammunition -terrain -fork -souls -counts -chelsea -durham -drives -cab -##bank -perth -realizing -palestinian -finn -simpson -##dal -betty -##ule -moreover -particles -cardinals -tent -evaluation -extraordinary -##oid -inscription -##works -wednesday -chloe -maintains -panels -ashley -trucks -##nation -cluster -sunlight -strikes -zhang -##wing -dialect -canon -##ap -tucked -##ws -collecting -##mas -##can -##sville -maker -quoted -evan -franco -aria -buying -cleaning -eva -closet -provision -apollo -clinic -rat -##ez -necessarily -ac -##gle -##ising -venues -flipped -cent -spreading -trustees -checking -authorized -##sco -disappointed -##ado -notion -duration -trumpet -hesitated -topped -brussels -rolls -theoretical -hint -define -aggressive -repeat -wash -peaceful -optical -width -allegedly -mcdonald -strict -copyright -##illa -investors -mar -jam -witnesses -sounding -miranda -michelle -privacy -hugo -harmony -##pp -valid -lynn -glared -nina -102 -headquartered -diving -boarding -gibson -##ncy -albanian -marsh -routine -dealt -enhanced -er -intelligent -substance -targeted -enlisted -discovers -spinning -observations -pissed -smoking -rebecca -capitol -visa -varied -costume -seemingly -indies -compensation -surgeon -thursday -arsenal -westminster -suburbs -rid -anglican -##ridge -knots -foods -alumni -lighter -fraser -whoever -portal -scandal -##ray -gavin -advised -instructor -flooding -terrorist -##ale -teenage -interim -senses -duck -teen -thesis -abby -eager -overcome -##ile -newport -glenn -rises -shame -##cc -prompted -priority -forgot -bomber -nicolas -protective -360 -cartoon -katherine -breeze -lonely -trusted -henderson -richardson -relax -banner -candy -palms -remarkable -##rio -legends -cricketer -essay -ordained -edmund -rifles -trigger -##uri -##away -sail -alert -1830 -audiences -penn -sussex -siblings -pursued -indianapolis -resist -rosa -consequence -succeed -avoided -1845 -##ulation -inland -##tie -##nna -counsel -profession -chronicle -hurried -##una -eyebrow -eventual -bleeding -innovative -cure -##dom -committees -accounting -con -scope -hardy -heather -tenor -gut -herald -codes -tore -scales -wagon -##oo -luxury -tin -prefer -fountain -triangle -bonds -darling -convoy -dried -traced -beings -troy -accidentally -slam -findings -smelled -joey -lawyers -outcome -steep -bosnia -configuration -shifting -toll -brook -performers -lobby -philosophical -construct -shrine -aggregate -boot -cox -phenomenon -savage -insane -solely -reynolds -lifestyle -##ima -nationally -holdings -consideration -enable -edgar -mo -mama -##tein -fights -relegation -chances -atomic -hub -conjunction -awkward -reactions -currency -finale -kumar -underwent -steering -elaborate -gifts -comprising -melissa -veins -reasonable -sunshine -chi -solve -trails -inhabited -elimination -ethics -huh -ana -molly -consent -apartments -layout -marines -##ces -hunters -bulk -##oma -hometown -##wall -##mont -cracked -reads -neighbouring -withdrawn -admission -wingspan -damned -anthology -lancashire -brands -batting -forgive -cuban -awful -##lyn -104 -dimensions -imagination -##ade -dante -##ship -tracking -desperately -goalkeeper -##yne -groaned -workshops -confident -burton -gerald -milton -circus -uncertain -slope -copenhagen -sophia -fog -philosopher -portraits -accent -cycling -varying -gripped -larvae -garrett -specified -scotia -mature -luther -kurt -rap -##kes -aerial -750 -ferdinand -heated -es -transported -##shan -safely -nonetheless -##orn -##gal -motors -demanding -##sburg -startled -##brook -ally -generate -caps -ghana -stained -demo -mentions -beds -ap -afterward -diary -##bling -utility -##iro -richards -1837 -conspiracy -conscious -shining -footsteps -observer -cyprus -urged -loyalty -developer -probability -olive -upgraded -gym -miracle -insects -graves -1844 -ourselves -hydrogen -amazon -katie -tickets -poets -##pm -planes -##pan -prevention -witnessed -dense -jin -randy -tang -warehouse -monroe -bang -archived -elderly -investigations -alec -granite -mineral -conflicts -controlling -aboriginal -carlo -##zu -mechanics -stan -stark -rhode -skirt -est -##berry -bombs -respected -##horn -imposed -limestone -deny -nominee -memphis -grabbing -disabled -##als -amusement -aa -frankfurt -corn -referendum -varies -slowed -disk -firms -unconscious -incredible -clue -sue -##zhou -twist -##cio -joins -idaho -chad -developers -computing -destroyer -103 -mortal -tucker -kingston -choices -yu -carson -1800 -os -whitney -geneva -pretend -dimension -staged -plateau -maya -##une -freestyle -##bc -rovers -hiv -##ids -tristan -classroom -prospect -##hus -honestly -diploma -lied -thermal -auxiliary -feast -unlikely -iata -##tel -morocco -pounding -treasury -lithuania -considerably -1841 -dish -1812 -geological -matching -stumbled -destroying -marched -brien -advances -cake -nicole -belle -settling -measuring -directing -##mie -tuesday -bassist -capabilities -stunned -fraud -torpedo -##list -##phone -anton -wisdom -surveillance -ruined -##ulate -lawsuit -healthcare -theorem -halls -trend -aka -horizontal -dozens -acquire -lasting -swim -hawk -gorgeous -fees -vicinity -decrease -adoption -tactics -##ography -pakistani -##ole -draws -##hall -willie -burke -heath -algorithm -integral -powder -elliott -brigadier -jackie -tate -varieties -darker -##cho -lately -cigarette -specimens -adds -##ree -##ensis -##inger -exploded -finalist -cia -murders -wilderness -arguments -nicknamed -acceptance -onwards -manufacture -robertson -jets -tampa -enterprises -blog -loudly -composers -nominations -1838 -ai -malta -inquiry -automobile -hosting -viii -rays -tilted -grief -museums -strategies -furious -euro -equality -cohen -poison -surrey -wireless -governed -ridiculous -moses -##esh -##room -vanished -##ito -barnes -attract -morrison -istanbul -##iness -absent -rotation -petition -janet -##logical -satisfaction -custody -deliberately -observatory -comedian -surfaces -pinyin -novelist -strictly -canterbury -oslo -monks -embrace -ibm -jealous -photograph -continent -dorothy -marina -doc -excess -holden -allegations -explaining -stack -avoiding -lance -storyline -majesty -poorly -spike -dos -bradford -raven -travis -classics -proven -voltage -pillow -fists -butt -1842 -interpreted -##car -1839 -gage -telegraph -lens -promising -expelled -casual -collector -zones -##min -silly -nintendo -##kh -##bra -downstairs -chef -suspicious -afl -flies -vacant -uganda -pregnancy -condemned -lutheran -estimates -cheap -decree -saxon -proximity -stripped -idiot -deposits -contrary -presenter -magnus -glacier -im -offense -edwin -##ori -upright -##long -bolt -##ois -toss -geographical -##izes -environments -delicate -marking -abstract -xavier -nails -windsor -plantation -occurring -equity -saskatchewan -fears -drifted -sequences -vegetation -revolt -##stic -1843 -sooner -fusion -opposing -nato -skating -1836 -secretly -ruin -lease -##oc -edit -##nne -flora -anxiety -ruby -##ological -##mia -tel -bout -taxi -emmy -frost -rainbow -compounds -foundations -rainfall -assassination -nightmare -dominican -##win -achievements -deserve -orlando -intact -armenia -##nte -calgary -valentine -106 -marion -proclaimed -theodore -bells -courtyard -thigh -gonzalez -console -troop -minimal -monte -everyday -##ence -##if -supporter -terrorism -buck -openly -presbyterian -activists -carpet -##iers -rubbing -uprising -##yi -cute -conceived -legally -##cht -millennium -cello -velocity -ji -rescued -cardiff -1835 -rex -concentrate -senators -beard -rendered -glowing -battalions -scouts -competitors -sculptor -catalogue -arctic -ion -raja -bicycle -wow -glancing -lawn -##woman -gentleman -lighthouse -publish -predicted -calculated -##val -variants -##gne -strain -##ui -winston -deceased -##nus -touchdowns -brady -caleb -sinking -echoed -crush -hon -blessed -protagonist -hayes -endangered -magnitude -editors -##tine -estimate -responsibilities -##mel -backup -laying -consumed -sealed -zurich -lovers -frustrated -##eau -ahmed -kicking -mit -treasurer -1832 -biblical -refuse -terrified -pump -agrees -genuine -imprisonment -refuses -plymouth -##hen -lou -##nen -tara -trembling -antarctic -ton -learns -##tas -crap -crucial -faction -atop -##borough -wrap -lancaster -odds -hopkins -erik -lyon -##eon -bros -##ode -snap -locality -tips -empress -crowned -cal -acclaimed -chuckled -##ory -clara -sends -mild -towel -##fl -##day -##а -wishing -assuming -interviewed -##bal -##die -interactions -eden -cups -helena -##lf -indie -beck -##fire -batteries -filipino -wizard -parted -##lam -traces -##born -rows -idol -albany -delegates -##ees -##sar -discussions -##ex -notre -instructed -belgrade -highways -suggestion -lauren -possess -orientation -alexandria -abdul -beats -salary -reunion -ludwig -alright -wagner -intimate -pockets -slovenia -hugged -brighton -merchants -cruel -stole -trek -slopes -repairs -enrollment -politically -underlying -promotional -counting -boeing -##bb -isabella -naming -##и -keen -bacteria -listing -separately -belfast -ussr -450 -lithuanian -anybody -ribs -sphere -martinez -cock -embarrassed -proposals -fragments -nationals -##fs -##wski -premises -fin -1500 -alpine -matched -freely -bounded -jace -sleeve -##af -gaming -pier -populated -evident -##like -frances -flooded -##dle -frightened -pour -trainer -framed -visitor -challenging -pig -wickets -##fold -infected -email -##pes -arose -##aw -reward -ecuador -oblast -vale -ch -shuttle -##usa -bach -rankings -forbidden -cornwall -accordance -salem -consumers -bruno -fantastic -toes -machinery -resolved -julius -remembering -propaganda -iceland -bombardment -tide -contacts -wives -##rah -concerto -macdonald -albania -implement -daisy -tapped -sudan -helmet -angela -mistress -##lic -crop -sunk -finest -##craft -hostile -##ute -##tsu -boxer -fr -paths -adjusted -habit -ballot -supervision -soprano -##zen -bullets -wicked -sunset -regiments -disappear -lamp -performs -app -##gia -##oa -rabbit -digging -incidents -entries -##cion -dishes -##oi -introducing -##ati -##fied -freshman -slot -jill -tackles -baroque -backs -##iest -lone -sponsor -destiny -altogether -convert -##aro -consensus -shapes -demonstration -basically -feminist -auction -artifacts -##bing -strongest -twitter -halifax -2019 -allmusic -mighty -smallest -precise -alexandra -viola -##los -##ille -manuscripts -##illo -dancers -ari -managers -monuments -blades -barracks -springfield -maiden -consolidated -electron -##end -berry -airing -wheat -nobel -inclusion -blair -payments -geography -bee -cc -eleanor -react -##hurst -afc -manitoba -##yu -su -lineup -fitness -recreational -investments -airborne -disappointment -##dis -edmonton -viewing -##row -renovation -##cast -infant -bankruptcy -roses -aftermath -pavilion -##yer -carpenter -withdrawal -ladder -##hy -discussing -popped -reliable -agreements -rochester -##abad -curves -bombers -220 -rao -reverend -decreased -choosing -107 -stiff -consulting -naples -crawford -tracy -ka -ribbon -cops -##lee -crushed -deciding -unified -teenager -accepting -flagship -explorer -poles -sanchez -inspection -revived -skilled -induced -exchanged -flee -locals -tragedy -swallow -loading -hanna -demonstrate -##ela -salvador -flown -contestants -civilization -##ines -wanna -rhodes -fletcher -hector -knocking -considers -##ough -nash -mechanisms -sensed -mentally -walt -unclear -##eus -renovated -madame -##cks -crews -governmental -##hin -undertaken -monkey -##ben -##ato -fatal -armored -copa -caves -governance -grasp -perception -certification -froze -damp -tugged -wyoming -##rg -##ero -newman -##lor -nerves -curiosity -graph -115 -##ami -withdraw -tunnels -dull -meredith -moss -exhibits -neighbors -communicate -accuracy -explored -raiders -republicans -secular -kat -superman -penny -criticised -##tch -freed -update -conviction -wade -ham -likewise -delegation -gotta -doll -promises -technological -myth -nationality -resolve -convent -##mark -sharon -dig -sip -coordinator -entrepreneur -fold -##dine -capability -councillor -synonym -blown -swan -cursed -1815 -jonas -haired -sofa -canvas -keeper -rivalry -##hart -rapper -speedway -swords -postal -maxwell -estonia -potter -recurring -##nn -##ave -errors -##oni -cognitive -1834 -##² -claws -nadu -roberto -bce -wrestler -ellie -##ations -infinite -ink -##tia -presumably -finite -staircase -108 -noel -patricia -nacional -##cation -chill -eternal -tu -preventing -prussia -fossil -limbs -##logist -ernst -frog -perez -rene -##ace -pizza -prussian -##ios -##vy -molecules -regulatory -answering -opinions -sworn -lengths -supposedly -hypothesis -upward -habitats -seating -ancestors -drank -yield -hd -synthesis -researcher -modest -##var -mothers -peered -voluntary -homeland -##the -acclaim -##igan -static -valve -luxembourg -alto -carroll -fe -receptor -norton -ambulance -##tian -johnston -catholics -depicting -jointly -elephant -gloria -mentor -badge -ahmad -distinguish -remarked -councils -precisely -allison -advancing -detection -crowded -##10 -cooperative -ankle -mercedes -dagger -surrendered -pollution -commit -subway -jeffrey -lesson -sculptures -provider -##fication -membrane -timothy -rectangular -fiscal -heating -teammate -basket -particle -anonymous -deployment -##ple -missiles -courthouse -proportion -shoe -sec -##ller -complaints -forbes -blacks -abandon -remind -sizes -overwhelming -autobiography -natalie -##awa -risks -contestant -countryside -babies -scorer -invaded -enclosed -proceed -hurling -disorders -##cu -reflecting -continuously -cruiser -graduates -freeway -investigated -ore -deserved -maid -blocking -phillip -jorge -shakes -dove -mann -variables -lacked -burden -accompanying -que -consistently -organizing -provisional -complained -endless -##rm -tubes -juice -georges -krishna -mick -labels -thriller -##uch -laps -arcade -sage -snail -##table -shannon -fi -laurence -seoul -vacation -presenting -hire -churchill -surprisingly -prohibited -savannah -technically -##oli -170 -##lessly -testimony -suited -speeds -toys -romans -mlb -flowering -measurement -talented -kay -settings -charleston -expectations -shattered -achieving -triumph -ceremonies -portsmouth -lanes -mandatory -loser -stretching -cologne -realizes -seventy -cornell -careers -webb -##ulating -americas -budapest -ava -suspicion -##ison -yo -conrad -##hai -sterling -jessie -rector -##az -1831 -transform -organize -loans -christine -volcanic -warrant -slender -summers -subfamily -newer -danced -dynamics -rhine -proceeds -heinrich -gastropod -commands -sings -facilitate -easter -ra -positioned -responses -expense -fruits -yanked -imported -25th -velvet -vic -primitive -tribune -baldwin -neighbourhood -donna -rip -hay -pr -##uro -1814 -espn -welcomed -##aria -qualifier -glare -highland -timing -##cted -shells -eased -geometry -louder -exciting -slovakia -##sion -##iz -##lot -savings -prairie -##ques -marching -rafael -tonnes -##lled -curtain -preceding -shy -heal -greene -worthy -##pot -detachment -bury -sherman -##eck -reinforced -seeks -bottles -contracted -duchess -outfit -walsh -##sc -mickey -##ase -geoffrey -archer -squeeze -dawson -eliminate -invention -##enberg -neal -##eth -stance -dealer -coral -maple -retire -polo -simplified -##ht -1833 -hid -watts -backwards -jules -##oke -genesis -mt -frames -rebounds -burma -woodland -moist -santos -whispers -drained -subspecies -##aa -streaming -ulster -burnt -correspondence -maternal -gerard -denis -stealing -##load -genius -duchy -##oria -inaugurated -momentum -suits -placement -sovereign -clause -thames -##hara -confederation -reservation -sketch -yankees -lets -rotten -charm -hal -verses -ultra -commercially -dot -salon -citation -adopt -winnipeg -mist -allocated -cairo -##boy -jenkins -interference -objectives -##wind -1820 -portfolio -armoured -sectors -##eh -initiatives -##world -integrity -exercises -robe -tap -ab -gazed -##tones -distracted -rulers -111 -favorable -jerome -tended -cart -factories -##eri -diplomat -valued -gravel -charitable -##try -calvin -exploring -chang -shepherd -terrace -pdf -pupil -##ural -reflects -ups -##rch -governors -shelf -depths -##nberg -trailed -crest -tackle -##nian -##ats -hatred -##kai -clare -makers -ethiopia -longtime -detected -embedded -lacking -slapped -rely -thomson -anticipation -iso -morton -successive -agnes -screenwriter -straightened -philippe -playwright -haunted -licence -iris -intentions -sutton -112 -logical -correctly -##weight -branded -licked -tipped -silva -ricky -narrator -requests -##ents -greeted -supernatural -cow -##wald -lung -refusing -employer -strait -gaelic -liner -##piece -zoe -sabha -##mba -driveway -harvest -prints -bates -reluctantly -threshold -algebra -ira -wherever -coupled -240 -assumption -picks -##air -designers -raids -gentlemen -##ean -roller -blowing -leipzig -locks -screw -dressing -strand -##lings -scar -dwarf -depicts -##nu -nods -##mine -differ -boris -##eur -yuan -flip -##gie -mob -invested -questioning -applying -##ture -shout -##sel -gameplay -blamed -illustrations -bothered -weakness -rehabilitation -##of -##zes -envelope -rumors -miners -leicester -subtle -kerry -##ico -ferguson -##fu -premiership -ne -##cat -bengali -prof -catches -remnants -dana -##rily -shouting -presidents -baltic -ought -ghosts -dances -sailors -shirley -fancy -dominic -##bie -madonna -##rick -bark -buttons -gymnasium -ashes -liver -toby -oath -providence -doyle -evangelical -nixon -cement -carnegie -embarked -hatch -surroundings -guarantee -needing -pirate -essence -##bee -filter -crane -hammond -projected -immune -percy -twelfth -##ult -regent -doctoral -damon -mikhail -##ichi -lu -critically -elect -realised -abortion -acute -screening -mythology -steadily -##fc -frown -nottingham -kirk -wa -minneapolis -##rra -module -algeria -mc -nautical -encounters -surprising -statues -availability -shirts -pie -alma -brows -munster -mack -soup -crater -tornado -sanskrit -cedar -explosive -bordered -dixon -planets -stamp -exam -happily -##bble -carriers -kidnapped -##vis -accommodation -emigrated -##met -knockout -correspondent -violation -profits -peaks -lang -specimen -agenda -ancestry -pottery -spelling -equations -obtaining -ki -linking -1825 -debris -asylum -##20 -buddhism -teddy -##ants -gazette -##nger -##sse -dental -eligibility -utc -fathers -averaged -zimbabwe -francesco -coloured -hissed -translator -lynch -mandate -humanities -mackenzie -uniforms -lin -##iana -##gio -asset -mhz -fitting -samantha -genera -wei -rim -beloved -shark -riot -entities -expressions -indo -carmen -slipping -owing -abbot -neighbor -sidney -##av -rats -recommendations -encouraging -squadrons -anticipated -commanders -conquered -##oto -donations -diagnosed -##mond -divide -##iva -guessed -decoration -vernon -auditorium -revelation -conversations -##kers -##power -herzegovina -dash -alike -protested -lateral -herman -accredited -mg -##gent -freeman -mel -fiji -crow -crimson -##rine -livestock -##pped -humanitarian -bored -oz -whip -##lene -##ali -legitimate -alter -grinning -spelled -anxious -oriental -wesley -##nin -##hole -carnival -controller -detect -##ssa -bowed -educator -kosovo -macedonia -##sin -occupy -mastering -stephanie -janeiro -para -unaware -nurses -noon -135 -cam -hopefully -ranger -combine -sociology -polar -rica -##eer -neill -##sman -holocaust -##ip -doubled -lust -1828 -109 -decent -cooling -unveiled -##card -1829 -nsw -homer -chapman -meyer -##gin -dive -mae -reagan -expertise -##gled -darwin -brooke -sided -prosecution -investigating -comprised -petroleum -genres -reluctant -differently -trilogy -johns -vegetables -corpse -highlighted -lounge -pension -unsuccessfully -elegant -aided -ivory -beatles -amelia -cain -dubai -sunny -immigrant -babe -click -##nder -underwater -pepper -combining -mumbled -atlas -horns -accessed -ballad -physicians -homeless -gestured -rpm -freak -louisville -corporations -patriots -prizes -rational -warn -modes -decorative -overnight -din -troubled -phantom -##ort -monarch -sheer -##dorf -generals -guidelines -organs -addresses -##zon -enhance -curling -parishes -cord -##kie -linux -caesar -deutsche -bavaria -##bia -coleman -cyclone -##eria -bacon -petty -##yama -##old -hampton -diagnosis -1824 -throws -complexity -rita -disputed -##₃ -pablo -##sch -marketed -trafficking -##ulus -examine -plague -formats -##oh -vault -faithful -##bourne -webster -##ox -highlights -##ient -##ann -phones -vacuum -sandwich -modeling -##gated -bolivia -clergy -qualities -isabel -##nas -##ars -wears -screams -reunited -annoyed -bra -##ancy -##rate -differential -transmitter -tattoo -container -poker -##och -excessive -resides -cowboys -##tum -augustus -trash -providers -statute -retreated -balcony -reversed -void -storey -preceded -masses -leap -laughs -neighborhoods -wards -schemes -falcon -santo -battlefield -pad -ronnie -thread -lesbian -venus -##dian -beg -sandstone -daylight -punched -gwen -analog -stroked -wwe -acceptable -measurements -dec -toxic -##kel -adequate -surgical -economist -parameters -varsity -##sberg -quantity -ella -##chy -##rton -countess -generating -precision -diamonds -expressway -ga -##ı -1821 -uruguay -talents -galleries -expenses -scanned -colleague -outlets -ryder -lucien -##ila -paramount -##bon -syracuse -dim -fangs -gown -sweep -##sie -toyota -missionaries -websites -##nsis -sentences -adviser -val -trademark -spells -##plane -patience -starter -slim -##borg -toe -incredibly -shoots -elliot -nobility -##wyn -cowboy -endorsed -gardner -tendency -persuaded -organisms -emissions -kazakhstan -amused -boring -chips -themed -##hand -llc -constantinople -chasing -systematic -guatemala -borrowed -erin -carey -##hard -highlands -struggles -1810 -##ifying -##ced -wong -exceptions -develops -enlarged -kindergarten -castro -##ern -##rina -leigh -zombie -juvenile -##most -consul -##nar -sailor -hyde -clarence -intensive -pinned -nasty -useless -jung -clayton -stuffed -exceptional -ix -apostolic -230 -transactions -##dge -exempt -swinging -cove -religions -##ash -shields -dairy -bypass -190 -pursuing -bug -joyce -bombay -chassis -southampton -chat -interact -redesignated -##pen -nascar -pray -salmon -rigid -regained -malaysian -grim -publicity -constituted -capturing -toilet -delegate -purely -tray -drift -loosely -striker -weakened -trinidad -mitch -itv -defines -transmitted -ming -scarlet -nodding -fitzgerald -fu -narrowly -sp -tooth -standings -virtue -##₁ -##wara -##cting -chateau -gloves -lid -##nel -hurting -conservatory -##pel -sinclair -reopened -sympathy -nigerian -strode -advocated -optional -chronic -discharge -##rc -suck -compatible -laurel -stella -shi -fails -wage -dodge -128 -informal -sorts -levi -buddha -villagers -##aka -chronicles -heavier -summoned -gateway -3000 -eleventh -jewelry -translations -accordingly -seas -##ency -fiber -pyramid -cubic -dragging -##ista -caring -##ops -android -contacted -lunar -##dt -kai -lisbon -patted -1826 -sacramento -theft -madagascar -subtropical -disputes -ta -holidays -piper -willow -mare -cane -itunes -newfoundland -benny -companions -dong -raj -observe -roar -charming -plaque -tibetan -fossils -enacted -manning -bubble -tina -tanzania -##eda -##hir -funk -swamp -deputies -cloak -ufc -scenario -par -scratch -metals -anthem -guru -engaging -specially -##boat -dialects -nineteen -cecil -duet -disability -messenger -unofficial -##lies -defunct -eds -moonlight -drainage -surname -puzzle -honda -switching -conservatives -mammals -knox -broadcaster -sidewalk -cope -##ried -benson -princes -peterson -##sal -bedford -sharks -eli -wreck -alberto -gasp -archaeology -lgbt -teaches -securities -madness -compromise -waving -coordination -davidson -visions -leased -possibilities -eighty -jun -fernandez -enthusiasm -assassin -sponsorship -reviewer -kingdoms -estonian -laboratories -##fy -##nal -applies -verb -celebrations -##zzo -rowing -lightweight -sadness -submit -mvp -balanced -dude -##vas -explicitly -metric -magnificent -mound -brett -mohammad -mistakes -irregular -##hing -##ass -sanders -betrayed -shipped -surge -##enburg -reporters -termed -georg -pity -verbal -bulls -abbreviated -enabling -appealed -##are -##atic -sicily -sting -heel -sweetheart -bart -spacecraft -brutal -monarchy -##tter -aberdeen -cameo -diane -##ub -survivor -clyde -##aries -complaint -##makers -clarinet -delicious -chilean -karnataka -coordinates -1818 -panties -##rst -pretending -ar -dramatically -kiev -bella -tends -distances -113 -catalog -launching -instances -telecommunications -portable -lindsay -vatican -##eim -angles -aliens -marker -stint -screens -bolton -##rne -judy -wool -benedict -plasma -europa -spark -imaging -filmmaker -swiftly -##een -contributor -##nor -opted -stamps -apologize -financing -butter -gideon -sophisticated -alignment -avery -chemicals -yearly -speculation -prominence -professionally -##ils -immortal -institutional -inception -wrists -identifying -tribunal -derives -gains -##wo -papal -preference -linguistic -vince -operative -brewery -##ont -unemployment -boyd -##ured -##outs -albeit -prophet -1813 -bi -##rr -##face -##rad -quarterly -asteroid -cleaned -radius -temper -##llen -telugu -jerk -viscount -menu -##ote -glimpse -##aya -yacht -hawaiian -baden -##rl -laptop -readily -##gu -monetary -offshore -scots -watches -##yang -##arian -upgrade -needle -xbox -lea -encyclopedia -flank -fingertips -##pus -delight -teachings -confirm -roth -beaches -midway -winters -##iah -teasing -daytime -beverly -gambling -bonnie -##backs -regulated -clement -hermann -tricks -knot -##shing -##uring -##vre -detached -ecological -owed -specialty -byron -inventor -bats -stays -screened -unesco -midland -trim -affection -##ander -##rry -jess -thoroughly -feedback -##uma -chennai -strained -heartbeat -wrapping -overtime -pleaded -##sworth -mon -leisure -oclc -##tate -##ele -feathers -angelo -thirds -nuts -surveys -clever -gill -commentator -##dos -darren -rides -gibraltar -##nc -##mu -dissolution -dedication -shin -meals -saddle -elvis -reds -chaired -taller -appreciation -functioning -niece -favored -advocacy -robbie -criminals -suffolk -yugoslav -passport -constable -congressman -hastings -vera -##rov -consecrated -sparks -ecclesiastical -confined -##ovich -muller -floyd -nora -1822 -paved -1827 -cumberland -ned -saga -spiral -##flow -appreciated -yi -collaborative -treating -similarities -feminine -finishes -##ib -jade -import -##nse -##hot -champagne -mice -securing -celebrities -helsinki -attributes -##gos -cousins -phases -ache -lucia -gandhi -submission -vicar -spear -shine -tasmania -biting -detention -constitute -tighter -seasonal -##gus -terrestrial -matthews -##oka -effectiveness -parody -philharmonic -##onic -1816 -strangers -encoded -consortium -guaranteed -regards -shifts -tortured -collision -supervisor -inform -broader -insight -theaters -armour -emeritus -blink -incorporates -mapping -##50 -##ein -handball -flexible -##nta -substantially -generous -thief -##own -carr -loses -1793 -prose -ucla -romeo -generic -metallic -realization -damages -mk -commissioners -zach -default -##ther -helicopters -lengthy -stems -spa -partnered -spectators -rogue -indication -penalties -teresa -1801 -sen -##tric -dalton -##wich -irving -photographic -##vey -dell -deaf -peters -excluded -unsure -##vable -patterson -crawled -##zio -resided -whipped -latvia -slower -ecole -pipes -employers -maharashtra -comparable -va -textile -pageant -##gel -alphabet -binary -irrigation -chartered -choked -antoine -offs -waking -supplement -##wen -quantities -demolition -regain -locate -urdu -folks -alt -114 -##mc -scary -andreas -whites -##ava -classrooms -mw -aesthetic -publishes -valleys -guides -cubs -johannes -bryant -conventions -affecting -##itt -drain -awesome -isolation -prosecutor -ambitious -apology -captive -downs -atmospheric -lorenzo -aisle -beef -foul -##onia -kidding -composite -disturbed -illusion -natives -##ffer -emi -rockets -riverside -wartime -painters -adolf -melted -##ail -uncertainty -simulation -hawks -progressed -meantime -builder -spray -breach -unhappy -regina -russians -##urg -determining -##tation -tram -1806 -##quin -aging -##12 -1823 -garion -rented -mister -diaz -terminated -clip -1817 -depend -nervously -disco -owe -defenders -shiva -notorious -disbelief -shiny -worcester -##gation -##yr -trailing -undertook -islander -belarus -limitations -watershed -fuller -overlooking -utilized -raphael -1819 -synthetic -breakdown -klein -##nate -moaned -memoir -lamb -practicing -##erly -cellular -arrows -exotic -##graphy -witches -117 -charted -rey -hut -hierarchy -subdivision -freshwater -giuseppe -aloud -reyes -qatar -marty -sideways -utterly -sexually -jude -prayers -mccarthy -softball -blend -damien -##gging -##metric -wholly -erupted -lebanese -negro -revenues -tasted -comparative -teamed -transaction -labeled -maori -sovereignty -parkway -trauma -gran -malay -121 -advancement -descendant -2020 -buzz -salvation -inventory -symbolic -##making -antarctica -mps -##gas -##bro -mohammed -myanmar -holt -submarines -tones -##lman -locker -patriarch -bangkok -emerson -remarks -predators -kin -afghan -confession -norwich -rental -emerge -advantages -##zel -rca -##hold -shortened -storms -aidan -##matic -autonomy -compliance -##quet -dudley -atp -##osis -1803 -motto -documentation -summary -professors -spectacular -christina -archdiocese -flashing -innocence -remake -##dell -psychic -reef -scare -employ -rs -sticks -meg -gus -leans -##ude -accompany -bergen -tomas -##iko -doom -wages -pools -##nch -##bes -breasts -scholarly -alison -outline -brittany -breakthrough -willis -realistic -##cut -##boro -competitor -##stan -pike -picnic -icon -designing -commercials -washing -villain -skiing -micro -costumes -auburn -halted -executives -##hat -logistics -cycles -vowel -applicable -barrett -exclaimed -eurovision -eternity -ramon -##umi -##lls -modifications -sweeping -disgust -##uck -torch -aviv -ensuring -rude -dusty -sonic -donovan -outskirts -cu -pathway -##band -##gun -##lines -disciplines -acids -cadet -paired -##40 -sketches -##sive -marriages -##⁺ -folding -peers -slovak -implies -admired -##beck -1880s -leopold -instinct -attained -weston -megan -horace -##ination -dorsal -ingredients -evolutionary -##its -complications -deity -lethal -brushing -levy -deserted -institutes -posthumously -delivering -telescope -coronation -motivated -rapids -luc -flicked -pays -volcano -tanner -weighed -##nica -crowds -frankie -gifted -addressing -granddaughter -winding -##rna -constantine -gomez -##front -landscapes -rudolf -anthropology -slate -werewolf -##lio -astronomy -circa -rouge -dreaming -sack -knelt -drowned -naomi -prolific -tracked -freezing -herb -##dium -agony -randall -twisting -wendy -deposit -touches -vein -wheeler -##bbled -##bor -batted -retaining -tire -presently -compare -specification -daemon -nigel -##grave -merry -recommendation -czechoslovakia -sandra -ng -roma -##sts -lambert -inheritance -sheikh -winchester -cries -examining -##yle -comeback -cuisine -nave -##iv -ko -retrieve -tomatoes -barker -polished -defining -irene -lantern -personalities -begging -tract -swore -1809 -175 -##gic -omaha -brotherhood -##rley -haiti -##ots -exeter -##ete -##zia -steele -dumb -pearson -210 -surveyed -elisabeth -trends -##ef -fritz -##rf -premium -bugs -fraction -calmly -viking -##birds -tug -inserted -unusually -##ield -confronted -distress -crashing -brent -turks -resign -##olo -cambodia -gabe -sauce -##kal -evelyn -116 -extant -clusters -quarry -teenagers -luna -##lers -##ister -affiliation -drill -##ashi -panthers -scenic -libya -anita -strengthen -inscriptions -##cated -lace -sued -judith -riots -##uted -mint -##eta -preparations -midst -dub -challenger -##vich -mock -cf -displaced -wicket -breaths -enables -schmidt -analyst -##lum -ag -highlight -automotive -axe -josef -newark -sufficiently -resembles -50th -##pal -flushed -mum -traits -##ante -commodore -incomplete -warming -titular -ceremonial -ethical -118 -celebrating -eighteenth -cao -lima -medalist -mobility -strips -snakes -##city -miniature -zagreb -barton -escapes -umbrella -automated -doubted -differs -cooled -georgetown -dresden -cooked -fade -wyatt -rna -jacobs -carlton -abundant -stereo -boost -madras -inning -##hia -spur -ip -malayalam -begged -osaka -groan -escaping -charging -dose -vista -##aj -bud -papa -communists -advocates -edged -tri -##cent -resemble -peaking -necklace -fried -montenegro -saxony -goose -glances -stuttgart -curator -recruit -grocery -sympathetic -##tting -##fort -127 -lotus -randolph -ancestor -##rand -succeeding -jupiter -1798 -macedonian -##heads -hiking -1808 -handing -fischer -##itive -garbage -node -##pies -prone -singular -papua -inclined -attractions -italia -pouring -motioned -grandma -garnered -jacksonville -corp -ego -ringing -aluminum -##hausen -ordering -##foot -drawer -traders -synagogue -##play -##kawa -resistant -wandering -fragile -fiona -teased -var -hardcore -soaked -jubilee -decisive -exposition -mercer -poster -valencia -hale -kuwait -1811 -##ises -##wr -##eed -tavern -gamma -122 -johan -##uer -airways -amino -gil -##ury -vocational -domains -torres -##sp -generator -folklore -outcomes -##keeper -canberra -shooter -fl -beams -confrontation -##lling -##gram -feb -aligned -forestry -pipeline -jax -motorway -conception -decay -##tos -coffin -##cott -stalin -1805 -escorted -minded -##nam -sitcom -purchasing -twilight -veronica -additions -passive -tensions -straw -123 -frequencies -1804 -refugee -cultivation -##iate -christie -clary -bulletin -crept -disposal -##rich -##zong -processor -crescent -##rol -bmw -emphasized -whale -nazis -aurora -##eng -dwelling -hauled -sponsors -toledo -mega -ideology -theatres -tessa -cerambycidae -saves -turtle -cone -suspects -kara -rusty -yelling -greeks -mozart -shades -cocked -participant -##tro -shire -spit -freeze -necessity -##cos -inmates -nielsen -councillors -loaned -uncommon -omar -peasants -botanical -offspring -daniels -formations -jokes -1794 -pioneers -sigma -licensing -##sus -wheelchair -polite -1807 -liquor -pratt -trustee -##uta -forewings -balloon -##zz -kilometre -camping -explicit -casually -shawn -foolish -teammates -nm -hassan -carrie -judged -satisfy -vanessa -knives -selective -cnn -flowed -##lice -eclipse -stressed -eliza -mathematician -cease -cultivated -##roy -commissions -browns -##ania -destroyers -sheridan -meadow -##rius -minerals -##cial -downstream -clash -gram -memoirs -ventures -baha -seymour -archie -midlands -edith -fare -flynn -invite -canceled -tiles -stabbed -boulder -incorporate -amended -camden -facial -mollusk -unreleased -descriptions -yoga -grabs -550 -raises -ramp -shiver -##rose -coined -pioneering -tunes -qing -warwick -tops -119 -melanie -giles -##rous -wandered -##inal -annexed -nov -30th -unnamed -##ished -organizational -airplane -normandy -stoke -whistle -blessing -violations -chased -holders -shotgun -##ctic -outlet -reactor -##vik -tires -tearing -shores -fortified -mascot -constituencies -nc -columnist -productive -tibet -##rta -lineage -hooked -oct -tapes -judging -cody -##gger -hansen -kashmir -triggered -##eva -solved -cliffs -##tree -resisted -anatomy -protesters -transparent -implied -##iga -injection -mattress -excluding -##mbo -defenses -helpless -devotion -##elli -growl -liberals -weber -phenomena -atoms -plug -##iff -mortality -apprentice -howe -convincing -aaa -swimmer -barber -leone -promptly -sodium -def -nowadays -arise -##oning -gloucester -corrected -dignity -norm -erie -##ders -elders -evacuated -sylvia -compression -##yar -hartford -pose -backpack -reasoning -accepts -24th -wipe -millimetres -marcel -##oda -dodgers -albion -1790 -overwhelmed -aerospace -oaks -1795 -showcase -acknowledge -recovering -nolan -ashe -hurts -geology -fashioned -disappearance -farewell -swollen -shrug -marquis -wimbledon -124 -rue -1792 -commemorate -reduces -experiencing -inevitable -calcutta -intel -##court -murderer -sticking -fisheries -imagery -bloom -280 -brake -##inus -gustav -hesitation -memorable -po -viral -beans -accidents -tunisia -antenna -spilled -consort -treatments -aye -perimeter -##gard -donation -hostage -migrated -banker -addiction -apex -lil -trout -##ously -conscience -##nova -rams -sands -genome -passionate -troubles -##lets -##set -amid -##ibility -##ret -higgins -exceed -vikings -##vie -payne -##zan -muscular -##ste -defendant -sucking -##wal -ibrahim -fuselage -claudia -vfl -europeans -snails -interval -##garh -preparatory -statewide -tasked -lacrosse -viktor -##lation -angola -##hra -flint -implications -employs -teens -patrons -stall -weekends -barriers -scrambled -nucleus -tehran -jenna -parsons -lifelong -robots -displacement -5000 -##bles -precipitation -##gt -knuckles -clutched -1802 -marrying -ecology -marx -accusations -declare -scars -kolkata -mat -meadows -bermuda -skeleton -finalists -vintage -crawl -coordinate -affects -subjected -orchestral -mistaken -##tc -mirrors -dipped -relied -260 -arches -candle -##nick -incorporating -wildly -fond -basilica -owl -fringe -rituals -whispering -stirred -feud -tertiary -slick -goat -honorable -whereby -skip -ricardo -stripes -parachute -adjoining -submerged -synthesizer -##gren -intend -positively -ninety -phi -beaver -partition -fellows -alexis -prohibition -carlisle -bizarre -fraternity -##bre -doubts -icy -cbc -aquatic -sneak -sonny -combines -airports -crude -supervised -spatial -merge -alfonso -##bic -corrupt -scan -undergo -##ams -disabilities -colombian -comparing -dolphins -perkins -##lish -reprinted -unanimous -bounced -hairs -underworld -midwest -semester -bucket -paperback -miniseries -coventry -demise -##leigh -demonstrations -sensor -rotating -yan -##hler -arrange -soils -##idge -hyderabad -labs -##dr -brakes -grandchildren -##nde -negotiated -rover -ferrari -continuation -directorate -augusta -stevenson -counterpart -gore -##rda -nursery -rican -ave -collectively -broadly -pastoral -repertoire -asserted -discovering -nordic -styled -fiba -cunningham -harley -middlesex -survives -tumor -tempo -zack -aiming -lok -urgent -##rade -##nto -devils -##ement -contractor -turin -##wl -##ool -bliss -repaired -simmons -moan -astronomical -cr -negotiate -lyric -1890s -lara -bred -clad -angus -pbs -##ience -engineered -posed -##lk -hernandez -possessions -elbows -psychiatric -strokes -confluence -electorate -lifts -campuses -lava -alps -##ep -##ution -##date -physicist -woody -##page -##ographic -##itis -juliet -reformation -sparhawk -320 -complement -suppressed -jewel -##½ -floated -##kas -continuity -sadly -##ische -inability -melting -scanning -paula -flour -judaism -safer -vague -##lm -solving -curb -##stown -financially -gable -bees -expired -miserable -cassidy -dominion -1789 -cupped -145 -robbery -facto -amos -warden -resume -tallest -marvin -ing -pounded -usd -declaring -gasoline -##aux -darkened -270 -650 -sophomore -##mere -erection -gossip -televised -risen -dial -##eu -pillars -##link -passages -profound -##tina -arabian -ashton -silicon -nail -##ead -##lated -##wer -##hardt -fleming -firearms -ducked -circuits -blows -waterloo -titans -##lina -atom -fireplace -cheshire -financed -activation -algorithms -##zzi -constituent -catcher -cherokee -partnerships -sexuality -platoon -tragic -vivian -guarded -whiskey -meditation -poetic -##late -##nga -##ake -porto -listeners -dominance -kendra -mona -chandler -factions -22nd -salisbury -attitudes -derivative -##ido -##haus -intake -paced -javier -illustrator -barrels -bias -cockpit -burnett -dreamed -ensuing -##anda -receptors -someday -hawkins -mattered -##lal -slavic -1799 -jesuit -cameroon -wasted -tai -wax -lowering -victorious -freaking -outright -hancock -librarian -sensing -bald -calcium -myers -tablet -announcing -barack -shipyard -pharmaceutical -##uan -greenwich -flush -medley -patches -wolfgang -pt -speeches -acquiring -exams -nikolai -##gg -hayden -kannada -##type -reilly -##pt -waitress -abdomen -devastated -capped -pseudonym -pharmacy -fulfill -paraguay -1796 -clicked -##trom -archipelago -syndicated -##hman -lumber -orgasm -rejection -clifford -lorraine -advent -mafia -rodney -brock -##ght -##used -##elia -cassette -chamberlain -despair -mongolia -sensors -developmental -upstream -##eg -##alis -spanning -165 -trombone -basque -seeded -interred -renewable -rhys -leapt -revision -molecule -##ages -chord -vicious -nord -shivered -23rd -arlington -debts -corpus -sunrise -bays -blackburn -centimetres -##uded -shuddered -gm -strangely -gripping -cartoons -isabelle -orbital -##ppa -seals -proving -##lton -refusal -strengthened -bust -assisting -baghdad -batsman -portrayal -mara -pushes -spears -og -##cock -reside -nathaniel -brennan -1776 -confirmation -caucus -##worthy -markings -yemen -nobles -ku -lazy -viewer -catalan -encompasses -sawyer -##fall -sparked -substances -patents -braves -arranger -evacuation -sergio -persuade -dover -tolerance -penguin -cum -jockey -insufficient -townships -occupying -declining -plural -processed -projection -puppet -flanders -introduces -liability -##yon -gymnastics -antwerp -taipei -hobart -candles -jeep -wes -observers -126 -chaplain -bundle -glorious -##hine -hazel -flung -sol -excavations -dumped -stares -sh -bangalore -triangular -icelandic -intervals -expressing -turbine -##vers -songwriting -crafts -##igo -jasmine -ditch -rite -##ways -entertaining -comply -sorrow -wrestlers -basel -emirates -marian -rivera -helpful -##some -caution -downward -networking -##atory -##tered -darted -genocide -emergence -replies -specializing -spokesman -convenient -unlocked -fading -augustine -concentrations -resemblance -elijah -investigator -andhra -##uda -promotes -bean -##rrell -fleeing -wan -simone -announcer -##ame -##bby -lydia -weaver -132 -residency -modification -##fest -stretches -##ast -alternatively -nat -lowe -lacks -##ented -pam -tile -concealed -inferior -abdullah -residences -tissues -vengeance -##ided -moisture -peculiar -groove -zip -bologna -jennings -ninja -oversaw -zombies -pumping -batch -livingston -emerald -installations -1797 -peel -nitrogen -rama -##fying -##star -schooling -strands -responding -werner -##ost -lime -casa -accurately -targeting -##rod -underway -##uru -hemisphere -lester -##yard -occupies -2d -griffith -angrily -reorganized -##owing -courtney -deposited -##dd -##30 -estadio -##ifies -dunn -exiled -##ying -checks -##combe -##о -##fly -successes -unexpectedly -blu -assessed -##flower -##ه -observing -sacked -spiders -kn -##tail -mu -nodes -prosperity -audrey -divisional -155 -broncos -tangled -adjust -feeds -erosion -paolo -surf -directory -snatched -humid -admiralty -screwed -gt -reddish -##nese -modules -trench -lamps -bind -leah -bucks -competes -##nz -##form -transcription -##uc -isles -violently -clutching -pga -cyclist -inflation -flats -ragged -unnecessary -##hian -stubborn -coordinated -harriet -baba -disqualified -330 -insect -wolfe -##fies -reinforcements -rocked -duel -winked -embraced -bricks -##raj -hiatus -defeats -pending -brightly -jealousy -##xton -##hm -##uki -lena -gdp -colorful -##dley -stein -kidney -##shu -underwear -wanderers -##haw -##icus -guardians -m³ -roared -habits -##wise -permits -gp -uranium -punished -disguise -bundesliga -elise -dundee -erotic -partisan -pi -collectors -float -individually -rendering -behavioral -bucharest -ser -hare -valerie -corporal -nutrition -proportional -##isa -immense -##kis -pavement -##zie -##eld -sutherland -crouched -1775 -##lp -suzuki -trades -endurance -operas -crosby -prayed -priory -rory -socially -##urn -gujarat -##pu -walton -cube -pasha -privilege -lennon -floods -thorne -waterfall -nipple -scouting -approve -##lov -minorities -voter -dwight -extensions -assure -ballroom -slap -dripping -privileges -rejoined -confessed -demonstrating -patriotic -yell -investor -##uth -pagan -slumped -squares -##cle -##kins -confront -bert -embarrassment -##aid -aston -urging -sweater -starr -yuri -brains -williamson -commuter -mortar -structured -selfish -exports -##jon -cds -##him -unfinished -##rre -mortgage -destinations -##nagar -canoe -solitary -buchanan -delays -magistrate -fk -##pling -motivation -##lier -##vier -recruiting -assess -##mouth -malik -antique -1791 -pius -rahman -reich -tub -zhou -smashed -airs -galway -xii -conditioning -honduras -discharged -dexter -##pf -lionel -129 -debates -lemon -tiffany -volunteered -dom -dioxide -procession -devi -sic -tremendous -advertisements -colts -transferring -verdict -hanover -decommissioned -utter -relate -pac -racism -##top -beacon -limp -similarity -terra -occurrence -ant -##how -becky -capt -updates -armament -richie -pal -##graph -halloween -mayo -##ssen -##bone -cara -serena -fcc -dolls -obligations -##dling -violated -lafayette -jakarta -exploitation -##ime -infamous -iconic -##lah -##park -kitty -moody -reginald -dread -spill -crystals -olivier -modeled -bluff -equilibrium -separating -notices -ordnance -extinction -onset -cosmic -attachment -sammy -expose -privy -anchored -##bil -abbott -admits -bending -baritone -emmanuel -policeman -vaughan -winged -climax -dresses -denny -polytechnic -mohamed -burmese -authentic -nikki -genetics -grandparents -homestead -gaza -postponed -metacritic -una -##sby -##bat -unstable -dissertation -##rial -##cian -curls -obscure -uncovered -bronx -praying -disappearing -##hoe -prehistoric -coke -turret -mutations -nonprofit -pits -monaco -##ي -##usion -prominently -dispatched -podium -##mir -uci -##uation -133 -fortifications -birthplace -kendall -##lby -##oll -preacher -rack -goodman -##rman -persistent -##ott -countless -jaime -recorder -lexington -persecution -jumps -renewal -wagons -##11 -crushing -##holder -decorations -##lake -abundance -wrath -laundry -£1 -garde -##rp -jeanne -beetles -peasant -##sl -splitting -caste -sergei -##rer -##ema -scripts -##ively -rub -satellites -##vor -inscribed -verlag -scrapped -gale -packages -chick -potato -slogan -kathleen -arabs -##culture -counterparts -reminiscent -choral -##tead -rand -retains -bushes -dane -accomplish -courtesy -closes -##oth -slaughter -hague -krakow -lawson -tailed -elias -ginger -##ttes -canopy -betrayal -rebuilding -turf -##hof -frowning -allegiance -brigades -kicks -rebuild -polls -alias -nationalism -td -rowan -audition -bowie -fortunately -recognizes -harp -dillon -horrified -##oro -renault -##tics -ropes -##α -presumed -rewarded -infrared -wiping -accelerated -illustration -##rid -presses -practitioners -badminton -##iard -detained -##tera -recognizing -relates -misery -##sies -##tly -reproduction -piercing -potatoes -thornton -esther -manners -hbo -##aan -ours -bullshit -ernie -perennial -sensitivity -illuminated -rupert -##jin -##iss -##ear -rfc -nassau -##dock -staggered -socialism -##haven -appointments -nonsense -prestige -sharma -haul -##tical -solidarity -gps -##ook -##rata -igor -pedestrian -##uit -baxter -tenants -wires -medication -unlimited -guiding -impacts -diabetes -##rama -sasha -pas -clive -extraction -131 -continually -constraints -##bilities -sonata -hunted -sixteenth -chu -planting -quote -mayer -pretended -abs -spat -##hua -ceramic -##cci -curtains -pigs -pitching -##dad -latvian -sore -dayton -##sted -##qi -patrols -slice -playground -##nted -shone -stool -apparatus -inadequate -mates -treason -##ija -desires -##liga -##croft -somalia -laurent -mir -leonardo -oracle -grape -obliged -chevrolet -thirteenth -stunning -enthusiastic -##ede -accounted -concludes -currents -basil -##kovic -drought -##rica -mai -##aire -shove -posting -##shed -pilgrimage -humorous -packing -fry -pencil -wines -smells -144 -marilyn -aching -newest -clung -bon -neighbours -sanctioned -##pie -mug -##stock -drowning -##mma -hydraulic -##vil -hiring -reminder -lilly -investigators -##ncies -sour -##eous -compulsory -packet -##rion -##graphic -##elle -cannes -##inate -depressed -##rit -heroic -importantly -theresa -##tled -conway -saturn -marginal -rae -##xia -corresponds -royce -pact -jasper -explosives -packaging -aluminium -##ttered -denotes -rhythmic -spans -assignments -hereditary -outlined -originating -sundays -lad -reissued -greeting -beatrice -##dic -pillar -marcos -plots -handbook -alcoholic -judiciary -avant -slides -extract -masculine -blur -##eum -##force -homage -trembled -owens -hymn -trey -omega -signaling -socks -accumulated -reacted -attic -theo -lining -angie -distraction -primera -talbot -##key -1200 -ti -creativity -billed -##hey -deacon -eduardo -identifies -proposition -dizzy -gunner -hogan -##yam -##pping -##hol -ja -##chan -jensen -reconstructed -##berger -clearance -darius -##nier -abe -harlem -plea -dei -circled -emotionally -notation -fascist -neville -exceeded -upwards -viable -ducks -##fo -workforce -racer -limiting -shri -##lson -possesses -1600 -kerr -moths -devastating -laden -disturbing -locking -##cture -gal -fearing -accreditation -flavor -aide -1870s -mountainous -##baum -melt -##ures -motel -texture -servers -soda -##mb -herd -##nium -erect -puzzled -hum -peggy -examinations -gould -testified -geoff -ren -devised -sacks -##law -denial -posters -grunted -cesar -tutor -ec -gerry -offerings -byrne -falcons -combinations -ct -incoming -pardon -rocking -26th -avengers -flared -mankind -seller -uttar -loch -nadia -stroking -exposing -##hd -fertile -ancestral -instituted -##has -noises -prophecy -taxation -eminent -vivid -pol -##bol -dart -indirect -multimedia -notebook -upside -displaying -adrenaline -referenced -geometric -##iving -progression -##ddy -blunt -announce -##far -implementing -##lav -aggression -liaison -cooler -cares -headache -plantations -gorge -dots -impulse -thickness -ashamed -averaging -kathy -obligation -precursor -137 -fowler -symmetry -thee -225 -hears -##rai -undergoing -ads -butcher -bowler -##lip -cigarettes -subscription -goodness -##ically -browne -##hos -##tech -kyoto -donor -##erty -damaging -friction -drifting -expeditions -hardened -prostitution -152 -fauna -blankets -claw -tossing -snarled -butterflies -recruits -investigative -coated -healed -138 -communal -hai -xiii -academics -boone -psychologist -restless -lahore -stephens -mba -brendan -foreigners -printer -##pc -ached -explode -27th -deed -scratched -dared -##pole -cardiac -1780 -okinawa -proto -commando -compelled -oddly -electrons -##base -replica -thanksgiving -##rist -sheila -deliberate -stafford -tidal -representations -hercules -ou -##path -##iated -kidnapping -lenses -##tling -deficit -samoa -mouths -consuming -computational -maze -granting -smirk -razor -fixture -ideals -inviting -aiden -nominal -##vs -issuing -julio -pitt -ramsey -docks -##oss -exhaust -##owed -bavarian -draped -anterior -mating -ethiopian -explores -noticing -##nton -discarded -convenience -hoffman -endowment -beasts -cartridge -mormon -paternal -probe -sleeves -interfere -lump -deadline -##rail -jenks -bulldogs -scrap -alternating -justified -reproductive -nam -seize -descending -secretariat -kirby -coupe -grouped -smash -panther -sedan -tapping -##18 -lola -cheer -germanic -unfortunate -##eter -unrelated -##fan -subordinate -##sdale -suzanne -advertisement -##ility -horsepower -##lda -cautiously -discourse -luigi -##mans -##fields -noun -prevalent -mao -schneider -everett -surround -governorate -kira -##avia -westward -##take -misty -rails -sustainability -134 -unused -##rating -packs -toast -unwilling -regulate -thy -suffrage -nile -awe -assam -definitions -travelers -affordable -##rb -conferred -sells -undefeated -beneficial -torso -basal -repeating -remixes -##pass -bahrain -cables -fang -##itated -excavated -numbering -statutory -##rey -deluxe -##lian -forested -ramirez -derbyshire -zeus -slamming -transfers -astronomer -banana -lottery -berg -histories -bamboo -##uchi -resurrection -posterior -bowls -vaguely -##thi -thou -preserving -tensed -offence -##inas -meyrick -callum -ridden -watt -langdon -tying -lowland -snorted -daring -truman -##hale -##girl -aura -overly -filing -weighing -goa -infections -philanthropist -saunders -eponymous -##owski -latitude -perspectives -reviewing -mets -commandant -radial -##kha -flashlight -reliability -koch -vowels -amazed -ada -elaine -supper -##rth -##encies -predator -debated -soviets -cola -##boards -##nah -compartment -crooked -arbitrary -fourteenth -##ctive -havana -majors -steelers -clips -profitable -ambush -exited -packers -##tile -nude -cracks -fungi -##е -limb -trousers -josie -shelby -tens -frederic -##ος -definite -smoothly -constellation -insult -baton -discs -lingering -##nco -conclusions -lent -staging -becker -grandpa -shaky -##tron -einstein -obstacles -sk -adverse -elle -economically -##moto -mccartney -thor -dismissal -motions -readings -nostrils -treatise -##pace -squeezing -evidently -prolonged -1783 -venezuelan -je -marguerite -beirut -takeover -shareholders -##vent -denise -digit -airplay -norse -##bbling -imaginary -pills -hubert -blaze -vacated -eliminating -##ello -vine -mansfield -##tty -retrospective -barrow -borne -clutch -bail -forensic -weaving -##nett -##witz -desktop -citadel -promotions -worrying -dorset -ieee -subdivided -##iating -manned -expeditionary -pickup -synod -chuckle -185 -barney -##rz -##ffin -functionality -karachi -litigation -meanings -uc -lick -turbo -anders -##ffed -execute -curl -oppose -ankles -typhoon -##د -##ache -##asia -linguistics -compassion -pressures -grazing -perfection -##iting -immunity -monopoly -muddy -backgrounds -136 -namibia -francesca -monitors -attracting -stunt -tuition -##ии -vegetable -##mates -##quent -mgm -jen -complexes -forts -##ond -cellar -bites -seventeenth -royals -flemish -failures -mast -charities -##cular -peruvian -capitals -macmillan -ipswich -outward -frigate -postgraduate -folds -employing -##ouse -concurrently -fiery -##tai -contingent -nightmares -monumental -nicaragua -##kowski -lizard -mal -fielding -gig -reject -##pad -harding -##ipe -coastline -##cin -##nos -beethoven -humphrey -innovations -##tam -##nge -norris -doris -solicitor -huang -obey -141 -##lc -niagara -##tton -shelves -aug -bourbon -curry -nightclub -specifications -hilton -##ndo -centennial -dispersed -worm -neglected -briggs -sm -font -kuala -uneasy -plc -##nstein -##bound -##aking -##burgh -awaiting -pronunciation -##bbed -##quest -eh -optimal -zhu -raped -greens -presided -brenda -worries -##life -venetian -marxist -turnout -##lius -refined -braced -sins -grasped -sunderland -nickel -speculated -lowell -cyrillic -communism -fundraising -resembling -colonists -mutant -freddie -usc -##mos -gratitude -##run -mural -##lous -chemist -wi -reminds -28th -steals -tess -pietro -##ingen -promoter -ri -microphone -honoured -rai -sant -##qui -feather -##nson -burlington -kurdish -terrorists -deborah -sickness -##wed -##eet -hazard -irritated -desperation -veil -clarity -##rik -jewels -xv -##gged -##ows -##cup -berkshire -unfair -mysteries -orchid -winced -exhaustion -renovations -stranded -obe -infinity -##nies -adapt -redevelopment -thanked -registry -olga -domingo -noir -tudor -ole -##atus -commenting -behaviors -##ais -crisp -pauline -probable -stirling -wigan -##bian -paralympics -panting -surpassed -##rew -luca -barred -pony -famed -##sters -cassandra -waiter -carolyn -exported -##orted -andres -destructive -deeds -jonah -castles -vacancy -suv -##glass -1788 -orchard -yep -famine -belarusian -sprang -##forth -skinny -##mis -administrators -rotterdam -zambia -zhao -boiler -discoveries -##ride -##physics -lucius -disappointing -outreach -spoon -##frame -qualifications -unanimously -enjoys -regency -##iidae -stade -realism -veterinary -rodgers -dump -alain -chestnut -castile -censorship -rumble -gibbs -##itor -communion -reggae -inactivated -logs -loads -##houses -homosexual -##iano -ale -informs -##cas -phrases -plaster -linebacker -ambrose -kaiser -fascinated -850 -limerick -recruitment -forge -mastered -##nding -leinster -rooted -threaten -##strom -borneo -##hes -suggestions -scholarships -propeller -documentaries -patronage -coats -constructing -invest -neurons -comet -entirety -shouts -identities -annoying -unchanged -wary -##antly -##ogy -neat -oversight -##kos -phillies -replay -constance -##kka -incarnation -humble -skies -minus -##acy -smithsonian -##chel -guerrilla -jar -cadets -##plate -surplus -audit -##aru -cracking -joanna -louisa -pacing -##lights -intentionally -##iri -diner -nwa -imprint -australians -tong -unprecedented -bunker -naive -specialists -ark -nichols -railing -leaked -pedal -##uka -shrub -longing -roofs -v8 -captains -neural -tuned -##ntal -##jet -emission -medina -frantic -codex -definitive -sid -abolition -intensified -stocks -enrique -sustain -genoa -oxide -##written -clues -cha -##gers -tributaries -fragment -venom -##rity -##ente -##sca -muffled -vain -sire -laos -##ingly -##hana -hastily -snapping -surfaced -sentiment -motive -##oft -contests -approximate -mesa -luckily -dinosaur -exchanges -propelled -accord -bourne -relieve -tow -masks -offended -##ues -cynthia -##mmer -rains -bartender -zinc -reviewers -lois -##sai -legged -arrogant -rafe -rosie -comprise -handicap -blockade -inlet -lagoon -copied -drilling -shelley -petals -##inian -mandarin -obsolete -##inated -onward -arguably -productivity -cindy -praising -seldom -busch -discusses -raleigh -shortage -ranged -stanton -encouragement -firstly -conceded -overs -temporal -##uke -cbe -##bos -woo -certainty -pumps -##pton -stalked -##uli -lizzie -periodic -thieves -weaker -##night -gases -shoving -chooses -wc -##chemical -prompting -weights -##kill -robust -flanked -sticky -hu -tuberculosis -##eb -##eal -christchurch -resembled -wallet -reese -inappropriate -pictured -distract -fixing -fiddle -giggled -burger -heirs -hairy -mechanic -torque -apache -obsessed -chiefly -cheng -logging -##tag -extracted -meaningful -numb -##vsky -gloucestershire -reminding -##bay -unite -##lit -breeds -diminished -clown -glove -1860s -##ن -##ug -archibald -focal -freelance -sliced -depiction -##yk -organism -switches -sights -stray -crawling -##ril -lever -leningrad -interpretations -loops -anytime -reel -alicia -delighted -##ech -inhaled -xiv -suitcase -bernie -vega -licenses -northampton -exclusion -induction -monasteries -racecourse -homosexuality -##right -##sfield -##rky -dimitri -michele -alternatives -ions -commentators -genuinely -objected -pork -hospitality -fencing -stephan -warships -peripheral -wit -drunken -wrinkled -quentin -spends -departing -chung -numerical -spokesperson -##zone -johannesburg -caliber -killers -##udge -assumes -neatly -demographic -abigail -bloc -##vel -mounting -##lain -bentley -slightest -xu -recipients -##jk -merlin -##writer -seniors -prisons -blinking -hindwings -flickered -kappa -##hel -80s -strengthening -appealing -brewing -gypsy -mali -lashes -hulk -unpleasant -harassment -bio -treaties -predict -instrumentation -pulp -troupe -boiling -mantle -##ffe -ins -##vn -dividing -handles -verbs -##onal -coconut -senegal -340 -thorough -gum -momentarily -##sto -cocaine -panicked -destined -##turing -teatro -denying -weary -captained -mans -##hawks -##code -wakefield -bollywood -thankfully -##16 -cyril -##wu -amendments -##bahn -consultation -stud -reflections -kindness -1787 -internally -##ovo -tex -mosaic -distribute -paddy -seeming -143 -##hic -piers -##15 -##mura -##verse -popularly -winger -kang -sentinel -mccoy -##anza -covenant -##bag -verge -fireworks -suppress -thrilled -dominate -##jar -swansea -##60 -142 -reconciliation -##ndi -stiffened -cue -dorian -##uf -damascus -amor -ida -foremost -##aga -porsche -unseen -dir -##had -##azi -stony -lexi -melodies -##nko -angular -integer -podcast -ants -inherent -jaws -justify -persona -##olved -josephine -##nr -##ressed -customary -flashes -gala -cyrus -glaring -backyard -ariel -physiology -greenland -html -stir -avon -atletico -finch -methodology -ked -##lent -mas -catholicism -townsend -branding -quincy -fits -containers -1777 -ashore -aragon -##19 -forearm -poisoning -##sd -adopting -conquer -grinding -amnesty -keller -finances -evaluate -forged -lankan -instincts -##uto -guam -bosnian -photographed -workplace -desirable -protector -##dog -allocation -intently -encourages -willy -##sten -bodyguard -electro -brighter -##ν -bihar -##chev -lasts -opener -amphibious -sal -verde -arte -##cope -captivity -vocabulary -yields -##tted -agreeing -desmond -pioneered -##chus -strap -campaigned -railroads -##ович -emblem -##dre -stormed -501 -##ulous -marijuana -northumberland -##gn -##nath -bowen -landmarks -beaumont -##qua -danube -##bler -attorneys -th -ge -flyers -critique -villains -cass -mutation -acc -##0s -colombo -mckay -motif -sampling -concluding -syndicate -##rell -neon -stables -ds -warnings -clint -mourning -wilkinson -##tated -merrill -leopard -evenings -exhaled -emil -sonia -ezra -discrete -stove -farrell -fifteenth -prescribed -superhero -##rier -worms -helm -wren -##duction -##hc -expo -##rator -hq -unfamiliar -antony -prevents -acceleration -fiercely -mari -painfully -calculations -cheaper -ign -clifton -irvine -davenport -mozambique -##np -pierced -##evich -wonders -##wig -##cate -##iling -crusade -ware -##uel -enzymes -reasonably -mls -##coe -mater -ambition -bunny -eliot -kernel -##fin -asphalt -headmaster -torah -aden -lush -pins -waived -##care -##yas -joao -substrate -enforce -##grad -##ules -alvarez -selections -epidemic -tempted -##bit -bremen -translates -ensured -waterfront -29th -forrest -manny -malone -kramer -reigning -cookies -simpler -absorption -205 -engraved -##ffy -evaluated -1778 -haze -146 -comforting -crossover -##abe -thorn -##rift -##imo -##pop -suppression -fatigue -cutter -##tr -201 -wurttemberg -##orf -enforced -hovering -proprietary -gb -samurai -syllable -ascent -lacey -tick -lars -tractor -merchandise -rep -bouncing -defendants -##yre -huntington -##ground -##oko -standardized -##hor -##hima -assassinated -nu -predecessors -rainy -liar -assurance -lyrical -##uga -secondly -flattened -ios -parameter -undercover -##mity -bordeaux -punish -ridges -markers -exodus -inactive -hesitate -debbie -nyc -pledge -savoy -nagar -offset -organist -##tium -hesse -marin -converting -##iver -diagram -propulsion -pu -validity -reverted -supportive -##dc -ministries -clans -responds -proclamation -##inae -##ø -##rea -ein -pleading -patriot -sf -birch -islanders -strauss -hates -##dh -brandenburg -concession -rd -##ob -1900s -killings -textbook -antiquity -cinematography -wharf -embarrassing -setup -creed -farmland -inequality -centred -signatures -fallon -370 -##ingham -##uts -ceylon -gazing -directive -laurie -##tern -globally -##uated -##dent -allah -excavation -threads -##cross -148 -frantically -icc -utilize -determines -respiratory -thoughtful -receptions -##dicate -merging -chandra -seine -147 -builders -builds -diagnostic -dev -visibility -goddamn -analyses -dhaka -cho -proves -chancel -concurrent -curiously -canadians -pumped -restoring -1850s -turtles -jaguar -sinister -spinal -traction -declan -vows -1784 -glowed -capitalism -swirling -install -universidad -##lder -##oat -soloist -##genic -##oor -coincidence -beginnings -nissan -dip -resorts -caucasus -combustion -infectious -##eno -pigeon -serpent -##itating -conclude -masked -salad -jew -##gr -surreal -toni -##wc -harmonica -151 -##gins -##etic -##coat -fishermen -intending -bravery -##wave -klaus -titan -wembley -taiwanese -ransom -40th -incorrect -hussein -eyelids -jp -cooke -dramas -utilities -##etta -##print -eisenhower -principally -granada -lana -##rak -openings -concord -##bl -bethany -connie -morality -sega -##mons -##nard -earnings -##kara -##cine -wii -communes -##rel -coma -composing -softened -severed -grapes -##17 -nguyen -analyzed -warlord -hubbard -heavenly -behave -slovenian -##hit -##ony -hailed -filmmakers -trance -caldwell -skye -unrest -coward -likelihood -##aging -bern -sci -taliban -honolulu -propose -##wang -1700 -browser -imagining -cobra -contributes -dukes -instinctively -conan -violinist -##ores -accessories -gradual -##amp -quotes -sioux -##dating -undertake -intercepted -sparkling -compressed -139 -fungus -tombs -haley -imposing -rests -degradation -lincolnshire -retailers -wetlands -tulsa -distributor -dungeon -nun -greenhouse -convey -atlantis -aft -exits -oman -dresser -lyons -##sti -joking -eddy -judgement -omitted -digits -##cts -##game -juniors -##rae -cents -stricken -une -##ngo -wizards -weir -breton -nan -technician -fibers -liking -royalty -##cca -154 -persia -terribly -magician -##rable -##unt -vance -cafeteria -booker -camille -warmer -##static -consume -cavern -gaps -compass -contemporaries -foyer -soothing -graveyard -maj -plunged -blush -##wear -cascade -demonstrates -ordinance -##nov -boyle -##lana -rockefeller -shaken -banjo -izzy -##ense -breathless -vines -##32 -##eman -alterations -chromosome -dwellings -feudal -mole -153 -catalonia -relics -tenant -mandated -##fm -fridge -hats -honesty -patented -raul -heap -cruisers -accusing -enlightenment -infants -wherein -chatham -contractors -zen -affinity -hc -osborne -piston -156 -traps -maturity -##rana -lagos -##zal -peering -##nay -attendant -dealers -protocols -subset -prospects -biographical -##cre -artery -##zers -insignia -nuns -endured -##eration -recommend -schwartz -serbs -berger -cromwell -crossroads -##ctor -enduring -clasped -grounded -##bine -marseille -twitched -abel -choke -https -catalyst -moldova -italians -##tist -disastrous -wee -##oured -##nti -wwf -nope -##piration -##asa -expresses -thumbs -167 -##nza -coca -1781 -cheating -##ption -skipped -sensory -heidelberg -spies -satan -dangers -semifinal -202 -bohemia -whitish -confusing -shipbuilding -relies -surgeons -landings -ravi -baku -moor -suffix -alejandro -##yana -litre -upheld -##unk -rajasthan -##rek -coaster -insists -posture -scenarios -etienne -favoured -appoint -transgender -elephants -poked -greenwood -defences -fulfilled -militant -somali -1758 -chalk -potent -##ucci -migrants -wink -assistants -nos -restriction -activism -niger -##ario -colon -shaun -##sat -daphne -##erated -swam -congregations -reprise -considerations -magnet -playable -xvi -##р -overthrow -tobias -knob -chavez -coding -##mers -propped -katrina -orient -newcomer -##suke -temperate -##pool -farmhouse -interrogation -##vd -committing -##vert -forthcoming -strawberry -joaquin -macau -ponds -shocking -siberia -##cellular -chant -contributors -##nant -##ologists -sped -absorb -hail -1782 -spared -##hore -barbados -karate -opus -originates -saul -##xie -evergreen -leaped -##rock -correlation -exaggerated -weekday -unification -bump -tracing -brig -afb -pathways -utilizing -##ners -mod -mb -disturbance -kneeling -##stad -##guchi -100th -pune -##thy -decreasing -168 -manipulation -miriam -academia -ecosystem -occupational -rbi -##lem -rift -##14 -rotary -stacked -incorporation -awakening -generators -guerrero -racist -##omy -cyber -derivatives -culminated -allie -annals -panzer -sainte -wikipedia -pops -zu -austro -##vate -algerian -politely -nicholson -mornings -educate -tastes -thrill -dartmouth -##gating -db -##jee -regan -differing -concentrating -choreography -divinity -##media -pledged -alexandre -routing -gregor -madeline -##idal -apocalypse -##hora -gunfire -culminating -elves -fined -liang -lam -programmed -tar -guessing -transparency -gabrielle -##gna -cancellation -flexibility -##lining -accession -shea -stronghold -nets -specializes -##rgan -abused -hasan -sgt -ling -exceeding -##₄ -admiration -supermarket -##ark -photographers -specialised -tilt -resonance -hmm -perfume -380 -sami -threatens -garland -botany -guarding -boiled -greet -puppy -russo -supplier -wilmington -vibrant -vijay -##bius -paralympic -grumbled -paige -faa -licking -margins -hurricanes -##gong -fest -grenade -ripping -##uz -counseling -weigh -##sian -needles -wiltshire -edison -costly -##not -fulton -tramway -redesigned -staffordshire -cache -gasping -watkins -sleepy -candidacy -##group -monkeys -timeline -throbbing -##bid -##sos -berth -uzbekistan -vanderbilt -bothering -overturned -ballots -gem -##iger -sunglasses -subscribers -hooker -compelling -ang -exceptionally -saloon -stab -##rdi -carla -terrifying -rom -##vision -coil -##oids -satisfying -vendors -31st -mackay -deities -overlooked -ambient -bahamas -felipe -olympia -whirled -botanist -advertised -tugging -##dden -disciples -morales -unionist -rites -foley -morse -motives -creepy -##₀ -soo -##sz -bargain -highness -frightening -turnpike -tory -reorganization -##cer -depict -biographer -##walk -unopposed -manifesto -##gles -institut -emile -accidental -kapoor -##dam -kilkenny -cortex -lively -##13 -romanesque -jain -shan -cannons -##ood -##ske -petrol -echoing -amalgamated -disappears -cautious -proposes -sanctions -trenton -##ر -flotilla -aus -contempt -tor -canary -cote -theirs -##hun -conceptual -deleted -fascinating -paso -blazing -elf -honourable -hutchinson -##eiro -##outh -##zin -surveyor -tee -amidst -wooded -reissue -intro -##ono -cobb -shelters -newsletter -hanson -brace -encoding -confiscated -dem -caravan -marino -scroll -melodic -cows -imam -##adi -##aneous -northward -searches -biodiversity -cora -310 -roaring -##bers -connell -theologian -halo -compose -pathetic -unmarried -dynamo -##oot -az -calculation -toulouse -deserves -humour -nr -forgiveness -tam -undergone -martyr -pamela -myths -whore -counselor -hicks -290 -heavens -battleship -electromagnetic -##bbs -stellar -establishments -presley -hopped -##chin -temptation -90s -wills -nas -##yuan -nhs -##nya -seminars -##yev -adaptations -gong -asher -lex -indicator -sikh -tobago -cites -goin -##yte -satirical -##gies -characterised -correspond -bubbles -lure -participates -##vid -eruption -skate -therapeutic -1785 -canals -wholesale -defaulted -sac -460 -petit -##zzled -virgil -leak -ravens -256 -portraying -##yx -ghetto -creators -dams -portray -vicente -##rington -fae -namesake -bounty -##arium -joachim -##ota -##iser -aforementioned -axle -snout -depended -dismantled -reuben -480 -##ibly -gallagher -##lau -##pd -earnest -##ieu -##iary -inflicted -objections -##llar -asa -gritted -##athy -jericho -##sea -##was -flick -underside -ceramics -undead -substituted -195 -eastward -undoubtedly -wheeled -chimney -##iche -guinness -cb -##ager -siding -##bell -traitor -baptiste -disguised -inauguration -149 -tipperary -choreographer -perched -warmed -stationary -eco -##ike -##ntes -bacterial -##aurus -flores -phosphate -##core -attacker -invaders -alvin -intersects -a1 -indirectly -immigrated -businessmen -cornelius -valves -narrated -pill -sober -ul -nationale -monastic -applicants -scenery -##jack -161 -motifs -constitutes -cpu -##osh -jurisdictions -sd -tuning -irritation -woven -##uddin -fertility -gao -##erie -antagonist -impatient -glacial -hides -boarded -denominations -interception -##jas -cookie -nicola -##tee -algebraic -marquess -bahn -parole -buyers -bait -turbines -paperwork -bestowed -natasha -renee -oceans -purchases -157 -vaccine -215 -##tock -fixtures -playhouse -integrate -jai -oswald -intellectuals -##cky -booked -nests -mortimer -##isi -obsession -sept -##gler -##sum -440 -scrutiny -simultaneous -squinted -##shin -collects -oven -shankar -penned -remarkably -##я -slips -luggage -spectral -1786 -collaborations -louie -consolidation -##ailed -##ivating -420 -hoover -blackpool -harness -ignition -vest -tails -belmont -mongol -skinner -##nae -visually -mage -derry -##tism -##unce -stevie -transitional -##rdy -redskins -drying -prep -prospective -##21 -annoyance -oversee -##loaded -fills -##books -##iki -announces -fda -scowled -respects -prasad -mystic -tucson -##vale -revue -springer -bankrupt -1772 -aristotle -salvatore -habsburg -##geny -dal -natal -nut -pod -chewing -darts -moroccan -walkover -rosario -lenin -punjabi -##ße -grossed -scattering -wired -invasive -hui -polynomial -corridors -wakes -gina -portrays -##cratic -arid -retreating -erich -irwin -sniper -##dha -linen -lindsey -maneuver -butch -shutting -socio -bounce -commemorative -postseason -jeremiah -pines -275 -mystical -beads -bp -abbas -furnace -bidding -consulted -assaulted -empirical -rubble -enclosure -sob -weakly -cancel -polly -yielded -##emann -curly -prediction -battered -70s -vhs -jacqueline -render -sails -barked -detailing -grayson -riga -sloane -raging -##yah -herbs -bravo -##athlon -alloy -giggle -imminent -suffers -assumptions -waltz -##itate -accomplishments -##ited -bathing -remixed -deception -prefix -##emia -deepest -##tier -##eis -balkan -frogs -##rong -slab -##pate -philosophers -peterborough -grains -imports -dickinson -rwanda -##atics -1774 -dirk -lan -tablets -##rove -clone -##rice -caretaker -hostilities -mclean -##gre -regimental -treasures -norms -impose -tsar -tango -diplomacy -variously -complain -192 -recognise -arrests -1779 -celestial -pulitzer -##dus -bing -libretto -##moor -adele -splash -##rite -expectation -lds -confronts -##izer -spontaneous -harmful -wedge -entrepreneurs -buyer -##ope -bilingual -translate -rugged -conner -circulated -uae -eaton -##gra -##zzle -lingered -lockheed -vishnu -reelection -alonso -##oom -joints -yankee -headline -cooperate -heinz -laureate -invading -##sford -echoes -scandinavian -##dham -hugging -vitamin -salute -micah -hind -trader -##sper -radioactive -##ndra -militants -poisoned -ratified -remark -campeonato -deprived -wander -prop -##dong -outlook -##tani -##rix -##eye -chiang -darcy -##oping -mandolin -spice -statesman -babylon -182 -walled -forgetting -afro -##cap -158 -giorgio -buffer -##polis -planetary -##gis -overlap -terminals -kinda -centenary -##bir -arising -manipulate -elm -ke -1770 -ak -##tad -chrysler -mapped -moose -pomeranian -quad -macarthur -assemblies -shoreline -recalls -stratford -##rted -noticeable -##evic -imp -##rita -##sque -accustomed -supplying -tents -disgusted -vogue -sipped -filters -khz -reno -selecting -luftwaffe -mcmahon -tyne -masterpiece -carriages -collided -dunes -exercised -flare -remembers -muzzle -##mobile -heck -##rson -burgess -lunged -middleton -boycott -bilateral -##sity -hazardous -lumpur -multiplayer -spotlight -jackets -goldman -liege -porcelain -rag -waterford -benz -attracts -hopeful -battling -ottomans -kensington -baked -hymns -cheyenne -lattice -levine -borrow -polymer -clashes -michaels -monitored -commitments -denounced -##25 -##von -cavity -##oney -hobby -akin -##holders -futures -intricate -cornish -patty -##oned -illegally -dolphin -##lag -barlow -yellowish -maddie -apologized -luton -plagued -##puram -nana -##rds -sway -fanny -łodz -##rino -psi -suspicions -hanged -##eding -initiate -charlton -##por -nak -competent -235 -analytical -annex -wardrobe -reservations -##rma -sect -162 -fairfax -hedge -piled -buckingham -uneven -bauer -simplicity -snyder -interpret -accountability -donors -moderately -byrd -continents -##cite -##max -disciple -hr -jamaican -ping -nominees -##uss -mongolian -diver -attackers -eagerly -ideological -pillows -miracles -apartheid -revolver -sulfur -clinics -moran -163 -##enko -ile -katy -rhetoric -##icated -chronology -recycling -##hrer -elongated -mughal -pascal -profiles -vibration -databases -domination -##fare -##rant -matthias -digest -rehearsal -polling -weiss -initiation -reeves -clinging -flourished -impress -ngo -##hoff -##ume -buckley -symposium -rhythms -weed -emphasize -transforming -##taking -##gence -##yman -accountant -analyze -flicker -foil -priesthood -voluntarily -decreases -##80 -##hya -slater -sv -charting -mcgill -##lde -moreno -##iu -besieged -zur -robes -##phic -admitting -api -deported -turmoil -peyton -earthquakes -##ares -nationalists -beau -clair -brethren -interrupt -welch -curated -galerie -requesting -164 -##ested -impending -steward -viper -##vina -complaining -beautifully -brandy -foam -nl -1660 -##cake -alessandro -punches -laced -explanations -##lim -attribute -clit -reggie -discomfort -##cards -smoothed -whales -##cene -adler -countered -duffy -disciplinary -widening -recipe -reliance -conducts -goats -gradient -preaching -##shaw -matilda -quasi -striped -meridian -cannabis -cordoba -certificates -##agh -##tering -graffiti -hangs -pilgrims -repeats -##ych -revive -urine -etat -##hawk -fueled -belts -fuzzy -susceptible -##hang -mauritius -salle -sincere -beers -hooks -##cki -arbitration -entrusted -advise -sniffed -seminar -junk -donnell -processors -principality -strapped -celia -mendoza -everton -fortunes -prejudice -starving -reassigned -steamer -##lund -tuck -evenly -foreman -##ffen -dans -375 -envisioned -slit -##xy -baseman -liberia -rosemary -##weed -electrified -periodically -potassium -stride -contexts -sperm -slade -mariners -influx -bianca -subcommittee -##rane -spilling -icao -estuary -##nock -delivers -iphone -##ulata -isa -mira -bohemian -dessert -##sbury -welcoming -proudly -slowing -##chs -musee -ascension -russ -##vian -waits -##psy -africans -exploit -##morphic -gov -eccentric -crab -peck -##ull -entrances -formidable -marketplace -groom -bolted -metabolism -patton -robbins -courier -payload -endure -##ifier -andes -refrigerator -##pr -ornate -##uca -ruthless -illegitimate -masonry -strasbourg -bikes -adobe -##³ -apples -quintet -willingly -niche -bakery -corpses -energetic -##cliffe -##sser -##ards -177 -centimeters -centro -fuscous -cretaceous -rancho -##yde -andrei -telecom -tottenham -oasis -ordination -vulnerability -presiding -corey -cp -penguins -sims -##pis -malawi -piss -##48 -correction -##cked -##ffle -##ryn -countdown -detectives -psychiatrist -psychedelic -dinosaurs -blouse -##get -choi -vowed -##oz -randomly -##pol -49ers -scrub -blanche -bruins -dusseldorf -##using -unwanted -##ums -212 -dominique -elevations -headlights -om -laguna -##oga -1750 -famously -ignorance -shrewsbury -##aine -ajax -breuning -che -confederacy -greco -overhaul -##screen -paz -skirts -disagreement -cruelty -jagged -phoebe -shifter -hovered -viruses -##wes -mandy -##lined -##gc -landlord -squirrel -dashed -##ι -ornamental -gag -wally -grange -literal -spurs -undisclosed -proceeding -yin -##text -billie -orphan -spanned -humidity -indy -weighted -presentations -explosions -lucian -##tary -vaughn -hindus -##anga -##hell -psycho -171 -daytona -protects -efficiently -rematch -sly -tandem -##oya -rebranded -impaired -hee -metropolis -peach -godfrey -diaspora -ethnicity -prosperous -gleaming -dar -grossing -playback -##rden -stripe -pistols -##tain -births -labelled -##cating -172 -rudy -alba -##onne -aquarium -hostility -##gb -##tase -shudder -sumatra -hardest -lakers -consonant -creeping -demos -homicide -capsule -zeke -liberties -expulsion -pueblo -##comb -trait -transporting -##ddin -##neck -##yna -depart -gregg -mold -ledge -hangar -oldham -playboy -termination -analysts -gmbh -romero -##itic -insist -cradle -filthy -brightness -slash -shootout -deposed -bordering -##truct -isis -microwave -tumbled -sheltered -cathy -werewolves -messy -andersen -convex -clapped -clinched -satire -wasting -edo -vc -rufus -##jak -mont -##etti -poznan -##keeping -restructuring -transverse -##rland -azerbaijani -slovene -gestures -roommate -choking -shear -##quist -vanguard -oblivious -##hiro -disagreed -baptism -##lich -coliseum -##aceae -salvage -societe -cory -locke -relocation -relying -versailles -ahl -swelling -##elo -cheerful -##word -##edes -gin -sarajevo -obstacle -diverted -##nac -messed -thoroughbred -fluttered -utrecht -chewed -acquaintance -assassins -dispatch -mirza -##wart -nike -salzburg -swell -yen -##gee -idle -ligue -samson -##nds -##igh -playful -spawned -##cise -tease -##case -burgundy -##bot -stirring -skeptical -interceptions -marathi -##dies -bedrooms -aroused -pinch -##lik -preferences -tattoos -buster -digitally -projecting -rust -##ital -kitten -priorities -addison -pseudo -##guard -dusk -icons -sermon -##psis -##iba -bt -##lift -##xt -ju -truce -rink -##dah -##wy -defects -psychiatry -offences -calculate -glucose -##iful -##rized -##unda -francaise -##hari -richest -warwickshire -carly -1763 -purity -redemption -lending -##cious -muse -bruises -cerebral -aero -carving -##name -preface -terminology -invade -monty -##int -anarchist -blurred -##iled -rossi -treats -guts -shu -foothills -ballads -undertaking -premise -cecilia -affiliates -blasted -conditional -wilder -minors -drone -rudolph -buffy -swallowing -horton -attested -##hop -rutherford -howell -primetime -livery -penal -##bis -minimize -hydro -wrecked -wrought -palazzo -##gling -cans -vernacular -friedman -nobleman -shale -walnut -danielle -##ection -##tley -sears -##kumar -chords -lend -flipping -streamed -por -dracula -gallons -sacrifices -gamble -orphanage -##iman -mckenzie -##gible -boxers -daly -##balls -##ان -208 -##ific -##rative -##iq -exploited -slated -##uity -circling -hillary -pinched -goldberg -provost -campaigning -lim -piles -ironically -jong -mohan -successors -usaf -##tem -##ught -autobiographical -haute -preserves -##ending -acquitted -comparisons -203 -hydroelectric -gangs -cypriot -torpedoes -rushes -chrome -derive -bumps -instability -fiat -pets -##mbe -silas -dye -reckless -settler -##itation -info -heats -##writing -176 -canonical -maltese -fins -mushroom -stacy -aspen -avid -##kur -##loading -vickers -gaston -hillside -statutes -wilde -gail -kung -sabine -comfortably -motorcycles -##rgo -169 -pneumonia -fetch -##sonic -axel -faintly -parallels -##oop -mclaren -spouse -compton -interdisciplinary -miner -##eni -181 -clamped -##chal -##llah -separates -versa -##mler -scarborough -labrador -##lity -##osing -rutgers -hurdles -como -166 -burt -divers -##100 -wichita -cade -coincided -##erson -bruised -mla -##pper -vineyard -##ili -##brush -notch -mentioning -jase -hearted -kits -doe -##acle -pomerania -##ady -ronan -seizure -pavel -problematic -##zaki -domenico -##ulin -catering -penelope -dependence -parental -emilio -ministerial -atkinson -##bolic -clarkson -chargers -colby -grill -peeked -arises -summon -##aged -fools -##grapher -faculties -qaeda -##vial -garner -refurbished -##hwa -geelong -disasters -nudged -bs -shareholder -lori -algae -reinstated -rot -##ades -##nous -invites -stainless -183 -inclusive -##itude -diocesan -til -##icz -denomination -##xa -benton -floral -registers -##ider -##erman -##kell -absurd -brunei -guangzhou -hitter -retaliation -##uled -##eve -blanc -nh -consistency -contamination -##eres -##rner -dire -palermo -broadcasters -diaries -inspire -vols -brewer -tightening -ky -mixtape -hormone -##tok -stokes -##color -##dly -##ssi -pg -##ometer -##lington -sanitation -##tility -intercontinental -apps -##adt -¹⁄₂ -cylinders -economies -favourable -unison -croix -gertrude -odyssey -vanity -dangling -##logists -upgrades -dice -middleweight -practitioner -##ight -206 -henrik -parlor -orion -angered -lac -python -blurted -##rri -sensual -intends -swings -angled -##phs -husky -attain -peerage -precinct -textiles -cheltenham -shuffled -dai -confess -tasting -bhutan -##riation -tyrone -segregation -abrupt -ruiz -##rish -smirked -blackwell -confidential -browning -amounted -##put -vase -scarce -fabulous -raided -staple -guyana -unemployed -glider -shay -##tow -carmine -troll -intervene -squash -superstar -##uce -cylindrical -len -roadway -researched -handy -##rium -##jana -meta -lao -declares -##rring -##tadt -##elin -##kova -willem -shrubs -napoleonic -realms -skater -qi -volkswagen -##ł -tad -hara -archaeologist -awkwardly -eerie -##kind -wiley -##heimer -##24 -titus -organizers -cfl -crusaders -lama -usb -vent -enraged -thankful -occupants -maximilian -##gaard -possessing -textbooks -##oran -collaborator -quaker -##ulo -avalanche -mono -silky -straits -isaiah -mustang -surged -resolutions -potomac -descend -cl -kilograms -plato -strains -saturdays -##olin -bernstein -##ype -holstein -ponytail -##watch -belize -conversely -heroine -perpetual -##ylus -charcoal -piedmont -glee -negotiating -backdrop -prologue -##jah -##mmy -pasadena -climbs -ramos -sunni -##holm -##tner -##tri -anand -deficiency -hertfordshire -stout -##avi -aperture -orioles -##irs -doncaster -intrigued -bombed -coating -otis -##mat -cocktail -##jit -##eto -amir -arousal -sar -##proof -##act -##ories -dixie -pots -##bow -whereabouts -159 -##fted -drains -bullying -cottages -scripture -coherent -fore -poe -appetite -##uration -sampled -##ators -##dp -derrick -rotor -jays -peacock -installment -##rro -advisors -##coming -rodeo -scotch -##mot -##db -##fen -##vant -ensued -rodrigo -dictatorship -martyrs -twenties -##н -towed -incidence -marta -rainforest -sai -scaled -##cles -oceanic -qualifiers -symphonic -mcbride -dislike -generalized -aubrey -colonization -##iation -##lion -##ssing -disliked -lublin -salesman -##ulates -spherical -whatsoever -sweating -avalon -contention -punt -severity -alderman -atari -##dina -##grant -##rop -scarf -seville -vertices -annexation -fairfield -fascination -inspiring -launches -palatinate -regretted -##rca -feral -##iom -elk -nap -olsen -reddy -yong -##leader -##iae -garment -transports -feng -gracie -outrage -viceroy -insides -##esis -breakup -grady -organizer -softer -grimaced -222 -murals -galicia -arranging -vectors -##rsten -bas -##sb -##cens -sloan -##eka -bitten -ara -fender -nausea -bumped -kris -banquet -comrades -detector -persisted -##llan -adjustment -endowed -cinemas -##shot -sellers -##uman -peek -epa -kindly -neglect -simpsons -talon -mausoleum -runaway -hangul -lookout -##cic -rewards -coughed -acquainted -chloride -##ald -quicker -accordion -neolithic -##qa -artemis -coefficient -lenny -pandora -tx -##xed -ecstasy -litter -segunda -chairperson -gemma -hiss -rumor -vow -nasal -antioch -compensate -patiently -transformers -##eded -judo -morrow -penis -posthumous -philips -bandits -husbands -denote -flaming -##any -##phones -langley -yorker -1760 -walters -##uo -##kle -gubernatorial -fatty -samsung -leroy -outlaw -##nine -unpublished -poole -jakob -##ᵢ -##ₙ -crete -distorted -superiority -##dhi -intercept -crust -mig -claus -crashes -positioning -188 -stallion -301 -frontal -armistice -##estinal -elton -aj -encompassing -camel -commemorated -malaria -woodward -calf -cigar -penetrate -##oso -willard -##rno -##uche -illustrate -amusing -convergence -noteworthy -##lma -##rva -journeys -realise -manfred -##sable -410 -##vocation -hearings -fiance -##posed -educators -provoked -adjusting -##cturing -modular -stockton -paterson -vlad -rejects -electors -selena -maureen -##tres -uber -##rce -swirled -##num -proportions -nanny -pawn -naturalist -parma -apostles -awoke -ethel -wen -##bey -monsoon -overview -##inating -mccain -rendition -risky -adorned -##ih -equestrian -germain -nj -conspicuous -confirming -##yoshi -shivering -##imeter -milestone -rumours -flinched -bounds -smacked -token -##bei -lectured -automobiles -##shore -impacted -##iable -nouns -nero -##leaf -ismail -prostitute -trams -##lace -bridget -sud -stimulus -impressions -reins -revolves -##oud -##gned -giro -honeymoon -##swell -criterion -##sms -##uil -libyan -prefers -##osition -211 -preview -sucks -accusation -bursts -metaphor -diffusion -tolerate -faye -betting -cinematographer -liturgical -specials -bitterly -humboldt -##ckle -flux -rattled -##itzer -archaeologists -odor -authorised -marshes -discretion -##ов -alarmed -archaic -inverse -##leton -explorers -##pine -drummond -tsunami -woodlands -##minate -##tland -booklet -insanity -owning -insert -crafted -calculus -##tore -receivers -##bt -stung -##eca -##nched -prevailing -travellers -eyeing -lila -graphs -##borne -178 -julien -##won -morale -adaptive -therapist -erica -cw -libertarian -bowman -pitches -vita -##ional -crook -##ads -##entation -caledonia -mutiny -##sible -1840s -automation -##ß -flock -##pia -ironic -pathology -##imus -remarried -##22 -joker -withstand -energies -##att -shropshire -hostages -madeleine -tentatively -conflicting -mateo -recipes -euros -ol -mercenaries -nico -##ndon -albuquerque -augmented -mythical -bel -freud -##child -cough -##lica -365 -freddy -lillian -genetically -nuremberg -calder -209 -bonn -outdoors -paste -suns -urgency -vin -restraint -tyson -##cera -##selle -barrage -bethlehem -kahn -##par -mounts -nippon -barony -happier -ryu -makeshift -sheldon -blushed -castillo -barking -listener -taped -bethel -fluent -headlines -pornography -rum -disclosure -sighing -mace -doubling -gunther -manly -##plex -rt -interventions -physiological -forwards -emerges -##tooth -##gny -compliment -rib -recession -visibly -barge -faults -connector -exquisite -prefect -##rlin -patio -##cured -elevators -brandt -italics -pena -173 -wasp -satin -ea -botswana -graceful -respectable -##jima -##rter -##oic -franciscan -generates -##dl -alfredo -disgusting -##olate -##iously -sherwood -warns -cod -promo -cheryl -sino -##ة -##escu -twitch -##zhi -brownish -thom -ortiz -##dron -densely -##beat -carmel -reinforce -##bana -187 -anastasia -downhill -vertex -contaminated -remembrance -harmonic -homework -##sol -fiancee -gears -olds -angelica -loft -ramsay -quiz -colliery -sevens -##cape -autism -##hil -walkway -##boats -ruben -abnormal -ounce -khmer -##bbe -zachary -bedside -morphology -punching -##olar -sparrow -convinces -##35 -hewitt -queer -remastered -rods -mabel -solemn -notified -lyricist -symmetric -##xide -174 -encore -passports -wildcats -##uni -baja -##pac -mildly -##ease -bleed -commodity -mounds -glossy -orchestras -##omo -damian -prelude -ambitions -##vet -awhile -remotely -##aud -asserts -imply -##iques -distinctly -modelling -remedy -##dded -windshield -dani -xiao -##endra -audible -powerplant -1300 -invalid -elemental -acquisitions -##hala -immaculate -libby -plata -smuggling -ventilation -denoted -minh -##morphism -430 -differed -dion -kelley -lore -mocking -sabbath -spikes -hygiene -drown -runoff -stylized -tally -liberated -aux -interpreter -righteous -aba -siren -reaper -pearce -millie -##cier -##yra -gaius -##iso -captures -##ttering -dorm -claudio -##sic -benches -knighted -blackness -##ored -discount -fumble -oxidation -routed -##ς -novak -perpendicular -spoiled -fracture -splits -##urt -pads -topology -##cats -axes -fortunate -offenders -protestants -esteem -221 -broadband -convened -frankly -hound -prototypes -isil -facilitated -keel -##sher -sahara -awaited -bubba -orb -prosecutors -186 -hem -520 -##xing -relaxing -remnant -romney -sorted -slalom -stefano -ulrich -##active -exemption -folder -pauses -foliage -hitchcock -epithet -204 -criticisms -##aca -ballistic -brody -hinduism -chaotic -youths -equals -##pala -pts -thicker -analogous -capitalist -improvised -overseeing -sinatra -ascended -beverage -##tl -straightforward -##kon -curran -##west -bois -325 -induce -surveying -emperors -sax -unpopular -##kk -cartoonist -fused -##mble -unto -##yuki -localities -##cko -##ln -darlington -slain -academie -lobbying -sediment -puzzles -##grass -defiance -dickens -manifest -tongues -alumnus -arbor -coincide -184 -appalachian -mustafa -examiner -cabaret -traumatic -yves -bracelet -draining -heroin -magnum -baths -odessa -consonants -mitsubishi -##gua -kellan -vaudeville -##fr -joked -null -straps -probation -##ław -ceded -interfaces -##pas -##zawa -blinding -viet -224 -rothschild -museo -640 -huddersfield -##vr -tactic -##storm -brackets -dazed -incorrectly -##vu -reg -glazed -fearful -manifold -benefited -irony -##sun -stumbling -##rte -willingness -balkans -mei -wraps -##aba -injected -##lea -gu -syed -harmless -##hammer -bray -takeoff -poppy -timor -cardboard -astronaut -purdue -weeping -southbound -cursing -stalls -diagonal -##neer -lamar -bryce -comte -weekdays -harrington -##uba -negatively -##see -lays -grouping -##cken -##henko -affirmed -halle -modernist -##lai -hodges -smelling -aristocratic -baptized -dismiss -justification -oilers -##now -coupling -qin -snack -healer -##qing -gardener -layla -battled -formulated -stephenson -gravitational -##gill -##jun -1768 -granny -coordinating -suites -##cd -##ioned -monarchs -##cote -##hips -sep -blended -apr -barrister -deposition -fia -mina -policemen -paranoid -##pressed -churchyard -covert -crumpled -creep -abandoning -tr -transmit -conceal -barr -understands -readiness -spire -##cology -##enia -##erry -610 -startling -unlock -vida -bowled -slots -##nat -##islav -spaced -trusting -admire -rig -##ink -slack -##70 -mv -207 -casualty -##wei -classmates -##odes -##rar -##rked -amherst -furnished -evolve -foundry -menace -mead -##lein -flu -wesleyan -##kled -monterey -webber -##vos -wil -##mith -##на -bartholomew -justices -restrained -##cke -amenities -191 -mediated -sewage -trenches -ml -mainz -##thus -1800s -##cula -##inski -caine -bonding -213 -converts -spheres -superseded -marianne -crypt -sweaty -ensign -historia -##br -spruce -##post -##ask -forks -thoughtfully -yukon -pamphlet -ames -##uter -karma -##yya -bryn -negotiation -sighs -incapable -##mbre -##ntial -actresses -taft -##mill -luce -prevailed -##amine -1773 -motionless -envoy -testify -investing -sculpted -instructors -provence -kali -cullen -horseback -##while -goodwin -##jos -gaa -norte -##ldon -modify -wavelength -abd -214 -skinned -sprinter -forecast -scheduling -marries -squared -tentative -##chman -boer -##isch -bolts -swap -fisherman -assyrian -impatiently -guthrie -martins -murdoch -194 -tanya -nicely -dolly -lacy -med -##45 -syn -decks -fashionable -millionaire -##ust -surfing -##ml -##ision -heaved -tammy -consulate -attendees -routinely -197 -fuse -saxophonist -backseat -malaya -##lord -scowl -tau -##ishly -193 -sighted -steaming -##rks -303 -911 -##holes -##hong -ching -##wife -bless -conserved -jurassic -stacey -unix -zion -chunk -rigorous -blaine -198 -peabody -slayer -dismay -brewers -nz -##jer -det -##glia -glover -postwar -int -penetration -sylvester -imitation -vertically -airlift -heiress -knoxville -viva -##uin -390 -macon -##rim -##fighter -##gonal -janice -##orescence -##wari -marius -belongings -leicestershire -196 -blanco -inverted -preseason -sanity -sobbing -##due -##elt -##dled -collingwood -regeneration -flickering -shortest -##mount -##osi -feminism -##lat -sherlock -cabinets -fumbled -northbound -precedent -snaps -##mme -researching -##akes -guillaume -insights -manipulated -vapor -neighbour -sap -gangster -frey -f1 -stalking -scarcely -callie -barnett -tendencies -audi -doomed -assessing -slung -panchayat -ambiguous -bartlett -##etto -distributing -violating -wolverhampton -##hetic -swami -histoire -##urus -liable -pounder -groin -hussain -larsen -popping -surprises -##atter -vie -curt -##station -mute -relocate -musicals -authorization -richter -##sef -immortality -tna -bombings -##press -deteriorated -yiddish -##acious -robbed -colchester -cs -pmid -ao -verified -balancing -apostle -swayed -recognizable -oxfordshire -retention -nottinghamshire -contender -judd -invitational -shrimp -uhf -##icient -cleaner -longitudinal -tanker -##mur -acronym -broker -koppen -sundance -suppliers -##gil -4000 -clipped -fuels -petite -##anne -landslide -helene -diversion -populous -landowners -auspices -melville -quantitative -##xes -ferries -nicky -##llus -doo -haunting -roche -carver -downed -unavailable -##pathy -approximation -hiroshima -##hue -garfield -valle -comparatively -keyboardist -traveler -##eit -congestion -calculating -subsidiaries -##bate -serb -modernization -fairies -deepened -ville -averages -##lore -inflammatory -tonga -##itch -co₂ -squads -##hea -gigantic -serum -enjoyment -retailer -verona -35th -cis -##phobic -magna -technicians -##vati -arithmetic -##sport -levin -##dation -amtrak -chow -sienna -##eyer -backstage -entrepreneurship -##otic -learnt -tao -##udy -worcestershire -formulation -baggage -hesitant -bali -sabotage -##kari -barren -enhancing -murmur -pl -freshly -putnam -syntax -aces -medicines -resentment -bandwidth -##sier -grins -chili -guido -##sei -framing -implying -gareth -lissa -genevieve -pertaining -admissions -geo -thorpe -proliferation -sato -bela -analyzing -parting -##gor -awakened -##isman -huddled -secrecy -##kling -hush -gentry -540 -dungeons -##ego -coasts -##utz -sacrificed -##chule -landowner -mutually -prevalence -programmer -adolescent -disrupted -seaside -gee -trusts -vamp -georgie -##nesian -##iol -schedules -sindh -##market -etched -hm -sparse -bey -beaux -scratching -gliding -unidentified -216 -collaborating -gems -jesuits -oro -accumulation -shaping -mbe -anal -##xin -231 -enthusiasts -newscast -##egan -janata -dewey -parkinson -179 -ankara -biennial -towering -dd -inconsistent -950 -##chet -thriving -terminate -cabins -furiously -eats -advocating -donkey -marley -muster -phyllis -leiden -##user -grassland -glittering -iucn -loneliness -217 -memorandum -armenians -##ddle -popularized -rhodesia -60s -lame -##illon -sans -bikini -header -orbits -##xx -##finger -##ulator -sharif -spines -biotechnology -strolled -naughty -yates -##wire -fremantle -milo -##mour -abducted -removes -##atin -humming -wonderland -##chrome -##ester -hume -pivotal -##rates -armand -grams -believers -elector -rte -apron -bis -scraped -##yria -endorsement -initials -##llation -eps -dotted -hints -buzzing -emigration -nearer -##tom -indicators -##ulu -coarse -neutron -protectorate -##uze -directional -exploits -pains -loire -1830s -proponents -guggenheim -rabbits -ritchie -305 -hectare -inputs -hutton -##raz -verify -##ako -boilers -longitude -##lev -skeletal -yer -emilia -citrus -compromised -##gau -pokemon -prescription -paragraph -eduard -cadillac -attire -categorized -kenyan -weddings -charley -##bourg -entertain -monmouth -##lles -nutrients -davey -mesh -incentive -practised -ecosystems -kemp -subdued -overheard -##rya -bodily -maxim -##nius -apprenticeship -ursula -##fight -lodged -rug -silesian -unconstitutional -patel -inspected -coyote -unbeaten -##hak -34th -disruption -convict -parcel -##cl -##nham -collier -implicated -mallory -##iac -##lab -susannah -winkler -##rber -shia -phelps -sediments -graphical -robotic -##sner -adulthood -mart -smoked -##isto -kathryn -clarified -##aran -divides -convictions -oppression -pausing -burying -##mt -federico -mathias -eileen -##tana -kite -hunched -##acies -189 -##atz -disadvantage -liza -kinetic -greedy -paradox -yokohama -dowager -trunks -ventured -##gement -gupta -vilnius -olaf -##thest -crimean -hopper -##ej -progressively -arturo -mouthed -arrondissement -##fusion -rubin -simulcast -oceania -##orum -##stra -##rred -busiest -intensely -navigator -cary -##vine -##hini -##bies -fife -rowe -rowland -posing -insurgents -shafts -lawsuits -activate -conor -inward -culturally -garlic -265 -##eering -eclectic -##hui -##kee -##nl -furrowed -vargas -meteorological -rendezvous -##aus -culinary -commencement -##dition -quota -##notes -mommy -salaries -overlapping -mule -##iology -##mology -sums -wentworth -##isk -##zione -mainline -subgroup -##illy -hack -plaintiff -verdi -bulb -differentiation -engagements -multinational -supplemented -bertrand -caller -regis -##naire -##sler -##arts -##imated -blossom -propagation -kilometer -viaduct -vineyards -##uate -beckett -optimization -golfer -songwriters -seminal -semitic -thud -volatile -evolving -ridley -##wley -trivial -distributions -scandinavia -jiang -##ject -wrestled -insistence -##dio -emphasizes -napkin -##ods -adjunct -rhyme -##ricted -##eti -hopeless -surrounds -tremble -32nd -smoky -##ntly -oils -medicinal -padded -steer -wilkes -219 -255 -concessions -hue -uniquely -blinded -landon -yahoo -##lane -hendrix -commemorating -dex -specify -chicks -##ggio -intercity -1400 -morley -##torm -highlighting -##oting -pang -oblique -stalled -##liner -flirting -newborn -1769 -bishopric -shaved -232 -currie -##ush -dharma -spartan -##ooped -favorites -smug -novella -sirens -abusive -creations -espana -##lage -paradigm -semiconductor -sheen -##rdo -##yen -##zak -nrl -renew -##pose -##tur -adjutant -marches -norma -##enity -ineffective -weimar -grunt -##gat -lordship -plotting -expenditure -infringement -lbs -refrain -av -mimi -mistakenly -postmaster -1771 -##bara -ras -motorsports -tito -199 -subjective -##zza -bully -stew -##kaya -prescott -1a -##raphic -##zam -bids -styling -paranormal -reeve -sneaking -exploding -katz -akbar -migrant -syllables -indefinitely -##ogical -destroys -replaces -applause -##phine -pest -##fide -218 -articulated -bertie -##thing -##cars -##ptic -courtroom -crowley -aesthetics -cummings -tehsil -hormones -titanic -dangerously -##ibe -stadion -jaenelle -auguste -ciudad -##chu -mysore -partisans -##sio -lucan -philipp -##aly -debating -henley -interiors -##rano -##tious -homecoming -beyonce -usher -henrietta -prepares -weeds -##oman -ely -plucked -##pire -##dable -luxurious -##aq -artifact -password -pasture -juno -maddy -minsk -##dder -##ologies -##rone -assessments -martian -royalist -1765 -examines -##mani -##rge -nino -223 -parry -scooped -relativity -##eli -##uting -##cao -congregational -noisy -traverse -##agawa -strikeouts -nickelodeon -obituary -transylvania -binds -depictions -polk -trolley -##yed -##lard -breeders -##under -dryly -hokkaido -1762 -strengths -stacks -bonaparte -connectivity -neared -prostitutes -stamped -anaheim -gutierrez -sinai -##zzling -bram -fresno -madhya -##86 -proton -##lena -##llum -##phon -reelected -wanda -##anus -##lb -ample -distinguishing -##yler -grasping -sermons -tomato -bland -stimulation -avenues -##eux -spreads -scarlett -fern -pentagon -assert -baird -chesapeake -ir -calmed -distortion -fatalities -##olis -correctional -pricing -##astic -##gina -prom -dammit -ying -collaborate -##chia -welterweight -33rd -pointer -substitution -bonded -umpire -communicating -multitude -paddle -##obe -federally -intimacy -##insky -betray -ssr -##lett -##lean -##lves -##therapy -airbus -##tery -functioned -ud -bearer -biomedical -netflix -##hire -##nca -condom -brink -ik -##nical -macy -##bet -flap -gma -experimented -jelly -lavender -##icles -##ulia -munro -##mian -##tial -rye -##rle -60th -gigs -hottest -rotated -predictions -fuji -bu -##erence -##omi -barangay -##fulness -##sas -clocks -##rwood -##liness -cereal -roe -wight -decker -uttered -babu -onion -xml -forcibly -##df -petra -sarcasm -hartley -peeled -storytelling -##42 -##xley -##ysis -##ffa -fibre -kiel -auditor -fig -harald -greenville -##berries -geographically -nell -quartz -##athic -cemeteries -##lr -crossings -nah -holloway -reptiles -chun -sichuan -snowy -660 -corrections -##ivo -zheng -ambassadors -blacksmith -fielded -fluids -hardcover -turnover -medications -melvin -academies -##erton -ro -roach -absorbing -spaniards -colton -##founded -outsider -espionage -kelsey -245 -edible -##ulf -dora -establishes -##sham -##tries -contracting -##tania -cinematic -costello -nesting -##uron -connolly -duff -##nology -mma -##mata -fergus -sexes -gi -optics -spectator -woodstock -banning -##hee -##fle -differentiate -outfielder -refinery -226 -312 -gerhard -horde -lair -drastically -##udi -landfall -##cheng -motorsport -odi -##achi -predominant -quay -skins -##ental -edna -harshly -complementary -murdering -##aves -wreckage -##90 -ono -outstretched -lennox -munitions -galen -reconcile -470 -scalp -bicycles -gillespie -questionable -rosenberg -guillermo -hostel -jarvis -kabul -volvo -opium -yd -##twined -abuses -decca -outpost -##cino -sensible -neutrality -##64 -ponce -anchorage -atkins -turrets -inadvertently -disagree -libre -vodka -reassuring -weighs -##yal -glide -jumper -ceilings -repertory -outs -stain -##bial -envy -##ucible -smashing -heightened -policing -hyun -mixes -lai -prima -##ples -celeste -##bina -lucrative -intervened -kc -manually -##rned -stature -staffed -bun -bastards -nairobi -priced -##auer -thatcher -##kia -tripped -comune -##ogan -##pled -brasil -incentives -emanuel -hereford -musica -##kim -benedictine -biennale -##lani -eureka -gardiner -rb -knocks -sha -##ael -##elled -##onate -efficacy -ventura -masonic -sanford -maize -leverage -##feit -capacities -santana -##aur -novelty -vanilla -##cter -##tour -benin -##oir -##rain -neptune -drafting -tallinn -##cable -humiliation -##boarding -schleswig -fabian -bernardo -liturgy -spectacle -sweeney -pont -routledge -##tment -cosmos -ut -hilt -sleek -universally -##eville -##gawa -typed -##dry -favors -allegheny -glaciers -##rly -recalling -aziz -##log -parasite -requiem -auf -##berto -##llin -illumination -##breaker -##issa -festivities -bows -govern -vibe -vp -333 -sprawled -larson -pilgrim -bwf -leaping -##rts -##ssel -alexei -greyhound -hoarse -##dler -##oration -seneca -##cule -gaping -##ulously -##pura -cinnamon -##gens -##rricular -craven -fantasies -houghton -engined -reigned -dictator -supervising -##oris -bogota -commentaries -unnatural -fingernails -spirituality -tighten -##tm -canadiens -protesting -intentional -cheers -sparta -##ytic -##iere -##zine -widen -belgarath -controllers -dodd -iaaf -navarre -##ication -defect -squire -steiner -whisky -##mins -560 -inevitably -tome -##gold -chew -##uid -##lid -elastic -##aby -streaked -alliances -jailed -regal -##ined -##phy -czechoslovak -narration -absently -##uld -bluegrass -guangdong -quran -criticizing -hose -hari -##liest -##owa -skier -streaks -deploy -##lom -raft -bose -dialed -huff -##eira -haifa -simplest -bursting -endings -ib -sultanate -##titled -franks -whitman -ensures -sven -##ggs -collaborators -forster -organising -ui -banished -napier -injustice -teller -layered -thump -##otti -roc -battleships -evidenced -fugitive -sadie -robotics -##roud -equatorial -geologist -##iza -yielding -##bron -##sr -internationale -mecca -##diment -sbs -skyline -toad -uploaded -reflective -undrafted -lal -leafs -bayern -##dai -lakshmi -shortlisted -##stick -##wicz -camouflage -donate -af -christi -lau -##acio -disclosed -nemesis -1761 -assemble -straining -northamptonshire -tal -##asi -bernardino -premature -heidi -42nd -coefficients -galactic -reproduce -buzzed -sensations -zionist -monsieur -myrtle -##eme -archery -strangled -musically -viewpoint -antiquities -bei -trailers -seahawks -cured -pee -preferring -tasmanian -lange -sul -##mail -##working -colder -overland -lucivar -massey -gatherings -haitian -##smith -disapproval -flaws -##cco -##enbach -1766 -npr -##icular -boroughs -creole -forums -techno -1755 -dent -abdominal -streetcar -##eson -##stream -procurement -gemini -predictable -##tya -acheron -christoph -feeder -fronts -vendor -bernhard -jammu -tumors -slang -##uber -goaltender -twists -curving -manson -vuelta -mer -peanut -confessions -pouch -unpredictable -allowance -theodor -vascular -##factory -bala -authenticity -metabolic -coughing -nanjing -##cea -pembroke -##bard -splendid -36th -ff -hourly -##ahu -elmer -handel -##ivate -awarding -thrusting -dl -experimentation -##hesion -##46 -caressed -entertained -steak -##rangle -biologist -orphans -baroness -oyster -stepfather -##dridge -mirage -reefs -speeding -##31 -barons -1764 -227 -inhabit -preached -repealed -##tral -honoring -boogie -captives -administer -johanna -##imate -gel -suspiciously -1767 -sobs -##dington -backbone -hayward -garry -##folding -##nesia -maxi -##oof -##ppe -ellison -galileo -##stand -crimea -frenzy -amour -bumper -matrices -natalia -baking -garth -palestinians -##grove -smack -conveyed -ensembles -gardening -##manship -##rup -##stituting -1640 -harvesting -topography -jing -shifters -dormitory -##carriage -##lston -ist -skulls -##stadt -dolores -jewellery -sarawak -##wai -##zier -fences -christy -confinement -tumbling -credibility -fir -stench -##bria -##plication -##nged -##sam -virtues -##belt -marjorie -pba -##eem -##made -celebrates -schooner -agitated -barley -fulfilling -anthropologist -##pro -restrict -novi -regulating -##nent -padres -##rani -##hesive -loyola -tabitha -milky -olson -proprietor -crambidae -guarantees -intercollegiate -ljubljana -hilda -##sko -ignorant -hooded -##lts -sardinia -##lidae -##vation -frontman -privileged -witchcraft -##gp -jammed -laude -poking -##than -bracket -amazement -yunnan -##erus -maharaja -linnaeus -264 -commissioning -milano -peacefully -##logies -akira -rani -regulator -##36 -grasses -##rance -luzon -crows -compiler -gretchen -seaman -edouard -tab -buccaneers -ellington -hamlets -whig -socialists -##anto -directorial -easton -mythological -##kr -##vary -rhineland -semantic -taut -dune -inventions -succeeds -##iter -replication -branched -##pired -jul -prosecuted -kangaroo -penetrated -##avian -middlesbrough -doses -bleak -madam -predatory -relentless -##vili -reluctance -##vir -hailey -crore -silvery -1759 -monstrous -swimmers -transmissions -hawthorn -informing -##eral -toilets -caracas -crouch -kb -##sett -295 -cartel -hadley -##aling -alexia -yvonne -##biology -cinderella -eton -superb -blizzard -stabbing -industrialist -maximus -##gm -##orus -groves -maud -clade -oversized -comedic -##bella -rosen -nomadic -fulham -montane -beverages -galaxies -redundant -swarm -##rot -##folia -##llis -buckinghamshire -fen -bearings -bahadur -##rom -gilles -phased -dynamite -faber -benoit -vip -##ount -##wd -booking -fractured -tailored -anya -spices -westwood -cairns -auditions -inflammation -steamed -##rocity -##acion -##urne -skyla -thereof -watford -torment -archdeacon -transforms -lulu -demeanor -fucked -serge -##sor -mckenna -minas -entertainer -##icide -caress -originate -residue -##sty -1740 -##ilised -##org -beech -##wana -subsidies -##ghton -emptied -gladstone -ru -firefighters -voodoo -##rcle -het -nightingale -tamara -edmond -ingredient -weaknesses -silhouette -285 -compatibility -withdrawing -hampson -##mona -anguish -giggling -##mber -bookstore -##jiang -southernmost -tilting -##vance -bai -economical -rf -briefcase -dreadful -hinted -projections -shattering -totaling -##rogate -analogue -indicted -periodical -fullback -##dman -haynes -##tenberg -##ffs -##ishment -1745 -thirst -stumble -penang -vigorous -##ddling -##kor -##lium -octave -##ove -##enstein -##inen -##ones -siberian -##uti -cbn -repeal -swaying -##vington -khalid -tanaka -unicorn -otago -plastered -lobe -riddle -##rella -perch -##ishing -croydon -filtered -graeme -tripoli -##ossa -crocodile -##chers -sufi -mined -##tung -inferno -lsu -##phi -swelled -utilizes -£2 -cale -periodicals -styx -hike -informally -coop -lund -##tidae -ala -hen -qui -transformations -disposed -sheath -chickens -##cade -fitzroy -sas -silesia -unacceptable -odisha -1650 -sabrina -pe -spokane -ratios -athena -massage -shen -dilemma -##drum -##riz -##hul -corona -doubtful -niall -##pha -##bino -fines -cite -acknowledging -bangor -ballard -bathurst -##resh -huron -mustered -alzheimer -garments -kinase -tyre -warship -##cp -flashback -pulmonary -braun -cheat -kamal -cyclists -constructions -grenades -ndp -traveller -excuses -stomped -signalling -trimmed -futsal -mosques -relevance -##wine -wta -##23 -##vah -##lter -hoc -##riding -optimistic -##´s -deco -sim -interacting -rejecting -moniker -waterways -##ieri -##oku -mayors -gdansk -outnumbered -pearls -##ended -##hampton -fairs -totals -dominating -262 -notions -stairway -compiling -pursed -commodities -grease -yeast -##jong -carthage -griffiths -residual -amc -contraction -laird -sapphire -##marine -##ivated -amalgamation -dissolve -inclination -lyle -packaged -altitudes -suez -canons -graded -lurched -narrowing -boasts -guise -wed -enrico -##ovsky -rower -scarred -bree -cub -iberian -protagonists -bargaining -proposing -trainers -voyages -vans -fishes -##aea -##ivist -##verance -encryption -artworks -kazan -sabre -cleopatra -hepburn -rotting -supremacy -mecklenburg -##brate -burrows -hazards -outgoing -flair -organizes -##ctions -scorpion -##usions -boo -234 -chevalier -dunedin -slapping -##34 -ineligible -pensions -##38 -##omic -manufactures -emails -bismarck -238 -weakening -blackish -ding -mcgee -quo -##rling -northernmost -xx -manpower -greed -sampson -clicking -##ange -##horpe -##inations -##roving -torre -##eptive -##moral -symbolism -38th -asshole -meritorious -outfits -splashed -biographies -sprung -astros -##tale -302 -737 -filly -raoul -nw -tokugawa -linden -clubhouse -##apa -tracts -romano -##pio -putin -tags -##note -chained -dickson -gunshot -moe -gunn -rashid -##tails -zipper -##bas -##nea -contrasted -##ply -##udes -plum -pharaoh -##pile -aw -comedies -ingrid -sandwiches -subdivisions -1100 -mariana -nokia -kamen -hz -delaney -veto -herring -##words -possessive -outlines -##roup -siemens -stairwell -rc -gallantry -messiah -palais -yells -233 -zeppelin -##dm -bolivar -##cede -smackdown -mckinley -##mora -##yt -muted -geologic -finely -unitary -avatar -hamas -maynard -rees -bog -contrasting -##rut -liv -chico -disposition -pixel -##erate -becca -dmitry -yeshiva -narratives -##lva -##ulton -mercenary -sharpe -tempered -navigate -stealth -amassed -keynes -##lini -untouched -##rrie -havoc -lithium -##fighting -abyss -graf -southward -wolverine -balloons -implements -ngos -transitions -##icum -ambushed -concacaf -dormant -economists -##dim -costing -csi -rana -universite -boulders -verity -##llon -collin -mellon -misses -cypress -fluorescent -lifeless -spence -##ulla -crewe -shepard -pak -revelations -##م -jolly -gibbons -paw -##dro -##quel -freeing -##test -shack -fries -palatine -##51 -##hiko -accompaniment -cruising -recycled -##aver -erwin -sorting -synthesizers -dyke -realities -sg -strides -enslaved -wetland -##ghan -competence -gunpowder -grassy -maroon -reactors -objection -##oms -carlson -gearbox -macintosh -radios -shelton -##sho -clergyman -prakash -254 -mongols -trophies -oricon -228 -stimuli -twenty20 -cantonese -cortes -mirrored -##saurus -bhp -cristina -melancholy -##lating -enjoyable -nuevo -##wny -downfall -schumacher -##ind -banging -lausanne -rumbled -paramilitary -reflex -ax -amplitude -migratory -##gall -##ups -midi -barnard -lastly -sherry -##hp -##nall -keystone -##kra -carleton -slippery -##53 -coloring -foe -socket -otter -##rgos -mats -##tose -consultants -bafta -bison -topping -##km -490 -primal -abandonment -transplant -atoll -hideous -mort -pained -reproduced -tae -howling -##turn -unlawful -billionaire -hotter -poised -lansing -##chang -dinamo -retro -messing -nfc -domesday -##mina -blitz -timed -##athing -##kley -ascending -gesturing -##izations -signaled -tis -chinatown -mermaid -savanna -jameson -##aint -catalina -##pet -##hers -cochrane -cy -chatting -##kus -alerted -computation -mused -noelle -majestic -mohawk -campo -octagonal -##sant -##hend -241 -aspiring -##mart -comprehend -iona -paralyzed -shimmering -swindon -rhone -##eley -reputed -configurations -pitchfork -agitation -francais -gillian -lipstick -##ilo -outsiders -pontifical -resisting -bitterness -sewer -rockies -##edd -##ucher -misleading -1756 -exiting -galloway -##nging -risked -##heart -246 -commemoration -schultz -##rka -integrating -##rsa -poses -shrieked -##weiler -guineas -gladys -jerking -owls -goldsmith -nightly -penetrating -##unced -lia -##33 -ignited -betsy -##aring -##thorpe -follower -vigorously -##rave -coded -kiran -knit -zoology -tbilisi -##28 -##bered -repository -govt -deciduous -dino -growling -##bba -enhancement -unleashed -chanting -pussy -biochemistry -##eric -kettle -repression -toxicity -nrhp -##arth -##kko -##bush -ernesto -commended -outspoken -242 -mca -parchment -sms -kristen -##aton -bisexual -raked -glamour -navajo -a2 -conditioned -showcased -##hma -spacious -youthful -##esa -usl -appliances -junta -brest -layne -conglomerate -enchanted -chao -loosened -picasso -circulating -inspect -montevideo -##centric -##kti -piazza -spurred -##aith -bari -freedoms -poultry -stamford -lieu -##ect -indigo -sarcastic -bahia -stump -attach -dvds -frankenstein -lille -approx -scriptures -pollen -##script -nmi -overseen -##ivism -tides -proponent -newmarket -inherit -milling -##erland -centralized -##rou -distributors -credentials -drawers -abbreviation -##lco -##xon -downing -uncomfortably -ripe -##oes -erase -franchises -##ever -populace -##bery -##khar -decomposition -pleas -##tet -daryl -sabah -##stle -##wide -fearless -genie -lesions -annette -##ogist -oboe -appendix -nair -dripped -petitioned -maclean -mosquito -parrot -rpg -hampered -1648 -operatic -reservoirs -##tham -irrelevant -jolt -summarized -##fp -medallion -##taff -##− -clawed -harlow -narrower -goddard -marcia -bodied -fremont -suarez -altering -tempest -mussolini -porn -##isms -sweetly -oversees -walkers -solitude -grimly -shrines -hk -ich -supervisors -hostess -dietrich -legitimacy -brushes -expressive -##yp -dissipated -##rse -localized -systemic -##nikov -gettysburg -##js -##uaries -dialogues -muttering -251 -housekeeper -sicilian -discouraged -##frey -beamed -kaladin -halftime -kidnap -##amo -##llet -1754 -synonymous -depleted -instituto -insulin -reprised -##opsis -clashed -##ctric -interrupting -radcliffe -insisting -medici -1715 -ejected -playfully -turbulent -##47 -starvation -##rini -shipment -rebellious -petersen -verification -merits -##rified -cakes -##charged -1757 -milford -shortages -spying -fidelity -##aker -emitted -storylines -harvested -seismic -##iform -cheung -kilda -theoretically -barbie -lynx -##rgy -##tius -goblin -mata -poisonous -##nburg -reactive -residues -obedience -##евич -conjecture -##rac -401 -hating -sixties -kicker -moaning -motown -##bha -emancipation -neoclassical -##hering -consoles -ebert -professorship -##tures -sustaining -assaults -obeyed -affluent -incurred -tornadoes -##eber -##zow -emphasizing -highlanders -cheated -helmets -##ctus -internship -terence -bony -executions -legislators -berries -peninsular -tinged -##aco -1689 -amplifier -corvette -ribbons -lavish -pennant -##lander -worthless -##chfield -##forms -mariano -pyrenees -expenditures -##icides -chesterfield -mandir -tailor -39th -sergey -nestled -willed -aristocracy -devotees -goodnight -raaf -rumored -weaponry -remy -appropriations -harcourt -burr -riaa -##lence -limitation -unnoticed -guo -soaking -swamps -##tica -collapsing -tatiana -descriptive -brigham -psalm -##chment -maddox -##lization -patti -caliph -##aja -akron -injuring -serra -##ganj -basins -##sari -astonished -launcher -##church -hilary -wilkins -sewing -##sf -stinging -##fia -##ncia -underwood -startup -##ition -compilations -vibrations -embankment -jurist -##nity -bard -juventus -groundwater -kern -palaces -helium -boca -cramped -marissa -soto -##worm -jae -princely -##ggy -faso -bazaar -warmly -##voking -229 -pairing -##lite -##grate -##nets -wien -freaked -ulysses -rebirth -##alia -##rent -mummy -guzman -jimenez -stilled -##nitz -trajectory -tha -woken -archival -professions -##pts -##pta -hilly -shadowy -shrink -##bolt -norwood -glued -migrate -stereotypes -devoid -##pheus -625 -evacuate -horrors -infancy -gotham -knowles -optic -downloaded -sachs -kingsley -parramatta -darryl -mor -##onale -shady -commence -confesses -kan -##meter -##placed -marlborough -roundabout -regents -frigates -io -##imating -gothenburg -revoked -carvings -clockwise -convertible -intruder -##sche -banged -##ogo -vicky -bourgeois -##mony -dupont -footing -##gum -pd -##real -buckle -yun -penthouse -sane -720 -serviced -stakeholders -neumann -bb -##eers -comb -##gam -catchment -pinning -rallies -typing -##elles -forefront -freiburg -sweetie -giacomo -widowed -goodwill -worshipped -aspirations -midday -##vat -fishery -##trick -bournemouth -turk -243 -hearth -ethanol -guadalajara -murmurs -sl -##uge -afforded -scripted -##hta -wah -##jn -coroner -translucent -252 -memorials -puck -progresses -clumsy -##race -315 -candace -recounted -##27 -##slin -##uve -filtering -##mac -howl -strata -heron -leveled -##ays -dubious -##oja -##т -##wheel -citations -exhibiting -##laya -##mics -##pods -turkic -##lberg -injunction -##ennial -##mit -antibodies -##44 -organise -##rigues -cardiovascular -cushion -inverness -##zquez -dia -cocoa -sibling -##tman -##roid -expanse -feasible -tunisian -algiers -##relli -rus -bloomberg -dso -westphalia -bro -tacoma -281 -downloads -##ours -konrad -duran -##hdi -continuum -jett -compares -legislator -secession -##nable -##gues -##zuka -translating -reacher -##gley -##ła -aleppo -##agi -tc -orchards -trapping -linguist -versatile -drumming -postage -calhoun -superiors -##mx -barefoot -leary -##cis -ignacio -alfa -kaplan -##rogen -bratislava -mori -##vot -disturb -haas -313 -cartridges -gilmore -radiated -salford -tunic -hades -##ulsive -archeological -delilah -magistrates -auditioned -brewster -charters -empowerment -blogs -cappella -dynasties -iroquois -whipping -##krishna -raceway -truths -myra -weaken -judah -mcgregor -##horse -mic -refueling -37th -burnley -bosses -markus -premio -query -##gga -dunbar -##economic -darkest -lyndon -sealing -commendation -reappeared -##mun -addicted -ezio -slaughtered -satisfactory -shuffle -##eves -##thic -##uj -fortification -warrington -##otto -resurrected -fargo -mane -##utable -##lei -##space -foreword -ox -##aris -##vern -abrams -hua -##mento -sakura -##alo -uv -sentimental -##skaya -midfield -##eses -sturdy -scrolls -macleod -##kyu -entropy -##lance -mitochondrial -cicero -excelled -thinner -convoys -perceive -##oslav -##urable -systematically -grind -burkina -287 -##tagram -ops -##aman -guantanamo -##cloth -##tite -forcefully -wavy -##jou -pointless -##linger -##tze -layton -portico -superficial -clerical -outlaws -##hism -burials -muir -##inn -creditors -hauling -rattle -##leg -calais -monde -archers -reclaimed -dwell -wexford -hellenic -falsely -remorse -##tek -dough -furnishings -##uttered -gabon -neurological -novice -##igraphy -contemplated -pulpit -nightstand -saratoga -##istan -documenting -pulsing -taluk -##firmed -busted -marital -##rien -disagreements -wasps -##yes -hodge -mcdonnell -mimic -fran -pendant -dhabi -musa -##nington -congratulations -argent -darrell -concussion -losers -regrets -thessaloniki -reversal -donaldson -hardwood -thence -achilles -ritter -##eran -demonic -jurgen -prophets -goethe -eki -classmate -buff -##cking -yank -irrational -##inging -perished -seductive -qur -sourced -##crat -##typic -mustard -ravine -barre -horizontally -characterization -phylogenetic -boise -##dit -##runner -##tower -brutally -intercourse -seduce -##bbing -fay -ferris -ogden -amar -nik -unarmed -##inator -evaluating -kyrgyzstan -sweetness -##lford -##oki -mccormick -meiji -notoriety -stimulate -disrupt -figuring -instructional -mcgrath -##zoo -groundbreaking -##lto -flinch -khorasan -agrarian -bengals -mixer -radiating -##sov -ingram -pitchers -nad -tariff -##cript -tata -##codes -##emi -##ungen -appellate -lehigh -##bled -##giri -brawl -duct -texans -##ciation -##ropolis -skipper -speculative -vomit -doctrines -stresses -253 -davy -graders -whitehead -jozef -timely -cumulative -haryana -paints -appropriately -boon -cactus -##ales -##pid -dow -legions -##pit -perceptions -1730 -picturesque -##yse -periphery -rune -wr -##aha -celtics -sentencing -whoa -##erin -confirms -variance -425 -moines -mathews -spade -rave -m1 -fronted -fx -blending -alleging -reared -##gl -237 -##paper -grassroots -eroded -##free -##physical -directs -ordeal -##sław -accelerate -hacker -rooftop -##inia -lev -buys -cebu -devote -##lce -specialising -##ulsion -choreographed -repetition -warehouses -##ryl -paisley -tuscany -analogy -sorcerer -hash -huts -shards -descends -exclude -nix -chaplin -gaga -ito -vane -##drich -causeway -misconduct -limo -orchestrated -glands -jana -##kot -u2 -##mple -##sons -branching -contrasts -scoop -longed -##virus -chattanooga -##75 -syrup -cornerstone -##tized -##mind -##iaceae -careless -precedence -frescoes -##uet -chilled -consult -modelled -snatch -peat -##thermal -caucasian -humane -relaxation -spins -temperance -##lbert -occupations -lambda -hybrids -moons -mp3 -##oese -247 -rolf -societal -yerevan -ness -##ssler -befriended -mechanized -nominate -trough -boasted -cues -seater -##hom -bends -##tangle -conductors -emptiness -##lmer -eurasian -adriatic -tian -##cie -anxiously -lark -propellers -chichester -jock -ev -2a -##holding -credible -recounts -tori -loyalist -abduction -##hoot -##redo -nepali -##mite -ventral -tempting -##ango -##crats -steered -##wice -javelin -dipping -laborers -prentice -looming -titanium -##ː -badges -emir -tensor -##ntation -egyptians -rash -denies -hawthorne -lombard -showers -wehrmacht -dietary -trojan -##reus -welles -executing -horseshoe -lifeboat -##lak -elsa -infirmary -nearing -roberta -boyer -mutter -trillion -joanne -##fine -##oked -sinks -vortex -uruguayan -clasp -sirius -##block -accelerator -prohibit -sunken -byu -chronological -diplomats -ochreous -510 -symmetrical -1644 -maia -##tology -salts -reigns -atrocities -##ия -hess -bared -issn -##vyn -cater -saturated -##cycle -##isse -sable -voyager -dyer -yusuf -##inge -fountains -wolff -##39 -##nni -engraving -rollins -atheist -ominous -##ault -herr -chariot -martina -strung -##fell -##farlane -horrific -sahib -gazes -saetan -erased -ptolemy -##olic -flushing -lauderdale -analytic -##ices -530 -navarro -beak -gorilla -herrera -broom -guadalupe -raiding -sykes -311 -bsc -deliveries -1720 -invasions -carmichael -tajikistan -thematic -ecumenical -sentiments -onstage -##rians -##brand -##sume -catastrophic -flanks -molten -##arns -waller -aimee -terminating -##icing -alternately -##oche -nehru -printers -outraged -##eving -empires -template -banners -repetitive -za -##oise -vegetarian -##tell -guiana -opt -cavendish -lucknow -synthesized -##hani -##mada -finalized -##ctable -fictitious -mayoral -unreliable -##enham -embracing -peppers -rbis -##chio -##neo -inhibition -slashed -togo -orderly -embroidered -safari -salty -236 -barron -benito -totaled -##dak -pubs -simulated -caden -devin -tolkien -momma -welding -sesame -##ept -gottingen -hardness -630 -shaman -temeraire -620 -adequately -pediatric -##kit -ck -assertion -radicals -composure -cadence -seafood -beaufort -lazarus -mani -warily -cunning -kurdistan -249 -cantata -##kir -ares -##41 -##clusive -nape -townland -geared -insulted -flutter -boating -violate -draper -dumping -malmo -##hh -##romatic -firearm -alta -bono -obscured -##clave -exceeds -panorama -unbelievable -##train -preschool -##essed -disconnected -installing -rescuing -secretaries -accessibility -##castle -##drive -##ifice -##film -bouts -slug -waterway -mindanao -##buro -##ratic -halves -##ل -calming -liter -maternity -adorable -bragg -electrification -mcc -##dote -roxy -schizophrenia -##body -munoz -kaye -whaling -239 -mil -tingling -tolerant -##ago -unconventional -volcanoes -##finder -deportivo -##llie -robson -kaufman -neuroscience -wai -deportation -masovian -scraping -converse -##bh -hacking -bulge -##oun -administratively -yao -580 -amp -mammoth -booster -claremont -hooper -nomenclature -pursuits -mclaughlin -melinda -##sul -catfish -barclay -substrates -taxa -zee -originals -kimberly -packets -padma -##ality -borrowing -ostensibly -solvent -##bri -##genesis -##mist -lukas -shreveport -veracruz -##ь -##lou -##wives -cheney -tt -anatolia -hobbs -##zyn -cyclic -radiant -alistair -greenish -siena -dat -independents -##bation -conform -pieter -hyper -applicant -bradshaw -spores -telangana -vinci -inexpensive -nuclei -322 -jang -nme -soho -spd -##ign -cradled -receptionist -pow -##43 -##rika -fascism -##ifer -experimenting -##ading -##iec -##region -345 -jocelyn -maris -stair -nocturnal -toro -constabulary -elgin -##kker -msc -##giving -##schen -##rase -doherty -doping -sarcastically -batter -maneuvers -##cano -##apple -##gai -##git -intrinsic -##nst -##stor -1753 -showtime -cafes -gasps -lviv -ushered -##thed -fours -restart -astonishment -transmitting -flyer -shrugs -##sau -intriguing -cones -dictated -mushrooms -medial -##kovsky -##elman -escorting -gaped -##26 -godfather -##door -##sell -djs -recaptured -timetable -vila -1710 -3a -aerodrome -mortals -scientology -##orne -angelina -mag -convection -unpaid -insertion -intermittent -lego -##nated -endeavor -kota -pereira -##lz -304 -bwv -glamorgan -insults -agatha -fey -##cend -fleetwood -mahogany -protruding -steamship -zeta -##arty -mcguire -suspense -##sphere -advising -urges -##wala -hurriedly -meteor -gilded -inline -arroyo -stalker -##oge -excitedly -revered -##cure -earle -introductory -##break -##ilde -mutants -puff -pulses -reinforcement -##haling -curses -lizards -stalk -correlated -##fixed -fallout -macquarie -##unas -bearded -denton -heaving -802 -##ocation -winery -assign -dortmund -##lkirk -everest -invariant -charismatic -susie -##elling -bled -lesley -telegram -sumner -bk -##ogen -##к -wilcox -needy -colbert -duval -##iferous -##mbled -allotted -attends -imperative -##hita -replacements -hawker -##inda -insurgency -##zee -##eke -casts -##yla -680 -ives -transitioned -##pack -##powering -authoritative -baylor -flex -cringed -plaintiffs -woodrow -##skie -drastic -ape -aroma -unfolded -commotion -nt -preoccupied -theta -routines -lasers -privatization -wand -domino -ek -clenching -nsa -strategically -showered -bile -handkerchief -pere -storing -christophe -insulting -316 -nakamura -romani -asiatic -magdalena -palma -cruises -stripping -405 -konstantin -soaring -##berman -colloquially -forerunner -havilland -incarcerated -parasites -sincerity -##utus -disks -plank -saigon -##ining -corbin -homo -ornaments -powerhouse -##tlement -chong -fastened -feasibility -idf -morphological -usable -##nish -##zuki -aqueduct -jaguars -keepers -##flies -aleksandr -faust -assigns -ewing -bacterium -hurled -tricky -hungarians -integers -wallis -321 -yamaha -##isha -hushed -oblivion -aviator -evangelist -friars -##eller -monograph -ode -##nary -airplanes -labourers -charms -##nee -1661 -hagen -tnt -rudder -fiesta -transcript -dorothea -ska -inhibitor -maccabi -retorted -raining -encompassed -clauses -menacing -1642 -lineman -##gist -vamps -##ape -##dick -gloom -##rera -dealings -easing -seekers -##nut -##pment -helens -unmanned -##anu -##isson -basics -##amy -##ckman -adjustments -1688 -brutality -horne -##zell -sui -##55 -##mable -aggregator -##thal -rhino -##drick -##vira -counters -zoom -##01 -##rting -mn -montenegrin -packard -##unciation -##♭ -##kki -reclaim -scholastic -thugs -pulsed -##icia -syriac -quan -saddam -banda -kobe -blaming -buddies -dissent -##lusion -##usia -corbett -jaya -delle -erratic -lexie -##hesis -435 -amiga -hermes -##pressing -##leen -chapels -gospels -jamal -##uating -compute -revolving -warp -##sso -##thes -armory -##eras -##gol -antrim -loki -##kow -##asian -##good -##zano -braid -handwriting -subdistrict -funky -pantheon -##iculate -concurrency -estimation -improper -juliana -##his -newcomers -johnstone -staten -communicated -##oco -##alle -sausage -stormy -##stered -##tters -superfamily -##grade -acidic -collateral -tabloid -##oped -##rza -bladder -austen -##ellant -mcgraw -##hay -hannibal -mein -aquino -lucifer -wo -badger -boar -cher -christensen -greenberg -interruption -##kken -jem -244 -mocked -bottoms -cambridgeshire -##lide -sprawling -##bbly -eastwood -ghent -synth -##buck -advisers -##bah -nominally -hapoel -qu -daggers -estranged -fabricated -towels -vinnie -wcw -misunderstanding -anglia -nothin -unmistakable -##dust -##lova -chilly -marquette -truss -##edge -##erine -reece -##lty -##chemist -##connected -272 -308 -41st -bash -raion -waterfalls -##ump -##main -labyrinth -queue -theorist -##istle -bharatiya -flexed -soundtracks -rooney -leftist -patrolling -wharton -plainly -alleviate -eastman -schuster -topographic -engages -immensely -unbearable -fairchild -1620 -dona -lurking -parisian -oliveira -ia -indictment -hahn -bangladeshi -##aster -vivo -##uming -##ential -antonia -expects -indoors -kildare -harlan -##logue -##ogenic -##sities -forgiven -##wat -childish -tavi -##mide -##orra -plausible -grimm -successively -scooted -##bola -##dget -##rith -spartans -emery -flatly -azure -epilogue -##wark -flourish -##iny -##tracted -##overs -##oshi -bestseller -distressed -receipt -spitting -hermit -topological -##cot -drilled -subunit -francs -##layer -eel -##fk -##itas -octopus -footprint -petitions -ufo -##say -##foil -interfering -leaking -palo -##metry -thistle -valiant -##pic -narayan -mcpherson -##fast -gonzales -##ym -##enne -dustin -novgorod -solos -##zman -doin -##raph -##patient -##meyer -soluble -ashland -cuffs -carole -pendleton -whistling -vassal -##river -deviation -revisited -constituents -rallied -rotate -loomed -##eil -##nting -amateurs -augsburg -auschwitz -crowns -skeletons -##cona -bonnet -257 -dummy -globalization -simeon -sleeper -mandal -differentiated -##crow -##mare -milne -bundled -exasperated -talmud -owes -segregated -##feng -##uary -dentist -piracy -props -##rang -devlin -##torium -malicious -paws -##laid -dependency -##ergy -##fers -##enna -258 -pistons -rourke -jed -grammatical -tres -maha -wig -512 -ghostly -jayne -##achal -##creen -##ilis -##lins -##rence -designate -##with -arrogance -cambodian -clones -showdown -throttle -twain -##ception -lobes -metz -nagoya -335 -braking -##furt -385 -roaming -##minster -amin -crippled -##37 -##llary -indifferent -hoffmann -idols -intimidating -1751 -261 -influenza -memo -onions -1748 -bandage -consciously -##landa -##rage -clandestine -observes -swiped -tangle -##ener -##jected -##trum -##bill -##lta -hugs -congresses -josiah -spirited -##dek -humanist -managerial -filmmaking -inmate -rhymes -debuting -grimsby -ur -##laze -duplicate -vigor -##tf -republished -bolshevik -refurbishment -antibiotics -martini -methane -newscasts -royale -horizons -levant -iain -visas -##ischen -paler -##around -manifestation -snuck -alf -chop -futile -pedestal -rehab -##kat -bmg -kerman -res -fairbanks -jarrett -abstraction -saharan -##zek -1746 -procedural -clearer -kincaid -sash -luciano -##ffey -crunch -helmut -##vara -revolutionaries -##tute -creamy -leach -##mmon -1747 -permitting -nes -plight -wendell -##lese -contra -ts -clancy -ipa -mach -staples -autopsy -disturbances -nueva -karin -pontiac -##uding -proxy -venerable -haunt -leto -bergman -expands -##helm -wal -##pipe -canning -celine -cords -obesity -##enary -intrusion -planner -##phate -reasoned -sequencing -307 -harrow -##chon -##dora -marred -mcintyre -repay -tarzan -darting -248 -harrisburg -margarita -repulsed -##hur -##lding -belinda -hamburger -novo -compliant -runways -bingham -registrar -skyscraper -ic -cuthbert -improvisation -livelihood -##corp -##elial -admiring -##dened -sporadic -believer -casablanca -popcorn -##29 -asha -shovel -##bek -##dice -coiled -tangible -##dez -casper -elsie -resin -tenderness -rectory -##ivision -avail -sonar -##mori -boutique -##dier -guerre -bathed -upbringing -vaulted -sandals -blessings -##naut -##utnant -1680 -306 -foxes -pia -corrosion -hesitantly -confederates -crystalline -footprints -shapiro -tirana -valentin -drones -45th -microscope -shipments -texted -inquisition -wry -guernsey -unauthorized -resigning -760 -ripple -schubert -stu -reassure -felony -##ardo -brittle -koreans -##havan -##ives -dun -implicit -tyres -##aldi -##lth -magnolia -##ehan -##puri -##poulos -aggressively -fei -gr -familiarity -##poo -indicative -##trust -fundamentally -jimmie -overrun -395 -anchors -moans -##opus -britannia -armagh -##ggle -purposely -seizing -##vao -bewildered -mundane -avoidance -cosmopolitan -geometridae -quartermaster -caf -415 -chatter -engulfed -gleam -purge -##icate -juliette -jurisprudence -guerra -revisions -##bn -casimir -brew -##jm -1749 -clapton -cloudy -conde -hermitage -278 -simulations -torches -vincenzo -matteo -##rill -hidalgo -booming -westbound -accomplishment -tentacles -unaffected -##sius -annabelle -flopped -sloping -##litz -dreamer -interceptor -vu -##loh -consecration -copying -messaging -breaker -climates -hospitalized -1752 -torino -afternoons -winfield -witnessing -##teacher -breakers -choirs -sawmill -coldly -##ege -sipping -haste -uninhabited -conical -bibliography -pamphlets -severn -edict -##oca -deux -illnesses -grips -##pl -rehearsals -sis -thinkers -tame -##keepers -1690 -acacia -reformer -##osed -##rys -shuffling -##iring -##shima -eastbound -ionic -rhea -flees -littered -##oum -rocker -vomiting -groaning -champ -overwhelmingly -civilizations -paces -sloop -adoptive -##tish -skaters -##vres -aiding -mango -##joy -nikola -shriek -##ignon -pharmaceuticals -##mg -tuna -calvert -gustavo -stocked -yearbook -##urai -##mana -computed -subsp -riff -hanoi -kelvin -hamid -moors -pastures -summons -jihad -nectar -##ctors -bayou -untitled -pleasing -vastly -republics -intellect -##η -##ulio -##tou -crumbling -stylistic -sb -##ی -consolation -frequented -h₂o -walden -widows -##iens -404 -##ignment -chunks -improves -288 -grit -recited -##dev -snarl -sociological -##arte -##gul -inquired -##held -bruise -clube -consultancy -homogeneous -hornets -multiplication -pasta -prick -savior -##grin -##kou -##phile -yoon -##gara -grimes -vanishing -cheering -reacting -bn -distillery -##quisite -##vity -coe -dockyard -massif -##jord -escorts -voss -##valent -byte -chopped -hawke -illusions -workings -floats -##koto -##vac -kv -annapolis -madden -##onus -alvaro -noctuidae -##cum -##scopic -avenge -steamboat -forte -illustrates -erika -##trip -570 -dew -nationalities -bran -manifested -thirsty -diversified -muscled -reborn -##standing -arson -##lessness -##dran -##logram -##boys -##kushima -##vious -willoughby -##phobia -286 -alsace -dashboard -yuki -##chai -granville -myspace -publicized -tricked -##gang -adjective -##ater -relic -reorganisation -enthusiastically -indications -saxe -##lassified -consolidate -iec -padua -helplessly -ramps -renaming -regulars -pedestrians -accents -convicts -inaccurate -lowers -mana -##pati -barrie -bjp -outta -someplace -berwick -flanking -invoked -marrow -sparsely -excerpts -clothed -rei -##ginal -wept -##straße -##vish -alexa -excel -##ptive -membranes -aquitaine -creeks -cutler -sheppard -implementations -ns -##dur -fragrance -budge -concordia -magnesium -marcelo -##antes -gladly -vibrating -##rral -##ggles -montrose -##omba -lew -seamus -1630 -cocky -##ament -##uen -bjorn -##rrick -fielder -fluttering -##lase -methyl -kimberley -mcdowell -reductions -barbed -##jic -##tonic -aeronautical -condensed -distracting -##promising -huffed -##cala -##sle -claudius -invincible -missy -pious -balthazar -ci -##lang -butte -combo -orson -##dication -myriad -1707 -silenced -##fed -##rh -coco -netball -yourselves -##oza -clarify -heller -peg -durban -etudes -offender -roast -blackmail -curvature -##woods -vile -309 -illicit -suriname -##linson -overture -1685 -bubbling -gymnast -tucking -##mming -##ouin -maldives -##bala -gurney -##dda -##eased -##oides -backside -pinto -jars -racehorse -tending -##rdial -baronetcy -wiener -duly -##rke -barbarian -cupping -flawed -##thesis -bertha -pleistocene -puddle -swearing -##nob -##tically -fleeting -prostate -amulet -educating -##mined -##iti -##tler -75th -jens -respondents -analytics -cavaliers -papacy -raju -##iente -##ulum -##tip -funnel -271 -disneyland -##lley -sociologist -##iam -2500 -faulkner -louvre -menon -##dson -276 -##ower -afterlife -mannheim -peptide -referees -comedians -meaningless -##anger -##laise -fabrics -hurley -renal -sleeps -##bour -##icle -breakout -kristin -roadside -animator -clover -disdain -unsafe -redesign -##urity -firth -barnsley -portage -reset -narrows -268 -commandos -expansive -speechless -tubular -##lux -essendon -eyelashes -smashwords -##yad -##bang -##claim -craved -sprinted -chet -somme -astor -wrocław -orton -266 -bane -##erving -##uing -mischief -##amps -##sund -scaling -terre -##xious -impairment -offenses -undermine -moi -soy -contiguous -arcadia -inuit -seam -##tops -macbeth -rebelled -##icative -##iot -590 -elaborated -frs -uniformed -##dberg -259 -powerless -priscilla -stimulated -980 -qc -arboretum -frustrating -trieste -bullock -##nified -enriched -glistening -intern -##adia -locus -nouvelle -ollie -ike -lash -starboard -ee -tapestry -headlined -hove -rigged -##vite -pollock -##yme -thrive -clustered -cas -roi -gleamed -olympiad -##lino -pressured -regimes -##hosis -##lick -ripley -##ophone -kickoff -gallon -rockwell -##arable -crusader -glue -revolutions -scrambling -1714 -grover -##jure -englishman -aztec -263 -contemplating -coven -ipad -preach -triumphant -tufts -##esian -rotational -##phus -328 -falkland -##brates -strewn -clarissa -rejoin -environmentally -glint -banded -drenched -moat -albanians -johor -rr -maestro -malley -nouveau -shaded -taxonomy -v6 -adhere -bunk -airfields -##ritan -1741 -encompass -remington -tran -##erative -amelie -mazda -friar -morals -passions -##zai -breadth -vis -##hae -argus -burnham -caressing -insider -rudd -##imov -##mini -##rso -italianate -murderous -textual -wainwright -armada -bam -weave -timer -##taken -##nh -fra -##crest -ardent -salazar -taps -tunis -##ntino -allegro -gland -philanthropic -##chester -implication -##optera -esq -judas -noticeably -wynn -##dara -inched -indexed -crises -villiers -bandit -royalties -patterned -cupboard -interspersed -accessory -isla -kendrick -entourage -stitches -##esthesia -headwaters -##ior -interlude -distraught -draught -1727 -##basket -biased -sy -transient -triad -subgenus -adapting -kidd -shortstop -##umatic -dimly -spiked -mcleod -reprint -nellie -pretoria -windmill -##cek -singled -##mps -273 -reunite -##orous -747 -bankers -outlying -##omp -##ports -##tream -apologies -cosmetics -patsy -##deh -##ocks -##yson -bender -nantes -serene -##nad -lucha -mmm -323 -##cius -##gli -cmll -coinage -nestor -juarez -##rook -smeared -sprayed -twitching -sterile -irina -embodied -juveniles -enveloped -miscellaneous -cancers -dq -gulped -luisa -crested -swat -donegal -ref -##anov -##acker -hearst -mercantile -##lika -doorbell -ua -vicki -##alla -##som -bilbao -psychologists -stryker -sw -horsemen -turkmenistan -wits -##national -anson -mathew -screenings -##umb -rihanna -##agne -##nessy -aisles -##iani -##osphere -hines -kenton -saskatoon -tasha -truncated -##champ -##itan -mildred -advises -fredrik -interpreting -inhibitors -##athi -spectroscopy -##hab -##kong -karim -panda -##oia -##nail -##vc -conqueror -kgb -leukemia -##dity -arrivals -cheered -pisa -phosphorus -shielded -##riated -mammal -unitarian -urgently -chopin -sanitary -##mission -spicy -drugged -hinges -##tort -tipping -trier -impoverished -westchester -##caster -267 -epoch -nonstop -##gman -##khov -aromatic -centrally -cerro -##tively -##vio -billions -modulation -sedimentary -283 -facilitating -outrageous -goldstein -##eak -##kt -ld -maitland -penultimate -pollard -##dance -fleets -spaceship -vertebrae -##nig -alcoholism -als -recital -##bham -##ference -##omics -m2 -##bm -trois -##tropical -##в -commemorates -##meric -marge -##raction -1643 -670 -cosmetic -ravaged -##ige -catastrophe -eng -##shida -albrecht -arterial -bellamy -decor -harmon -##rde -bulbs -synchronized -vito -easiest -shetland -shielding -wnba -##glers -##ssar -##riam -brianna -cumbria -##aceous -##rard -cores -thayer -##nsk -brood -hilltop -luminous -carts -keynote -larkin -logos -##cta -##ا -##mund -##quay -lilith -tinted -277 -wrestle -mobilization -##uses -sequential -siam -bloomfield -takahashi -274 -##ieving -presenters -ringo -blazed -witty -##oven -##ignant -devastation -haydn -harmed -newt -therese -##peed -gershwin -molina -rabbis -sudanese -001 -innate -restarted -##sack -##fus -slices -wb -##shah -enroll -hypothetical -hysterical -1743 -fabio -indefinite -warped -##hg -exchanging -525 -unsuitable -##sboro -gallo -1603 -bret -cobalt -homemade -##hunter -mx -operatives -##dhar -terraces -durable -latch -pens -whorls -##ctuated -##eaux -billing -ligament -succumbed -##gly -regulators -spawn -##brick -##stead -filmfare -rochelle -##nzo -1725 -circumstance -saber -supplements -##nsky -##tson -crowe -wellesley -carrot -##9th -##movable -primate -drury -sincerely -topical -##mad -##rao -callahan -kyiv -smarter -tits -undo -##yeh -announcements -anthologies -barrio -nebula -##islaus -##shaft -##tyn -bodyguards -2021 -assassinate -barns -emmett -scully -##mah -##yd -##eland -##tino -##itarian -demoted -gorman -lashed -prized -adventist -writ -##gui -alla -invertebrates -##ausen -1641 -amman -1742 -align -healy -redistribution -##gf -##rize -insulation -##drop -adherents -hezbollah -vitro -ferns -yanking -269 -php -registering -uppsala -cheerleading -confines -mischievous -tully -##ross -49th -docked -roam -stipulated -pumpkin -##bry -prompt -##ezer -blindly -shuddering -craftsmen -frail -scented -katharine -scramble -shaggy -sponge -helix -zaragoza -279 -##52 -43rd -backlash -fontaine -seizures -posse -cowan -nonfiction -telenovela -wwii -hammered -undone -##gpur -encircled -irs -##ivation -artefacts -oneself -searing -smallpox -##belle -##osaurus -shandong -breached -upland -blushing -rankin -infinitely -psyche -tolerated -docking -evicted -##col -unmarked -##lving -gnome -lettering -litres -musique -##oint -benevolent -##jal -blackened -##anna -mccall -racers -tingle -##ocene -##orestation -introductions -radically -292 -##hiff -##باد -1610 -1739 -munchen -plead -##nka -condo -scissors -##sight -##tens -apprehension -##cey -##yin -hallmark -watering -formulas -sequels -##llas -aggravated -bae -commencing -##building -enfield -prohibits -marne -vedic -civilized -euclidean -jagger -beforehand -blasts -dumont -##arney -##nem -740 -conversions -hierarchical -rios -simulator -##dya -##lellan -hedges -oleg -thrusts -shadowed -darby -maximize -1744 -gregorian -##nded -##routed -sham -unspecified -##hog -emory -factual -##smo -##tp -fooled -##rger -ortega -wellness -marlon -##oton -##urance -casket -keating -ley -enclave -##ayan -char -influencing -jia -##chenko -412 -ammonia -erebidae -incompatible -violins -cornered -##arat -grooves -astronauts -columbian -rampant -fabrication -kyushu -mahmud -vanish -##dern -mesopotamia -##lete -ict -##rgen -caspian -kenji -pitted -##vered -999 -grimace -roanoke -tchaikovsky -twinned -##analysis -##awan -xinjiang -arias -clemson -kazakh -sizable -1662 -##khand -##vard -plunge -tatum -vittorio -##nden -cholera -##dana -##oper -bracing -indifference -projectile -superliga -##chee -realises -upgrading -299 -porte -retribution -##vies -nk -stil -##resses -ama -bureaucracy -blackberry -bosch -testosterone -collapses -greer -##pathic -ioc -fifties -malls -##erved -bao -baskets -adolescents -siegfried -##osity -##tosis -mantra -detecting -existent -fledgling -##cchi -dissatisfied -gan -telecommunication -mingled -sobbed -6000 -controversies -outdated -taxis -##raus -fright -slams -##lham -##fect -##tten -detectors -fetal -tanned -##uw -fray -goth -olympian -skipping -mandates -scratches -sheng -unspoken -hyundai -tracey -hotspur -restrictive -##buch -americana -mundo -##bari -burroughs -diva -vulcan -##6th -distinctions -thumping -##ngen -mikey -sheds -fide -rescues -springsteen -vested -valuation -##ece -##ely -pinnacle -rake -sylvie -##edo -almond -quivering -##irus -alteration -faltered -##wad -51st -hydra -ticked -##kato -recommends -##dicated -antigua -arjun -stagecoach -wilfred -trickle -pronouns -##pon -aryan -nighttime -##anian -gall -pea -stitch -##hei -leung -milos -##dini -eritrea -nexus -starved -snowfall -kant -parasitic -cot -discus -hana -strikers -appleton -kitchens -##erina -##partisan -##itha -##vius -disclose -metis -##channel -1701 -tesla -##vera -fitch -1735 -blooded -##tila -decimal -##tang -##bai -cyclones -eun -bottled -peas -pensacola -basha -bolivian -crabs -boil -lanterns -partridge -roofed -1645 -necks -##phila -opined -patting -##kla -##lland -chuckles -volta -whereupon -##nche -devout -euroleague -suicidal -##dee -inherently -involuntary -knitting -nasser -##hide -puppets -colourful -courageous -southend -stills -miraculous -hodgson -richer -rochdale -ethernet -greta -uniting -prism -umm -##haya -##itical -##utation -deterioration -pointe -prowess -##ropriation -lids -scranton -billings -subcontinent -##koff -##scope -brute -kellogg -psalms -degraded -##vez -stanisław -##ructured -ferreira -pun -astonishing -gunnar -##yat -arya -prc -gottfried -##tight -excursion -##ographer -dina -##quil -##nare -huffington -illustrious -wilbur -gundam -verandah -##zard -naacp -##odle -constructive -fjord -kade -##naud -generosity -thrilling -baseline -cayman -frankish -plastics -accommodations -zoological -##fting -cedric -qb -motorized -##dome -##otted -squealed -tackled -canucks -budgets -situ -asthma -dail -gabled -grasslands -whimpered -writhing -judgments -##65 -minnie -pv -##carbon -bananas -grille -domes -monique -odin -maguire -markham -tierney -##estra -##chua -libel -poke -speedy -atrium -laval -notwithstanding -##edly -fai -kala -##sur -robb -##sma -listings -luz -supplementary -tianjin -##acing -enzo -jd -ric -scanner -croats -transcribed -##49 -arden -cv -##hair -##raphy -##lver -##uy -357 -seventies -staggering -alam -horticultural -hs -regression -timbers -blasting -##ounded -montagu -manipulating -##cit -catalytic -1550 -troopers -##meo -condemnation -fitzpatrick -##oire -##roved -inexperienced -1670 -castes -##lative -outing -314 -dubois -flicking -quarrel -ste -learners -1625 -iq -whistled -##class -282 -classify -tariffs -temperament -355 -folly -liszt -##yles -immersed -jordanian -ceasefire -apparel -extras -maru -fished -##bio -harta -stockport -assortment -craftsman -paralysis -transmitters -##cola -blindness -##wk -fatally -proficiency -solemnly -##orno -repairing -amore -groceries -ultraviolet -##chase -schoolhouse -##tua -resurgence -nailed -##otype -##× -ruse -saliva -diagrams -##tructing -albans -rann -thirties -1b -antennas -hilarious -cougars -paddington -stats -##eger -breakaway -ipod -reza -authorship -prohibiting -scoffed -##etz -##ttle -conscription -defected -trondheim -##fires -ivanov -keenan -##adan -##ciful -##fb -##slow -locating -##ials -##tford -cadiz -basalt -blankly -interned -rags -rattling -##tick -carpathian -reassured -sync -bum -guildford -iss -staunch -##onga -astronomers -sera -sofie -emergencies -susquehanna -##heard -duc -mastery -vh1 -williamsburg -bayer -buckled -craving -##khan -##rdes -bloomington -##write -alton -barbecue -##bians -justine -##hri -##ndt -delightful -smartphone -newtown -photon -retrieval -peugeot -hissing -##monium -##orough -flavors -lighted -relaunched -tainted -##games -##lysis -anarchy -microscopic -hopping -adept -evade -evie -##beau -inhibit -sinn -adjustable -hurst -intuition -wilton -cisco -44th -lawful -lowlands -stockings -thierry -##dalen -##hila -##nai -fates -prank -tb -maison -lobbied -provocative -1724 -4a -utopia -##qual -carbonate -gujarati -purcell -##rford -curtiss -##mei -overgrown -arenas -mediation -swallows -##rnik -respectful -turnbull -##hedron -##hope -alyssa -ozone -##ʻi -ami -gestapo -johansson -snooker -canteen -cuff -declines -empathy -stigma -##ags -##iner -##raine -taxpayers -gui -volga -##wright -##copic -lifespan -overcame -tattooed -enactment -giggles -##ador -##camp -barrington -bribe -obligatory -orbiting -peng -##enas -elusive -sucker -##vating -cong -hardship -empowered -anticipating -estrada -cryptic -greasy -detainees -planck -sudbury -plaid -dod -marriott -kayla -##ears -##vb -##zd -mortally -##hein -cognition -radha -319 -liechtenstein -meade -richly -argyle -harpsichord -liberalism -trumpets -lauded -tyrant -salsa -tiled -lear -promoters -reused -slicing -trident -##chuk -##gami -##lka -cantor -checkpoint -##points -gaul -leger -mammalian -##tov -##aar -##schaft -doha -frenchman -nirvana -##vino -delgado -headlining -##eron -##iography -jug -tko -1649 -naga -intersections -##jia -benfica -nawab -##suka -ashford -gulp -##deck -##vill -##rug -brentford -frazier -pleasures -dunne -potsdam -shenzhen -dentistry -##tec -flanagan -##dorff -##hear -chorale -dinah -prem -quezon -##rogated -relinquished -sutra -terri -##pani -flaps -##rissa -poly -##rnet -homme -aback -##eki -linger -womb -##kson -##lewood -doorstep -orthodoxy -threaded -westfield -##rval -dioceses -fridays -subsided -##gata -loyalists -##biotic -##ettes -letterman -lunatic -prelate -tenderly -invariably -souza -thug -winslow -##otide -furlongs -gogh -jeopardy -##runa -pegasus -##umble -humiliated -standalone -tagged -##roller -freshmen -klan -##bright -attaining -initiating -transatlantic -logged -viz -##uance -1723 -combatants -intervening -stephane -chieftain -despised -grazed -317 -cdc -galveston -godzilla -macro -simulate -##planes -parades -##esses -960 -##ductive -##unes -equator -overdose -##cans -##hosh -##lifting -joshi -epstein -sonora -treacherous -aquatics -manchu -responsive -##sation -supervisory -##christ -##llins -##ibar -##balance -##uso -kimball -karlsruhe -mab -##emy -ignores -phonetic -reuters -spaghetti -820 -almighty -danzig -rumbling -tombstone -designations -lured -outset -##felt -supermarkets -##wt -grupo -kei -kraft -susanna -##blood -comprehension -genealogy -##aghan -##verted -redding -##ythe -1722 -bowing -##pore -##roi -lest -sharpened -fulbright -valkyrie -sikhs -##unds -swans -bouquet -merritt -##tage -##venting -commuted -redhead -clerks -leasing -cesare -dea -hazy -##vances -fledged -greenfield -servicemen -##gical -armando -blackout -dt -sagged -downloadable -intra -potion -pods -##4th -##mism -xp -attendants -gambia -stale -##ntine -plump -asteroids -rediscovered -buds -flea -hive -##neas -1737 -classifications -debuts -##eles -olympus -scala -##eurs -##gno -##mute -hummed -sigismund -visuals -wiggled -await -pilasters -clench -sulfate -##ances -bellevue -enigma -trainee -snort -##sw -clouded -denim -##rank -##rder -churning -hartman -lodges -riches -sima -##missible -accountable -socrates -regulates -mueller -##cr -1702 -avoids -solids -himalayas -nutrient -pup -##jevic -squat -fades -nec -##lates -##pina -##rona -##ου -privateer -tequila -##gative -##mpton -apt -hornet -immortals -##dou -asturias -cleansing -dario -##rries -##anta -etymology -servicing -zhejiang -##venor -##nx -horned -erasmus -rayon -relocating -£10 -##bags -escalated -promenade -stubble -2010s -artisans -axial -liquids -mora -sho -yoo -##tsky -bundles -oldies -##nally -notification -bastion -##ths -sparkle -##lved -1728 -leash -pathogen -highs -##hmi -immature -880 -gonzaga -ignatius -mansions -monterrey -sweets -bryson -##loe -polled -regatta -brightest -pei -rosy -squid -hatfield -payroll -addict -meath -cornerback -heaviest -lodging -##mage -capcom -rippled -##sily -barnet -mayhem -ymca -snuggled -rousseau -##cute -blanchard -284 -fragmented -leighton -chromosomes -risking -##md -##strel -##utter -corinne -coyotes -cynical -hiroshi -yeomanry -##ractive -ebook -grading -mandela -plume -agustin -magdalene -##rkin -bea -femme -trafford -##coll -##lun -##tance -52nd -fourier -upton -##mental -camilla -gust -iihf -islamabad -longevity -##kala -feldman -netting -##rization -endeavour -foraging -mfa -orr -##open -greyish -contradiction -graz -##ruff -handicapped -marlene -tweed -oaxaca -spp -campos -miocene -pri -configured -cooks -pluto -cozy -pornographic -##entes -70th -fairness -glided -jonny -lynne -rounding -sired -##emon -##nist -remade -uncover -##mack -complied -lei -newsweek -##jured -##parts -##enting -##pg -293 -finer -guerrillas -athenian -deng -disused -stepmother -accuse -gingerly -seduction -521 -confronting -##walker -##going -gora -nostalgia -sabres -virginity -wrenched -##minated -syndication -wielding -eyre -##56 -##gnon -##igny -behaved -taxpayer -sweeps -##growth -childless -gallant -##ywood -amplified -geraldine -scrape -##ffi -babylonian -fresco -##rdan -##kney -##position -1718 -restricting -tack -fukuoka -osborn -selector -partnering -##dlow -318 -gnu -kia -tak -whitley -gables -##54 -##mania -mri -softness -immersion -##bots -##evsky -1713 -chilling -insignificant -pcs -##uis -elites -lina -purported -supplemental -teaming -##americana -##dding -##inton -proficient -rouen -##nage -##rret -niccolo -selects -##bread -fluffy -1621 -gruff -knotted -mukherjee -polgara -thrash -nicholls -secluded -smoothing -thru -corsica -loaf -whitaker -inquiries -##rrier -##kam -indochina -289 -marlins -myles -peking -##tea -extracts -pastry -superhuman -connacht -vogel -##ditional -##het -##udged -##lash -gloss -quarries -refit -teaser -##alic -##gaon -20s -materialized -sling -camped -pickering -tung -tracker -pursuant -##cide -cranes -soc -##cini -##typical -##viere -anhalt -overboard -workout -chores -fares -orphaned -stains -##logie -fenton -surpassing -joyah -triggers -##itte -grandmaster -##lass -##lists -clapping -fraudulent -ledger -nagasaki -##cor -##nosis -##tsa -eucalyptus -tun -##icio -##rney -##tara -dax -heroism -ina -wrexham -onboard -unsigned -##dates -moshe -galley -winnie -droplets -exiles -praises -watered -noodles -##aia -fein -adi -leland -multicultural -stink -bingo -comets -erskine -modernized -canned -constraint -domestically -chemotherapy -featherweight -stifled -##mum -darkly -irresistible -refreshing -hasty -isolate -##oys -kitchener -planners -##wehr -cages -yarn -implant -toulon -elects -childbirth -yue -##lind -##lone -cn -rightful -sportsman -junctions -remodeled -specifies -##rgh -291 -##oons -complimented -##urgent -lister -ot -##logic -bequeathed -cheekbones -fontana -gabby -##dial -amadeus -corrugated -maverick -resented -triangles -##hered -##usly -nazareth -tyrol -1675 -assent -poorer -sectional -aegean -##cous -296 -nylon -ghanaian -##egorical -##weig -cushions -forbid -fusiliers -obstruction -somerville -##scia -dime -earrings -elliptical -leyte -oder -polymers -timmy -atm -midtown -piloted -settles -continual -externally -mayfield -##uh -enrichment -henson -keane -persians -1733 -benji -braden -pep -324 -##efe -contenders -pepsi -valet -##isches -298 -##asse -##earing -goofy -stroll -##amen -authoritarian -occurrences -adversary -ahmedabad -tangent -toppled -dorchester -1672 -modernism -marxism -islamist -charlemagne -exponential -racks -unicode -brunette -mbc -pic -skirmish -##bund -##lad -##powered -##yst -hoisted -messina -shatter -##ctum -jedi -vantage -##music -##neil -clemens -mahmoud -corrupted -authentication -lowry -nils -##washed -omnibus -wounding -jillian -##itors -##opped -serialized -narcotics -handheld -##arm -##plicity -intersecting -stimulating -##onis -crate -fellowships -hemingway -casinos -climatic -fordham -copeland -drip -beatty -leaflets -robber -brothel -madeira -##hedral -sphinx -ultrasound -##vana -valor -forbade -leonid -villas -##aldo -duane -marquez -##cytes -disadvantaged -forearms -kawasaki -reacts -consular -lax -uncles -uphold -##hopper -concepcion -dorsey -lass -##izan -arching -passageway -1708 -researches -tia -internationals -##graphs -##opers -distinguishes -javanese -divert -##uven -plotted -##listic -##rwin -##erik -##tify -affirmative -signifies -validation -##bson -kari -felicity -georgina -zulu -##eros -##rained -##rath -overcoming -##dot -argyll -##rbin -1734 -chiba -ratification -windy -earls -parapet -##marks -hunan -pristine -astrid -punta -##gart -brodie -##kota -##oder -malaga -minerva -rouse -##phonic -bellowed -pagoda -portals -reclamation -##gur -##odies -##⁄₄ -parentheses -quoting -allergic -palette -showcases -benefactor -heartland -nonlinear -##tness -bladed -cheerfully -scans -##ety -##hone -1666 -girlfriends -pedersen -hiram -sous -##liche -##nator -1683 -##nery -##orio -##umen -bobo -primaries -smiley -##cb -unearthed -uniformly -fis -metadata -1635 -ind -##oted -recoil -##titles -##tura -##ια -406 -hilbert -jamestown -mcmillan -tulane -seychelles -##frid -antics -coli -fated -stucco -##grants -1654 -bulky -accolades -arrays -caledonian -carnage -optimism -puebla -##tative -##cave -enforcing -rotherham -seo -dunlop -aeronautics -chimed -incline -zoning -archduke -hellenistic -##oses -##sions -candi -thong -##ople -magnate -rustic -##rsk -projective -slant -##offs -danes -hollis -vocalists -##ammed -congenital -contend -gesellschaft -##ocating -##pressive -douglass -quieter -##cm -##kshi -howled -salim -spontaneously -townsville -buena -southport -##bold -kato -1638 -faerie -stiffly -##vus -##rled -297 -flawless -realising -taboo -##7th -bytes -straightening -356 -jena -##hid -##rmin -cartwright -berber -bertram -soloists -411 -noses -417 -coping -fission -hardin -inca -##cen -1717 -mobilized -vhf -##raf -biscuits -curate -##85 -##anial -331 -gaunt -neighbourhoods -1540 -##abas -blanca -bypassed -sockets -behold -coincidentally -##bane -nara -shave -splinter -terrific -##arion -##erian -commonplace -juris -redwood -waistband -boxed -caitlin -fingerprints -jennie -naturalized -##ired -balfour -craters -jody -bungalow -hugely -quilt -glitter -pigeons -undertaker -bulging -constrained -goo -##sil -##akh -assimilation -reworked -##person -persuasion -##pants -felicia -##cliff -##ulent -1732 -explodes -##dun -##inium -##zic -lyman -vulture -hog -overlook -begs -northwards -ow -spoil -##urer -fatima -favorably -accumulate -sargent -sorority -corresponded -dispersal -kochi -toned -##imi -##lita -internacional -newfound -##agger -##lynn -##rigue -booths -peanuts -##eborg -medicare -muriel -nur -##uram -crates -millennia -pajamas -worsened -##breakers -jimi -vanuatu -yawned -##udeau -carousel -##hony -hurdle -##ccus -##mounted -##pod -rv -##eche -airship -ambiguity -compulsion -recapture -##claiming -arthritis -##osomal -1667 -asserting -ngc -sniffing -dade -discontent -glendale -ported -##amina -defamation -rammed -##scent -fling -livingstone -##fleet -875 -##ppy -apocalyptic -comrade -lcd -##lowe -cessna -eine -persecuted -subsistence -demi -hoop -reliefs -710 -coptic -progressing -stemmed -perpetrators -1665 -priestess -##nio -dobson -ebony -rooster -itf -tortricidae -##bbon -##jian -cleanup -##jean -##øy -1721 -eighties -taxonomic -holiness -##hearted -##spar -antilles -showcasing -stabilized -##nb -gia -mascara -michelangelo -dawned -##uria -##vinsky -extinguished -fitz -grotesque -£100 -##fera -##loid -##mous -barges -neue -throbbed -cipher -johnnie -##a1 -##mpt -outburst -##swick -spearheaded -administrations -c1 -heartbreak -pixels -pleasantly -##enay -lombardy -plush -##nsed -bobbie -##hly -reapers -tremor -xiang -minogue -substantive -hitch -barak -##wyl -kwan -##encia -910 -obscene -elegance -indus -surfer -bribery -conserve -##hyllum -##masters -horatio -##fat -apes -rebound -psychotic -##pour -iteration -##mium -##vani -botanic -horribly -antiques -dispose -paxton -##hli -##wg -timeless -1704 -disregard -engraver -hounds -##bau -##version -looted -uno -facilitates -groans -masjid -rutland -antibody -disqualification -decatur -footballers -quake -slacks -48th -rein -scribe -stabilize -commits -exemplary -tho -##hort -##chison -pantry -traversed -##hiti -disrepair -identifiable -vibrated -baccalaureate -##nnis -csa -interviewing -##iensis -##raße -greaves -wealthiest -343 -classed -jogged -£5 -##58 -##atal -illuminating -knicks -respecting -##uno -scrubbed -##iji -##dles -kruger -moods -growls -raider -silvia -chefs -kam -vr -cree -percival -##terol -gunter -counterattack -defiant -henan -ze -##rasia -##riety -equivalence -submissions -##fra -##thor -bautista -mechanically -##heater -cornice -herbal -templar -##mering -outputs -ruining -ligand -renumbered -extravagant -mika -blockbuster -eta -insurrection -##ilia -darkening -ferocious -pianos -strife -kinship -##aer -melee -##anor -##iste -##may -##oue -decidedly -weep -##jad -##missive -##ppel -354 -puget -unease -##gnant -1629 -hammering -kassel -ob -wessex -##lga -bromwich -egan -paranoia -utilization -##atable -##idad -contradictory -provoke -##ols -##ouring -##tangled -knesset -##very -##lette -plumbing -##sden -##¹ -greensboro -occult -sniff -338 -zev -beaming -gamer -haggard -mahal -##olt -##pins -mendes -utmost -briefing -gunnery -##gut -##pher -##zh -##rok -1679 -khalifa -sonya -##boot -principals -urbana -wiring -##liffe -##minating -##rrado -dahl -nyu -skepticism -np -townspeople -ithaca -lobster -somethin -##fur -##arina -##−1 -freighter -zimmerman -biceps -contractual -##herton -amend -hurrying -subconscious -##anal -336 -meng -clermont -spawning -##eia -##lub -dignitaries -impetus -snacks -spotting -twigs -##bilis -##cz -##ouk -libertadores -nic -skylar -##aina -##firm -gustave -asean -##anum -dieter -legislatures -flirt -bromley -trolls -umar -##bbies -##tyle -blah -parc -bridgeport -crank -negligence -##nction -46th -constantin -molded -bandages -seriousness -00pm -siegel -carpets -compartments -upbeat -statehood -##dner -##edging -marko -730 -platt -##hane -paving -##iy -1738 -abbess -impatience -limousine -nbl -##talk -441 -lucille -mojo -nightfall -robbers -##nais -karel -brisk -calves -replicate -ascribed -telescopes -##olf -intimidated -##reen -ballast -specialization -##sit -aerodynamic -caliphate -rainer -visionary -##arded -epsilon -##aday -##onte -aggregation -auditory -boosted -reunification -kathmandu -loco -robyn -402 -acknowledges -appointing -humanoid -newell -redeveloped -restraints -##tained -barbarians -chopper -1609 -italiana -##lez -##lho -investigates -wrestlemania -##anies -##bib -690 -##falls -creaked -dragoons -gravely -minions -stupidity -volley -##harat -##week -musik -##eries -##uously -fungal -massimo -semantics -malvern -##ahl -##pee -discourage -embryo -imperialism -1910s -profoundly -##ddled -jiangsu -sparkled -stat -##holz -sweatshirt -tobin -##iction -sneered -##cheon -##oit -brit -causal -smyth -##neuve -diffuse -perrin -silvio -##ipes -##recht -detonated -iqbal -selma -##nism -##zumi -roasted -##riders -tay -##ados -##mament -##mut -##rud -840 -completes -nipples -cfa -flavour -hirsch -##laus -calderon -sneakers -moravian -##ksha -1622 -rq -294 -##imeters -bodo -##isance -##pre -##ronia -anatomical -excerpt -##lke -dh -kunst -##tablished -##scoe -biomass -panted -unharmed -gael -housemates -montpellier -##59 -coa -rodents -tonic -hickory -singleton -##taro -451 -1719 -aldo -breaststroke -dempsey -och -rocco -##cuit -merton -dissemination -midsummer -serials -##idi -haji -polynomials -##rdon -gs -enoch -prematurely -shutter -taunton -£3 -##grating -##inates -archangel -harassed -##asco -326 -archway -dazzling -##ecin -1736 -sumo -wat -##kovich -1086 -honneur -##ently -##nostic -##ttal -##idon -1605 -403 -1716 -blogger -rents -##gnan -hires -##ikh -##dant -howie -##rons -handler -retracted -shocks -1632 -arun -duluth -kepler -trumpeter -##lary -peeking -seasoned -trooper -##mara -laszlo -##iciencies -##rti -heterosexual -##inatory -##ssion -indira -jogging -##inga -##lism -beit -dissatisfaction -malice -##ately -nedra -peeling -##rgeon -47th -stadiums -475 -vertigo -##ains -iced -restroom -##plify -##tub -illustrating -pear -##chner -##sibility -inorganic -rappers -receipts -watery -##kura -lucinda -##oulos -reintroduced -##8th -##tched -gracefully -saxons -nutritional -wastewater -rained -favourites -bedrock -fisted -hallways -likeness -upscale -##lateral -1580 -blinds -prequel -##pps -##tama -deter -humiliating -restraining -tn -vents -1659 -laundering -recess -rosary -tractors -coulter -federer -##ifiers -##plin -persistence -##quitable -geschichte -pendulum -quakers -##beam -bassett -pictorial -buffet -koln -##sitor -drills -reciprocal -shooters -##57 -##cton -##tees -converge -pip -dmitri -donnelly -yamamoto -aqua -azores -demographics -hypnotic -spitfire -suspend -wryly -roderick -##rran -sebastien -##asurable -mavericks -##fles -##200 -himalayan -prodigy -##iance -transvaal -demonstrators -handcuffs -dodged -mcnamara -sublime -1726 -crazed -##efined -##till -ivo -pondered -reconciled -shrill -sava -##duk -bal -cad -heresy -jaipur -goran -##nished -341 -lux -shelly -whitehall -##hre -israelis -peacekeeping -##wled -1703 -demetrius -ousted -##arians -##zos -beale -anwar -backstroke -raged -shrinking -cremated -##yck -benign -towing -wadi -darmstadt -landfill -parana -soothe -colleen -sidewalks -mayfair -tumble -hepatitis -ferrer -superstructure -##gingly -##urse -##wee -anthropological -translators -##mies -closeness -hooves -##pw -mondays -##roll -##vita -landscaping -##urized -purification -sock -thorns -thwarted -jalan -tiberius -##taka -saline -##rito -confidently -khyber -sculptors -##ij -brahms -hammersmith -inspectors -battista -fivb -fragmentation -hackney -##uls -arresting -exercising -antoinette -bedfordshire -##zily -dyed -##hema -1656 -racetrack -variability -##tique -1655 -austrians -deteriorating -madman -theorists -aix -lehman -weathered -1731 -decreed -eruptions -1729 -flaw -quinlan -sorbonne -flutes -nunez -1711 -adored -downwards -fable -rasped -1712 -moritz -mouthful -renegade -shivers -stunts -dysfunction -restrain -translit -327 -pancakes -##avio -##cision -##tray -351 -vial -##lden -bain -##maid -##oxide -chihuahua -malacca -vimes -##rba -##rnier -1664 -donnie -plaques -##ually -337 -bangs -floppy -huntsville -loretta -nikolay -##otte -eater -handgun -ubiquitous -##hett -eras -zodiac -1634 -##omorphic -1820s -##zog -cochran -##bula -##lithic -warring -##rada -dalai -excused -blazers -mcconnell -reeling -bot -este -##abi -geese -hoax -taxon -##bla -guitarists -##icon -condemning -hunts -inversion -moffat -taekwondo -##lvis -1624 -stammered -##rest -##rzy -sousa -fundraiser -marylebone -navigable -uptown -cabbage -daniela -salman -shitty -whimper -##kian -##utive -programmers -protections -rm -##rmi -##rued -forceful -##enes -fuss -##tao -##wash -brat -oppressive -reykjavik -spartak -ticking -##inkles -##kiewicz -adolph -horst -maui -protege -straighten -cpc -landau -concourse -clements -resultant -##ando -imaginative -joo -reactivated -##rem -##ffled -##uising -consultative -##guide -flop -kaitlyn -mergers -parenting -somber -##vron -supervise -vidhan -##imum -courtship -exemplified -harmonies -medallist -refining -##rrow -##ка -amara -##hum -780 -goalscorer -sited -overshadowed -rohan -displeasure -secretive -multiplied -osman -##orth -engravings -padre -##kali -##veda -miniatures -mis -##yala -clap -pali -rook -##cana -1692 -57th -antennae -astro -oskar -1628 -bulldog -crotch -hackett -yucatan -##sure -amplifiers -brno -ferrara -migrating -##gree -thanking -turing -##eza -mccann -ting -andersson -onslaught -gaines -ganga -incense -standardization -##mation -sentai -scuba -stuffing -turquoise -waivers -alloys -##vitt -regaining -vaults -##clops -##gizing -digger -furry -memorabilia -probing -##iad -payton -rec -deutschland -filippo -opaque -seamen -zenith -afrikaans -##filtration -disciplined -inspirational -##merie -banco -confuse -grafton -tod -##dgets -championed -simi -anomaly -biplane -##ceptive -electrode -##para -1697 -cleavage -crossbow -swirl -informant -##lars -##osta -afi -bonfire -spec -##oux -lakeside -slump -##culus -##lais -##qvist -##rrigan -1016 -facades -borg -inwardly -cervical -xl -pointedly -050 -stabilization -##odon -chests -1699 -hacked -ctv -orthogonal -suzy -##lastic -gaulle -jacobite -rearview -##cam -##erted -ashby -##drik -##igate -##mise -##zbek -affectionately -canine -disperse -latham -##istles -##ivar -spielberg -##orin -##idium -ezekiel -cid -##sg -durga -middletown -##cina -customized -frontiers -harden -##etano -##zzy -1604 -bolsheviks -##66 -coloration -yoko -##bedo -briefs -slabs -debra -liquidation -plumage -##oin -blossoms -dementia -subsidy -1611 -proctor -relational -jerseys -parochial -ter -##ici -esa -peshawar -cavalier -loren -cpi -idiots -shamrock -1646 -dutton -malabar -mustache -##endez -##ocytes -referencing -terminates -marche -yarmouth -##sop -acton -mated -seton -subtly -baptised -beige -extremes -jolted -kristina -telecast -##actic -safeguard -waldo -##baldi -##bular -endeavors -sloppy -subterranean -##ensburg -##itung -delicately -pigment -tq -##scu -1626 -##ound -collisions -coveted -herds -##personal -##meister -##nberger -chopra -##ricting -abnormalities -defective -galician -lucie -##dilly -alligator -likened -##genase -burundi -clears -complexion -derelict -deafening -diablo -fingered -champaign -dogg -enlist -isotope -labeling -mrna -##erre -brilliance -marvelous -##ayo -1652 -crawley -ether -footed -dwellers -deserts -hamish -rubs -warlock -skimmed -##lizer -870 -buick -embark -heraldic -irregularities -##ajan -kiara -##kulam -##ieg -antigen -kowalski -##lge -oakley -visitation -##mbit -vt -##suit -1570 -murderers -##miento -##rites -chimneys -##sling -condemn -custer -exchequer -havre -##ghi -fluctuations -##rations -dfb -hendricks -vaccines -##tarian -nietzsche -biking -juicy -##duced -brooding -scrolling -selangor -##ragan -352 -annum -boomed -seminole -sugarcane -##dna -departmental -dismissing -innsbruck -arteries -ashok -batavia -daze -kun -overtook -##rga -##tlan -beheaded -gaddafi -holm -electronically -faulty -galilee -fractures -kobayashi -##lized -gunmen -magma -aramaic -mala -eastenders -inference -messengers -bf -##qu -407 -bathrooms -##vere -1658 -flashbacks -ideally -misunderstood -##jali -##weather -mendez -##grounds -505 -uncanny -##iii -1709 -friendships -##nbc -sacrament -accommodated -reiterated -logistical -pebbles -thumped -##escence -administering -decrees -drafts -##flight -##cased -##tula -futuristic -picket -intimidation -winthrop -##fahan -interfered -339 -afar -francoise -morally -uta -cochin -croft -dwarfs -##bruck -##dents -##nami -biker -##hner -##meral -nano -##isen -##ometric -##pres -##ан -brightened -meek -parcels -securely -gunners -##jhl -##zko -agile -hysteria -##lten -##rcus -bukit -champs -chevy -cuckoo -leith -sadler -theologians -welded -##section -1663 -jj -plurality -xander -##rooms -##formed -shredded -temps -intimately -pau -tormented -##lok -##stellar -1618 -charred -ems -essen -##mmel -alarms -spraying -ascot -blooms -twinkle -##abia -##apes -internment -obsidian -##chaft -snoop -##dav -##ooping -malibu -##tension -quiver -##itia -hays -mcintosh -travers -walsall -##ffie -1623 -beverley -schwarz -plunging -structurally -m3 -rosenthal -vikram -##tsk -770 -ghz -##onda -##tiv -chalmers -groningen -pew -reckon -unicef -##rvis -55th -##gni -1651 -sulawesi -avila -cai -metaphysical -screwing -turbulence -##mberg -augusto -samba -56th -baffled -momentary -toxin -##urian -##wani -aachen -condoms -dali -steppe -##3d -##app -##oed -##year -adolescence -dauphin -electrically -inaccessible -microscopy -nikita -##ega -atv -##cel -##enter -##oles -##oteric -##ы -accountants -punishments -wrongly -bribes -adventurous -clinch -flinders -southland -##hem -##kata -gough -##ciency -lads -soared -##ה -undergoes -deformation -outlawed -rubbish -##arus -##mussen -##nidae -##rzburg -arcs -##ingdon -##tituted -1695 -wheelbase -wheeling -bombardier -campground -zebra -##lices -##oj -##bain -lullaby -##ecure -donetsk -wylie -grenada -##arding -##ης -squinting -eireann -opposes -##andra -maximal -runes -##broken -##cuting -##iface -##ror -##rosis -additive -britney -adultery -triggering -##drome -detrimental -aarhus -containment -jc -swapped -vichy -##ioms -madly -##oric -##rag -brant -##ckey -##trix -1560 -1612 -broughton -rustling -##stems -##uder -asbestos -mentoring -##nivorous -finley -leaps -##isan -apical -pry -slits -substitutes -##dict -intuitive -fantasia -insistent -unreasonable -##igen -##vna -domed -hannover -margot -ponder -##zziness -impromptu -jian -lc -rampage -stemming -##eft -andrey -gerais -whichever -amnesia -appropriated -anzac -clicks -modifying -ultimatum -cambrian -maids -verve -yellowstone -##mbs -conservatoire -##scribe -adherence -dinners -spectra -imperfect -mysteriously -sidekick -tatar -tuba -##aks -##ifolia -distrust -##athan -##zle -c2 -ronin -zac -##pse -celaena -instrumentalist -scents -skopje -##mbling -comical -compensated -vidal -condor -intersect -jingle -wavelengths -##urrent -mcqueen -##izzly -carp -weasel -422 -kanye -militias -postdoctoral -eugen -gunslinger -##ɛ -faux -hospice -##for -appalled -derivation -dwarves -##elis -dilapidated -##folk -astoria -philology -##lwyn -##otho -##saka -inducing -philanthropy -##bf -##itative -geek -markedly -sql -##yce -bessie -indices -rn -##flict -495 -frowns -resolving -weightlifting -tugs -cleric -contentious -1653 -mania -rms -##miya -##reate -##ruck -##tucket -bien -eels -marek -##ayton -##cence -discreet -unofficially -##ife -leaks -##bber -1705 -332 -dung -compressor -hillsborough -pandit -shillings -distal -##skin -381 -##tat -##you -nosed -##nir -mangrove -undeveloped -##idia -textures -##inho -##500 -##rise -ae -irritating -nay -amazingly -bancroft -apologetic -compassionate -kata -symphonies -##lovic -airspace -##lch -930 -gifford -precautions -fulfillment -sevilla -vulgar -martinique -##urities -looting -piccolo -tidy -##dermott -quadrant -armchair -incomes -mathematicians -stampede -nilsson -##inking -##scan -foo -quarterfinal -##ostal -shang -shouldered -squirrels -##owe -344 -vinegar -##bner -##rchy -##systems -delaying -##trics -ars -dwyer -rhapsody -sponsoring -##gration -bipolar -cinder -starters -##olio -##urst -421 -signage -##nty -aground -figurative -mons -acquaintances -duets -erroneously -soyuz -elliptic -recreated -##cultural -##quette -##ssed -##tma -##zcz -moderator -scares -##itaire -##stones -##udence -juniper -sighting -##just -##nsen -britten -calabria -ry -bop -cramer -forsyth -stillness -##л -airmen -gathers -unfit -##umber -##upt -taunting -##rip -seeker -streamlined -##bution -holster -schumann -tread -vox -##gano -##onzo -strive -dil -reforming -covent -newbury -predicting -##orro -decorate -tre -##puted -andover -ie -asahi -dept -dunkirk -gills -##tori -buren -huskies -##stis -##stov -abstracts -bets -loosen -##opa -1682 -yearning -##glio -##sir -berman -effortlessly -enamel -napoli -persist -##peration -##uez -attache -elisa -b1 -invitations -##kic -accelerating -reindeer -boardwalk -clutches -nelly -polka -starbucks -##kei -adamant -huey -lough -unbroken -adventurer -embroidery -inspecting -stanza -##ducted -naia -taluka -##pone -##roids -chases -deprivation -florian -##jing -##ppet -earthly -##lib -##ssee -colossal -foreigner -vet -freaks -patrice -rosewood -triassic -upstate -##pkins -dominates -ata -chants -ks -vo -##400 -##bley -##raya -##rmed -555 -agra -infiltrate -##ailing -##ilation -##tzer -##uppe -##werk -binoculars -enthusiast -fujian -squeak -##avs -abolitionist -almeida -boredom -hampstead -marsden -rations -##ands -inflated -334 -bonuses -rosalie -patna -##rco -329 -detachments -penitentiary -54th -flourishing -woolf -##dion -##etched -papyrus -##lster -##nsor -##toy -bobbed -dismounted -endelle -inhuman -motorola -tbs -wince -wreath -##ticus -hideout -inspections -sanjay -disgrace -infused -pudding -stalks -##urbed -arsenic -leases -##hyl -##rrard -collarbone -##waite -##wil -dowry -##bant -##edance -genealogical -nitrate -salamanca -scandals -thyroid -necessitated -##! -##" -### -##$ -##% -##& -##' -##( -##) -##* -##+ -##, -##- -##. -##/ -##: -##; -##< -##= -##> -##? -##@ -##[ -##\ -##] -##^ -##_ -##` -##{ -##| -##} -##~ -##¡ -##¢ -##£ -##¤ -##¥ -##¦ -##§ -##¨ -##© -##ª -##« -##¬ -##® -##± -##´ -##µ -##¶ -##· -##º -##» -##¼ -##¾ -##¿ -##æ -##ð -##÷ -##þ -##đ -##ħ -##ŋ -##œ -##ƒ -##ɐ -##ɑ -##ɒ -##ɔ -##ɕ -##ə -##ɡ -##ɣ -##ɨ -##ɪ -##ɫ -##ɬ -##ɯ -##ɲ -##ɴ -##ɹ -##ɾ -##ʀ -##ʁ -##ʂ -##ʃ -##ʉ -##ʊ -##ʋ -##ʌ -##ʎ -##ʐ -##ʑ -##ʒ -##ʔ -##ʰ -##ʲ -##ʳ -##ʷ -##ʸ -##ʻ -##ʼ -##ʾ -##ʿ -##ˈ -##ˡ -##ˢ -##ˣ -##ˤ -##β -##γ -##δ -##ε -##ζ -##θ -##κ -##λ -##μ -##ξ -##ο -##π -##ρ -##σ -##τ -##υ -##φ -##χ -##ψ -##ω -##б -##г -##д -##ж -##з -##м -##п -##с -##у -##ф -##х -##ц -##ч -##ш -##щ -##ъ -##э -##ю -##ђ -##є -##і -##ј -##љ -##њ -##ћ -##ӏ -##ա -##բ -##գ -##դ -##ե -##թ -##ի -##լ -##կ -##հ -##մ -##յ -##ն -##ո -##պ -##ս -##վ -##տ -##ր -##ւ -##ք -##־ -##א -##ב -##ג -##ד -##ו -##ז -##ח -##ט -##י -##ך -##כ -##ל -##ם -##מ -##ן -##נ -##ס -##ע -##ף -##פ -##ץ -##צ -##ק -##ר -##ש -##ת -##، -##ء -##ب -##ت -##ث -##ج -##ح -##خ -##ذ -##ز -##س -##ش -##ص -##ض -##ط -##ظ -##ع -##غ -##ـ -##ف -##ق -##ك -##و -##ى -##ٹ -##پ -##چ -##ک -##گ -##ں -##ھ -##ہ -##ے -##अ -##आ -##उ -##ए -##क -##ख -##ग -##च -##ज -##ट -##ड -##ण -##त -##थ -##द -##ध -##न -##प -##ब -##भ -##म -##य -##र -##ल -##व -##श -##ष -##स -##ह -##ा -##ि -##ी -##ो -##। -##॥ -##ং -##অ -##আ -##ই -##উ -##এ -##ও -##ক -##খ -##গ -##চ -##ছ -##জ -##ট -##ড -##ণ -##ত -##থ -##দ -##ধ -##ন -##প -##ব -##ভ -##ম -##য -##র -##ল -##শ -##ষ -##স -##হ -##া -##ি -##ী -##ে -##க -##ச -##ட -##த -##ந -##ன -##ப -##ம -##ய -##ர -##ல -##ள -##வ -##ா -##ி -##ு -##ே -##ை -##ನ -##ರ -##ಾ -##ක -##ය -##ර -##ල -##ව -##ා -##ก -##ง -##ต -##ท -##น -##พ -##ม -##ย -##ร -##ล -##ว -##ส -##อ -##า -##เ -##་ -##། -##ག -##ང -##ད -##ན -##པ -##བ -##མ -##འ -##ར -##ལ -##ས -##မ -##ა -##ბ -##გ -##დ -##ე -##ვ -##თ -##ი -##კ -##ლ -##მ -##ნ -##ო -##რ -##ს -##ტ -##უ -##ᄀ -##ᄂ -##ᄃ -##ᄅ -##ᄆ -##ᄇ -##ᄉ -##ᄊ -##ᄋ -##ᄌ -##ᄎ -##ᄏ -##ᄐ -##ᄑ -##ᄒ -##ᅡ -##ᅢ -##ᅥ -##ᅦ -##ᅧ -##ᅩ -##ᅪ -##ᅭ -##ᅮ -##ᅯ -##ᅲ -##ᅳ -##ᅴ -##ᅵ -##ᆨ -##ᆫ -##ᆯ -##ᆷ -##ᆸ -##ᆼ -##ᴬ -##ᴮ -##ᴰ -##ᴵ -##ᴺ -##ᵀ -##ᵃ -##ᵇ -##ᵈ -##ᵉ -##ᵍ -##ᵏ -##ᵐ -##ᵒ -##ᵖ -##ᵗ -##ᵘ -##ᵣ -##ᵤ -##ᵥ -##ᶜ -##ᶠ -##‐ -##‑ -##‒ -##– -##— -##― -##‖ -##‘ -##’ -##‚ -##“ -##” -##„ -##† -##‡ -##• -##… -##‰ -##′ -##″ -##› -##‿ -##⁄ -##⁰ -##ⁱ -##⁴ -##⁵ -##⁶ -##⁷ -##⁸ -##⁹ -##⁻ -##ⁿ -##₅ -##₆ -##₇ -##₈ -##₉ -##₊ -##₍ -##₎ -##ₐ -##ₑ -##ₒ -##ₓ -##ₕ -##ₖ -##ₗ -##ₘ -##ₚ -##ₛ -##ₜ -##₤ -##₩ -##€ -##₱ -##₹ -##ℓ -##№ -##ℝ -##™ -##⅓ -##⅔ -##← -##↑ -##→ -##↓ -##↔ -##↦ -##⇄ -##⇌ -##⇒ -##∂ -##∅ -##∆ -##∇ -##∈ -##∗ -##∘ -##√ -##∞ -##∧ -##∨ -##∩ -##∪ -##≈ -##≡ -##≤ -##≥ -##⊂ -##⊆ -##⊕ -##⊗ -##⋅ -##─ -##│ -##■ -##▪ -##● -##★ -##☆ -##☉ -##♠ -##♣ -##♥ -##♦ -##♯ -##⟨ -##⟩ -##ⱼ -##⺩ -##⺼ -##⽥ -##、 -##。 -##〈 -##〉 -##《 -##》 -##「 -##」 -##『 -##』 -##〜 -##あ -##い -##う -##え -##お -##か -##き -##く -##け -##こ -##さ -##し -##す -##せ -##そ -##た -##ち -##っ -##つ -##て -##と -##な -##に -##ぬ -##ね -##の -##は -##ひ -##ふ -##へ -##ほ -##ま -##み -##む -##め -##も -##や -##ゆ -##よ -##ら -##り -##る -##れ -##ろ -##を -##ん -##ァ -##ア -##ィ -##イ -##ウ -##ェ -##エ -##オ -##カ -##キ -##ク -##ケ -##コ -##サ -##シ -##ス -##セ -##タ -##チ -##ッ -##ツ -##テ -##ト -##ナ -##ニ -##ノ -##ハ -##ヒ -##フ -##ヘ -##ホ -##マ -##ミ -##ム -##メ -##モ -##ャ -##ュ -##ョ -##ラ -##リ -##ル -##レ -##ロ -##ワ -##ン -##・ -##ー -##一 -##三 -##上 -##下 -##不 -##世 -##中 -##主 -##久 -##之 -##也 -##事 -##二 -##五 -##井 -##京 -##人 -##亻 -##仁 -##介 -##代 -##仮 -##伊 -##会 -##佐 -##侍 -##保 -##信 -##健 -##元 -##光 -##八 -##公 -##内 -##出 -##分 -##前 -##劉 -##力 -##加 -##勝 -##北 -##区 -##十 -##千 -##南 -##博 -##原 -##口 -##古 -##史 -##司 -##合 -##吉 -##同 -##名 -##和 -##囗 -##四 -##国 -##國 -##土 -##地 -##坂 -##城 -##堂 -##場 -##士 -##夏 -##外 -##大 -##天 -##太 -##夫 -##奈 -##女 -##子 -##学 -##宀 -##宇 -##安 -##宗 -##定 -##宣 -##宮 -##家 -##宿 -##寺 -##將 -##小 -##尚 -##山 -##岡 -##島 -##崎 -##川 -##州 -##巿 -##帝 -##平 -##年 -##幸 -##广 -##弘 -##張 -##彳 -##後 -##御 -##德 -##心 -##忄 -##志 -##忠 -##愛 -##成 -##我 -##戦 -##戸 -##手 -##扌 -##政 -##文 -##新 -##方 -##日 -##明 -##星 -##春 -##昭 -##智 -##曲 -##書 -##月 -##有 -##朝 -##木 -##本 -##李 -##村 -##東 -##松 -##林 -##森 -##楊 -##樹 -##橋 -##歌 -##止 -##正 -##武 -##比 -##氏 -##民 -##水 -##氵 -##氷 -##永 -##江 -##沢 -##河 -##治 -##法 -##海 -##清 -##漢 -##瀬 -##火 -##版 -##犬 -##王 -##生 -##田 -##男 -##疒 -##発 -##白 -##的 -##皇 -##目 -##相 -##省 -##真 -##石 -##示 -##社 -##神 -##福 -##禾 -##秀 -##秋 -##空 -##立 -##章 -##竹 -##糹 -##美 -##義 -##耳 -##良 -##艹 -##花 -##英 -##華 -##葉 -##藤 -##行 -##街 -##西 -##見 -##訁 -##語 -##谷 -##貝 -##貴 -##車 -##軍 -##辶 -##道 -##郎 -##郡 -##部 -##都 -##里 -##野 -##金 -##鈴 -##镇 -##長 -##門 -##間 -##阝 -##阿 -##陳 -##陽 -##雄 -##青 -##面 -##風 -##食 -##香 -##馬 -##高 -##龍 -##龸 -##fi -##fl -##! -##( -##) -##, -##- -##. -##/ -##: -##? -##~ diff --git a/tutorials/fastnlp_test_tutorial.ipynb b/tutorials/fastnlp_test_tutorial.ipynb deleted file mode 100644 index 9b0c1b2e..00000000 --- a/tutorials/fastnlp_test_tutorial.ipynb +++ /dev/null @@ -1,97 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## fastNLP测试说明\n", - "### 测试环境\n", - "fastNLP使用pytest对代码进行单元测试,测试代码在test文件夹下,测试所需数据在test/data_for_tests文件夹下\n", - "测试的步骤主要分为准备数据,执行测试,比对结果,清除环境四步\n", - "测试代码以test_xxx.py命名,以DataSet的测试代码为例,测试代码文件名为test_dataset.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import unittest # 单元测试需要用到unittest\n", - "\n", - "from fastNLP.core.dataset import DataSet\n", - "from fastNLP.core.fieldarray import FieldArray\n", - "from fastNLP.core.instance import Instance\n", - "# 在这个单元测试文件中,需要测试DataSet、FieldArray、以及Instance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class TestDataSet(unittest.TestCase): # 类名字以Test打头,继承unittest.TestCase\n", - "\n", - " def test_init_v1(self): # 测试样例1, 函数名称以test_打头\n", - " # 该测试样例测试的是DataSet的初始化\n", - " ins = Instance(x=[1, 2, 3, 4], y=[5, 6]) # 准备数据\n", - " ds = DataSet([ins] * 40) # 执行测试(调用DataSet的初始化函数)\n", - " self.assertTrue(\"x\" in ds.field_arrays and \"y\" in ds.field_arrays) # 比对结果:'x'跟'y'都是ds的field\n", - " self.assertEqual(ds.field_arrays[\"x\"].content, [[1, 2, 3, 4], ] * 40) # 比对结果: field 'x'的内容正确\n", - " self.assertEqual(ds.field_arrays[\"y\"].content, [[5, 6], ] * 40) # 比对结果: field 'y'的内容正确\n", - " \n", - " def test_init_v2(self): # 测试样例2,该样例测试DataSet的另一种初始化方式\n", - " ds = DataSet({\"x\": [[1, 2, 3, 4]] * 40, \"y\": [[5, 6]] * 40})\n", - " self.assertTrue(\"x\" in ds.field_arrays and \"y\" in ds.field_arrays)\n", - " self.assertEqual(ds.field_arrays[\"x\"].content, [[1, 2, 3, 4], ] * 40)\n", - " self.assertEqual(ds.field_arrays[\"y\"].content, [[5, 6], ] * 40)\n", - " \n", - " def test_init_assert(self): # 测试样例3,该样例测试不规范初始化DataSet时是否会报正确错误\n", - " with self.assertRaises(AssertionError):\n", - " _ = DataSet({\"x\": [[1, 2, 3, 4]] * 40, \"y\": [[5, 6]] * 100})\n", - " with self.assertRaises(AssertionError):\n", - " _ = DataSet([[1, 2, 3, 4]] * 10)\n", - " with self.assertRaises(ValueError):\n", - " _ = DataSet(0.00001)\n", - " \n", - " def test_contains(self): # 测试样例4,该样例测试DataSet的contains函数,是功能测试\n", - " ds = DataSet({\"x\": [[1, 2, 3, 4]] * 40, \"y\": [[5, 6]] * 40})\n", - " self.assertTrue(\"x\" in ds)\n", - " self.assertTrue(\"y\" in ds)\n", - " self.assertFalse(\"z\" in ds)\n", - " \n", - " # 更多测试样例见test/core/test_dataset.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorials/quickstart.ipynb b/tutorials/quickstart.ipynb new file mode 100644 index 00000000..00c30c93 --- /dev/null +++ b/tutorials/quickstart.ipynb @@ -0,0 +1,280 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "# 快速入门" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP.io import CSVLoader\n", + "\n", + "loader = CSVLoader(headers=('raw_sentence', 'label'), sep='\\t')\n", + "dataset = loader.load(\"./sample_data/tutorial_sample_dataset.csv\")\n", + "dataset[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str,\n", + "'sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'words': ['a', 'series', 'of', 'escapades', 'demonstrating', 'the', 'adage', 'that', 'what', 'is', 'good', 'for', 'the', 'goose', 'is', 'also', 'good', 'for', 'the', 'gander', ',', 'some', 'of', 'which', 'occasionally', 'amuses', 'but', 'none', 'of', 'which', 'amounts', 'to', 'much', 'of', 'a', 'story', '.'] type=list}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 将所有字母转为小写, 并所有句子变成单词序列\n", + "dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='sentence')\n", + "dataset.apply(lambda x: x['sentence'].split(), new_field_name='words', is_input=True)\n", + "dataset[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str,\n", + "'sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'words': [4, 1, 6, 1, 1, 2, 1, 11, 153, 10, 28, 17, 2, 1, 10, 1, 28, 17, 2, 1, 5, 154, 6, 149, 1, 1, 23, 1, 6, 149, 1, 8, 30, 6, 4, 35, 3] type=list}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Vocabulary\n", + "\n", + "# 使用Vocabulary类统计单词,并将单词序列转化为数字序列\n", + "vocab = Vocabulary(min_freq=2).from_dataset(dataset, field_name='words')\n", + "vocab.index_dataset(dataset, field_name='words',new_field_name='words')\n", + "dataset[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str,\n", + "'sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'words': [4, 1, 6, 1, 1, 2, 1, 11, 153, 10, 28, 17, 2, 1, 10, 1, 28, 17, 2, 1, 5, 154, 6, 149, 1, 1, 23, 1, 6, 149, 1, 8, 30, 6, 4, 35, 3] type=list,\n", + "'target': 1 type=int}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 将label转为整数,并设置为 target\n", + "dataset.apply(lambda x: int(x['label']), new_field_name='target', is_target=True)\n", + "dataset[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CNNText(\n", + " (embed): Embedding(\n", + " 177, 50\n", + " (dropout): Dropout(p=0.0)\n", + " )\n", + " (conv_pool): ConvMaxpool(\n", + " (convs): ModuleList(\n", + " (0): Conv1d(50, 3, kernel_size=(3,), stride=(1,), padding=(2,))\n", + " (1): Conv1d(50, 4, kernel_size=(4,), stride=(1,), padding=(2,))\n", + " (2): Conv1d(50, 5, kernel_size=(5,), stride=(1,), padding=(2,))\n", + " )\n", + " )\n", + " (dropout): Dropout(p=0.1)\n", + " (fc): Linear(in_features=12, out_features=5, bias=True)\n", + ")" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP.models import CNNText\n", + "model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1)\n", + "model" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(62, 15)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 分割训练集/验证集\n", + "train_data, dev_data = dataset.split(0.2)\n", + "len(train_data), len(dev_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input fields after batch(if batch size is 2):\n", + "\twords: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26]) \n", + "target fields after batch(if batch size is 2):\n", + "\ttarget: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "\n", + "training epochs started 2019-05-09-10-59-39\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=20), HTML(value='')), layout=Layout(display='…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.333333\n", + "\n", + "Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.533333\n", + "\n", + "Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.533333\n", + "\n", + "Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.533333\n", + "\n", + "Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.6\n", + "\n", + "Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.8\n", + "\n", + "Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.8\n", + "\n", + "Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.733333\n", + "\n", + "Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.733333\n", + "\n", + "Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.733333\n", + "\n", + "\n", + "In Epoch:6/Step:12, got best dev performance:AccuracyMetric: acc=0.8\n", + "Reloaded the best model.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'best_eval': {'AccuracyMetric': {'acc': 0.8}},\n", + " 'best_epoch': 6,\n", + " 'best_step': 12,\n", + " 'seconds': 0.22}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Trainer, CrossEntropyLoss, AccuracyMetric\n", + "\n", + "# 定义trainer并进行训练\n", + "trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data,\n", + " loss=CrossEntropyLoss(), metrics=AccuracyMetric())\n", + "trainer.train()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/tutorials/tutorial_1.ipynb b/tutorials/tutorial_1.ipynb new file mode 100644 index 00000000..db302238 --- /dev/null +++ b/tutorials/tutorial_1.ipynb @@ -0,0 +1,831 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "# 详细指南" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 数据读入" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str}" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP.io import CSVLoader\n", + "\n", + "loader = CSVLoader(headers=('raw_sentence', 'label'), sep='\\t')\n", + "dataset = loader.load(\"./sample_data/tutorial_sample_dataset.csv\")\n", + "dataset[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instance表示一个样本,由一个或多个field(域,属性,特征)组成,每个field有名字和值。\n", + "\n", + "在初始化Instance时即可定义它包含的域,使用 \"field_name=field_value\"的写法。" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': fake data type=str,\n", + "'label': 0 type=str}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Instance\n", + "\n", + "dataset.append(Instance(raw_sentence='fake data', label='0'))\n", + "dataset[-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 数据处理" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str,\n", + "'sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'words': [4, 1, 6, 1, 1, 2, 1, 11, 153, 10, 28, 17, 2, 1, 10, 1, 28, 17, 2, 1, 5, 154, 6, 149, 1, 1, 23, 1, 6, 149, 1, 8, 30, 6, 4, 35, 3] type=list,\n", + "'target': 1 type=int}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Vocabulary\n", + "\n", + "# 将所有字母转为小写, 并所有句子变成单词序列\n", + "dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='sentence')\n", + "dataset.apply_field(lambda x: x.split(), field_name='sentence', new_field_name='words')\n", + "\n", + "# 使用Vocabulary类统计单词,并将单词序列转化为数字序列\n", + "vocab = Vocabulary(min_freq=2).from_dataset(dataset, field_name='words')\n", + "vocab.index_dataset(dataset, field_name='words',new_field_name='words')\n", + "\n", + "# 将label转为整数\n", + "dataset.apply(lambda x: int(x['label']), new_field_name='target')\n", + "dataset[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'raw_sentence': A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'label': 1 type=str,\n", + "'sentence': a series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story . type=str,\n", + "'words': [4, 1, 6, 1, 1, 2, 1, 11, 153, 10, 28, 17, 2, 1, 10, 1, 28, 17, 2, 1, 5, 154, 6, 149, 1, 1, 23, 1, 6, 149, 1, 8, 30, 6, 4, 35, 3] type=list,\n", + "'target': 1 type=int,\n", + "'seq_len': 37 type=int}\n" + ] + } + ], + "source": [ + "# 增加长度信息\n", + "dataset.apply_field(lambda x: len(x), field_name='words', new_field_name='seq_len')\n", + "print(dataset[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 使用内置模块CNNText\n", + "设置为符合内置模块的名称" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CNNText(\n", + " (embed): Embedding(\n", + " 177, 50\n", + " (dropout): Dropout(p=0.0)\n", + " )\n", + " (conv_pool): ConvMaxpool(\n", + " (convs): ModuleList(\n", + " (0): Conv1d(50, 3, kernel_size=(3,), stride=(1,), padding=(2,))\n", + " (1): Conv1d(50, 4, kernel_size=(4,), stride=(1,), padding=(2,))\n", + " (2): Conv1d(50, 5, kernel_size=(5,), stride=(1,), padding=(2,))\n", + " )\n", + " )\n", + " (dropout): Dropout(p=0.1)\n", + " (fc): Linear(in_features=12, out_features=5, bias=True)\n", + ")" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP.models import CNNText\n", + "\n", + "model_cnn = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1)\n", + "model_cnn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们在使用内置模块的时候,还应该使用应该注意把 field 设定成符合内置模型输入输出的名字。" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "words\n", + "seq_len\n", + "target\n" + ] + } + ], + "source": [ + "from fastNLP import Const\n", + "\n", + "dataset.rename_field('words', Const.INPUT)\n", + "dataset.rename_field('seq_len', Const.INPUT_LEN)\n", + "dataset.rename_field('target', Const.TARGET)\n", + "\n", + "dataset.set_input(Const.INPUT, Const.INPUT_LEN)\n", + "dataset.set_target(Const.TARGET)\n", + "\n", + "print(Const.INPUT)\n", + "print(Const.INPUT_LEN)\n", + "print(Const.TARGET)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 分割训练集/验证集/测试集" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(64, 7, 7)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_dev_data, test_data = dataset.split(0.1)\n", + "train_data, dev_data = train_dev_data.split(0.1)\n", + "len(train_data), len(dev_data), len(test_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 训练(model_cnn)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### loss\n", + "训练模型需要提供一个损失函数\n", + "\n", + "下面提供了一个在分类问题中常用的交叉熵损失。注意它的**初始化参数**。\n", + "\n", + "pred参数对应的是模型的forward返回的dict的一个key的名字,这里是\"output\"。\n", + "\n", + "target参数对应的是dataset作为标签的field的名字,这里是\"label_seq\"。" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from fastNLP import CrossEntropyLoss\n", + "\n", + "# loss = CrossEntropyLoss()\n", + "# 等价于\n", + "loss = CrossEntropyLoss(pred=Const.OUTPUT, target=Const.TARGET)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Metric\n", + "定义评价指标\n", + "\n", + "这里使用准确率。参数的“命名规则”跟上面类似。\n", + "\n", + "pred参数对应的是模型的predict方法返回的dict的一个key的名字,这里是\"predict\"。\n", + "\n", + "target参数对应的是dataset作为标签的field的名字,这里是\"label_seq\"。" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from fastNLP import AccuracyMetric\n", + "\n", + "# metrics=AccuracyMetric()\n", + "# 等价于\n", + "metrics=AccuracyMetric(pred=Const.OUTPUT, target=Const.TARGET)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input fields after batch(if batch size is 2):\n", + "\twords: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 16]) \n", + "\tseq_len: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "target fields after batch(if batch size is 2):\n", + "\ttarget: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "\n", + "training epochs started 2019-05-12-21-38-34\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=20), HTML(value='')), layout=Layout(display='…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.285714\n", + "\n", + "Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.428571\n", + "\n", + "Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.428571\n", + "\n", + "Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.428571\n", + "\n", + "Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.428571\n", + "\n", + "Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.428571\n", + "\n", + "Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.428571\n", + "\n", + "Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.857143\n", + "\n", + "\n", + "In Epoch:8/Step:16, got best dev performance:AccuracyMetric: acc=0.857143\n", + "Reloaded the best model.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'best_eval': {'AccuracyMetric': {'acc': 0.857143}},\n", + " 'best_epoch': 8,\n", + " 'best_step': 16,\n", + " 'seconds': 0.21}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Trainer\n", + "\n", + "trainer = Trainer(model=model_cnn, train_data=train_data, dev_data=dev_data, loss=loss, metrics=metrics)\n", + "trainer.train()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 测试(model_cnn)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[tester] \n", + "AccuracyMetric: acc=0.857143\n" + ] + }, + { + "data": { + "text/plain": [ + "{'AccuracyMetric': {'acc': 0.857143}}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Tester\n", + "\n", + "tester = Tester(test_data, model_cnn, metrics=AccuracyMetric())\n", + "tester.test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 编写自己的模型\n", + "\n", + "完全支持 pytorch 的模型,与 pytorch 唯一不同的是返回结果是一个字典,字典中至少需要包含 \"pred\" 这个字段" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "\n", + "class LSTMText(nn.Module):\n", + " def __init__(self, vocab_size, embedding_dim, output_dim, hidden_dim=64, num_layers=2, dropout=0.5):\n", + " super().__init__()\n", + "\n", + " self.embedding = nn.Embedding(vocab_size, embedding_dim)\n", + " self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=num_layers, bidirectional=True, dropout=dropout)\n", + " self.fc = nn.Linear(hidden_dim * 2, output_dim)\n", + " self.dropout = nn.Dropout(dropout)\n", + "\n", + " def forward(self, words):\n", + " # (input) words : (batch_size, seq_len)\n", + " words = words.permute(1,0)\n", + " # words : (seq_len, batch_size)\n", + "\n", + " embedded = self.dropout(self.embedding(words))\n", + " # embedded : (seq_len, batch_size, embedding_dim)\n", + " output, (hidden, cell) = self.lstm(embedded)\n", + " # output: (seq_len, batch_size, hidden_dim * 2)\n", + " # hidden: (num_layers * 2, batch_size, hidden_dim)\n", + " # cell: (num_layers * 2, batch_size, hidden_dim)\n", + "\n", + " hidden = torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1)\n", + " hidden = self.dropout(hidden)\n", + " # hidden: (batch_size, hidden_dim * 2)\n", + "\n", + " pred = self.fc(hidden.squeeze(0))\n", + " # result: (batch_size, output_dim)\n", + " return {\"pred\":pred}" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input fields after batch(if batch size is 2):\n", + "\twords: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 16]) \n", + "\tseq_len: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "target fields after batch(if batch size is 2):\n", + "\ttarget: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "\n", + "training epochs started 2019-05-12-21-38-36\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=20), HTML(value='')), layout=Layout(display='…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.714286\n", + "\n", + "Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.857143\n", + "\n", + "\n", + "In Epoch:6/Step:12, got best dev performance:AccuracyMetric: acc=0.857143\n", + "Reloaded the best model.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'best_eval': {'AccuracyMetric': {'acc': 0.857143}},\n", + " 'best_epoch': 6,\n", + " 'best_step': 12,\n", + " 'seconds': 2.15}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_lstm = LSTMText(len(vocab),50,5)\n", + "trainer = Trainer(model=model_lstm, train_data=train_data, dev_data=dev_data, loss=loss, metrics=metrics)\n", + "trainer.train()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[tester] \n", + "AccuracyMetric: acc=0.857143\n" + ] + }, + { + "data": { + "text/plain": [ + "{'AccuracyMetric': {'acc': 0.857143}}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tester = Tester(test_data, model_lstm, metrics=AccuracyMetric())\n", + "tester.test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 使用 Batch编写自己的训练过程" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 0 Avg Loss: 3.11 18ms\n", + "Epoch 1 Avg Loss: 2.88 30ms\n", + "Epoch 2 Avg Loss: 2.69 42ms\n", + "Epoch 3 Avg Loss: 2.47 54ms\n", + "Epoch 4 Avg Loss: 2.38 67ms\n", + "Epoch 5 Avg Loss: 2.10 78ms\n", + "Epoch 6 Avg Loss: 2.06 91ms\n", + "Epoch 7 Avg Loss: 1.92 103ms\n", + "Epoch 8 Avg Loss: 1.91 114ms\n", + "Epoch 9 Avg Loss: 1.76 126ms\n", + "[tester] \n", + "AccuracyMetric: acc=0.571429\n" + ] + }, + { + "data": { + "text/plain": [ + "{'AccuracyMetric': {'acc': 0.571429}}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import BucketSampler\n", + "from fastNLP import Batch\n", + "import torch\n", + "import time\n", + "\n", + "model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1)\n", + "\n", + "def train(epoch, data):\n", + " optim = torch.optim.Adam(model.parameters(), lr=0.001)\n", + " lossfunc = torch.nn.CrossEntropyLoss()\n", + " batch_size = 32\n", + "\n", + " # 定义一个Batch,传入DataSet,规定batch_size和去batch的规则。\n", + " # 顺序(Sequential),随机(Random),相似长度组成一个batch(Bucket)\n", + " train_sampler = BucketSampler(batch_size=batch_size, seq_len_field_name='seq_len')\n", + " train_batch = Batch(batch_size=batch_size, dataset=data, sampler=train_sampler)\n", + " \n", + " start_time = time.time()\n", + " for i in range(epoch):\n", + " loss_list = []\n", + " for batch_x, batch_y in train_batch:\n", + " optim.zero_grad()\n", + " output = model(batch_x['words'])\n", + " loss = lossfunc(output['pred'], batch_y['target'])\n", + " loss.backward()\n", + " optim.step()\n", + " loss_list.append(loss.item())\n", + " print('Epoch {:d} Avg Loss: {:.2f}'.format(i, sum(loss_list) / len(loss_list)),end=\" \")\n", + " print('{:d}ms'.format(round((time.time()-start_time)*1000)))\n", + " loss_list.clear()\n", + " \n", + "train(10, train_data)\n", + "tester = Tester(test_data, model, metrics=AccuracyMetric())\n", + "tester.test()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 使用 Callback 实现自己想要的效果" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "input fields after batch(if batch size is 2):\n", + "\twords: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 16]) \n", + "\tseq_len: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "target fields after batch(if batch size is 2):\n", + "\ttarget: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) \n", + "\n", + "training epochs started 2019-05-12-21-38-40\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, layout=Layout(flex='2'), max=20), HTML(value='')), layout=Layout(display='…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.285714\n", + "\n", + "Sum Time: 51ms\n", + "\n", + "\n", + "Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.285714\n", + "\n", + "Sum Time: 69ms\n", + "\n", + "\n", + "Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.285714\n", + "\n", + "Sum Time: 91ms\n", + "\n", + "\n", + "Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Sum Time: 107ms\n", + "\n", + "\n", + "Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Sum Time: 125ms\n", + "\n", + "\n", + "Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Sum Time: 142ms\n", + "\n", + "\n", + "Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Sum Time: 158ms\n", + "\n", + "\n", + "Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.571429\n", + "\n", + "Sum Time: 176ms\n", + "\n", + "\n", + "Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.714286\n", + "\n", + "Sum Time: 193ms\n", + "\n", + "\n", + "Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.857143\n", + "\n", + "Sum Time: 212ms\n", + "\n", + "\n", + "\n", + "In Epoch:10/Step:20, got best dev performance:AccuracyMetric: acc=0.857143\n", + "Reloaded the best model.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'best_eval': {'AccuracyMetric': {'acc': 0.857143}},\n", + " 'best_epoch': 10,\n", + " 'best_step': 20,\n", + " 'seconds': 0.2}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from fastNLP import Callback\n", + "\n", + "start_time = time.time()\n", + "\n", + "class MyCallback(Callback):\n", + " def on_epoch_end(self):\n", + " print('Sum Time: {:d}ms\\n\\n'.format(round((time.time()-start_time)*1000)))\n", + " \n", + "\n", + "model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1)\n", + "trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data,\n", + " loss=CrossEntropyLoss(), metrics=AccuracyMetric(), callbacks=[MyCallback()])\n", + "trainer.train()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/tutorials/tutorial_for_developer.md b/tutorials/tutorial_for_developer.md deleted file mode 100644 index b2ec0b98..00000000 --- a/tutorials/tutorial_for_developer.md +++ /dev/null @@ -1,283 +0,0 @@ -# fastNLP开发者指南 -#### 本教程涉及以下类: -- DataSet -- Sampler -- Batch -- Model -- Loss -- Metric -- Trainer -- Tester - -#### DataSet: 用于承载数据。 -1. DataSet里面每个元素只能是以下的三类`np.float64`, `np.int64`, `np.str`。如果传入的数据是`int`则被转换为`np.int64`, `float`被转为`np.float64`。 -2. DataSet可以将field设置为input或者target。其中被设置为input的field会被传递给Model.forward, 这个过程中我们是通过键匹配完成传递的。举例来说,假设DataSet中有'x1', 'x2', 'x3'被设置为了input,而 - - 函数是Model.forward(self, x1, x3), 那么DataSet中'x1', 'x3'会被传递给forward函数。多余的'x2'会被忽略 - - 函数是Model.forward(self, x1, x4), 这里多需要了一个'x4', 但是DataSet的input field中没有这个field,会报错。 - - 函数是Model.forward(self, x1, **kwargs), 会把'x1', 'x2', 'x3'都传入。但如果是Model.forward(self, x4, **kwargs)就会发生报错,因为没有'x4'。 -3. 对于设置为target的field的名称,我们建议取名为'target'(如果只有一个需要predict的值),但是不强制。后面会讲为什么target可以不强制。 -DataSet应该是不需要单独再开发的,如果有不能满足的场景,请在开发群提出或者github提交issue。 - -#### Sampler: 给定一个DataSet,返回一个序号的list,Batch按照这个list输出数据。 -Sampler需要继承fastNLP.core.sampler.BaseSampler -```python -class BaseSampler(object): - """The base class of all samplers. - - Sub-classes must implement the __call__ method. - __call__ takes a DataSet object and returns a list of int - the sampling indices. - """ -def __call__(self, *args, **kwargs): - raise NotImplementedError - -# 子类需要复写__call__方法。这个函数只能有一个必选参数, 且必须是DataSet类别, 否则Trainer没法调 -class SonSampler(BaseSampler): - def __init__(self, xxx): - # 可以实现init也不可以不实现。 - pass - def __call__(self, data_set): - pass -``` - -#### Batch: 将DataSet中设置为input和target的field取出来构成batch_x, batch_y -并且根据情况(主要根据数据类型能不能转为Tensor)将数据转换为pytorch的Tensor。batch中sample的取出顺序是由Sampler决定的。 -Sampler是传入一个DataSet,返回一个与DataSet等长的序号list,Batch一次会取出batch_size个sample(最后一个batch可能数量不足batch_size个)。 -举例: -1. SequentialSampler是顺序采样 - - 假设传入的DataSet长度是100, SequentialSampler返回的序号list就是[0, 1, ...,98, 99]. batch_size如果被设置为4,那么第一个batch所获取的instance就是[0, 1, 2, 3]这四个instance. 第二个batch所获取instace就是[4, 5, 6, 7], ...直到采完所有的sample。 -2. RandomSampler是随机采样 - - 假设传入的DataSet长度是100, RandomSampler返回的序号list可能是[0, 99, 20, 5, 3, 1, ...]. 依次按照batch_size的大小取出sample。 - -Batch应该不需要继承与开发,如果你有特殊需求请在开发群里提出。 - -#### Model:用户自定的Model -必须是nn.Module的子类 -1. 必须实现forward方法,并且forward方法不能出现*arg这种参数. 例如 - ```python - def forward(self, word_seq, *args): #这是不允许的. - # ... - pass - ``` - 返回值必须是dict的 - ```python - def forward(self, word_seq, seq_lens): - xxx = "xxx" - return {'pred': xxx} #return的值必须是dict的。里面的预测的key推荐使用pred,但是不做强制限制。输出元素数目不限。 - ``` -2. 如果实现了predict方法,在做evaluation的时候将调用predict方法而不是forward。如果没有predict方法,则在evaluation时调用forward方法。predict方法也不能使用*args这种参数形式,同时结果也必须返回一个dict,同样推荐key为'pred'。 - -#### Loss: 根据model.forward()返回的prediction(是一个dict)和batch_y计算相应的loss -1. 先介绍"键映射"。 如在DataSet, Model一节所看见的那样,fastNLP并不限制Model.forward()的返回值,也不限制DataSet中target field的key。计算的loss的时候,怎么才能知道从哪里取值呢? -这里以CrossEntropyLoss为例,一般情况下, 计算CrossEntropy需要prediction和target两个值。而在CrossEntropyLoss初始化时可以传入两个参数(pred=None, target=None), 这两个参数接受的类型是str,假设(pred='output', target='label'),那么CrossEntropyLoss会使用'output'这个key在forward的output与batch_y中寻找值;'label'也是在forward的output与batch_y中寻找值。注意这里pred或target的来源并不一定非要来自于model.forward与batch_y,也可以只来自于forward的结果。 -2. 如何创建一个自己的loss - - 使用fastNLP.LossInForward, 在model.forward()的结果中包含一个为loss的key。 - - trainer中使用loss(假设loss=CrossEntropyLoss())的时候其实是 - los = loss(prediction, batch_y),即直接调用的是`loss.__call__()`方法,但是CrossEntropyLoss里面并没有自己实现`__call__`方法,这是因为`__call__`在LossBase中实现了。所有的loss必须继承fastNLP.core.loss.LossBase, 下面先说一下LossBase的几个方法,见下一节。 -3. 尽量不要复写`__call__()`, `_init_param_map()`方法。 - -```python -class LossBase(): - def __init__(self): - self.param_map = {} # 一般情况下也不需要自己创建。调用_init_param_map()更好 - self._checked = False # 这个参数可以忽略 - - def _init_param_map(self, key_map=None, **kwargs): - # 这个函数是用于注册Loss的“键映射”,有两种传值方法, - # 第一种是通过key_map传入dict,取值是用value到forward和batch_y取 - # key_map = {'pred': 'output', 'target': 'label'} - # 第二种是自己写 - # _init_param_map(pred='output', target='label') - # 为什么会提供这么一个方法?通过调用这个方法会自动注册param_map,并会做一些检查,防止出现传入的key其实并不是get_loss - # 的一个参数。注意传入这个方法的参数必须都是需要做键映射的内容,其它loss参数不要传入。如果传入(pred=None, target=None) - # 则__call__()会到pred_dict与target_dict去寻找key为'pred'和'target'的值。 - # 但这个参数不是必须要调用的。 - - def __call__(self, pred_dict, target_dict, check=False): # check=False忽略这个参数,之后应该会被删除的 - # 这个函数主要会做一些check的工作,比如pred_dict与target_dict中是否包含了计算loss所必须的key等。检查通过,则调用get_loss - # 方法。 - fast_param = self._fast_param_map(predict_dict, target_dict): - if fast_param: - return self.get_loss(**fast_param) - # 如果没有fast_param则通过匹配参数然后调用get_loss完成 - xxxx - return loss # 返回为Tensor的loss - def _fast_param_map(self, pred_dict, target_dict): - # 这是一种快速计算loss的机制,因为在很多情况下其实都不需要通过"键映射",比如计算loss时,pred_dict只有一个元素, - # target_dict也只有一个元素,那么无歧义地就可以把预测值与实际值用于计算loss, 基类判断了这种情况(可能还有其它无歧义的情况)。 - # 即_fast_param_map成功的话,就不需要使用键映射,这样即使在没有传递或者传递错误"键映射"的情况也可以直接计算loss。 - # 返回值是一个dict, 如果匹配成功,应该返回类似{'pred':value, 'target': value}的结果;如果dict为空则说明匹配失败, - # __call__方法会继续执行。 - - def get_loss(self, *args, **kwargs): - # 这个是一定需要实现的,计算loss的地方。 - # (1) get_loss中一定不能包含*arg这种参数形式。 - # (2) 如果包含**kwargs这种参数,这会将pred_dict与target_dict中所有参数传入。但是建议不要用这个参数 - raise NotImplementedError - -# 下面使用L1Loss举例 -class L1Loss(LossBase): # 继承LossBase - # 初始化需要映射的值,这里需要映射的值'pred', 'target'必须与get_loss需要参数名是对应的 - def __init__(self, pred=None, target=None): - super(L1Loss, self).__init__() - # 这里传入_init_param_map以使得pred和target被正确注册,但这一步不是必须的, 建议调用。传入_init_param_map的是用于 - # “键映射"的键值对。假设初始化__init__(pred=None, target=None, threshold=0.1)中threshold是用于控制loss计算的,则 - # 不要将threshold传入_init_param_map. - self._init_param_map(pred=pred, target=target) - - def get_loss(self, pred, target): - # 这里'pred', 'target'必须和初始化的映射是一致的。 - return F.l1_loss(input=pred, target=target) #直接返回一个loss即可 -``` - -### Metric: 根据Model.forward()或者Model.predict()的结果计算metric -metric的设计和loss的设计类似。都是传入pred_dict与target_dict进行计算。但是metric的pred_dict来源可能是Model.forward的返回值, 也可能是Model.predict(如果Model具有predict方法则会调用predict方法)的返回值,下面统一用pred_dict代替。 -1. 这里的"键映射"与loss的"键映射"是类似的。举例来说,若Metric(pred='output', target='label'),则使用'output'到pred_dict和target_dict中寻找pred, 用'label'寻找target。 -2. 如何创建一个自己的Metric方法 -Metric与loss的计算不同在于,Metric的计算有两个步骤。 - - **每个batch的输出**都会调用Metric的``__call__(pred_dict, target_dict)``方法,而``__call__``方法会调用evaluate()(需要实现)方法。 - - 在所有batch传入之后,调用Metric的get_metric()方法得到最终的metric值。 - - 所以Metric在调用evaluate方法时,根据拿到的数据: pred_dict与batch_y, 改变自己的状态(比如累加正确的次数,总的sample数等)。在调用get_metric()的时候给出一个最终计算结果。 - 所有的Metric必须继承自fastNLP.core.metrics.MetricBase. 例子见下一个cell -3. 尽量不要复写``__call__()``,``_init_param_map()``方法。 - -```python -class MetricBase: - def __init__(self): - self.param_map = {} # 一般情况下也不需要自己创建。调用_init_param_map()更好 - self._checked = False # 这个参数可以忽略 - - def _init_param_map(self, key_map=None, **kwargs): - # 这个函数是用于注册Metric的“键映射”,有两种传值方法, - # 第一种是通过key_map传入dict,取值是用value到forward和batch_y取 - # key_map = {'pred': 'output', 'target': 'label'} - # 第二种是自己写(建议使用改种方式) - # _init_param_map(pred='output', target='label') - # 为什么会提供这么一个方法?通过调用这个方法会自动注册param_map,并会做一些检查,防止出现传入的key其实并不是evaluate() - # 的一个参数。注意传入这个方法的参数必须都是需要做键映射的内容,其它evaluate参数不要传入。如果传入(pred=None, target=None) - # 则__call__()会到pred_dict与target_dict去寻找key为'pred'和'target'的值。 - # 但这个参数不是必须要调用的。 - pass - - def __call__(self, pred_dict, target_dict, check=False): # check=False忽略这个参数,之后应该会被删除的 - # 这个函数主要会做一些check的工作,比如pred_dict与target_dict中是否包含了计算evaluate所必须的key等。检查通过,则调用 - # evaluate方法。 - fast_param = self._fast_param_map(predict_dict, target_dict): - if fast_param: - return self.evaluate(**fast_param) - # 如果没有fast_param则通过匹配参数然后调用get_loss完成 - # xxxx - - def _fast_param_map(self, pred_dict, target_dict): - # 这是一种快速计算loss的机制,因为在很多情况下其实都不需要通过"键映射",比如evaluate时,pred_dict只有一个元素, - # target_dict也只有一个元素,那么无歧义地就可以把预测值与实际值用于计算metric, 基类判断了这种情况(可能还有其它无歧义的 - # 情况)。即_fast_param_map成功的话,就不需要使用键映射,这样即使在没有传递或者传递错误"键映射"的情况也可以直接计算metric。 - # 返回值是一个dict, 如果匹配成功,应该返回类似{'pred':value, 'target': value}的结果;如果dict为空则说明匹配失败, - # __call__方法会继续尝试匹配。 - pass - - def evaluate(self, *args, **kwargs): - # 这个是一定需要实现的,累加metric状态 - # (1) evaluate()中一定不能包含*arg这种参数形式。 - # (2) 如果包含**kwargs这种参数,这会将pred_dict与target_dict中所有参数传入。但是建议不要用这个参数 - raise NotImplementedError - - def get_metric(self, reset=True): - # 这是一定需要实现的,获取最终的metric。返回值必须是一个dict。会在所有batch传入之后调用 - raise NotImplementedError - -# 下面使用AccuracyMetric举例 -class AccuracyMetric(MetricBase): # MetricBase - # 初始化需要映射的值,这里需要映射的值'pred', 'target'必须与evaluate()需要参数名是对应的 - def __init__(self, pred=None, target=None): - super(AccuracyMetric, self).__init__() - # 这里传入_init_param_map以使得pred和target被正确注册,但这一步不是必须的, 建议调用。传入_init_param_map的是用于 - # “键映射"的键值对。假设初始化__init__(pred=None, target=None, threshold=0.1)中threshold是用于控制loss计算的,则 - # 不要将threshold传入_init_param_map. - self._init_param_map(pred=pred, target=target) - - self.total = 0 # 用于累加一共有多少sample - self.corr = 0 # 用于累加一共有多少正确的sample - - def evaluate(self, pred, target): - # 对pred和target做一些基本的判断或者预处理等 - if pred.size()==target.size() and len(pred.size())=1: #如果pred已经做了argmax - pass - elif len(pred.size())==2 and len(target.size())==1: # pred还没有进行argmax - pred = pred.argmax(dim=1) - else: - raise ValueError("The shape of pred and target should be ((B, n_classes), (B, )) or (" - "(B,),(B,)).") - assert pred.size(0)==target.size(0), "Mismatch batch size." - # 进行相应的累加 - self.total += pred.size(0) - self.corr += torch.sum(torch.eq(pred, target).float()).item() - - def get_metric(self, reset=True): - # reset用于指示是否清空累加信息。默认为True - # 这个函数需要返回dict,可以包含多个metric。 - metric = {} - metric['acc'] = self.corr/self.total - if reset: - self.total = 0 - self.corr = 0 - return metric -``` - -#### Tester: 用于做evaluation,应该不需要更改 -重要的初始化参数有data, model, metric;比较重要的function是test()。 - -test中的运行过程 -``` -predict_func = 如果有model.predict则为model.predict, 否则是model.forward -for batch_x, batch_y in batch: -# (1) 同步数据与model -# (2) 根据predict_func的参数从batch_x中取出数据传入到predict_func中,得到结果pred_dict -# (3) 调用metric(pred_dict, batch_y -# (4) 当所有batch都运行完毕,会调用metric的get_metric方法,并且以返回的值作为evaluation的结果 -metric.get_metric() -``` - -#### Trainer: 对训练过程的封装。 -里面比较重要的function是train() -train()中的运行过程 -``` -(1) 创建batch - batch = Batch(dataset, batch_size, sampler=sampler) - for batch_x, batch_y in batch: - # ... - batch_x,batch_y都是dict。batch_x是DataSet中被设置为input的field;batch_y是DataSet中被设置为target的field。 - 两个dict中的key就是DataSet中的key,value会根据情况做好padding的tensor。 -(2)会将batch_x, batch_y中tensor移动到model所在的device -(3)根据model.forward的参数列表, 从batch_x中取出需要传递给forward的数据。 -(4)获取model.forward的输出结果pred_dict,并与batch_y一起传递给loss函数, 求得loss -(5)对loss进行反向梯度并更新参数 -(6) 如果有验证集,则需要做验证 - tester = Tester(model, dev_data,metric) - eval_results = tester.test() -(7) 如果eval_results是当前的最佳结果,则保存模型。 -``` - -#### 其他 -Trainer中还提供了"预跑"的功能。该功能通过check_code_level管理,如果check_code_level为-1,则不进行"预跑"。 - -check_code_level=0,1,2代表不同的提醒级别。 -目前不同提醒级别对应的是对DataSet中设置为input或target但又没有使用的field的提醒级别。 -0是忽略(默认);1是会warning发生了未使用field的情况;2是出现了unused会直接报错并退出运行 - -"预跑"的主要目的有两个: -- 防止train完了之后进行evaluation的时候出现错误。之前的train就白费了 -- 由于存在"键映射",直接运行导致的报错可能不太容易debug,通过"预跑"过程的报错会有一些debug提示 - -"预跑"会进行以下的操作: -- 使用很小的batch_size, 检查batch_x中是否包含Model.forward所需要的参数。只会运行两个循环。 -- 将Model.foward的输出pred_dict与batch_y输入到loss中, 并尝试backward. 不会更新参数,而且grad会被清零 - 如果传入了dev_data,还将进行metric的测试 -- 创建Tester,并传入少量数据,检测是否可以正常运行 - -"预跑"操作是在Trainer初始化的时候执行的。 - -正常情况下,应该不需要改动"预跑"的代码。但如果你遇到bug或者有什么好的建议,欢迎在开发群或者github提交issue。 - - From 927d3867d48bb73b6226545858964a581790adb2 Mon Sep 17 00:00:00 2001 From: ChenXin Date: Wed, 22 May 2019 20:40:16 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E6=8A=8A=E6=96=87=E6=A1=A3=E7=9A=84?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E6=94=BE=E5=9C=A8=E4=B8=8A=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f4a8f2a6..fd6b593c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,11 @@ fastNLP 依赖如下包: pip install fastNLP ``` +## 参考资源 + +- [文档](https://fastnlp.readthedocs.io/zh/latest/) +- [源码](https://github.com/fastnlp/fastNLP) + ## 内置组件 @@ -102,12 +107,6 @@ fastNLP的大致工作流程如上图所示,而项目结构如下: -## 参考资源 - -- [教程](https://github.com/fastnlp/fastNLP/tree/master/tutorials) -- [文档](https://fastnlp.readthedocs.io/en/latest/) -- [源码](https://github.com/fastnlp/fastNLP) - -*In memory of @FengZiYjun. May his soul rest in peace. We will miss you very very much!* \ No newline at end of file +*In memory of @FengZiYjun. May his soul rest in peace. We will miss you very very much!* From 34a638175f17b87d238a33a2979f46676575e81a Mon Sep 17 00:00:00 2001 From: ChenXin Date: Thu, 23 May 2019 21:24:56 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=BB=93?= =?UTF-8?q?=E5=90=88=20fitlog=20=E7=9A=84=E6=95=99=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/figures/fitlogChart.png | Bin 0 -> 271505 bytes docs/source/figures/fitlogTable.png | Bin 0 -> 168457 bytes docs/source/index.rst | 1 + docs/source/user/with_fitlog.rst | 119 +++++++++++++++++++++++++++- fastNLP/core/callback.py | 40 +++++----- 5 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 docs/source/figures/fitlogChart.png create mode 100644 docs/source/figures/fitlogTable.png diff --git a/docs/source/figures/fitlogChart.png b/docs/source/figures/fitlogChart.png new file mode 100644 index 0000000000000000000000000000000000000000..57ae16830641570bd7ebacb364c7ca7a951050be GIT binary patch literal 271505 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

K|K8tHt^Bjdt1Zfx1%3#oOJ+U7s603h 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

Ehqb@$*9R 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 z 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 快速入门 详细指南 + 科研指南 API 文档 ------------- diff --git a/docs/source/user/with_fitlog.rst b/docs/source/user/with_fitlog.rst index 97c3ea71..14cf7d91 100644 --- a/docs/source/user/with_fitlog.rst +++ b/docs/source/user/with_fitlog.rst @@ -2,4 +2,121 @@ 科研向导 ================= -本文介绍使用 fastNLP 和 fitlog 进行科学研究的方法 \ No newline at end of file +本文介绍使用 fastNLP 和 fitlog 结合进行科研的方法。 + +首先,我们需要安装 `fitlog `_ 。你需要确认你的电脑中没有其它名为为 `fitlog` 的命令。 + +我们从命令行中进入到一个文件夹,现在我们要在文件夹中创建我们的 fastNLP 项目。你可以在命令行输入 `fitlog init test1` , +然后你会看到如下提示:: + + Initialized empty Git repository in /Users/fdujyn/workspaces/test1/.git/ + Auto commit by fitlog + Initialized empty Git repository in /Users/fdujyn/workspaces/test1/.git/ + Fitlog project test1 is initialized. + +这表明你已经创建成功了项目文件夹,并且在项目文件夹中已经初始化了 Git。如果你不想初始化 Git, +可以参考文档 `命令行工具 `_ + +现在我们进入你创建的项目文件夹 test1 中,可以看到有一个名为 logs 的文件夹,后面我们将会在里面存放你的实验记录。 +同时也有一个名为 main.py 的文件,是我们推荐你使用的入口文件。文件的内容如下:: + + import fitlog + + fitlog.commit(__file__) # auto commit your codes + fitlog.add_hyper_in_file (__file__) # record your hyperparameters + + """ + Your training code here, you may use these functions to log your result: + fitlog.add_hyper() + fitlog.add_loss() + fitlog.add_metric() + fitlog.add_best_metric() + ...... + """ + + fitlog.finish() # finish the logging + +我们推荐你保留除注释外的四行代码,它们有助于你的实验, +他们的具体用处参见文档 `用户 API `_ + +我们假定你要进行前两个教程中的实验,并已经把数据复制到了项目根目录下的 tutorial_sample_dataset.csv 文件中。 +现在我们编写如下的训练代码,使用 :class:`~fastNLP.core.callback.FitlogCallback` 进行实验记录保存:: + + import fitlog + from fastNLP import Vocabulary, Trainer, CrossEntropyLoss, AccuracyMetric + from fastNLP.io import CSVLoader + from fastNLP.models import CNNText + from fastNLP.core.callback import FitlogCallback + + fitlog.commit(__file__) # auto commit your codes + fitlog.add_hyper_in_file (__file__) # record your hyperparameters + + ############hyper + word_embed = 50 + dropout = 0.1 + ############hyper + + loader = CSVLoader(headers=('raw_sentence', 'label'), sep='\t') + dataset = loader.load("tutorial_sample_dataset.csv") + + dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='sentence') + dataset.apply(lambda x: x['sentence'].split(), new_field_name='words', is_input=True) + dataset.apply(lambda x: int(x['label']), new_field_name='target', is_target=True) + vocab = Vocabulary(min_freq=2).from_dataset(dataset, field_name='words') + vocab.index_dataset(dataset, field_name='words',new_field_name='words') + + model = CNNText((len(vocab),word_embed), num_classes=5, padding=2, dropout=dropout) + + train_dev_data, test_data = dataset.split(0.1) + train_data, dev_data = train_dev_data.split(0.1) + + trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, + loss=CrossEntropyLoss(), metrics=AccuracyMetric(), + callbacks=[FitlogCallback(test_data)]) + trainer.train() + + fitlog.finish() # finish the logging + +用命令行在项目目录下执行 `python main.py` 之后,输出结果如下:: + + Auto commit by fitlog + input fields after batch(if batch size is 2): + words: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 11]) + target fields after batch(if batch size is 2): + target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + + training epochs started 2019-05-23-21-11-51 + Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.428571 + + Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.571429 + + Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.571429 + + Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.142857 + + Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.571429 + + + In Epoch:5/Step:10, got best dev performance:AccuracyMetric: acc=0.571429 + Reloaded the best model. + +现在,我们在项目目录下输入 `fitlog log logs` ,命令行会启动一个网页,默认 url 为 ``0.0.0.0:5000`` 。 +我们在浏览器中打开网页,可以看到如下的统计表格: + +.. image:: ../figures/fitlogTable.png + +如果我们点击action中的最后一个键钮,可以看到详细的 loss 图: + +.. image:: ../figures/fitlogChart.png + +更多的教程还在编写中,敬请期待~ \ No newline at end of file diff --git a/fastNLP/core/callback.py b/fastNLP/core/callback.py index 7fad2d0b..07654cc8 100644 --- a/fastNLP/core/callback.py +++ b/fastNLP/core/callback.py @@ -54,6 +54,7 @@ __all__ = [ "GradientClipCallback", "EarlyStopCallback", "TensorboardCallback", + "FitlogCallback", "LRScheduler", "ControlC", @@ -65,6 +66,7 @@ import os import torch from copy import deepcopy + try: from tensorboardX import SummaryWriter @@ -81,6 +83,7 @@ try: except: pass + class Callback(object): """ 别名::class:`fastNLP.Callback` :class:`fastNLP.core.callback.Callback` @@ -431,14 +434,13 @@ class EarlyStopCallback(Callback): else: raise exception # 抛出陌生Error + class FitlogCallback(Callback): """ - 别名: :class:`fastNLP.FitlogCallback` :class:`fastNLP.core.callback.FitlogCallback` - 该callback将loss和progress自动写入到fitlog中; 如果Trainer有dev的数据,将自动把dev的结果写入到log中; 同时还支持传入 - 一个(或多个)test数据集进行测试(只有在trainer具有dev时才能使用),每次在dev上evaluate之后会在这些数据集上验证一下。 - 并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 - fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 + 一个(或多个)test数据集进行测试(只有在trainer具有dev时才能使用),每次在dev上evaluate之后会在这些数据集上验证一下。 + 并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 + fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 :param DataSet,dict(DataSet) data: 传入DataSet对象,会使用多个Trainer中的metric对数据进行验证。如果需要传入多个 DataSet请通过dict的方式传入,dict的key将作为对应dataset的name传递给fitlog。若tester不为None时,data需要通过 @@ -447,7 +449,9 @@ class FitlogCallback(Callback): :param int verbose: 是否在终端打印内容,0不打印 :param bool log_exception: fitlog是否记录发生的exception信息 """ - + # 还没有被导出到 fastNLP 层 + # 别名: :class:`fastNLP.FitlogCallback` :class:`fastNLP.core.callback.FitlogCallback` + def __init__(self, data=None, tester=None, verbose=0, log_exception=False): super().__init__() self.datasets = {} @@ -460,7 +464,7 @@ class FitlogCallback(Callback): assert 'test' not in data, "Cannot use `test` as DataSet key, when tester is passed." setattr(tester, 'verbose', 0) self.testers['test'] = tester - + if isinstance(data, dict): for key, value in data.items(): assert isinstance(value, DataSet), f"Only DataSet object is allowed, not {type(value)}." @@ -470,23 +474,23 @@ class FitlogCallback(Callback): self.datasets['test'] = data else: raise TypeError("data receives dict[DataSet] or DataSet object.") - + self.verbose = verbose - + def on_train_begin(self): - if (len(self.datasets)>0 or len(self.testers)>0 ) and self.trainer.dev_data is None: + if (len(self.datasets) > 0 or len(self.testers) > 0) and self.trainer.dev_data is None: raise RuntimeError("Trainer has no dev data, you cannot pass extra data to do evaluation.") - - if len(self.datasets)>0: + + if len(self.datasets) > 0: for key, data in self.datasets.items(): tester = Tester(data=data, model=self.model, batch_size=self.batch_size, metrics=self.trainer.metrics, verbose=0) self.testers[key] = tester fitlog.add_progress(total_steps=self.n_steps) - + def on_backward_begin(self, loss): fitlog.add_loss(loss.item(), name='loss', step=self.step, epoch=self.epoch) - + def on_valid_end(self, eval_result, metric_key, optimizer, better_result): if better_result: eval_result = deepcopy(eval_result) @@ -494,11 +498,11 @@ class FitlogCallback(Callback): eval_result['epoch'] = self.epoch fitlog.add_best_metric(eval_result) fitlog.add_metric(eval_result, step=self.step, epoch=self.epoch) - if len(self.testers)>0: + if len(self.testers) > 0: for key, tester in self.testers.items(): try: eval_result = tester.test() - if self.verbose!=0: + if self.verbose != 0: self.pbar.write("Evaluation on DataSet {}:".format(key)) self.pbar.write(tester._format_eval_results(eval_result)) fitlog.add_metric(eval_result, name=key, step=self.step, epoch=self.epoch) @@ -506,10 +510,10 @@ class FitlogCallback(Callback): fitlog.add_best_metric(eval_result, name=key) except Exception: self.pbar.write("Exception happens when evaluate on DataSet named `{}`.".format(key)) - + def on_train_end(self): fitlog.finish() - + def on_exception(self, exception): fitlog.finish(status=1) if self._log_exception: From 186b9367f0fb15cc0ee81c5061c8acf00ecb296f Mon Sep 17 00:00:00 2001 From: ChenXin Date: Thu, 23 May 2019 21:31:06 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E6=94=B9=E4=BA=86=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/user/with_fitlog.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/user/with_fitlog.rst b/docs/source/user/with_fitlog.rst index 14cf7d91..51445775 100644 --- a/docs/source/user/with_fitlog.rst +++ b/docs/source/user/with_fitlog.rst @@ -2,9 +2,9 @@ 科研向导 ================= -本文介绍使用 fastNLP 和 fitlog 结合进行科研的方法。 +本文介绍结合使用 fastNLP 和 fitlog 进行科研的方法。 -首先,我们需要安装 `fitlog `_ 。你需要确认你的电脑中没有其它名为为 `fitlog` 的命令。 +首先,我们需要安装 `fitlog `_ 。你需要确认你的电脑中没有其它名为 `fitlog` 的命令。 我们从命令行中进入到一个文件夹,现在我们要在文件夹中创建我们的 fastNLP 项目。你可以在命令行输入 `fitlog init test1` , 然后你会看到如下提示:: @@ -18,7 +18,7 @@ 可以参考文档 `命令行工具 `_ 现在我们进入你创建的项目文件夹 test1 中,可以看到有一个名为 logs 的文件夹,后面我们将会在里面存放你的实验记录。 -同时也有一个名为 main.py 的文件,是我们推荐你使用的入口文件。文件的内容如下:: +同时也有一个名为 main.py 的文件,这是我们推荐你使用的训练入口文件。文件的内容如下:: import fitlog From e1133f600e70f5e6537e67fae1e071164232e614 Mon Sep 17 00:00:00 2001 From: ChenXin Date: Fri, 24 May 2019 01:12:21 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docs/source/figures/workflow.png | Bin 0 -> 336236 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/source/figures/workflow.png diff --git a/README.md b/README.md index 1e040f4b..9d949482 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,6 @@ fastNLP的大致工作流程如上图所示,而项目结构如下: - +
*In memory of @FengZiYjun. May his soul rest in peace. We will miss you very very much!* diff --git a/docs/source/figures/workflow.png b/docs/source/figures/workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f22df84050a0b74e73ca729bb9231ea049b3d6 GIT binary patch literal 336236 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%0AVFfebQkty{&fodb_g; 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%0DeW>cs!yO|P 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

*sjS)olf50lR5GL}b`)_E4&u>v7-N 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�xwr>SxwuJeh=7GpVtU?bm+9 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 e*#DcO7VFg*iD}!PLB_ej$K=E*!=mGu(0>Dt Date: Fri, 24 May 2019 01:21:29 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E6=96=87=E6=A1=A3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastNLP/core/callback.py | 9 +++++---- fastNLP/core/trainer.py | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fastNLP/core/callback.py b/fastNLP/core/callback.py index 07654cc8..e617cf2a 100644 --- a/fastNLP/core/callback.py +++ b/fastNLP/core/callback.py @@ -370,16 +370,17 @@ class GradientClipCallback(Callback): 每次backward前,将parameter的gradient clip到某个范围。 - :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。如果为None则默认对Trainer - 的model中所有参数进行clip + :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。 + 如果为None则默认对Trainer的model中所有参数进行clip :param float clip_value: 将gradient 限制到[-clip_value, clip_value]。clip_value应该为正数 :param str clip_type: 支持'norm', 'value' 两种:: 1 'norm', 将gradient的norm rescale到[-clip_value, clip_value] - 2 'value', 将gradient限制在[-clip_value, clip_value], 小于-clip_value的gradient被赋值为-clip_value; - 大于clip_value的gradient被赋值为clip_value. + 2 'value', 将gradient限制在[-clip_value, clip_value], + 小于-clip_value的gradient被赋值为-clip_value; + 大于clip_value的gradient被赋值为clip_value. """ diff --git a/fastNLP/core/trainer.py b/fastNLP/core/trainer.py index 702cb6e7..2523a957 100644 --- a/fastNLP/core/trainer.py +++ b/fastNLP/core/trainer.py @@ -498,14 +498,15 @@ class Trainer(object): """ 使用该函数使Trainer开始训练。 - :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效,如果True, trainer将在返回之前重新加载dev表现 - 最好的模型参数。 + :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效, + 如果True, trainer将在返回之前重新加载dev表现最好的模型参数。 :return dict: 返回一个字典类型的数据, 内含以下内容:: seconds: float, 表示训练时长 以下三个内容只有在提供了dev_data的情况下会有。 - best_eval: Dict of Dict, 表示evaluation的结果。第一层的key为Metric的名称,第二层的key为具体的Metric + best_eval: Dict of Dict, 表示evaluation的结果。第一层的key为Metric的名称, + 第二层的key为具体的Metric best_epoch: int,在第几个epoch取得的最佳值 best_step: int, 在第几个step(batch)更新取得的最佳值 From 8dec821fad7792441f462346da20dbaa5807d217 Mon Sep 17 00:00:00 2001 From: ChenXin Date: Fri, 24 May 2019 01:58:04 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- docs/source/figures/fitlogChart.png | Bin 0 -> 271505 bytes docs/source/figures/fitlogTable.png | Bin 0 -> 168457 bytes docs/source/figures/workflow.png | Bin 0 -> 336236 bytes docs/source/index.rst | 1 + docs/source/user/with_fitlog.rst | 119 ++++++++++++++++++++++++++- fastNLP/core/batch.py | 7 +- fastNLP/core/callback.py | 49 ++++++----- fastNLP/core/dataset.py | 8 +- fastNLP/core/field.py | 4 +- fastNLP/core/instance.py | 4 +- fastNLP/core/losses.py | 8 +- fastNLP/core/trainer.py | 7 +- fastNLP/core/utils.py | 8 +- fastNLP/core/vocabulary.py | 20 ++--- fastNLP/io/dataset_loader.py | 2 +- fastNLP/modules/encoder/embedding.py | 7 ++ fastNLP/modules/utils.py | 6 +- readthedocs.yml | 12 ++- setup.py | 3 +- 20 files changed, 195 insertions(+), 75 deletions(-) create mode 100644 docs/source/figures/fitlogChart.png create mode 100644 docs/source/figures/fitlogTable.png create mode 100644 docs/source/figures/workflow.png diff --git a/README.md b/README.md index fd6b593c..9d949482 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/fastnlp/fastNLP.svg?branch=master)](https://travis-ci.org/fastnlp/fastNLP) [![codecov](https://codecov.io/gh/fastnlp/fastNLP/branch/master/graph/badge.svg)](https://codecov.io/gh/fastnlp/fastNLP) -[![PyPI version](https://badge.fury.io/py/fastNLP.svg)](https://badge.fury.io/py/fastNLP) +[![Pypi](https://img.shields.io/pypi/v/fastNLP.svg)](https://pypi.org/project/fastNLP) ![Hex.pm](https://img.shields.io/hexpm/l/plug.svg) [![Documentation Status](https://readthedocs.org/projects/fastnlp/badge/?version=latest)](http://fastnlp.readthedocs.io/?badge=latest) @@ -32,12 +32,14 @@ fastNLP 依赖如下包: pip install fastNLP ``` + ## 参考资源 - [文档](https://fastnlp.readthedocs.io/zh/latest/) - [源码](https://github.com/fastnlp/fastNLP) + ## 内置组件 大部分用于的 NLP 任务神经网络都可以看做由编码(encoder)、聚合(aggregator)、解码(decoder)三种模块组成。 @@ -108,5 +110,6 @@ fastNLP的大致工作流程如上图所示,而项目结构如下: +


*In memory of @FengZiYjun. May his soul rest in peace. We will miss you very very much!* diff --git a/docs/source/figures/fitlogChart.png b/docs/source/figures/fitlogChart.png new file mode 100644 index 0000000000000000000000000000000000000000..57ae16830641570bd7ebacb364c7ca7a951050be GIT binary patch literal 271505 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

K|K8tHt^Bjdt1Zfx1%3#oOJ+U7s603h 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

Ehqb@$*9R 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 z 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%0AVFfebQkty{&fodb_g; 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%0DeW>cs!yO|P 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

*sjS)olf50lR5GL}b`)_E4&u>v7-N z{pKD@#7?05a%1QP^OOhlyNb;7&k>F8eeJ?hk@SQ6QkJ_;Lo$6gx%_e>VglBa-N@*c zqauhAYpu&W={x4UU4NcNVXY!7zH6V1(xCWFQxvXZ#bR4tM68-Q!+xsl(=Ck!(9x^W zByDAMmantL$b75xXj#qK_=g zJ?K>TepE^DuXoK^+(y(!iNK5R4oRiaZDl9gEZq|Wx{D(2Z^{%#*wvPPZUl)lldiid zl^2QSU=%Y2TIhb>Yp3Fq@#hS^`tENFyK=K8spt6LWGe_6g57Y^epql zR2e@Hn`Djt-@C4X1y26MRW$!-PQni#ZY+i6SX;qOPEi7u6W$lQ?GK~)_;=IKD|F`{ z8WV-EfFwSkAJk}CrKG|2Uca?k z8gA6M+8si<%qihZL5P>6Lg0#3s}&Vn+&DFA*fM(ZV5TOFZX(ij;G=-H(D}o)oPE=n zZFlsoZXVZEJIWMz>$FFL1Oa0dBH|S!-l;C{4P6snLH$kEnlH$saox_cflS`}AL^Y%N-M-a__TTkuj_}_QwSW&*XR1Y_euXs(ZA6GVgWC2zvc=v3AMs<-TkiNV7GCQY(8Lu<*z^COOO}W=gSFY|01}+Aw0i0f? z7VB7a@CBxQT*_jk`95Kh_94`mvwVpR`bb-%-}P4EI>@t)LermX&N&&6&^MbkXouID z_yRMd88eXLNH<|M${(HKk)akF83v2vD))5V@?yOT54hMvnEIVKQG4}ThZDFdgWU5t zhqsm@Sg`;f)UGj0~mxuJA-EfZV}ionOz0|EaDhpWPmI>4O~oNApz zPmRY;_MVy{mStTcsc6xhbO7(&+>bV%%%UOnSxW>Y5#n+}F6=EL6+IaBs0+{~)B}4W zK;MeWlRG~B2swRyAs=vr=99#P8kqDPG#pQZjB=VamFSR!CU>6GIi?z%e^+K}7XK8P zbU$BbeSeajU)|JL^Q7y#4?`(GFMfVq7{(nNK>ONw+GCBHO>e)0s!;`rcr z(}Twuf-Tl+_MIX7K-J*v$Bxjdm8YJ4H84ieHPKcoHcg-cd&_+S^Bed zDS;5}oE9dDvneW@TQ$n+pJpQ>k>R8_do9T$q6@!P=e7epwwhSwh@w1r&I7m%=p>5!BHxc^V4Ou*%3( z751(b(PxSd_Ujb8lng>679mYVqvm_Fy5|yJ>l8rSs_?IZy=fA8*w1O)2bB+8K%z#iWs*!R5 zY+$@7I&Z_XTBzb(x72r7f+Ukw{jJQUV%5Dj6V!lIuacgKWSCF2KNN)$c1Bh^ROG-G5qV{#F{_6!Gdf zgHKK7xNzBrOGv(RbMUl@S#}RMn(9)5B}%Jj1^$ZA7y*+LHsH+8Q+u)`Au{LmLyBN_ zHQtNq(iQTCJMr@R!X28QeEK#o2_?+2u}N*l^j&l&bQ zul!O&GF4K_6vd);OnPR0^pJoJHn1=xk(KngaP@JFepB1+wg2C?F+KFy7Q@(oiU%n% z{c!@_hs-D_iBNGZlC)gHY;R>qF;4X8i5p@ z$|bj~63aWpfTYnU>IWvM`iLliROj>=%J0(A%VB+U=^bf(o1kumUB+hd*e6(vnh?I} ziUY0D15B~GJrTg+GaXRopQ{oUFn6RBAR2KF9dEp>5^=ziSX2QNN79o0~Aom^9x>X zGMMlNk?*`}5>fhwZlG(yF~aXv8>ec#HI-f7~oT)8ukFQ<7S^o_~n-YUR5b z2mnx}okE$h#aeU@IVk)X zW&geA?(11}G6ZENcnJ1qMlov}|->hIbwyw6n6 z{v}k7qBuf6(<77U{_Qtsg&f+){pL21jn3>wbW$w?&BpfT%_~$YslVn2leDTx zv2g~Tcsa&1JjQpSE#{mO(Sa2FVh>4B3HsvSCU57I{>TrR4{%_0M_Bly+P4H8ZE+rI z4HpQ~pTu@`bKE9tD3ytsCo5lKeD`$>$(%{dK_R>@Uptr!x~2$m|G6H^7mmI@R?y;b zC&Nje#go8#jgw)~%S`SSb;A`qz@#567kk1f%q%}8#Au$E^wcecj`1*91J9bAKGyuz z3A2gGUr&bLngP!pO^a=QlK#tM^QtfToa@P}`OXiHuOeReP4PAya*%7eRnh;JopF}? zp~h$ju6e%mn~`-jhq`t6g0CGDnpI=%!SzX3W)E%|%-5Z^jDqH5w*hIM{rmN=^cW#` z)#KWfy2BtAlpsNQab&#Gm`$u}yI)-_w=sYJawNaR0>cg^ge3Ifala0K){+GXTR$PO zld5;(caoDe4b7gU`xJjsTm<{%JN)^?jxO7w`>em~TjT!D;);koj<{E+9AV2Du!eIO_?U<#UTLGM_>IO=OkxAm@JIyGCMWlPP6?C{;bz zZ(-_)m2t+ML6qxwQ@sPIg5C8Oza6igA{*6(b2QaFn?tTA5<|82=we@aJbXh2JVagz zts(a&>8!Z(n^D7`69c&vMwBiJ@~7Ks1RQol4%MemV2DG)8lJVOLdA`4Ztai-yf)dwjp4&Z_HH`vZ*fMYBL%}##sTV+U zw}dCm6jGAv02v83F~!`M>%R$Ob4^Z62$r=(=m2~Xf8Mp@$^?#-UIs9WB{hyIN!C&o zqQ&tH99%;>u9ULCI0vUXslT3tfp0IpP9A#2pFyp6pV{7h zjxdQzA`tHyez+RVT9!{dSW>fK)ws*}G9qx2JlFF&8Br7U`bGnjrA;A&53CAG9g?** zu01Ts`noj@U>H=mgi}a32E58*DNkCev3Nx66ep(d^-K0qaFopM3aukgr2$l$*Nb56 zf1O`@V&UJ0bG>$XYOBS`o)J4=yAn@TTg-B(^}T)s4e#jx1{Lg5wehJP|FBAXF~4V1 z-}FT^U89a`6@rlFWf$f=OZ@nheAJCj_?JC0mi)=+#SzC?5?twS)U&Pn=xthX>!_Ga zcOM1_@?B$3lXiFj;6@f>HNizHhIz7tU$)uZ%}>ql1o@+dk;ANY5*M*5xarL467M9I zW^s+%37M~s^iQ&ZpAugEhFxn;Xv5D0Ydnu-KC_q;k7>i;X010lu-;%)&z$bT{_@#x z4v0KxMP1=UCEw}_8{GY0V}TqG`uv4NW}F_RRfB9c!O<%Ll6h6M6krJ?B|X^WS_UU# zp&nmu#%|b~p%AH(`95CeUKz;7;9osf+0;pW#X!=o2_xiNMa)5e+O5vW^7#6>dC6B@ zt_CL>f+B_TnzfOCSv!bT__qJ{I%=Z}6@fpXJDJbGxu1%OB2-nhwU5n7mA=#Pc?HM_ zuP2ykv$UGvy!RB;k6>D1GXkllKRi{_2JHwI8^gLt?m9W@P~AW5J;A|Ch0D^!={kRC zvNna?a`{{vO2R$24vc`0z)AJ53*sPUaSpns3riedQATvS^kYX&TW@m}tA}`1vcC!G z*;GDFfhmk%=#ABW-?`>uWj85}6Nm9NX5`kL z<*5Dgx=B{wh44FxSlW|#RzUahaNNB0oR06u1tOw}E~BjO#uHU0nf*c#xjm9?E8gm| z#W$kZ@wkILH{#8)2NEiR(;9vk#G)M?43>MO8AG|T? zy)Jkr`!8C46#sQ*z#B|=!ES$Txz<75--_#n5LFlFwdQ41TYnugh{AU@uewG$ywaY( zb%W)&0oP1C?1GRx4ja9)&rMN3M9kS4YMt^j#DV)(m!?BzfPVU^V97g&m_T7Gnr<~XkxeGE!=Jk8AtG|L@v zx{u6PH{XpaQvl}Q{lNd@ZXvVObL#5lEPtfruR8fj%7kwFr`u&KNGgHd!}vuUTe;t4 z%vl~wnlATPNpS0cj|?t2#Dp?I8Ih>M6$TYqZNMDkd7xN!j3tkV!N|(2hj*_o25$M* z`CkxlM@B1$!iYb(U00kRm}^uDq5uHLjq;iw`>iXCZJj7y>+~d}a$;gUWfGe7t}n~S zG!s`1vMEe<&2!_#SGD&Xg2evYiF^^*w8T;~3}-9XGxZs3X)TuO;(%f-w0X-BNZcud zN*>h16&V7x~vJX>mEU(x34IfA<+ z?|TiyicNY>d6n)DHM8svEwc?@;}GcweQzWvF9|0MqkS-7v_&-kgS|yJD|V;F>{GG( zPR%-xdGz|ES8#kj4T!UGIf{gM8QfxU^mH z%8wx}SLI+7XW!DKbH8d4hfBN`t9uu9DUN;w`wpJd-iyI6{9n%eKv5|cUD)11NAzXU3+8VDQ?M#FmIJaNS-wTzS%6 z>ft9dZV7!wO+angfbx4Kv~dLQV$0h7l~X9VqOEgppylP=xIhawtQTNXxboI2dOSBq z#7=D>#J{m!zrR76>QTenMFlDz6PUX2i<|et_~3`E?8yuC>ggQ~6gY5t0mJjX#IX-7 zI3&7SZ7p6hjRxjJREoL(0{d?vIyU)3wLR!vqr1pDVXyFA}RBp zcNsA;Fd4C`bonX&x29~rZhBtL{{E%hD{c3Z_}S*w>D1~dG+a?6jalwcpHfV}Avo&7 zsccW)h!#19FZ>=oyWc43uLD`(wkIQw{;ozRR+Qxy>8#4< zn6;S2(c>nF=eh{?PE6`Pm+& z$Db_IT~X4Nan1s=zr32HTr3>!5wf@2NWx_Vt%+6&^*6~Q+FOMGBK>Fct}m0y$0rp6 ze~Nv%HfRlg4;QQ2fFyu;QhR-fhMHfm$w+eHk4B2jVM*2}+UU^4MAbv)IzAlC@b|TM zu9`eq%;<-2pD(`1l_BE{^^nz4Y4Hj$G$-qS(}6sc%g@WIUZ$0&{mM)YQw+UWt%Uwr z#*BsrWN%=pVTG$Xo!COCPbjtu_^%@`JiIY&y0oq(OrELsXz+LbYaB>yMDwahPk9G- z+@?s;Bh4aGxiQ;VXH+5Qn%}2`XJtgGh=~9;f{GOVD+YR`^{>~k`%2U0snj;! zb_(4-w_{>b*nMTtjcpVUQV-z3fvaXWt%c)sjG+$4OpV#Fqg*=#qlYT_Fwm=89iav< zHMP+CDml$8yX$us`^87}=dPlk5Z%Ay>X4dSl~3m14PBY12C;N;1lvuT-}b!MJ^Pty z(!q=w>zt!7`_YoW&G6p@2X_SarDN|WFUmu?p_UKCiy1=4@~$Izx7FcRwsuWje>QXk znRuoA(?M(1KJ28&vktaaJ$gtn5HV)tRoMR6iFNZ< zBldT3zm%J{w3Fru|Jd1~l%)rWsA7=92 zKe(U415PXMEcCU8V-e=|&@*`Yg|O(00{Zs=+J8zJFSF9NMVUK^e^JLHmQB^Bhy{5-6!3Vz_VbL6n(@Dyy4jY7CEH&kf6d~ zOVFtFD3RMl5kT0}h(U+p$Y&;mTV?DXFMEG=VAg^zSy~L^BmiMKTxX5TM%|uk z#2y^;*n>>sIi8d*5h0=kLyGV+uDT=}N_5xWHNt6}RXsR2}#=h zV@W8IQd;4xR9P=N-*-$I`XC=^KAI#GVLRGkFsMzDyjNX})}rdyTSa%$9vTiy@*F)E zvnrFQM}1lscEYo{iwzklO?vd&BIQCbB2FTQQ)%+~lu=G5AAG*V%xHHtDGym$J8X1+ z;`iT;;1SsD>z@Y(^fiF3hlCP^)uaOE6OUsEW+$+W$H$0lUy)~`@b95r8S{u)(|}W4 z$B1e-aXMJ4o;nv-msSDuH01pIM@4rA-XnhQl096-KX~o+Fy13rZ?&@MSna7glNfRx z5m=(tk6Ey+UpGgf-%!^UyjCY-w781~qbR5DyR zKSWxWcQnt?M`ghJo8}yQG!$j~ME}Z8{Kw!*5rmQcU?c)<4LXZlmRYhJ{eKq#NrziP zcy{8hR@{^&Q8rjyV1?V1@dJYw{L4gG;KtewN#^RV>YhAUOmE;~;L}w(0lGEXH)GNH zUcggQ6$4x!B!4lf%@y)GUY`oubL!*pB_%%*T+7))qz|sw%f-6gs?FC5;7_sB-X7_@sj#vklh2~J1q_x6@R7Um?4lBNYg=dADl#I(DoF!czOE^ml&E304jK!j(y3HVvDjXa_4Km7n8DqJAU;v0_Jsf4Os@2^ za@Cu@5Sd7ImGtKaJA5mb5rS;bJ+eIGlpILnQa9doW~JkD8#BG_HMOU;+nw(cKZdL` zL2ZGJ*iA-WYs{rHYBP@A|Vl-j&?~Lue#_net<{(CHQ^S)(BJHU@p4anw$6I$uWD z>^UR^3lbPjvmUN)7!I$%P11OfE=`)IRaM=g#IEc%ypW)azKCUVBE=-h!@|SycXLmP zXA6g4^<&jGys=J65Sewt>!`hH-i&ekZ+z#)K?tX?TyxC`7H_bQvvjYX$nPTPTQ+E} zw=+rJkrUanV4bk)CXWZxue@qg4WYmE$i#R7UDynGqWT`ZcM?zM=dA zITlDfROm7}+c1*Ih0cGBs$!z>&`y7GB|iZt0O%^*sWlIbcqb#FzH4w=%&u=u=J$nJ z%~XeqT_df4C-f=ZSWFK=x<6XcVjJPzZ{YHA+|jJyID*0Up;Ii1pUzJ9J*lLlk-z_| z`@YQOcq+=#s*nKB!%u+juI>S5)6=5khS$-IKG98`zl>>BW&azGhEp)txD(jjO^S7{ zLanbjv!TFo03STw-j%V@-=InWnx;0nJ%1i})ijVhvN+3u#XT@xV-2nMqtuvYrB$AD z8EBt-hR`GI_Xywor~(~uk$lFI@~Uml>|n)O@eQa}kQO6Lg@JF3$b$>qfhSrMuv<`i^eMxafuMpb1hr|bPHl1KA%Yu9YN%1JKk>O-Q!Gx{Er#)Lv)oSB{7+99n7mT68a`;PpKY!Ysuym-Q@Sv^3f8I01ECljR zNp601EgT&r?T2Mq52ifirq%i*)cw~;0he0W(pr(7m?}*O?4Hf`^{i?*d1r+f;ztej zk)DwECNhL=c8~^;@oejb1w*ud)y8 zM1e3Gm^BzC!H1eVxr0ibsoxakE~EeSTLy6W28jgTdS5xg1Z(?kXxa_W@x*>GyHEVA z?y1rE058E~lhZYOR03qSjW-}cjgfOZ?#BxkCx%l(r1{cAsaWDY#+3cv;Vxq@59q`-c@*q* zc*OCcGG*@CG>vmqSDhfBgWPj0}5rKz*{|-nz>xQU*P%WE^Y=uYRlhE4iyxiV7_#Oq0s|_HrIy zULXF$)BkS%;}_{cfwM35X_-0jG(XHXE^w|5DWxoB!XSSfF`N$7No!=q{!&&KQGA@} z2zDN#n)R6#So=dl=BwquZL$KZWE`1S=)~xGKbPR@QROVr=&@!A$;uP$R-zr;IF+?3 z7-5KSdO(k*Z(v<_Rb5wR`PYjbcZ*uvRQ=NIJM5@nXnHZ#b*xOTAdRNxc7XyFcE6+&I(T%n(#{UgJiI`r+ zO+`(+ymu44!oDtE3C?X?mbbiWu13cQ0lSw#rRVv#{rOz46MMYC-kggLpCEJ#ulw|a zxqj6XLN1*1Wc5bk%K@p-jYaJT=Wp(J_RKQF5#6~JqmyG{j%Jg1)Znd*Rp#DKvTD34aJ5J8BJh{V@PyMD&sDjtY>mFAVx zU7x|HzC>%DUB!NFuQA5CuqQj5HaKzZ=+Tx?qUg3D9Q=LGA4I&|wfiqkd!hw<;Lhuh zAATAf9Ow9mx;4yzc!lO=YHBZLwsZ3D+1jR*Z1$$;?K5=I`cmsYJvl|ZG}fG4uMm+) zO9lTZ^;5dF9z`%l3P@YVs31Il*gr(Tm`#n6_)9VQ3wNObW3A9uM&$iA%@w})R$zAi znh~O+wQ7rGu>z|RP3+XM4{WCWhL} z4WFU7cK>9ffzR!g7T6(8vraqMtt&TvvoaT=x}p@BV=<0V%8)P!c3FEu*Y)-~xW*w< z;tQ2P>9e;3Mv{n0_B_K^zTdJzvwfK)6S^T()YlP3Q3dx;^?CkMNF};Am*3kq*bw0$ z=lv9oOxmM-#QpLAdy+^<$X;Z^!*0B3oc*`!WJ&4snu;*&&ST&3d;g`c zbWss{G`9ZiXgDeHDut_admw0FK-|yHXKC`j zz-1&rucXwvNt0o7ZE$Zxdd1l4(*$coWcB7So#b)ctBBB2Qp6Ln@Q?I)n%jY9Q)0Ul zt2;#pMwkmO25>VfiCq;s&CK=UwmH$U+3*OSOPgeu4}C^uPTKUE64&|B6@=CV?H8D< z&woy8?0dO}1S_=<_({m249%3Ni2S8Y0gSu56{l*ZSg4&!6i0KOz6O@HEo>5SWs7w^ z0kt!68?GbCmcIQTD0fLwd}~3_%=X+bQZQlHXLD0sq%6qm>$&_u(Hg#FPxB|7i#K7g|b#62P z8@p>h6y-Q#8_7`3qrR))dxFQFh?!Cm16HTwl`Io@$GMJMJPdmd0|%gXMWQsGsobZ& z7B9&G!n};q4g}s2QVME(GK$+4cikE(*{|$qr-{-q(|D=A#P_j_NyK?0#_W>1>!+$t zs+wBk)gX+rM=Ncpv@1-J2BuRap}n16*`cYg1;w)CiIwtCAJdItZdO12pz9stdg@cp zRS+N?i@Gs+j1wjrkXkMTyWure7&6dFDbRQb5)3|`ASp4Nh@w~e=b}Qc14X{&U92}n z>JO7+iLjBdAL3>|Ze7#1)mbf#x6`Nhi%wvQ55iT`$JV06_@Y1L(HdtFy6^lg>@hTO zl}18OjFAtv1LZ%k{J`_@9`M#~nZMZY>PujRum!Ta`zz!KDDYksb9!z(qCe{9{p6P< z(SMhBT7s}+9UCuop4-*#GNl7-2sLOyAng|z)a>!wg6q`OpfyUgW0=GjatR-p9q?Ms z>%GBt2?|g+hW#110`?IgN|>vG>e=ez46sA^lg5Oa5~ow$RM$&a+wJU2&<`GG8_w(r z<`6sHNuS1RP23?ZER+G?Z^Nv!XL`Zx^yyb%KhUuD8r_A-Vye;EL8Z?Vtmw&z9YV_# z38Yw&y<12|doM>JDJq|ZV{o`rt8%PG^SHxv2tKtvh0`pM@1Nz?UxXaZ9BlNL@g zg29a9%}jo8E1^AF0nWuqZ=MKLK&uok>+H{`x7^r-TuK6Mu$A2!Wf&VLd>BzPe%ZN- z#IRVte;*a+?>px_`NL{()Nx#RUEeuh3AaC4nb?kwDTel>jI0?YL#EwIkJbp;`4EH8 z5F)oSt-O97Bv5o>Z9*q4T7`W|B2m%}Y`^oJ%|&B51Yi^bv2Lp5TO-z$GEFaWQ{|0Q zFKL{dMmP@76={5k1VjXJRO;J?3c zdbm)cSM>cJzJTQ)uI>$AO}NL> zAr?0F&%Qw>#w<&vKygH#Z4SBa!mS$c?tX}hb*jktH$?I}fYdT?|647K84ys{TxAS5 zUdV2UrCvefZqQ{1LWY>WA&FO+Phg2$2ZIW{{Xb;=svj7$C(eRp{7(-hfYS3K_Bq86 zg=*CioL1@w#Tx8UbGqfggZ_Xr`VMH?z4WoSrmM`3X14|dzj0?PMaM@c-9mu1e|-|{ zReZRkhgEglXz-DWlHg{Kl4;c4f*^*B*S%3o0PpnRO*k*gIn0U|wzO054clipJp3-3 z9;`(#86l#r>te9z@Z^i+?2U#Qovwicm{Y8vVe8&!dy0qIUZ){yLXB`AlXkz(XJyPT(ktCATFD4fT0y_Jfzw$$~1 z*g!+y8Zu(tyaLrJzmv1k$GD#Yq@0?ag|6S7gz32G(9WH48lToL%}b$zn_f8%8e8K0 zuOARxR}hn6EsNc;K492o+p)g}@A&FIgbvBUPBxx~4q*E4%@{g4Kwi97DeRyoRhM8&^QGI+gN*4Zo92XfMMuHC@aq3`3_ct| z0DX8l;ZE{1xJ~ z;4cxdKwBe2o2qf5UlEQN4`vvs@EJ{LQ^Q_(WfP7LCSD2yy%)G8JbM)Z*{%{*FPX*?cJk;1=P`Gp#f3w;hxrHX^^y97sP@T&L?sc# zACE_I0n7!h7m%B@AA`1ua5cltK)|QdN}pg2MlUpw$Wu>0CyUCLT>whrRnVBihEoV( zNrx-0=GwAv?hBmKni=_ijFuLhOX>byZ$n`-{%-!NLXAE(iFs3WdLEwEBig@Ne?0g* zPk$5maCh18*KF$-rucLuB2>z>msF>XG}E{jLglImv0lOrYu9OZMH<@^mmjskbo!POJcfK+1a4O z>8RAq7MX2NB09L#NjbG>KgN8uaic=>ioX6(7F3@W?D(FZG=^~F#f6VU*Qk|rtbfPP zHegn=MW%i9wuBWO+ux7=W1Z{@_TTC`wuqg)6kWx>Ins+)0-Jp3>PvXrBJVT*Y9R^l zc*Kgzl2%j1?@hBQsN(=CV~)597`@(|{1W1^a^q|jaf6r3R#4wAN7y3%J&Wuq3*_I) zu))|QCIcUP=?p5nO4N(X7aC%5oMc^P@JL#Z`iI5qfukN+*qY^cK+&Aw2ovU%AR8U2d*|H{oDvvZN%|+ z`yTm0cX)qyde&<8VVWC4Z_|VA^-e~dAxBs#qWaa2E2Znprai^40f?pAzT>32~2}vkjcUi_+ z)WZ(L;u*|8I95~Aa4mmCw0lZ10e&%%UAs-)(zhp==9VjYt=@&Q&_5@kEHQ+ed3mq5 z9$UEYZ&pZUGb+b_T^a8=8U~}lB9r%QbMEY4FND`-z)1=9X(5T$HvQ-)|6S-ZCV+5R z%P@aYqUr}@EK=VGOMky5#SP~!J6jjac+fc=g*?6;J+57rAd=qu;Zg`d-IJP45`BCfH=;x6mur3a<5Bte_FO=l2~Qt-*2Yf2GA#Rq zGL;74Y9TMaGA^8#53z@CUS_sG>0dBV*-R(>D4Bno-D9WU#9{p(#@4Tfu|aWMHGi^| z;pDiF*@s}mko`PqnV7Ya-=yBMWpb3NAG`h>AgKVA{@S+EAl=f7c-(!Oe$Yht6?CM2Y{9TS=~ z_Ct55Zgbn;ssUaJgqE*~_hNq9w&dHWG=5ivzf4b^g&1=#zslCS9PH_Wvj?oRr?=-i z5iTSKluE~v0woTf(|OcJewS#FU5=M>H|pYSmYU^t8x=X0Hg#gmiG;{pvNOxW=C_OvwWij&p_YcF@Q40&8D46!p36L41WYv11 z_$!gb%bXvX{_(tfS+4&8pYQrmz4o0E``fTAmPh!q7CC$(a$N;eY0fM>pl+|Yd$|}~ zpXm5Jt938ilZmE;{DwxTvdiXdy{1y-UV; zFgSkYR)>R8)FD~4AM502u~?pZzwQOE^gpzVRc2d}Devqd@w=;)pdS#7^hB(B+B?Ss z?1({|^n|A0ijP6_4)!NXa##^m&21zV_#U@it`AD6dYb>X(=hP~%d*ygvn8iofd;}_=or;R znYIV?PK$=<_|8^eWOJ!^lKM(NKYd{vhfa;BcY}|5uSeOVx&BNt8hN}yO1x%UwRxpJ zEe*|QuMOK&F*!b!IVb)5W3?co^~FBT)S&vNN=kyPH{n%s!=A>BVcF~o{!nxm&C(SA z@r{_!l-~dU70Xdi%+B=bIqad}0$3%}4i`k?AiEgBpGbe(!l0THj_*-8PZN z<5+)tcndfZsnY8EMNL99fy~BsFY--Gu?8ON!;7R^h~Vm`5B{^hH1~S|MmO_~R07MQ zYr#bYpl~gM!5Y(tD8GjMvA<73eplt~P^FMBzHg}8uB**(JNdQ2@Z{_cw#Zsl%|NKh zer>Ruc0ZObZIPzaLgg5?I-#iBBlfp!V@g~$Ab83tl#ZnXeM&-cvp0&7 zTSOyAi2+q*j<&QyB|bD)FfUUF@w&K`aSUI2!Z`gH109b&iT|!g8<3!0oeTJ7$HSe2 zQQ`*WA}J+s?UU4C2uq6-byg)Vud&){z0rYrWi3P_iKw+rD6A z)@k{uMpvks{h`gV{^`i9hp1Ec>|hiEB^RM<{{Z^o0ls5KpfRL}+H&6nT^T25ALo#8 zU$@01kyAY<3&4hDp7GrAuJg+7gBy_#0|aX$NIH<_j-^Erf^4~!9Zfafx?RT>L{Uj8 zYB>`FR}fJi@-2(EZ=t7yPe=Q`BJO=t@83XzqFu_w*VGo!U%YarwMTffM$#3klI!Se z+SyFwa08GX*A*)QWG=>rJPZRe;bpl6VF(ln?9m3XN&54N@%sU>t)bb{?w8A+4I7(8 z>hcU${HYmSsRd>Mtw{A#J|avKBHRNa`^0kb@DOGOx?^&@9`z&`MNvl#2m5iw`WS_4 z7_G9hd7hZLeOT*8toqUYHM*4W2Yw;SA~QH)@N;~}rTN62FbeSB+rqK5htRxUN6 zKPJHOv{n{?xdP6_I?!VZn-;x$v^4e{@x%`7H3=!l0|5nxV3zV%Hk<^+;f_PQOo%v^ z&wkN@%G}_@@Au(-Ho+^cFJ1S7eV#?etyhnAUWrVvJl*>5Bb}NeHBXKLl(_ycu-5Ts zK#jeMHq%wE^<SLwKn3h=HTA){J76#i25>M(Gk-&iODbsAy zh*0qUIo_4Fv;s|&Pv+%0cqv5mend5nqzfb$*||E%>gKZ+PSvg%$s`!wsaK@(Cf`wu zxte?#_F>W+aD+B;rIU2Qh}aW5L&LjXO70Afs^db2Qhjji1?v1C@R_UAbJH=x1+!|0 zLoPE-c)2ol25zw3$F0}B9`ze^6c|2LfhK|D6p92W#j>Jw1L7H%XA*)z-rJI^^ERxe zFr=7`(|H|e-Q;eZeu<&JR{}v^Ao^QH5tlof{Tzu0$~-9O0Li=)35~0lsmKA+9D`DS zZ9A`t4AKA3-4x^A3RoS%?D=TB^?79@d@H#9&i>>oPV&fxU9j&f1B(##l^xWOp4mS8 z*}bIp3>$Q$WMipKwY{fENz?~Z(Otwn$aVwIm>JjGkAeQ;X{~WG{Z4u7TT`eJNamYd zhoDLYA&=JP+ik7y2l#R20rSHqGd3zzNE%F%x}EJLVk+sxzu&S$+gD1cmTNtw5ZP^i zx+l;w<5~Nc8MR89x>u1ELljRMWYq!&bX->F%tsn*KHL!9#N%+FcvFXFDtDh8b)Z8* zbsKTq0adil*hQD){0x4GF0H_a%fOBT-6&0k2G;Eqr$6UO5l9*J4<5hMdKBoq z7JkKYZ#U{lLF*B zn+_Uf>WuS*HEs z&gkyqgwD}54uA@g8*Q37B+ppK2c%phCpBf458mI`xQn+b80K+{H}k}XloHd)tiBPc zan=i+R9^TQkgI;zZ9sI>E^@e7JOiMI{bVZV+}yl&{xkD@j&-bRy^(~+&A|sVs~3N+ z&SW26X}crpCMBZ6uA`qJ*`hc6n#F>XISw#BDAFqqQueFn;G-kG7?Z(`9)|rh;*NPx z3yiScjzDimN(8M(jih3kfB-FpHw>YZIsHg(ki^i_4NEZ{lC3xI_;a=eNo0OAmCHMv z_USPZ_x4yun9sP1eaiGVi>Whv7mCCdQwB%TFU1LTQn?CXx+-`5hUbiU$X;vG!x0`J_OBQta>X?mh`g zbv}RRAHZ}~TJyM1ljB{-v)Vd~{93`>-Cqh#l@_Nyak$qe(`ZrmY|N&&=9SnlHB*p} z{9c9(EO0dtNgjc-;(|yJ*`1Dc4*FnCrUX_7TEt*Ov{ z_-KUCg@`fO1b&n13Dqp#vC}p$NA&$)7@h(L!9;a`EGPneRos#yM!B9#gnea?KE4Eg zTUBX?{RXPQY&`x&*m{1uelYereFe>~Pr%3hgbvxnuN#1uO3p zSY-K1Bn;Nt@z?~8{zOSePQ;`%A#K4^CHq$@eS*&mYUE~mrozU+xC5}2yeEm;ZaKX zt}WOX5n|VqPoG=+NW8c+w{Xz4lJS01QAJBX+nPr5UUUlS4)?-yLIsOFcal9#h!@IA z^bazTTP<(`(IH}DJN?o&f~k$qdFweH&gaXq-qu>%WL0NeUek z&h^C6{pA%7_*dxLL@8naHh`Lm(BRc2mxL4I@a&D?e^;Wx+@Rmz`q%R@v&l_V0vZP> z&T1vHH+vK}69F-(_L=X}%|oo-DdFN`!>9H)Kz$y8u*4h4)B~b9RJ`yr49ZJUNg!y zUHERKDIeOwFIOpmTjsuoV-p*}Q#HXeycfJ$l~5(D+>pBs)kL?LzN7{Sdq7B0G;p58x| z9K;%b;w$fxk}LER^ayqzlY_5bpAp5UNH^>6Y~l1%cQQHr_!c#7=5ljh8Q4Y>!oP@j zna*}Olouvel7pM5Oq9v|=`C!qALIlv%wn?0YggnFyAD#Z9ja7#OZ$2(DjB2b`$g24 z6RGJf1{VEGvG8cpp^Fe zCq|v<<*Pd#^Vj;J5A_lu_zzRkRdJjm0W{(Va>ZRGr0u&;3ysR8r38OkjNKfgJA~0k zeq0&wo7_~j{5ab)ZHEuTbpLbBQQ;syy3jDX_(@_}!s$&k+a-Ng;25iDlKjdBYI<8q zW#Wr2?-lhA#tIpn=~7_Nm4)4TwGUERfTVx$u`}8byToM*N9C$~cLo8)CqOGdig0&N zdh*9uzBDyiBR2=uYCcMJ<5d696-{@62Qgk#uxOl4FstX$5vn^X_36-L*M8#5EO>`y zA;d1cDm!DMJ<;5#^ zvaudI#lY2GuL`fYoBuv@-aR@NXdh(f*H3Ozx_Twk1>|%H@$3`7XX>+ng9)}S-GMBX zrQN01TiLThEv>o?x6{V-15cq#EnLgq<6sTwdi$hNrCc$pd-c&>gq@({xh{ zVN4z5&L^L(a+!VAG!qG|mr=?`Y`FQ6Ej(p1^r`kNnW)3>(9bai7KF!#Yu=to{Q3?( z9JE2#Ef=c372PbmwXD|=pO$O3NifXKU?*+;rdC-i`x{B>{`B*xU z)VZzyhj&iy`Rqn|b07g;&~r(Eg#_+xVeYoL`}82gZr~^8-#1gDQ2Y9v{<-3$96E;` z`jx~S;yH#BawTFMfA;Qgj-4cX1?Px7Rum}*ylK*y()iLF zRX7HM%3snTc#gXt@d=ZQF!*^`W<2V*qhsmi&D06s4+?7DZ!|Oe$mJYd_4?mMP=2Osg zkM9X;4q~pj1JEhN@{Lz=_-daJjH+(S8fihS#MqD9EzUpas!(We5@w@PCt0UBflrgD zHwX{K>hZ8SAxwRg8_+y92PqX!$D z0-#6$_PF(jHI*~(D(2*$Q?$qG;|*tQP~6_$AEw4}W}}@P6Ja{#d%&otkuOBJ3n3D|Yxan6+(A z_`i2OSrfy8UelcSD-XY}U3IT=_&@FKVNINS7yU7;Hd19hdScysjpJ!iuLfk2k0^sz zm3GB=vb;G`a;3l1Y4+o>SJkTr^VLLV57cxlHPMPq*tT_rq;|(Z*0<1R`ypR6ia3Qj z5?RJ4N@;?ej;D}3Ug;pt;*8s0+7=|tyxqc-62sl$Joq$D4jT=MEdqi*)qLPRkK%G? z^o}>lq}z+Yvzjm*>EDdaB;g*jrP>E^r?=ct)o(V-`BE6GG$ztKKQZoeOb{wCRD z+rCYG$N%I6ss(ahVRMbb?LEBR>^=-I=>Wx|aDe8tM!?zYR?fA8lW>Hrd$8diuZK%D zabAL&h=s%`BcrENTx8^D6Wx6_bPgFU0jd+PdBB8p&Z!Q3zSU`mkx4i_3)PG;c)c}u zQQ_HE`d8LdxIcn4Ef-j#)=Ng8cSc{&^Yp0Ncb`Q2_{UG9YVQpPiwQ zYA0#lta1mu_KkH;!?+3w?TDh-mpv`~nUtN&@xd+Br-}Hh(}K>+$}c9_=VBgZ{N$lw z=8N!D7CBrS) z0(`dWV_{u^xe^SPU2^m0QhVKs&DK}?JDp{GY`be~687&t^Qez&C#Q8*Le(Xlnl^uG z{YwC@wl+>d3&{QPcE(Ii#3GJzP0dDo5nXhg^qm0qp+80@(9KHc9ZOGor$BbRDOByB zP3v#K=Zv!V0!+8C|k|tZQn^gJ095lw_MF1Z6()|ELGP zmyZd;8h((Xi$bg=eUb%u=Gq*7r-=$MBylDhN@?FCraS+%oN(yEJ-pf<(5>x`5~1`L zSM7uNiuq7tm^U14HIC;CcIFiu2bkt7zBZYyUUmf*j~bp@IR~!#ytYghuqzfr)nfLD zZq%thO;?W38P?ubx=lY|XIai)d&N+}mAJk42hx@ld|?~;%`}kFua_g9B>3KKsMdSp z{CYpo7GBfo=oy+bx(7Xhbw{&PfllFTqpBhfgtN~&pKrib8P81~m`LkfQAXf~ZnXb~ zt5%UP!b>G+(m(6fJ1qLghijF*>f`K(#g%7U5W(hkP#)LaM+p*)1=%=2Uq*Y7a$l6% za;RZ1>n3Pt=VUB^k_st&_b0Gw(^>V{8-zJ4xGSZ26Z0FE?w4H}7$S8)4F3NV>gI<> zq(Y2coaA%(BTk%7UA`k)Bkp3_`f}ybcM~q}YOmrR))KUCpYnE-4r=dqXMDI|y@B~-G{c;2BG2wI)uohu;^acz^GIwQr{^J2Qb1ge(_vE;c z?hvUd>AhB!$H~b1k(l#gOdlJ;yRTfQ_#p$Yu6LTC(w9bwrS|&c(PNE6az42Ip$D&$ zQaeRjYP^JG=dr&r z4L%4|P%Bjzhjf5M&*|k?HX-X4pi8q4W?y{$aime6dz<1$#?n&WYXeQ!=+uQnyZmuN zzpI!H7}+po{@-;7&0c9qVHR1w-&91mUOG*A9OTBU`d?ZQf*w+|hV;7e6< ziS!f~3BE>(75@?#Bb$Tq*Eu+Q-&Dikn4^5eCbPDLP zkX1&tvOHU1r9+6yA)&2SZP9R*_t^HY*e}T|1kieSG=+a9agU__JAtoNuStDbX7gmm+MxVBq-2BPX?0p1v zHaVZ>EF@}>I!S>xYa!6CPe0Tf_xm#quoaX#emQ{ewVZKSgj5+Zw3dw(j*_z;4!5vxJEI=I zka9Ip1--_K4(toMPm(DJK!qrAVa}{)b7UWnHQj&q0 zi(lyIR}9=`zIwq(ADqcrvrG2rDxxwnALSDNvAFccsh4hFbvsbghhAn@U-}0| z3dkTb=Q6d=UE#r;X>>G(XR|k*&ueyd&dEQ-T*9Aa1!Wh@AXP`pDBEvDzX_ZvGxCODRsQjdAcN>x+_@1dmAhM zV25@RWM1-Y7G-@gxZJ@5V*{CF@;^~IpS}P-<2SD5xpyuG(G~G$AfxPL?keMCt$cNy zzpn&d{|9X}R=+f}-?)hxpp2$D$KJ?#|2;D%mk-)w2<3|TyW*--EQPiDtq0SDhE@UV zcAZo_M06c;g{VJc*j(3#A#X+fn%8xl#SYq7a^Y3Aa?FZCb|g=3wEj8r&VF!6z3M)R$#2YBuxM>%@LIy$K z|1KGe{1932KHJ#iw-VeNC^?3!trAO@$a^^`_%z>YC+%{FCOzag37su`&Bk?7LsU4G zRQ49Me`D`$uZaxZIhYvAZ%1lhea0YU5J6SZub`o$Sf0J8xW11FRk~Jk$Wfn>#)e5# zUy-L<@($-9Ey_;w4A2B?I0{&mT!RWtog0v z?ix$H=V?<}l$g?@L0A*mEY7h_)dw6E<}|ciZ@kHpFJ?AZw&(Un;`@0}rirk1rOlhe z9RU4UWM)aV{;I;N*mYv$guRxjx6#n2ZF46JsF>ay&wz>iVr2-p{_XVOz|JoYmg7GQ zhFMAb3!XuWdpR<{qdqRd_5(wF?|?r3uiW@VGH3oWl&t1w+_S$7CC6Yl7Gc9!3dNBY zPp)R3xp+$hlt#>lmx)&E&Rva^VAvxG%3sBm2c5X;e}GxOjU4`(go}-Ff=2vmp^sAC zXsSHctb?Dii1I(7^`r2|(wFFZ#3m=kLv4v$(u+yhc1m4#NFuKx+RAn+yu&0;vZWp&`M)63VLt5R~q%Ho(3(cGlg>7e55f~06du7 zvqFo0`)qv>kvi~u_^0c0``WzoY!3N|1Q0hU8r8eF@hC_cL|){Ew>u^6gPm<+B{%ej zh;i4B=`x8-%*-(#U37HnK|{hp>%Zgf#x)8GX`U3dK#HmsuuoVNGI!Kc87Wd-kI_lR z(YuX38%sKRE3+rswLZ*gy7@JJ(!UET9uf!r%PII=bKxG1qew$o`YlXuTBW)SO(cmTz`l;}h?us+?4)XF7 zjT@f|hpWyD!-4Do20dr_6o!s(XRp$=_0DA+Ud5HE9?-On^im2u{o%2 zt|@UxO&iJh&wNVM(ui^OP`}EHLZKnDkm3L8T`Cv|V1iq`Udos)URS*o@v6-mUr!8N zmitI(MgD?E7Du`0xU!h&A)zB@oM4? zE_LJ`*kL!Wf6{tK95((iV09W}j|*Zafb&Rh4eON0Y9;KvEDhB z&3Jq#XN(D!`TAKnsM7u#fqQ+MqLMQ_zEQnmtv=4O>z`(Uy-MJ@WZM3}uay>GG(R}C z5ih$UHLhMU$`zJs?2y_ZuftAaK8IToY%P~|C5ZT3Z8@8VVciXevb_-%zT)zb@aQ9v zh^8PKqy|U5W&1PqBSoI3Mx@G{)ma8K^;|xt2_R}>I(_yms@ewK_SPjkVqq*$#PEqo zt%=RhLmMP^bKYFaC=`NR|{xP3!T3~6olABM( zy&QvETCL?Xe^=Xc#m>dBO#8mCK0A z{Y-(#vP5c`3VpPWqj7(aUtyy|e4KF+Z#?6vrMHlU@h6Tbni_x4EHgL$eoFyxmqYN5 zAljJTgIAfkaBjg0*-gkRv=Qo;R2Y++#Qz_^P?~|+UUKl#z$N;9GD|Lb`~aua>{D;5 z6~zcV_)`YOlm33}mVp6XA8Zu?sB0beW~6J#B>F>&CbIXc@wECSgY@%$ymbf4>|Kd* zmI1fMH@@c1ry;#J&(uwJl@O$)e@sP_{~t|f9o6*zzJGc&C`yhYqD-Z`OQZw_iU>$I zNOyNCEiGLl(%mgxV{|u+9%Ix9e|vwv=lAFS+1c4SujliAUH5fe52}&~?euNvf3>)~ zd1?_xf3t{D1mmSNlKyRP3XZl zuhi99)3rlTNc=0bJbUAMxGt2K$Z0Nl{k4(;KZhEm;!d3?=Y z_9=$I)n$=BC+%!?XXKnm0q!jwn1;)v3)B0xy|VQT9xj9Mm-s19nw&JSCb;J+eEaoC zb4U!WUw@ivINiO`ZN4tM`1zk+)~*-9A|})vI@iT>$dCD!SkD3h(_1AWmZh~{)<6pW9TH3t(*>9t|wjO=)BBraRfr~^vT*_WzmZZ67{ykLLRR`i& z;W9Bd-+J!~UuqXwsy7-LIb|zkDCO|tE``=)NjJm-(#7%jE=+dzK2nhOZ62jtW-o9B zE`_D`Ejm?ki$ssTo0=i+?xN9^9#43;o?h4rqjtlYPXSAP$<1-M#r=s{b;CyAM-fxh zuKvMth*CxuI>_#OBoUK*Leod!f$fBY>B|eZ@ZaPrg{oSmx|dxSMEF;hq1w^@38(eV zVJTN_dlEL#>8qGiU(TY-))(&>?gJ@zu(q2}cz%-(;|!j-E%_QqTXQBt$cU#>&`W&k zC>&NV);b{3*@_C{*Cfkyp#*Qb%D|F7#3a2?A-))*9Lb3V0zEZgGJudHApN*>?~mOZ z(=a6f?^(EM8~dkc&jNw?K8slHoEN6imv+lS?fa_t5dZKnCG1t#XX3(FCf+iRM7}vc zev9H2;F)s&RAkaO$-JXr3_pn2W19#YuW)i?of&Ao*anCX3X*eVk`D?8>ABd}DKXLK zev@;Ix`^#Gh31m{uv{&c`a|Kln2lR-i{~f%0Cg>JBW}Ccgnr2ATCrxN=B9m|3Exio zHMo@LP8-|_l88WhvSRG~71k26Y~C6;EC7Ws3a&6aQxuE~&#Nvci-Y>1X1)M&U|}pP zNurh=wmMWCG4|hEAdXCoJ zoE;D_^`^=J*eewTT%KI;IC?u!T)WPgo#84FyHO`8i!q~AzLBzNxm*=TVw@vPei%ZV zt>MM7nZ43U@E9d$g^ZZo9;HL*X>a#N5OQ}=U}_nkgL}0{mC&HVKsOsEdCgOd=dxVsGVXK{k?M7ziz(b&H8w~GLYWYO z02KI5H|}Yw;TQh?sUf#&ODmPe;MOrG*zeLk2BP8bqmS97Y96U{7M$DRJ=A$kk3DVu zh%6~3AV5h^Tk-%L+l~t(Sxf=QcvcnD7GV*7Ms^!RrpEYHcdMOknA&=xM>(( zAeMF-_XJ*Wdg#o3g9XQFTXZ~qkUHSFvQy?=;=SrNIR3{ha_w zM396Zp-~-<;cv%}m%Iiajg*2IDDX3Px0b55^GMYGAS;jtpUxzCnulriXohc7SER&; zPUK1OuYJe%R{4-pqF5aF_x$^firi7;3Fvxx>j^5%?m72!1V+$RLKv#`=Y^Dp-;K8lR#*#dN!pF> zcNO{J<&bZx)1=F@^0?|IAxH7z^ZHC0z4?9>w!^b^ZK1T>NHLa|htBNfRQ}y!j_mGY$APxJ8Ver~ zYAszan%|P=sgk)0OssQWG60#Y!WZ!-8h%4|7lof2;%LucW$y`Cs5j;reOW2mEx&Rn z5UqbUKqBTAv7ya~3KN`e_#g$TvNfQt!{?1ER}T0-9*3Ls|S^BvF}e4p&YA) za`7#d#H2*}#CLh(_8D1+{Js7T2N@b&VYLQWDO{CfXUkgK{8YNyS1xJgoocX?w1(Ht z>=I~QzVRc|&vM>4k2?^t%Ms#?KJ`WxCzj0%XOfy91(OM-b__NH+LIHoxa`8hC7GDx zTAD&rvWFU@Q~&pWsbaT90e`#DyA!k(AtR6TaqiYHg#yln+U+u8Ubx6I$k`#gEEl*3 z1@0a@yfDi{X@Y!8{*l63dnt|igciSfGrAB2DW>57`dp8E@2dH6;M$(@zZ?i7LANW@#CRKF0!1>^SRY zUqa=ehIDxzBsrtg|6J%31#=U-ID{L#pozZtF2H5MiTrzrl-1dl@2gznc9=V|piwO~ zY`mXJ0c0A|LEl(rD^OAep3&Z}JSDOchy^>&k&7qexN3gnU{AYM1KS+sx zH(QB2#96;f9h~@iKj~4>r^C6FnFoTU6E+2`pV!)vs``s{cu_;IcYBv4g4IM(s!5>+X%ZDahfivcx4cIBE;HVT&9W zekl>eRbdB_o(`jHf-^0uwG`spBv^63<8#|^?3UIF@junJ$BF;A@qJTcs#!~cgx|_$ z0DtR!@Y(jDX>0U$WjLj4dtSt$=!1T*D*c1O8pmnslX+6&I`O>-spZ!7MmmHLmAvN* zgLMOy@X)yM)XzF;$fDY*sDzZv@*tTjOw!ra;dhKZmj5~w8%Q_kIQrXaGi**-7DA4n zeb0J`BN5E*@kO>&A^vIxUIJmA_B2C9POEuM;k69iuKjy>!1U<_D~X0O5*?%}nXj`c zH&3#>U23D5Vm_#zhW)NpD)NQ~{c!lQ%ywseTA*NCM|x8)0LFm;teEGMjRD1JL9!X8 ziFMf`M9N>rSz<+IJ4oLwl5uW#T+Zx0ZWC*Ed7qFPEeqyf{2uR|R0=}32x6VP6#!}F>gz7I+n^DCy=fu9#Y>cHX{_6}#v z+15N>z~K!k0bH7q<+ec#mO21XRZ4(Z0zOVbci7nYqY?_~*>Wn}P&{Kzo|Iq$;lbSSt<6@d@HObdfyz>s?i<-u4=NOK4Z)e4Vy;+Oy4wA)+Gb2Iwc$jqzfYMQ@DogNwy>Uj zcOx-Ddm1oZ0Gi2p{O)8nFh}xGXX0Q^w2*;j(8qbevQmFneLF@J7LZwEDH%<0UEgfE zt9gB?E{*FPF^0#a8gF3W`?-0t5^2yO7_VxCiTPHH3a%BdC3p;zh))Qz6yEm*r6X~q ztl>uWc5iisUpgRdUwkGP^Fz_`&b0x3?a~_(>|eqERp1@V7boUCJaeCsUR40ANKk>Z z)?ggozwNO0&_2KuffygZl&qenVM{R)lNvS{*_JIG{mk9gaN3nilb-I;BS%pq>-e;v zM?NB}(>e{T8$tRkUscH1kL=B^GJ5a4b2F~Fq6s?E-e-+QGrWQ=I@$Mp4he!Q`X-88 zrH`89^tB#@arqi|##`sjW4+W<#o}p)sx%|s-$VnbpA+(t>Pv!g)1LI51@bH;I0RvH zdC}6XZ}!CNT6{UHyjnNPE%s2r>o_v!IvgoGUGfoLys$PoAbcJRkd?{$Scx|9g6q(b z5qi88PZZuSav|&fjj(8^&~tk0xN=m36EI5)!h5qtRh+E9vQNW$NQ4g7e`=-n&8#q% zS<3B$R6qHBC4u7w*FDQz{&ef6&gq5PYDMg9*~O$G?E*WwI{)jZRtf(6pSUKXbzSR? z68)dKCs7SBw7#93(tgY3`aPjw(@nNn>&m8LrSzTK3wlP+E)(P#1Myhu1w1+*@WapZ z-u|n06O1OJ|M)qS#DKF6E88_vbe}##=wXLHxyAIopNdhy@tnV%8IQ0xUek4}xaW#X z&h4^)L?Rt@H!Fi{XFXzev0QgVkcZHlN1j4O;A-+286MR=EFpVx-xEg$`uVUh*t zpct|4D)Ji2?Av&jjd=t6UqSe1nx#bW1)_CzuRZNilc*t#35v^sID(rU?9l^?dop(< z9c85uE~=Oo0aU?%qj5p!!(r#il7fW)$)10O6g~?t$33pCcVW~P_Q0c720FbX{^?yD ziOJvy|0JW$LF=ICB=Ri549?d!uUHk5!LtzSRHz-B6J5^)AC+~iWdC>kF6a;rF(XUo zj7(syccJ}#uf)HPT&H?y%rUyn2Vvy;nhCqPz_+eztXZtq-{6Az2@Q)bUtJ1Z*pRhE zg`Pf6Wgx&Ow{;`*W>25J=#(ShxR2iGu(WlO<~}*aXx|qu54008MM~HX-C!WDt%69_ zU%o26VPx4b4nnv>mvCGUQ!~n*7J%WH=(8C5@v@GSZYaS>N=U#xjhF$~W?QO*z`}FG z+if3P@E;CSnO588(|ev_k;_$ke+KEyLRmm02#VY%*gjB0Q)aWp*F&Y8y0}bUwO-bEPwMP$`_?h*c ze44tSi=#alGskz)hu|jY8_fnb5LvTW!;7p6m>iF;ycAGD87-w?zZ6*xvE(k{v1Y9D zLbqxGGxG9(RSc8La4<@acY-Lio8`V>c7?Wt#B%tfKkP-^-V~ns@qz6d$X!)J(A02Z zHmb10J`el)q-Z8U1oTNPx22@AdflYdtDi4>^6KT5f`L1S^>lz<)hitP-A7gtWbE0hgd`$65HhK4oH=c~$@mukdN=C98vP~G;5#~#>~ zJ-CDKGj7=DKc~x*#~W`9UCWOccFlhelp;&csbT3ww)XF;?zY)>?)DrML9W4)zhgPu zbdK8Hll%Egk+V7h!P-Yz!7;(oQWp_DlG5o zX_7pA7k%I<9WZIX$nJK4ofKenyo=pxZiRl!(R(|+bj#oj4fiB!uMh<}E*7#J;7*EA zhmp^@**V)eJ?kBz&_1Bl5Ss9ear;VnuH>mc7fH`&dh)X(!S1L2^u?PsIzcPaC#gXn z>4qqixv5*n4xcx>vQlM>AlY%qYh$?i&T7>ugC>5D4mwz}I5_0L$4Oo|qU zz(Y`Z2Amk-adM@_v_BlM^KQjlf4KhWnK*BzXVBDthBe;3lZ*uqrltW6TD|sotCjWWq2s?XAra?@GOHh|@8cRdPQGCuq#2nF>?c|6Nryf{EgV}0 z(J(az+Q(*L3V*FfrP^d$5Mak%T?9=HEtiG`8R<9b)?_(WMP-AZ4rqPcl|WP6VwrRH z^T9zD$9>`G9$=#lg4}*;x?p#t{f-mmH5p|v&9y$@j^z>6FEXYi0mriC-6#p-9sMv4Vb@!?B3$_`ml&$H`fA zaw21$P%CwsQ7higR3b;PW>$*5e9l`EHxDPt^-<}od<7TOsBnpJ3t%oSui(~~l~FyA zrdaQu*-zx+h|2Zf_S3)35nMn-Aba@5x69su1K0MI?|>uwxce$ma2*ja zAK)J}=fUb|?3>0UKNhmK9dZWh)gGc$y<|vG*rcYo zhX1O9wj%i24hg@Oy$`Al8?p#3s`&^pva>AHSLvbSr2UOro7Q50gM%%-49;c@O`yXr zb_Bl!$Lb9Xi5UBaOK!IHxdMh)70bj)Ub*K^iym-g0(b?XvYmPAT@H^Ss|!T=QhaF5 z0)m1Re8%q_z0}_LzaDsqPUH_73B5c425vK}1*6kv@%Fq8|Mvt(0NX4F+~)p;vQ>Vu zWyFcS4%0`wO)j0s;C*kkNilLBSHGz%mM9n6{Go5g(;Oy6-+FB*+1Qn9{iEyKWUxGn zuz!*kq0zB|V0HIg#e4*F%wP1Wk0`ezZLW{!t4&=GN{TPVu<4``M>?*Ap214wPQQMO zzFXgzF+{^sz4z2f*oNieoV@W?u-fR85}f}WMS++vMTq1Xb26WYB$aYZThdEBM+||G z;Tqp+Xe$FYX#9=e6;FMR9$c6EWo_u@TvzBkN!Pyd7W5OyOM9&!M-V?)8)GI}fM!gz31fX$+XOBiTPH&5~2z zRS+@I->Ol1DIMtFq#H)7@9x#u)iEgcOq5ryBQ2_9PvTTZ;g<^_)a$dDQki?(%077}Y!1l+Oc2Caq5!mB~H!e7`Ga`bj*G`WoR>ZMg? zVR^o9T@;E(;sL{t!PDN|-9FdB3YO;&|1ldXrFxd^1I~T;VdNEW&T&23wHX{at0PbN z*|6T%1_=rHjqNvpmJ4R;D{E%D^n_S-dB<$W%_{``6|NJ<(#(|-A@T?@W z+`lJqC&(RCN?fZ#8MH%!PH8B?26f9V>I9>#MwxrV3~UWtEj*jL!T~LdcN!P2Hx~)X zkenaX*$4Y3TGAe-iiVXJC7!`-WM7mLpOJaOEl2x?ig#5P1ycyhF!qNX!l~_3D$Xvc zoa6Sp@15NoVwHk0}OI|M+)FIy%M@vLM8gMl^P_% z#t*ZQ7m0Iiy8;D5Z5w2z4NvQy`-m{X#S->mQzP>!U!@i5(mt=o|66>!0MrsWnvAlo zayr0a49R>_=9JKCWK&vK-?4U33hSL@ofE$Bl3(pV>!MZk(e&+3oU0YTS~?GHP&uXH z=qlMvZn4Eva|tVp`1|Ooe)-(wjrf$>8&#o?;}$y`u8ud!K6Cta=f=uDf2X(gwFg32 z;?@&Zly)UD^!y#uP2Y-D!f`L(Olr(IlRSR>mmplpFjl}eN`RXszI`ON_$O!2P;Fk2 zZLTHk!}-g04+NnLhGKA{qkef(n{#g~wa%kIP9B=YxK0E!Ax?+SC{0oN52XrQPiF*P zDG}nxH3#n{;bfn(YO?l+DqB4jp6WMp5g(isK^p7f#jf})4#eW(tn_f>5*ep@zov|-vgE}j5!U6>Gi2{RAX8A_KqdM)6&EL{(4an(*hB1OBi0yM*2oevrQ}X) z<#OEpiV~aMkL;i`dn-uXuA_@Ei^w6077-a726?T8cH+jdVW}3}b#(H+qN8*QzMSSd zB8J*Oc}nG42Q{1{aZJvB=9ctJG9h=owZ)W{u6eoT$re@(h)rZd_MDSD|ve zwP#9cxG-otJDR`wk}1y}k(%dQu90(n$+{}64U?oX z69v}WZl}exhp7Wf)Uy^MOV!pVSy<8*ixR(7e3vzM#D0c_+bv*>-Gkq#CRs9r%`F+c zR3dWu7LFCDBu0>E*s!}qme2n2A7@?EkSG9eb8^}Fy! zRV&@wATkpe$5_DNjnC(+v?qN2fAe@mN&F)|f7_FxGB5VE%jTZo)#&f&mpVctDL7ld zJ{`qgneYB_VulDghN(or&M14Qm?t?_kZ#xT8W%9&cxx{bb zIjly_U7H1Z#oR=DQ8$b5U+ypw!y5LJx9GCtuEb;}AW}8GZDKB9X97Lnh_=%6uT63l zN?I4Vo`sT5yO$!b&A;CH$(lq-!T81=t@J9zCPX?{*WdFTK=Upo5dhUTB+s#?`)Gd?hVwgFa zwk4udnf@ZJf&8h0bIwZmBkgwfNbR!8lgXFsuyCd82zg{v>gN@X#XC$k(wu@=)mOgT zQtTrIKdj?5mn+oYF+b6qC+7?u!gOb}W{4W8W2SFXu=K|C*_u~WM(YWT75d!72!JCd zyP@L*fR@xtbS@-EWRw_UOE_1Ip+aHUN zD`u!V``Or&4jc&++DzDX9SP}m&=BIGFG&qU`O*)*77`QF50=fPvR{yY?lUkW<(LVT zwrD@pKcyGJU#zdGsUb*Bk|IypTi2ng?y`Rbip{oD$c=Nx`F&`Ls2;+BV3LNO$PX*R zEp?bQyzz6Axl%yu1Jl1VrmM@S3m8mmpJH3b&O;c+5fL2InyHF+A3CnsiOX`&db~}S z)zweIV4M7CA>Cb#Za7M^RsZ;@-Xg7mr8}5vezd1#hV|*OOz{F?1w7=ltDt-Fe2Q=c zRA^sCCn^K4vx8(zcUkDLQBA?{OD2uq>{VougvnQAMdx=xf!|}QR+H;k5Q6*acLl5P zzt?qtHF28-{Tj4rx?7fBTEfn`&%Kppptu4k$f(jy#ao1plZ4YN+eWBrgC}TLgAD$Dd zsuG`S8M%>mXd~42Lyy_WS#T!#5$JDKNbAsg*=R`MGrg?c_*|95Q=l@yBRuUtKVv&H zY|lAe*GGRlzF9jNh#Vv~sOT9_+;;HpexYt- zx4r%Zh*Nj1GpabAlxr=Bp`?Z7;urlQu&&QT7scjTs-{{#(E__^XEyf!t=1lXr>n)` zGy9-EVso0TmVOzd1i=!ZWl0L59bY$P|M+;Fn+yb_y?tUZ5Tdn{xoPuZAD8J`x^nbT z)~JA|h{S#>>4THOwvXS=59Pymowq=_fwfd)r68}5>%UoOwXYz)T+OKA*hagFLYtr7 z^yk-I)>-G+&2ARO%Nc|TG45p;iIur1Uph0&on#g?*?CV6X7AcCg6Onq7iSJ7N2UFb6+EbUiVJuBR4!ame+YXQz9yQ zKOAEa9Xkh;ahc@n1>|mEt?gN1PuYo?zGCXfP>A0U?wt-pf)9`~lU2Hb&m`Ul_J*qb z%*lH+p8SynN!B1WDn3GT*VE|u4Y&5~34c4ZMBTF_*AA9_AF^vW9KM#WFZI@icZG;J zp)#Soo@}`{r{e2d>uLbROrrRmYwo}JB_bsEDz|7+Wf;LzcUWV!Xg4Ot;13>ey?9G{9Kul2ii}nhHl*Iw^r!Hpdbo(f@AEnfjP<|1I(2yVjJf=gyskWc2cIuddDg!W9(uEwsCkEk- z@yQi~@5G@#E&<}NnXdyAz0^Gm_=A*ob?&MVW7qr5C=*EJF3K#knLTYdRu8|D4EQYj z0abiO;E#Ymm7FqLm1hmv_^zTcOPNV+f2*5~_+lsfkgr5`ARl*KWQZ=Bd@png%9mBm z@o}T+dJ1QTjDB|k{QPR;Mkw(-BepN22vfiqy|68?-I9#xWqxT2Sd2Sbvg8b~s?qsU#c+?71|C$nH zaxx(3YatZ(S4xEYCno7&*6bRb=rH<^Wi9+VsLpffb0|VZ{K=~A^Hxy?{FO+MvHwU4 zj-j(;4gQ*2H&c!){^vej{WoiPON(my+8+N#EhTGnCYHF3H@xUqMt%}9;CWVh&tRAp z3EAlB;>*~q>VJ5Z->0Awy7+)k&YCMU9B>NT4XbbRnDg@m*y8g*$Y8*6dWYuz+N|;| zlp*^(a}Pz%3`ww3Yy0gr@1eJ@wXk#m*e5|u?cMrM=VIg&4m9OBp=lLren#8g2FejmXje9^HiF+Ex_mHH8Qk(zp}>y7PI|+#6;`I ztyYrStr6^m%VQXMS^--$H4e1cidjMJz^m*M`2huf5DQI6w66rlj;0?sNE?nyu7`% zBypp&{8D+R8@fau0o*yPq9>tw^&t}uBviEQ!nB}D%#NeuW|@<=%vFVtz~h31k8E}7 z@9>lDu(|�xwr>SxwuJeh=7GpVtU?bm+9 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 e*#DcO7VFg*iD}!PLB_ej$K=E*!=mGu(0>Dt 快速入门 详细指南 + 科研指南 API 文档 ------------- diff --git a/docs/source/user/with_fitlog.rst b/docs/source/user/with_fitlog.rst index 97c3ea71..51445775 100644 --- a/docs/source/user/with_fitlog.rst +++ b/docs/source/user/with_fitlog.rst @@ -2,4 +2,121 @@ 科研向导 ================= -本文介绍使用 fastNLP 和 fitlog 进行科学研究的方法 \ No newline at end of file +本文介绍结合使用 fastNLP 和 fitlog 进行科研的方法。 + +首先,我们需要安装 `fitlog `_ 。你需要确认你的电脑中没有其它名为 `fitlog` 的命令。 + +我们从命令行中进入到一个文件夹,现在我们要在文件夹中创建我们的 fastNLP 项目。你可以在命令行输入 `fitlog init test1` , +然后你会看到如下提示:: + + Initialized empty Git repository in /Users/fdujyn/workspaces/test1/.git/ + Auto commit by fitlog + Initialized empty Git repository in /Users/fdujyn/workspaces/test1/.git/ + Fitlog project test1 is initialized. + +这表明你已经创建成功了项目文件夹,并且在项目文件夹中已经初始化了 Git。如果你不想初始化 Git, +可以参考文档 `命令行工具 `_ + +现在我们进入你创建的项目文件夹 test1 中,可以看到有一个名为 logs 的文件夹,后面我们将会在里面存放你的实验记录。 +同时也有一个名为 main.py 的文件,这是我们推荐你使用的训练入口文件。文件的内容如下:: + + import fitlog + + fitlog.commit(__file__) # auto commit your codes + fitlog.add_hyper_in_file (__file__) # record your hyperparameters + + """ + Your training code here, you may use these functions to log your result: + fitlog.add_hyper() + fitlog.add_loss() + fitlog.add_metric() + fitlog.add_best_metric() + ...... + """ + + fitlog.finish() # finish the logging + +我们推荐你保留除注释外的四行代码,它们有助于你的实验, +他们的具体用处参见文档 `用户 API `_ + +我们假定你要进行前两个教程中的实验,并已经把数据复制到了项目根目录下的 tutorial_sample_dataset.csv 文件中。 +现在我们编写如下的训练代码,使用 :class:`~fastNLP.core.callback.FitlogCallback` 进行实验记录保存:: + + import fitlog + from fastNLP import Vocabulary, Trainer, CrossEntropyLoss, AccuracyMetric + from fastNLP.io import CSVLoader + from fastNLP.models import CNNText + from fastNLP.core.callback import FitlogCallback + + fitlog.commit(__file__) # auto commit your codes + fitlog.add_hyper_in_file (__file__) # record your hyperparameters + + ############hyper + word_embed = 50 + dropout = 0.1 + ############hyper + + loader = CSVLoader(headers=('raw_sentence', 'label'), sep='\t') + dataset = loader.load("tutorial_sample_dataset.csv") + + dataset.apply(lambda x: x['raw_sentence'].lower(), new_field_name='sentence') + dataset.apply(lambda x: x['sentence'].split(), new_field_name='words', is_input=True) + dataset.apply(lambda x: int(x['label']), new_field_name='target', is_target=True) + vocab = Vocabulary(min_freq=2).from_dataset(dataset, field_name='words') + vocab.index_dataset(dataset, field_name='words',new_field_name='words') + + model = CNNText((len(vocab),word_embed), num_classes=5, padding=2, dropout=dropout) + + train_dev_data, test_data = dataset.split(0.1) + train_data, dev_data = train_dev_data.split(0.1) + + trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, + loss=CrossEntropyLoss(), metrics=AccuracyMetric(), + callbacks=[FitlogCallback(test_data)]) + trainer.train() + + fitlog.finish() # finish the logging + +用命令行在项目目录下执行 `python main.py` 之后,输出结果如下:: + + Auto commit by fitlog + input fields after batch(if batch size is 2): + words: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 11]) + target fields after batch(if batch size is 2): + target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) + + training epochs started 2019-05-23-21-11-51 + Evaluation at Epoch 1/10. Step:2/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 2/10. Step:4/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 3/10. Step:6/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 4/10. Step:8/20. AccuracyMetric: acc=0.428571 + + Evaluation at Epoch 5/10. Step:10/20. AccuracyMetric: acc=0.571429 + + Evaluation at Epoch 6/10. Step:12/20. AccuracyMetric: acc=0.571429 + + Evaluation at Epoch 7/10. Step:14/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 8/10. Step:16/20. AccuracyMetric: acc=0.142857 + + Evaluation at Epoch 9/10. Step:18/20. AccuracyMetric: acc=0.285714 + + Evaluation at Epoch 10/10. Step:20/20. AccuracyMetric: acc=0.571429 + + + In Epoch:5/Step:10, got best dev performance:AccuracyMetric: acc=0.571429 + Reloaded the best model. + +现在,我们在项目目录下输入 `fitlog log logs` ,命令行会启动一个网页,默认 url 为 ``0.0.0.0:5000`` 。 +我们在浏览器中打开网页,可以看到如下的统计表格: + +.. image:: ../figures/fitlogTable.png + +如果我们点击action中的最后一个键钮,可以看到详细的 loss 图: + +.. image:: ../figures/fitlogChart.png + +更多的教程还在编写中,敬请期待~ \ No newline at end of file diff --git a/fastNLP/core/batch.py b/fastNLP/core/batch.py index c1289adf..109d4fe9 100644 --- a/fastNLP/core/batch.py +++ b/fastNLP/core/batch.py @@ -30,11 +30,8 @@ class Batch(object): """ 别名::class:`fastNLP.Batch` :class:`fastNLP.core.batch.Batch` - Batch 用于从 `DataSet` 中按一定的顺序, 依次按 ``batch_size`` 的大小将数据取出. - 组成 `x` 和 `y` - - - Example:: + Batch 用于从 `DataSet` 中按一定的顺序, 依次按 ``batch_size`` 的大小将数据取出, + 组成 `x` 和 `y`:: batch = Batch(data_set, batch_size=16, sampler=SequentialSampler()) num_batch = len(batch) diff --git a/fastNLP/core/callback.py b/fastNLP/core/callback.py index 7fad2d0b..e617cf2a 100644 --- a/fastNLP/core/callback.py +++ b/fastNLP/core/callback.py @@ -54,6 +54,7 @@ __all__ = [ "GradientClipCallback", "EarlyStopCallback", "TensorboardCallback", + "FitlogCallback", "LRScheduler", "ControlC", @@ -65,6 +66,7 @@ import os import torch from copy import deepcopy + try: from tensorboardX import SummaryWriter @@ -81,6 +83,7 @@ try: except: pass + class Callback(object): """ 别名::class:`fastNLP.Callback` :class:`fastNLP.core.callback.Callback` @@ -367,16 +370,17 @@ class GradientClipCallback(Callback): 每次backward前,将parameter的gradient clip到某个范围。 - :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。如果为None则默认对Trainer - 的model中所有参数进行clip + :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。 + 如果为None则默认对Trainer的model中所有参数进行clip :param float clip_value: 将gradient 限制到[-clip_value, clip_value]。clip_value应该为正数 :param str clip_type: 支持'norm', 'value' 两种:: 1 'norm', 将gradient的norm rescale到[-clip_value, clip_value] - 2 'value', 将gradient限制在[-clip_value, clip_value], 小于-clip_value的gradient被赋值为-clip_value; - 大于clip_value的gradient被赋值为clip_value. + 2 'value', 将gradient限制在[-clip_value, clip_value], + 小于-clip_value的gradient被赋值为-clip_value; + 大于clip_value的gradient被赋值为clip_value. """ @@ -431,14 +435,13 @@ class EarlyStopCallback(Callback): else: raise exception # 抛出陌生Error + class FitlogCallback(Callback): """ - 别名: :class:`fastNLP.FitlogCallback` :class:`fastNLP.core.callback.FitlogCallback` - 该callback将loss和progress自动写入到fitlog中; 如果Trainer有dev的数据,将自动把dev的结果写入到log中; 同时还支持传入 - 一个(或多个)test数据集进行测试(只有在trainer具有dev时才能使用),每次在dev上evaluate之后会在这些数据集上验证一下。 - 并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 - fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 + 一个(或多个)test数据集进行测试(只有在trainer具有dev时才能使用),每次在dev上evaluate之后会在这些数据集上验证一下。 + 并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 + fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 :param DataSet,dict(DataSet) data: 传入DataSet对象,会使用多个Trainer中的metric对数据进行验证。如果需要传入多个 DataSet请通过dict的方式传入,dict的key将作为对应dataset的name传递给fitlog。若tester不为None时,data需要通过 @@ -447,7 +450,9 @@ class FitlogCallback(Callback): :param int verbose: 是否在终端打印内容,0不打印 :param bool log_exception: fitlog是否记录发生的exception信息 """ - + # 还没有被导出到 fastNLP 层 + # 别名: :class:`fastNLP.FitlogCallback` :class:`fastNLP.core.callback.FitlogCallback` + def __init__(self, data=None, tester=None, verbose=0, log_exception=False): super().__init__() self.datasets = {} @@ -460,7 +465,7 @@ class FitlogCallback(Callback): assert 'test' not in data, "Cannot use `test` as DataSet key, when tester is passed." setattr(tester, 'verbose', 0) self.testers['test'] = tester - + if isinstance(data, dict): for key, value in data.items(): assert isinstance(value, DataSet), f"Only DataSet object is allowed, not {type(value)}." @@ -470,23 +475,23 @@ class FitlogCallback(Callback): self.datasets['test'] = data else: raise TypeError("data receives dict[DataSet] or DataSet object.") - + self.verbose = verbose - + def on_train_begin(self): - if (len(self.datasets)>0 or len(self.testers)>0 ) and self.trainer.dev_data is None: + if (len(self.datasets) > 0 or len(self.testers) > 0) and self.trainer.dev_data is None: raise RuntimeError("Trainer has no dev data, you cannot pass extra data to do evaluation.") - - if len(self.datasets)>0: + + if len(self.datasets) > 0: for key, data in self.datasets.items(): tester = Tester(data=data, model=self.model, batch_size=self.batch_size, metrics=self.trainer.metrics, verbose=0) self.testers[key] = tester fitlog.add_progress(total_steps=self.n_steps) - + def on_backward_begin(self, loss): fitlog.add_loss(loss.item(), name='loss', step=self.step, epoch=self.epoch) - + def on_valid_end(self, eval_result, metric_key, optimizer, better_result): if better_result: eval_result = deepcopy(eval_result) @@ -494,11 +499,11 @@ class FitlogCallback(Callback): eval_result['epoch'] = self.epoch fitlog.add_best_metric(eval_result) fitlog.add_metric(eval_result, step=self.step, epoch=self.epoch) - if len(self.testers)>0: + if len(self.testers) > 0: for key, tester in self.testers.items(): try: eval_result = tester.test() - if self.verbose!=0: + if self.verbose != 0: self.pbar.write("Evaluation on DataSet {}:".format(key)) self.pbar.write(tester._format_eval_results(eval_result)) fitlog.add_metric(eval_result, name=key, step=self.step, epoch=self.epoch) @@ -506,10 +511,10 @@ class FitlogCallback(Callback): fitlog.add_best_metric(eval_result, name=key) except Exception: self.pbar.write("Exception happens when evaluate on DataSet named `{}`.".format(key)) - + def on_train_end(self): fitlog.finish() - + def on_exception(self, exception): fitlog.finish(status=1) if self._log_exception: diff --git a/fastNLP/core/dataset.py b/fastNLP/core/dataset.py index 2f3e35ca..9f24adf2 100644 --- a/fastNLP/core/dataset.py +++ b/fastNLP/core/dataset.py @@ -571,9 +571,7 @@ class DataSet(object): def set_input(self, *field_names, flag=True): """ - 将field_names的field设置为input - - Example:: + 将field_names的field设置为input:: dataset.set_input('words', 'seq_len') # 将words和seq_len这两个field的input属性设置为True dataset.set_input('words', flag=False) # 将words这个field的input属性设置为False @@ -605,9 +603,7 @@ class DataSet(object): def set_padder(self, field_name, padder): """ - 为field_name设置padder - - Example:: + 为field_name设置padder:: from fastNLP import EngChar2DPadder padder = EngChar2DPadder() diff --git a/fastNLP/core/field.py b/fastNLP/core/field.py index 21ead327..fca1cee1 100644 --- a/fastNLP/core/field.py +++ b/fastNLP/core/field.py @@ -448,9 +448,7 @@ class EngChar2DPadder(Padder): 但这个Padder只能处理index为int的情况。 padded过后的batch内容,形状为(batch_size, max_sentence_length, max_word_length). max_sentence_length为这个batch中最大句 - 子长度;max_word_length为这个batch中最长的word的长度 - - Example:: + 子长度;max_word_length为这个batch中最长的word的长度:: from fastNLP import DataSet from fastNLP import EngChar2DPadder diff --git a/fastNLP/core/instance.py b/fastNLP/core/instance.py index 07ae6495..5408522e 100644 --- a/fastNLP/core/instance.py +++ b/fastNLP/core/instance.py @@ -13,9 +13,7 @@ class Instance(object): 别名::class:`fastNLP.Instance` :class:`fastNLP.core.instance.Instance` Instance是fastNLP中对应一个sample的类。每个sample在fastNLP中是一个Instance对象。 - Instance一般与 :class:`~fastNLP.DataSet` 一起使用, Instance的初始化如下面的Example所示 - - Example:: + Instance一般与 :class:`~fastNLP.DataSet` 一起使用, Instance的初始化如下面的Example所示:: >>>from fastNLP import Instance >>>ins = Instance(field_1=[1, 1, 1], field_2=[2, 2, 2]) diff --git a/fastNLP/core/losses.py b/fastNLP/core/losses.py index ddc2c49f..9dc02f3d 100644 --- a/fastNLP/core/losses.py +++ b/fastNLP/core/losses.py @@ -190,10 +190,10 @@ class LossFunc(LossBase): 找到相对应的参数名为value的参数,并传入func中作为参数名为key的参数 :param kwargs: 除了参数映射表以外可以用key word args的方式设置参数映射关系 - Example:: + 使用方法:: - >>> func = torch.nn.CrossEntropyLoss() - >>> loss_func = LossFunc(func, input="pred", target="label") + func = torch.nn.CrossEntropyLoss() + loss_func = LossFunc(func, input="pred", target="label") # 这表示构建了一个损失函数类,由func计算损失函数,其中将从模型返回值或者DataSet的target=True的field # 当中找到一个参数名为`pred`的参数传入func一个参数名为`input`的参数;找到一个参数名为`label`的参数 # 传入func作为一个名为`target`的参数 @@ -227,7 +227,7 @@ class CrossEntropyLoss(LossBase): Example:: - >>> loss = CrossEntropyLoss(pred='pred', target='label', padding_idx=0) + loss = CrossEntropyLoss(pred='pred', target='label', padding_idx=0) """ diff --git a/fastNLP/core/trainer.py b/fastNLP/core/trainer.py index 702cb6e7..2523a957 100644 --- a/fastNLP/core/trainer.py +++ b/fastNLP/core/trainer.py @@ -498,14 +498,15 @@ class Trainer(object): """ 使用该函数使Trainer开始训练。 - :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效,如果True, trainer将在返回之前重新加载dev表现 - 最好的模型参数。 + :param bool load_best_model: 该参数只有在初始化提供了dev_data的情况下有效, + 如果True, trainer将在返回之前重新加载dev表现最好的模型参数。 :return dict: 返回一个字典类型的数据, 内含以下内容:: seconds: float, 表示训练时长 以下三个内容只有在提供了dev_data的情况下会有。 - best_eval: Dict of Dict, 表示evaluation的结果。第一层的key为Metric的名称,第二层的key为具体的Metric + best_eval: Dict of Dict, 表示evaluation的结果。第一层的key为Metric的名称, + 第二层的key为具体的Metric best_epoch: int,在第几个epoch取得的最佳值 best_step: int, 在第几个step(batch)更新取得的最佳值 diff --git a/fastNLP/core/utils.py b/fastNLP/core/utils.py index 518c8213..79af296b 100644 --- a/fastNLP/core/utils.py +++ b/fastNLP/core/utils.py @@ -68,18 +68,14 @@ def cache_results(_cache_fp, _refresh=False, _verbose=1): # res = [5 4 9 1 8] # 0.0040721893310546875 - 可以看到第二次运行的时候,只用了0.0001s左右,是由于第二次运行将直接从cache.pkl这个文件读取数据,而不会经过再次预处理 - - Example:: + 可以看到第二次运行的时候,只用了0.0001s左右,是由于第二次运行将直接从cache.pkl这个文件读取数据,而不会经过再次预处理:: # 还是以上面的例子为例,如果需要重新生成另一个cache,比如另一个数据集的内容,通过如下的方式调用即可 process_data(_cache_fp='cache2.pkl') # 完全不影响之前的‘cache.pkl' 上面的_cache_fp是cache_results会识别的参数,它将从'cache2.pkl'这里缓存/读取数据,即这里的'cache2.pkl'覆盖默认的 'cache.pkl'。如果在你的函数前面加上了@cache_results()则你的函数会增加三个参数[_cache_fp, _refresh, _verbose]。 - 上面的例子即为使用_cache_fp的情况,这三个参数不会传入到你的函数中,当然你写的函数参数名也不可能包含这三个名称。 - - Example:: + 上面的例子即为使用_cache_fp的情况,这三个参数不会传入到你的函数中,当然你写的函数参数名也不可能包含这三个名称:: process_data(_cache_fp='cache2.pkl', _refresh=True) # 这里强制重新生成一份对预处理的cache。 # _verbose是用于控制输出信息的,如果为0,则不输出任何内容;如果为1,则会提醒当前步骤是读取的cache还是生成了新的cache diff --git a/fastNLP/core/vocabulary.py b/fastNLP/core/vocabulary.py index 43f590fd..cbde9cba 100644 --- a/fastNLP/core/vocabulary.py +++ b/fastNLP/core/vocabulary.py @@ -44,9 +44,7 @@ class Vocabulary(object): """ 别名::class:`fastNLP.Vocabulary` :class:`fastNLP.core.vocabulary.Vocabulary` - 用于构建, 存储和使用 `str` 到 `int` 的一一映射 - - Example:: + 用于构建, 存储和使用 `str` 到 `int` 的一一映射:: vocab = Vocabulary() word_list = "this is a word list".split() @@ -159,9 +157,7 @@ class Vocabulary(object): def has_word(self, w): """ - 检查词是否被记录 - - Example:: + 检查词是否被记录:: has_abc = vocab.has_word('abc') # equals to @@ -189,9 +185,7 @@ class Vocabulary(object): @_check_build_vocab def index_dataset(self, *datasets, field_name, new_field_name=None): """ - 将DataSet中对应field的词转为数字. - - Example:: + 将DataSet中对应field的词转为数字,Example:: # remember to use `field_name` vocab.index_dataset(train_data, dev_data, test_data, field_name='words') @@ -234,9 +228,7 @@ class Vocabulary(object): def from_dataset(self, *datasets, field_name): """ - 使用dataset的对应field中词构建词典 - - Example:: + 使用dataset的对应field中词构建词典:: # remember to use `field_name` vocab.from_dataset(train_data1, train_data2, field_name='words') @@ -280,9 +272,7 @@ class Vocabulary(object): def to_index(self, w): """ 将词转为数字. 若词不再词典中被记录, 将视为 unknown, 若 ``unknown=None`` , 将抛出 - ``ValueError`` - - Example:: + ``ValueError``:: index = vocab.to_index('abc') # equals to diff --git a/fastNLP/io/dataset_loader.py b/fastNLP/io/dataset_loader.py index 32cca88f..0abaa42b 100644 --- a/fastNLP/io/dataset_loader.py +++ b/fastNLP/io/dataset_loader.py @@ -106,7 +106,7 @@ class DataSetLoader: """ 别名::class:`fastNLP.io.DataSetLoader` :class:`fastNLP.io.dataset_loader.DataSetLoader` - 定义了各种 DataSetLoader 所需的API 接口,开发者应该继承它实现各种的 DataSetLoader。 + 定义了各种 DataSetLoader (针对特定数据上的特定任务) 所需的API 接口,开发者应该继承它实现各种的 DataSetLoader。 开发者至少应该编写如下内容: diff --git a/fastNLP/modules/encoder/embedding.py b/fastNLP/modules/encoder/embedding.py index f3c1f475..c2dfab65 100644 --- a/fastNLP/modules/encoder/embedding.py +++ b/fastNLP/modules/encoder/embedding.py @@ -41,3 +41,10 @@ class Embedding(nn.Embedding): """ x = super().forward(x) return self.dropout(x) + + def size(self): + """ + Embedding的大小 + :return: torch.Size() + """ + return self.weight.size() diff --git a/fastNLP/modules/utils.py b/fastNLP/modules/utils.py index c9a1f682..741429bb 100644 --- a/fastNLP/modules/utils.py +++ b/fastNLP/modules/utils.py @@ -74,9 +74,9 @@ def get_embeddings(init_embed): """ 根据输入的init_embed生成nn.Embedding对象。 - :param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 - embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, - 此时就以传入的对象作为embedding + :param init_embed: 可以是 tuple:(num_embedings, embedding_dim), 即embedding的大小和每个词的维度;也可以传入 + nn.Embedding 对象, 此时就以传入的对象作为embedding; 传入np.ndarray也行,将使用传入的ndarray作为作为Embedding初始 + 化; 传入orch.Tensor, 将使用传入的值作为Embedding初始化。 :return nn.Embedding embeddings: """ if isinstance(init_embed, tuple): diff --git a/readthedocs.yml b/readthedocs.yml index 9b172987..e6d5bafd 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,6 +1,16 @@ +version: 2 + +sphinx: + configuration: docs/source/conf.py + build: image: latest python: version: 3.6 - setup_py_install: true \ No newline at end of file + install: + - method: setuptools + path: . + +formats: + - htmlzip \ No newline at end of file diff --git a/setup.py b/setup.py index b7834d8d..49646761 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,8 @@ setup( version='0.4.0', description='fastNLP: Deep Learning Toolkit for NLP, developed by Fudan FastNLP Team', long_description=readme, - license=license, + long_description_content_type='text/markdown', + license='Apache License', author='FudanNLP', python_requires='>=3.6', packages=find_packages(),

6M9JW6{$|G23c16-kT`NqgTP9 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%0A!9bJTQr(*jpbfdzbha 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

6M9JW6{$|G23c16-kT`NqgTP9 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%0A!9bJTQr(*jpbfdzbha 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