机器人之机器阅读理解模型Stanford Attentive Reader源码解析
小标 2018-10-17 来源 : 阅读 2115 评论 0

摘要:本文主要向大家介绍了机器人之机器阅读理解模型Stanford Attentive Reader源码解析,通过具体的内容向大家展现,希望对大家学习机器人有所帮助。

本文主要向大家介绍了机器人之机器阅读理解模型Stanford Attentive Reader源码解析,通过具体的内容向大家展现,希望对大家学习机器人有所帮助。

0 前言

Stanford Attentive Reader是斯坦福在2016年的ACL会议上的《A Thorough Examination of the CNN/Daily Mail Reading Comprehension Task》(论文地址,GitHub源码,代码要求Python 2.7,Theano >= 0.7,深度学习框架Lasagne 0.2.dev1,代码整体风格非常简洁易懂)发布的一个机器阅读理解模型。我们对Stanford Attentive Reader模型的源码进行了解析,力求通过分析其源码来深入研究一个机器阅读理解模型是如何工作的。


采用的数据集有两个:CNN和Daily Mail,下载地址分别为CNN (546M)和Daily Mail (1.4G);100维glove词向量下载地址。


1 载入数据load_data

这部分主要是载入训练集、验证集以及测试集的CNN和Daily Mail数据,输入参数主要有三个:



in_file:表示数据文件地址;


max_example:如果在debug模式下,该值为100,表示只采用数据集中前100个样本,如果在非debug模式下,该值为None,表示采用数据集中全部数据;


relabeling:bool型,如果要求对数据集中实体类单词重新编号,则该值为True,若要求重新编号则按照实体类单词出现的顺序进行编号,默认为True。



输出为文档集合、问题集合和答案集合。


从load_data输入输出可看出,其功能主要是分别提取每个数据集样本中的文档、问题以及答案,并按对应顺序放到三个不同的list中,从answer = entity_dict[answer]中可以看出,答案一定是文档或问题中的某个实体类单词。


源码为:


def load_data(in_file, max_example=None, relabeling=True):
    documents = []
    questions = []
    answers = []
    num_examples = 0
    f = open(in_file, 'r')
    while True:
        line = f.readline()
        if not line:
            break
        question = line.strip().lower()
        answer = f.readline().strip()
        document = f.readline().strip().lower()

        if relabeling:
            q_words = question.split(' ')
            d_words = document.split(' ')
            assert answer in d_words

            entity_dict = {}
            entity_id = 0
            for word in d_words + q_words:
                if (word.startswith('@entity')) and (word not in entity_dict):
                    entity_dict[word] = '@entity' + str(entity_id)
                    entity_id += 1

            q_words = [entity_dict[w] if w in entity_dict else w for w in q_words]
            d_words = [entity_dict[w] if w in entity_dict else w for w in d_words]
            answer = entity_dict[answer]

            question = ' '.join(q_words)
            document = ' '.join(d_words)

        questions.append(question)
        answers.append(answer)
        documents.append(document)
        num_examples += 1

        f.readline()
        if (max_example is not None) and (num_examples >= max_example):
            break
    f.close()
    return (documents, questions, answers)


载入训练集和验证集数据后,则开始构建单词字典。


2 单词字典build_dict

输入为文档集合和问题集合sentences;最大词数max_words,默认为50000。


输出即为文档和问题集合的单词字典word_dict,key为单词,value为按照词频排序的序号。


build_dict使用了Python集合类的Counter子类来完成文档与问题的单词统计,Counter可以便捷和快速地进行哈希的对象计数,并返回一个无序的容器,元素被作为字典的key存储,它们的计数作为字典的value存储。然后使用most_common方法从多到少返回一个有前max_words多的元素的列表ls,相同数量的元素次序任意。最后返回一个由ls组成的单词字典,值得注意的是,ls列表的数据从字典的第三个开始填充,单词字典第一个为“无法识别的词”即字典第一个元素为”<UNK>”:0,第二个为分隔符即”|||”:1。


源码为:


def build_dict(sentences, max_words=50000):
    word_count = Counter()
    for sent in sentences:
        for w in sent.split(' '):
            word_count[w] += 1

    ls = word_count.most_common(max_words)

    return {w[0]: index + 2 for (index, w) in enumerate(ls)}


把机器阅读理解问题看作一个分类问题。


entity_markers = list(set([w for w in word_dict.keys() if w.startswith('@entity')] + train_examples[2]))
entity_markers = ['<unk_entity>'] + entity_markers
entity_dict = {w: index for (index, w) in enumerate(entity_markers)}
args.num_labels = len(entity_dict)


3 embedding字典

根据单词字典word_dict来构建嵌入层gen_embeddings,gen_embeddings的输入包含四个参数:



