renll / StateNet

[EMNLP 2018] Towards Universal Dialogue State Tracking
Other
42 stars 11 forks source link

请教如何复现模型 #4

Closed YourThomasLee closed 4 years ago

YourThomasLee commented 4 years ago

作者大大好,我直接下载了整个项目,运行get_embedding.py获取到embed_vN3.npy词向量文件文件后,执行mat_data.py获取了test_nbest.json、test_nbest_tagged.json、train_nbest.json、train_nbest_tagged.json、dev_nbest.json、dev_nbest_tagged.json后,运行offline_model_dstc中的函数train_dstc2(1),更改设置了变量ctx='cpu',但运行多个epoch后发现评价指标并没有发生变化,以下为运行的部分输出: image

我现在有两个请求,如果能够得到作者的回应对我的研究有莫大的帮助!如下 第一,能简述下复现模型的步骤么?(先执行哪一个文件后执行哪一个文件) 二,能简述下项目中数据预处理的文件么?(数据预处理的大致功能效果) 不论是否能够得到解答,感谢作者的代码贡献和分享!

renll commented 4 years ago

你好! 一,只要能生成所有模型需要的文件并可以执行训练就可以,没有特定的顺序。你的问题可能在于使用cpu训练,这是我们没有考虑过的情况。我建议尝试使用mxnet1.1.0版本和cuda8.0的gpu环境进行训练。 二,预处理部分事实上你只需要阅读mat_data.py的genTurnData_nbest函数就好,因为*tagged.json这类文件合并了tagged的数据和没有tagged的数据,而我们在训练时事实上并没有使用tagged部分的数据,只是作为legacy data被保留了下来。

YourThomasLee commented 4 years ago

非常感谢作者大大的回应,在环境配置方面,今日我在服务器上创建了python2的虚拟环境,因为cuda驱动版本10.1,所以配置了mxnet-cu101==1.5.1.post0(小的心里暗自揣摩应该不影响的)。之后生成了模型依赖的文件,之后就是执行train_dstc(0): image

执行train_dstc(1)的结果:

image

我发现在模型进行初始化后第0个epoch和第1个epoch上在custom accuarcy上是有变化的,但是经历一个轮次的训练后发现训练测试上的custom accuracy上再也没变化过了,由于我也没有深入去看整个模型的代码,是否有可能在我生成*_nbest.json上出现了问题呢?在生成相应依赖文件上是否有需要注意的地方呢?再次谢谢作者大大的回应

renll commented 4 years ago

事实上你可以直接查看*_nbest.json和embed_vN3.npy的内容,如果有明显的能导致训练无法进行的问题,你应该立即可以发现。mxnet自从加入Amazon开始每一代的变动都很大,当初我们也遇到过升级到1.2版本后模型预测精度发生变化的问题,所以framework的版本事实上会很有影响。我现在苦于没有cuda8.0环境而无法对代码进行维护(因为该项目开源是在我离开交大实验室环境之后),因此我的建议是首先请你确认生成的依赖文件没有肉眼可见的问题,然后配置一个cuda8.0环境,如果在这种情况下仍无法复现模型,请务必联系我,我可以提供在线debug。 之前也有大量同行通过邮件询问我复现相关的问题,然而在我一一回复后,他们也并未告知是否复现成功。非常感谢你能提出这个issue,我也希望这次对复现的问题能有一个清晰的答复,能将问题就此终结。 保持联系!

YourThomasLee commented 4 years ago

得到作者大大的回复,简单查看了下_nbest.json文件没有发现问题后,我立刻搞了一波docker,得到docker镜像容器环境配置如下 image image 而后执行train_dstc(1)运行结果截图如下 image image customAcc确实有变化,但是大概从epoch6左右后就再也没有变化了,不知道咋回事,开始秃头

renll commented 4 years ago

等一下,所以你是使用的_nbest.json文件吗?如果是的话,请尝试使用_nbest_tagged.json文件。因为在mat_io.py中会索引前半部分未tagged的文件,如果使用_nbest.json, 那么只会在一个sample上训练。https://github.com/renll/StateNet/blob/a1e41c0f7b1b885e8832f4e53f54e97bb01219f4/mat_io.py#L117

YourThomasLee commented 4 years ago

我猜测应该是数据配置的问题,稍后仔细看下模型中数据的配置吧,代码写有点乱,看的有点头晕,谢谢作者大大的回复

YourThomasLee commented 4 years ago

