本文基于AllenNLP中文教程
有一篇帖子总结了一下学习处理NLP问题中间的坑。NLP数据预处理要比CV的麻烦很多。
- 去除停用词,建立词典,加载各种预训练词向量,Sentence -> Word ID -> Word Embedding的过程(Tobias Lee:文本预处理方法小记),其中不仅需要学习pytorch,可能还要学习spacy,NLTK,numpy,pandas,tensorboardX等常用python包。
- 用到RNN时,还要经过pad,pack,pad的过程,像这样的很多函数在使用时需要有数学基础加上简单的实践,感觉对一个新人来说,高维数据的流动有点抽象,不容易理解。
- 数据集的读取,tensorboardX的使用。。。。各种东西要学习。在运行别人的代码后打印出信息,不仅看着上档次,而且可以看到很多实用的信息。。。
AllenNLP是在pytorch基础上的封装,它的目标是处理NLP任务,可以减少很多额外的学习。
- 分词,帮你用spacy,NLTK,或者简单的按空格分词处理。
- 数据集的读取,它内置了很多数据集的读取,你可以在通过学习它的读取方式,在它的基础上对自己需要的数据集进行读取。 、
- 在Sentence -> Word ID -> Word Embedding的过程中,Glove,ELMo,BERT等常用的都可以直接使用,需要word,char粒度的都可以。
- log打印输出,在内置的输出项之外,你可以很方便地加入想要输出的信息。模型的各个组件中的参数都可以存在一个json/jsonnet文件中,修改参数进行实验很方便。
3. 完整实例,预测论文发表场合
第二部分 预测和Demo
在完成构建和训练模型后,需要使用这个模型去做一些事情,这个教程完成
- 如何使用模型完成一次预测
- 如何实现一个网络Demo
教程中将代码放在了Github上,可以选择自己训练或是下载那个模型。
第一步 创建预测器Predictor
模型中最重要的是forward()方法。
@overrides
def forward(self, # type: ignore
title: Dict[str, torch.LongTensor],
abstract: Dict[str, torch.LongTensor],
label: torch.LongTensor = None) -> Dict[str, torch.Tensor]:
pass
它需要一个Tensor-ized标题和抽象(可能还有一个标签)并返回一些Tensor输出。这对于计算损失函数和性能指标非常有用,但它对于进行预测和提供演示不太有用。我们希望实现的是接受JSON输入并返回JSON结果。
AllenNLP提供一个Predictor抽象类,其中打包了一个模型并精确地实现它。大多数需要Predictor的功能都已经在这个基础类中实现。通畅只需要实现predict_json功能(能够将输入的JSON字典转为AllenNLP的Instance)。因为希望返回Instance,我们使用predict_instace来序列化这个JSON字典。在DatasetReader中已经有了text_to_instance方法,能够从JSON中抽取出我们需要的信息。
有了以上的方法,我们的Predictor会很简单。
@Predictor.register('paper-classifier')
class PaperClassifierPredictor(Predictor):
# Predictor打包的AcademicPaperClassifier
def predict_json(self, inputs: JsonDict) -> JsonDict:
instance = self._json_to_instance(inputs)
output_dict = self.predict_instance(instance)
# label_dict will be like {0: "ACL", 1: "AI", ...}
label_dict = self._model.vocab.get_index_to_token_vocabulary('labels')
# Convert it to list ["ACL", "AI", ...]
all_labels = [label_dict[i] for i in range(len(label_dict))]
output_dict["all_labels"] = all_labels
return output_dict
@overrides
def _json_to_instance(self, json_dict: JsonDict) -> Instance:
title = json_dict['title']
abstract = json_dict['paperAbstract']
return self._dataset_reader.text_to_instance(title=title, abstract=abstract)
每一个Instance都有title和paperAbstract,并将其喂给text_to_instance。在这个例子中,我们同样希望返回一个可能的labels列表。我们首先得到从索引到labels的映射,然后我们将它转换为位置0为标签0的列表,依此类推。
predict_json返回一个JSON字典,第一个元素是Instance,第二个元素是一个字典(包括了Model.forward_on_instance的元素),总之就是我们的forward的返回值没有提供的但是我们又需要的东西都放在这里就好啦。
在这里我们只是存储所有可能的标签,其他的东西暂时就不存啦。当然,如果你连标签都用不着,那你就放个空空的字典在这里占位就好啦。
第二步 测试预测器
和前几个类似,我们需要写一个测试看看是否能用。
这里的主要问题是我们的测试将(隐式)需要按名称实例化我们的模型,数据集读取器和预测器,这意味着它们需要在我们的测试运行之前注册。在my_library / predictors /__ init__.py中将它们全部添加为导入,然后在构建predictor_test时只需要导入该包
init.py
# pylint: disable=wildcard-import
from my_library.dataset_readers import *
from my_library.models import *
from my_library.predictors import *
predict_test.py
import my_library
测试很简单,提供输入所要求的JSON格式({title: title, abstract: abstract}),然后在模型中run即可。
# pylint: disable=no-self-use,invalid-name,unused-import
from unittest import TestCase
from pytest import approx
from allennlp.models.archival import load_archive
from allennlp.predictors import Predictor
# required so that our custom model + predictor + dataset reader
# will be registered by name
import my_library
class TestPaperClassifierPredictor(TestCase):
def test_uses_named_inputs(self):
# JSON格式输入
inputs = {
"title": "Interferring Discourse Relations in Context",
"paperAbstract": (
"We investigate various contextual effects on text "
"interpretation, and account for them by providing "
"contextual constraints in a logical theory of text "
"interpretation. On the basis of the way these constraints "
"interact with the other knowledge sources, we draw some "
"general conclusions about the role of domain-specific "
"information, top-down and bottom-up discourse information "
"flow, and the usefulness of formalisation in discourse theory."
)
}
archive = load_archive(r'C:/Users/01/Desktop/allennlp-as-a-library-example-master/tests/fixtures/model.tar.gz')
predictor = Predictor.from_archive(archive, 'paper-classifier')
result = predictor.predict_json(inputs)
label = result.get("label")
assert label in {'AI', 'ML', 'ACL'}
all_labels = result.get("all_labels")
assert all_labels == ['AI', 'ACL', 'ML']
class_probabilities = result.get("class_probabilities")
assert class_probabilities is not None
assert all(cp > 0 for cp in class_probabilities)
assert sum(class_probabilities) == approx(1.0)
运行命令
allennlp predict tests/fixtures/model.tar.gz tests/fixtures/s2_papers.jsonl --include-package my_library --predictor paper-classifier
使用验证集进行验证。
结果如下,看了一下十篇文本都是ACL,肯定不对,原因可能是我训练集用的是下载的那个训练集,里面的内容不是机器学习这方面的。
prediction: {"logits": [-0.11413432657718658, 0.43438369035720825, 0.07639823108911514], "class_probabilities": [0.25376972556114197, 0.43919578194618225, 0.3070344030857086], "label": "ACL", "all_labels": ["AI", "ACL", "ML"]}
prediction: {"logits": [-0.08729098737239838, 0.2393871545791626, -0.04727406054735184], "class_probabilities": [0.29178479313850403, 0.40451735258102417, 0.3036978840827942], "label": "ACL", "all_labels": ["AI", "ACL", "ML"]}
prediction: {"logits": [-0.06943605840206146, 0.23592156171798706, -0.039462827146053314], "class_probabilities": [0.2951996922492981, 0.4006185531616211, 0.3041817247867584], "label": "ACL", "all_labels": ["AI", "ACL", "ML"]}
prediction: {"logits": [-0.10107310116291046, 0.24393372237682343, -0.05196814239025116], "class_probabilities": [0.28882285952568054, 0.4078177213668823, 0.3033594489097595], "label": "ACL", "all_labels": ["AI", "ACL", "ML"]}
prediction: {"logits": [-0.15188564360141754, 0.2519247531890869, -0.07553312927484512], "class_probabilities": [0.27957457304000854, 0.4186685085296631, 0.3017568588256836], "label": "ACL", "all_labels": ["AI", "ACL", "ML"]}
.....