word_dict:表示单词字典;


dim:表示词向量维度;


in_file:如果为None,则表示不使用预训练好的词向量来初始化嵌入层,这时使用第四个参数的参数初始化方法lasagne.init.Uniform(),即对嵌入层权值进行均匀分布初始化,如果不为None,则应该输入预训练词向量的文件地址,这里默认使用的是100维的glove词向量来初始化嵌入层,值得注意的是,类似于“@entity1”这种单词显然不包含在glove词向量中,那么对于这类词也采用均匀分布初始化的方法;


init:默认为使用深度学习框架lasagne的均匀分布初始化方法。



gen_embeddings的输出即为embeddings字典,key表示word_dict中对应词的value,即按照词频排序的序号,value为一个列表,表示key的词向量,词向量中的值为float类型。


源码为:


def gen_embeddings(word_dict, dim, in_file=None,
                   init=lasagne.init.Uniform()):

    num_words = max(word_dict.values()) + 1
    embeddings = init((num_words, dim))

    if in_file is not None:
        pre_trained = 0
        for line in open(in_file).readlines():
            sp = line.split()
            if sp[0] in word_dict:
                pre_trained += 1
                embeddings[word_dict[sp[0]]] = [float(x) for x in sp[1:]]
    return embeddings


4 Encode层

将构建的embeddings字典来初始化lasagne.layers.EmbeddingLayer的权重,这里默认使用双向GRU(lasagne.layers.GRULayer)来构建文档和问题的encode层,在rnn_layer中的参数backwards表示了是否使用双向GRU。对于文档的encode,即name=‘d’的stack_rnn,其中参数only_return_final表示是否要只返回GRU的最终输出,args.att_func默认为bilinear,所以only_return_final为False,即对于文档的encode是使用GRU的正向与反向的隐藏层输出来构建的;对于问题的encode,即name=‘q’的stack_rnn,only_return_final=True代表问题的encode是使用GRU的正向与反向的最终输出来构建的。


源码为:


l_in1 = lasagne.layers.InputLayer((None, None), in_x1)
l_mask1 = lasagne.layers.InputLayer((None, None), in_mask1)
l_emb1 = lasagne.layers.EmbeddingLayer(l_in1, args.vocab_size, args.embedding_size, W=embeddings)

l_in2 = lasagne.layers.InputLayer((None, None), in_x2)
l_mask2 = lasagne.layers.InputLayer((None, None), in_mask2)
l_emb2 = lasagne.layers.EmbeddingLayer(l_in2, args.vocab_size, args.embedding_size, W=l_emb1.W)

network1 = nn_layers.stack_rnn(l_emb1, l_mask1, args.num_layers, args.hidden_size,
                               grad_clipping=args.grad_clipping,
                               dropout_rate=args.dropout_rate,
                               only_return_final=(args.att_func == 'last'),
                               bidir=args.bidir,
                               name='d',
                               rnn_layer=args.rnn_layer)

network2 = nn_layers.stack_rnn(l_emb2, l_mask2, args.num_layers, args.hidden_size,
                               grad_clipping=args.grad_clipping,
                               dropout_rate=args.dropout_rate,
                               only_return_final=True,
                               bidir=args.bidir,
                               name='q',
                               rnn_layer=args.rnn_layer)


stack_rnn封装了lasagne.layers.GRULayer,对外使用stack_rnn来构建双向GRU。


def stack_rnn(l_emb, l_mask, num_layers, num_units,
              grad_clipping=10, dropout_rate=0.,
              bidir=True,
              only_return_final=False,
              name='',
              rnn_layer=lasagne.layers.LSTMLayer):

    def _rnn(backwards=True, name=''):
        network = l_emb
        for layer in range(num_layers):
            if dropout_rate > 0:
                network = lasagne.layers.DropoutLayer(network, p=dropout_rate)
            c_only_return_final = only_return_final and (layer == num_layers - 1)
            network = rnn_layer(network, num_units,
                                grad_clipping=grad_clipping,
                                mask_input=l_mask,
                                only_return_final=c_only_return_final,
                                backwards=backwards,
                                name=name + '_layer' + str(layer + 1))
        return network

    network = _rnn(True, name)
    if bidir:
        network = lasagne.layers.ConcatLayer([network, _rnn(False, name + '_back')], axis=-1)
    return network


5 Attention层

从Encode层得到文档的上下文表示network1以及问题的上下文表示network2,使用一个双线性函数nn_layers.BilinearAttentionLayer作为network1和network2的匹配函数。


att = nn_layers.BilinearAttentionLayer([network1, network2], args.rnn_output_size, mask_input=l_m    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标人工智能智能机器人频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved