AllenNLP之入门解读代码

  本文解读代码https://github.com/dmesquita/easy-deep-learning-with-AllenNLP,用不到三百行的代码解决一个文本分类的任务,实现了NLP的整个流程。
一、前期准备:
配环境:参见我的上一篇博客https://blog.csdn.net/Findingxu/article/details/90722995
下代码:在Ubuntu上 git clone 一下代码。
 

二、AlleNLP介绍:
从图中我们可以知道在AllenNLP中构建一个模型的组成和数据流动,

分为四个部分:dataset_reader, model,iterator,training。数据用dataset_reader读取和处理,用迭代器iterator 定义的参数,迭代输入到model 中,model提供了整个网络结构和参数,然后用training定义的参数进行训练。

AllenNLP的厉害之处就是能通过一个配置文件就把上面四部分的参数都写好,然后调用就行。还有封装很多功能,可以提供字典,各种网络结构等。

三、解读代码:

(一) 先来看最重要的json文件:

{
  "dataset_reader": {
    "type": "20newsgroups"
  },
  "train_data_path": "train",
  "test_data_path": "test",
  "evaluate_on_test": true,
  "model": {
    "type": "20newsgroups_classifier",
    "model_text_field_embedder": {
      "tokens": {
        "type": "embedding",
        "pretrained_file": "https://s3-us-west-2.amazonaws.com/allennlp/datasets/glove/glove.6B.100d.txt.gz",
        "embedding_dim": 100,
        "trainable": false
      }
    },
    "internal_text_encoder": {
      "type": "lstm",
      "bidirectional": true,
      "input_size": 100,
      "hidden_size": 100,
      "num_layers": 1,
      "dropout": 0.2
    },
    "classifier_feedforward": {
      "input_dim": 200,
      "num_layers": 2,
      "hidden_dims": [200, 100],
      "activations": ["relu", "linear"],
      "dropout": [0.2, 0.0]
    }
  },
  "iterator": {
    "type": "bucket",
    "sorting_keys": [["text", "num_tokens"]],
    "batch_size": 64
  },
  "trainer": {
    "num_epochs": 30,
    "patience": 3,
    "cuda_device": 0,
    "grad_clipping": 5.0,
    "validation_metric": "+accuracy",
    "optimizer": {
      "type": "adagrad"
    }
  }
}

包括以下几个部分:
1. 数据输入:datareader
    告诉AllenNLP输入的数据集和如何读取它,DatasetReader从某个位置读取数据并构造Dataset。除文件路径之外的读取数据所需的所有参数都应递给DatasetReader的构造器。
2.模型:model
设置'model'键值来指定模型,在'model'键值中还有三个参数:'model_text_field_embedder','internal_text_encoder'和'classifier_feedforward'
分别对应着embedding 层,encoder 层,feedforward层的参数。
3.数据迭代器:iterator
分批分离训练数据。 AllenNLP提供了一个名为BucketIterator的迭代器,通过对每批最大输入长度填充批量,使计算(填充)更高效。
4.训练器:trainer
提供训练的参数,patience 当模型的性能经过多少个epoch没有增加时,停止训练。

(二)然后,编写AllenNLP python 类:
1 .Dataset_readers 文件夹下的:fetch_newsgroups.py
为了引用JSON文件中的DatasetReader,需要注册它:用以下两行

@DatasetReader.register("20newsgroups")
class NewsgroupsDatasetReader(DatasetReader):

用到两个方法:
(1)read()

@overrides 
def _read(self, file_path):
        instances = []
        if file_path == "train":
            logger.info("Reading instances from: %s", file_path)
            categories = ["comp.graphics","sci.space","rec.sport.baseball"]
            newsgroups_data = fetch_20newsgroups(subset='train',categories=categories)
            
        elif file_path == "test":
            logger.info("Reading instances from: %s", file_path)
            categories = ["comp.graphics","sci.space","rec.sport.baseball"]
            newsgroups_data = fetch_20newsgroups(subset='test',categories=categories)
            
        else:
            raise ConfigurationError("Path string not specified in read method")
            
        for i,text in enumerate(newsgroups_data.data):
            if file_path == "validate":
                if i == 400:
                    break
            text = newsgroups_data.data[i]
            target = newsgroups_data.target[i]
            yield self.text_to_instance(text, target) # 提取需要的字段,利用text_to_instance将这些字段转换成instance类型的数据


read()从scikit-learn获取数据。通过AllenNLP,你可以设置数据文件的路径(例如JSON文件的路径),但在我们的例子中,我们只需像Python模块一样导入数据。 我们将读取数据集中的每个文本和每个标签,并用text_to_instance()包装它。
(2)text_to_instance()

@overrides
    def text_to_instance(self, text: str, target: str = None) -> Instance:  # type: ignore
        # pylint: disable=arguments-differ 
        tokenized_text = self._tokenizer.tokenize(text) # 把文本变成你想要的格式:单词字母等
        text_field = TextField(tokenized_text, self._token_indexers) # TextField表示转化成了序号之后(tokenized)的文本数据
        fields = {'text': text_field}
        if target is not None:
            fields['label'] = LabelField(int(target),skip_indexing=True) # LabelField表示类别标签
        return Instance(fields)