看了下,modSel=1,相应配置如下: elif modSel==1: offline_config_dict = { 'nn_type': 'doublelstm', 'model_dir': os.path.join(cur_dir, 'models_dstc_caplstmSCLT'), 'train_json': os.path.join(cur_dir, 'train_nbest_tagged.json'), 'dev_json': os.path.join(cur_dir, 'dev_nbest_tagged.json'), 'test_json': os.path.join(cur_dir, 'test_nbest_tagged.json') } 细致查看作者大大指示的函数代码并在末尾增加代码如下: image 输出如下: image 可以看到各项变量长度为1612,确实是dstc2数据集中训练部分数据的长度。看了下doblelstm模型代码的调用有点乱,因为我做的也是对话状态跟踪,近期打算写个repo复现下这个模型,如果有不懂的可以请教作者大大么?

renll commented 4 years ago

没问题,辛苦你了!

YourThomasLee commented 4 years ago

作者大大好,我来请教问题了,关于输入输出有几个地方上理解有些不清楚。先把架构图放上来 image1 $r_u^n$是用户话语的表示,$r_a^m$是机器动作的表示,$s$是槽的表示,$V_s$是槽s可取的值集合

1. 关于输入

输入主要三部分组成,用户话语,机器动作,槽位状态。我的疑问挺多的,作者大大有空解答就行,我不急,再次谢谢作者大大

1.1. 用户话语部分的疑问

stateNet模型的输入为nbest_tagged.json文件,该文件内容生成的入口代码

elif output_type == 'nbest_tagged':
        data = []
        res_data1 = gen_data(genTurnData_nbest)
        data.append(res_data1)
        res_data2 = gen_data(genTurnData_nbest_tagged)
        data.append(res_data2)
        res_data = data

tagged.json文件与非tagged文件内容的差异之处由于多调用了genTurnData_nbest_tagged, 方便作者大大查看,直接贴函数代码到这里了:

def genTurnData_nbest_tagged(turn, labelJson):
    turnData = genTurnData_nbest(turn, labelJson)
    turnData = tagTurnData_matFS(turnData, ontologyDict)
    return turnData

该函数中调用的第二个函数tagTurnData_matFS函数,该函数的内容如下

def tagTurnData_matFS(turnData, ontology):
    """将一个turn的数据进行tag替换"""
    tagged_turnData = copy.deepcopy(turnData)
    tag_dict = {}
    for slot in ["food", "name"]:
        for slot_val in ontology["informable"][slot]:
            if slot_val.startswith("#%s"%slot):
                continue
            cur_tag = "#%s#" % (slot,)
            replace_flag = False
            # process user_input
            for i in xrange(len(tagged_turnData["user_input"])):
                sentence = tagged_turnData["user_input"][i]['asr-hyp']
                tag_sentence = sentence.replace(slot_val, cur_tag)
                if tag_sentence != sentence:
                    tagged_turnData["user_input"][i]['asr-hyp'] = tag_sentence
                    tag_dict[cur_tag] = slot_val
                    replace_flag = True
            # process machine_output
            for act in tagged_turnData["machine_output"]:
                for pair in act["slots"]:
                    if len(pair) >= 2 and pair[0] == slot and pair[1] == slot_val:
                        pair[0] = "#slot#"
                        pair[1] = cur_tag
                        tag_dict[cur_tag] = slot_val
                        replace_flag = True
        for i in xrange(len(tagged_turnData["user_input"])):
            sentence = tagged_turnData["user_input"][i]['asr-hyp']
            tag_sentence = sentence.replace(slot, "#slot#")
    return tagged_turnData

我主要想确认下该函数是否将所有轮次对话数据中的ASR模块输出内容、策略模块输出的动作与food、name槽有关的信息打上标记,如果是的话,从代码上可以看出打上标签的数据是直接缀加到未打标记数据上的,那么在训练中是否有使用到这部分标签呢?如果使用了是如何使用的呢(因为没有仔细看后面的模型,复现时候又需要用到,所以来问作者大大了)。

1.2. 槽位状态的编码

这里主要是确认下,例如对于food槽位,那么架构图中的s就是food的词向量,对于pricerange槽位,架构图中的s就是price的词向量和range的词向量的和

1.3. 策略模块输出动作的表示

论文中对于动作表示的描述如下,我对字典是怎么做的没有看明白,the m order n-gram of bag of words这个东西不咋明白,对于bag of words来说应该是无序的,可是前面的the m order n-gram我觉得是在有序的基础上的定义的概念,不太明白,我猜词典应该是vocab_actN.dict文件,可我不太清楚咋处理出来的。

image2

总结下:请教对话动作是怎么表示的?vocab_actN.dict是怎么构建的呢?对应的py文件和相应的函数是哪一个呢?顺便请教作者大大vocab_matNN.dict的生成方法和相应的函数

2. 关于输出

对于dstc2数据,该数据包含三种标签,分别是goal(food, pricerange, name, area),method,request slots, 在代码里我看到将goal,method的标签值转化成了ontology里的下标索引,而对于request slots则采用了one-hot的编码。在论文中对于目标槽的描述如下: image3

架构图中的$V_s$的设定在文中描述是使用槽可取值的词向量,词向量的维度是300维

image4

我在这里确认下stateNet中是否将request slot和method作为了预测目标之一。如果将request slot和method作为了预测目标,假设将method和request slots看作是槽,那么request slots的可取值的表示使用了one hot编码,与method、goal_food的槽值对应的词向量维度存在不一致在模型中是如何解决的呢?

谢谢作者大大的回应一定要加粗!【手动配图 :)】

renll commented 4 years ago

1.1 没有用到tagged数据, dataIdx我们只用了0,就是没有tagged的部分。https://github.com/renll/StateNet/blob/a1e41c0f7b1b885e8832f4e53f54e97bb01219f4/mat_io.py#L79 1.2 对 1.3 这个就是m-gram词袋, 这里我们使用m=3。已上传extract_vocab.py,当初没有上传的原因是unicode编码格式有点问题,所以使用时请首先确认生成的文件和repo中已有的dict是匹配的。

  1. 没有将其作为预测目标,我们只关注joint goal prediction。
YourThomasLee commented 4 years ago

作者大大新年好!我这边使用了上传后的extract_vocab.py成功生成了vocab_matNN.dict, 目前在处理生成vocab_actN.dict上遇到了困难,使用代码如下

#生成mat_actN.dict代码,使用训练集上的act生成act_words然后输出
act_bag=dict()
for call in train_data:
        for turn, label in call:
            # dialog acts
            # act: {'slots': [['pricerange', 'moderate'], ['food', 'swedish']], 'act': 'canthelp'}
            # act_words: canthelp pricerange moderate food swedish
            machine_act_words = []
            for act_item in turn["output"]["dialog-acts"]:
                if "act" in act_item:
                    machine_act_words.append(act_item["act"])
                if "slots" in act_item:
                    for item in act_item["slots"]:
                        for item_val in item:
                            machine_act_words.append(item_val)
            machine_act = ' '.join(machine_act_words)
            act_bag=add_words_2_vocab(machine_act,act_bag,N=3)
            joblib.dump(word_bag,CUR_DIR+'/vocab_act_3_gram.dict')

def add_word_2_vocab(word:str,vocab:dict)->dict:
        vocab[word]=vocab.get(word,0)+1 # word, frequency
        return vocab

def add_words_2_vocab(words:str,vocab:dict,N=1)->dict:
        words=words.strip()
        if len(words)==0: return vocab
        word_list=words.split()
        for word in [' '.join(word_list[i:min(i+N,len(word_list))]) for i in range(max(1,len(word_list)-N+1))]:
            add_word_2_vocab(word,vocab)
        return vocab

vocab_act3=joblib.load(CUR_DIR+'/vocab_act_3_gram.dict')
vocab_actN=pickle.load(open(CUR_DIR+'/vocab_actN.dict','rb'))#项目中的文件
print(len(vocab_actN),len(vocab_act3))
print(vocab_act3.keys()==vocab_actN.keys())

输出如下

1273 1417 False

请教的问题有两个,第一个是vocab_actN.dict生成方法和流程是怎样的呢;第二个是我想确认对话动作表示是否就是基于构造词典的one-hot编码表示,还是使用n-gram中所有单词对应的词向量之和作为对话动作的表示(主要是论文里动作$r_a^m$表示和用户话语$r_u^n$表示上特别相似,所以特意问下)

igorhuangchao commented 4 years ago

您好

我最近也遇到类似问题,训练的结果没有变化,且测试和验证上的结果都为零。

目前我使用的环境是 python 2.7 mxnet-cu80==1.1.0 其他没有太大的 变化。

数据是dstc2, 运行get_embedding.py获取到embed_vN3.npy词向量文件文件后,执行mat_data.py获取了test_nbest.json、test_nbest_tagged.json、train_nbest.json、train_nbest_tagged.json、dev_nbest.json、dev_nbest_tagged.json后

运行offline_model_dstc默认参数。使用*_nbest_tagged.json作为输入。

您在上面的回答中提到,对比*_nbest.json, 和embed_vN3.npy 的内容,这里不明白如何去对比。请指教。

不知道我为啥不能上传图片, 总之结果和上面的小哥很相似。

renll commented 4 years ago

@BrickLee 1.通过修改extract_vocab.py就可以生成。取消注释64-68行并注释掉106-118行大概就可以了。 2.与词向量无关,就是个vocab大小的词袋。具体可以查看如下函数: https://github.com/renll/StateNet/blob/d0860408a4541f5212f4ac48c9974a1806c9c815/mat_io.py#L178

@igorhuangchao 没有对比,就是简单查看一下这两个文件有没有明显不合理的地方(比如词向量都是零之类的)。