此方法“进行任何符号化或必要的处理,来把文本输入转为Instance”(AllenNLP Documentation),将来自20个新闻组的文本和标签包装到TextField和LabelField中。
Tokenizer是把你的文本转换成单词啦,字母啦,比特对啦等等常见的你想要的形式。TokenIndexer是给这些形式编个号,并且把最终的文本转换成序号表示的形式。举个例子来说,如果你的token是单词,那么我们强大的TokenIndexer可以自动的为你生成单词编号,字母编号,pos_tags的编号。

2 .models 文件夹下的:newsgroups_classifier.py 编写模型类
首先初始化:

def __init__(self, vocab: Vocabulary,
                 model_text_field_embedder: TextFieldEmbedder, # 这三个都是allennlp.modules的内容
                 internal_text_encoder: Seq2VecEncoder,
                 classifier_feedforward: FeedForward,
                 initializer: InitializerApplicator = InitializerApplicator(), 
                 regularizer: Optional[RegularizerApplicator] = None) -> None:

  (1)vocab: Vocabulary
         来自allennlp.data。这个数据字典其实是个复合字典,包括所有TextField的字典,以及LabelField自己单独的字典。
  (2)model_text_field_embedder: TextFieldEmbedder
         来自allennlp.modules,提供了结构,只要把参数填进去就可以。

# 在from_params里面填参数:L126
embedder_params = params.pop("model_text_field_embedder")
model_text_field_embedder =TextFieldEmbedder.from_params(embedder_params, vocab=vocab)

    (3)internal_text_encoder: Seq2VecEncoder
             Seq2VecEncoder可以用来做encoder,可以有很多种,CNN,RNN等各种模型都有用...,这里传进的参数是双向LSTM
    (4)classifier_feedforward: FeedForward
    (5)initializer: InitializerApplicator = InitializerApplicator()
             来自allennlp.nn ,InitializerApplicator包含着所有参数的基本初始化方法。如果你想自定义初始化,就需要时候用RegularizerApplicator

  然后,重写一下forward函数就可以了(同pytorch)

@overrides #pytorch 在这里构建网络,在init里面定义网络参数结构
    def forward(self,  # type: ignore
                text: Dict[str, torch.LongTensor],
                label: torch.LongTensor = None) -> Dict[str, torch.Tensor]:
        embedded_text = self.model_text_field_embedder(text)
        text_mask = util.get_text_field_mask(text) # 获得掩码来表示符号序列的哪些元素仅用于填充
        encoded_text = self.internal_text_encoder(embedded_text, text_mask) 
        logits = self.classifier_feedforward(encoded_text) 
        output_dict = {'logits': logits}
        if label is not None:
            loss = self.loss(logits, label.squeeze(-1))
            for metric in self.metrics.values():
                metric(logits, label.squeeze(-1))
            output_dict["loss"] = loss
        return output_dict

decode() :接收forward的输出,并对其进行任何必要的推理或解码,得到你要的结果形式。

 @overrides
    def decode(self, output_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
        class_probabilities = F.softmax(output_dict['logits'])
        output_dict['class_probabilities'] = class_probabilities

        predictions = class_probabilities.cpu().data.numpy()
        argmax_indices = numpy.argmax(predictions, axis=-1)
        labels = [self.vocab.get_token_from_index(x, namespace="labels")
                  for x in argmax_indices]
        output_dict['label'] = labels
        return output_dict

最后是AllenNLP为所有的注册的类都实现了一个from_params的方法,这个方法能够非常好根据json配置文件中提供的信息,对应的调用构造函数,为我们构造DatasetReader以及model实例。

@classmethod
    def from_params(cls, vocab: Vocabulary, params: Params) -> 'Fetch20NewsgroupsClassifier': # ->返回值注释 
        embedder_params = params.pop("model_text_field_embedder")
        model_text_field_embedder = TextFieldEmbedder.from_params(embedder_params, vocab=vocab)
        internal_text_encoder = Seq2VecEncoder.from_params(params.pop("internal_text_encoder"))
        classifier_feedforward = FeedForward.from_params(params.pop("classifier_feedforward"))

        initializer = InitializerApplicator.from_params(params.pop('initializer', []))
        regularizer = RegularizerApplicator.from_params(params.pop('regularizer', []))

        return cls(vocab=vocab, #cls 类函数 ,cls 和self的区别就是 self要实例化之后才能用,cls不需要
                   model_text_field_embedder=model_text_field_embedder,
                   internal_text_encoder=internal_text_encoder,
                   classifier_feedforward=classifier_feedforward,
                   initializer=initializer,
                   regularizer=regularizer)

四、运行代码和解决bug 
  命令行运行  python run.py train experiments/newsgroups_with_cuda.json -s tmp/result(保存结果的文件)
  但是 报错了:
  
  然后发现,给的代码中少写了一个参数:
  需要加上, lazy=False   super().__init__(lazy=lazy)

def __init__(self,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None, lazy=False) -> None:
        super().__init__(lazy=lazy)

这个参数的含义可以看这个官方文档的解释
好了,代码运行成功了。
要了解代码执行的过程,可以到你保存结果的路径下,找到log信息文件查看。
这个路径包含很多,有词汇表,有可视化summary等。

总的来说,AllenNLP还是很简易便捷的。

参考:

https://mp.weixin.qq.com/s/ESIQbw43f8Y7M-d3qPeEng

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值