Home

从Theano到Pytorch的迁移

字数统计: 2.8k阅读时长: 11 min
2020/02/27 Share

前言

 原项目Theano的github地址:https://github.com/julianser/hed-dlg-truncated

 论文地址: https://arxiv.org/abs/1605.06069

 首先是对模型ADEM介绍以及对评估模型的学习。 ADEM,short for “automatic dialogue evaluation model”,对话自动评估模型。模型在利用RNN(recurrent neural network,循环神经网络)半监督式(semi-supervised)训练下来评分。

有关RNN的学习就不浪费时间整理已有的东西,重点精力放在我在此项目的总结与感受。有关RNN的开篇学习,我参考了这篇https://blog.csdn.net/zhaojc1995/article/details/80572098,markdown有关数学公式的编辑参考了这篇简书https://www.jianshu.com/p/e74eb43960a1

一 The aim of the project

 NLP中重要一环是对“dialogue”的评价,而dialogue中包括reference(人工回复作为“参考”)和candidate(模型给出的答复),基本思路是利用一些评估标准来对比candidate和reference,并给出一个评分。本项目就是基于原有的评估思路上,给定一系列responses,得到一个准确的人工评分。responses要有一定的多样性,包含相关、不相关的,也需要包含连贯性强和连贯性较弱的内容。

 为了达到上述的“多样化的responses”,研究人员们采用了4种模型产生的candidate responses。

  1. TF-IDF retrieval-based model
  2. Dual Encoder(DE)
  3. responses generated using the hierarchical recurrent encoder-decoder (HRED) model
  4. human-generated models

评估方式

Word-Overlap Metrics

 利用词语的覆盖程度来计算。其中一个最广为使用的当属BLEU和METEOR,两者广泛应用于评价机器翻译的方法当中。但是机器翻译指标与人类评估结果还存在较大差异,尤其在对话系统中,尽管一个对话的responses可以跟reference词语重叠很少(导致Word-Overlap评分很低),但可以被人类评价为一个“好的回复”。

BLEU尽管可以采用n-gram方法来优化评价方式,但在对话评价上有自己的局限性。机器翻译工作中词语选来选去大多不离那些candidate,所以bleu可以很好的被应用。但是在对话当中,与reference词语重叠程度小,也可以是好的回复,这里如果仅以BLEU评分作为评估就显得不合理了。而且回复也需要考虑前后的语境(context)。

ADEM

 Automatic Dialogue Evaluation Model是为了弥补word-overlap metrics的缺陷。而且(1)能够捕捉到语义相似性 (2)挖掘语境和参考回答对于评分模型的作用。

二 train.py 中的小细节

argparse模块使用。

 argparse是python用于解析命令行参数和选项的标准模块,类似于linux中的ls指令,后面可以跟着不同的参数选项以实现不同的功能,argparse就可以解析命令行然后执行相应的操作。
使用时需要三步(步骤总结来自于python中argparse模块使用):

  1. 创建 ArgumentParser() 对象
  2. 调用 add_argument() 方法添加参数
  3. 使用 parse_args() 解析添加的参数
1
2
3
4
5
def parse_args():
parser = argparse.ArgumentParser() "创建对象"
parser.add_argument("--prototype", type=str, help="Prototype to use (must be specified)", default='prototype_state') "调用方法添加参数"
args = parser.parse_args()
return args

os.path模块

os.path.exists()判断括号里的文件是否存在的意思,括号内的可以是文件路径。而且返回值是Boolean类型。

1
2
3
def create_experiment(config):
if not os.path.exists(config['exp_folder']):
os.makedirs(config['exp_folder'])

eval函数

eval()返回括号里表达式的值

三 python中的下划线

 多学了python后对python当中的下划线很感兴趣。不同于c或者java中局限于“命名作用”作用的下划线,python中的下划线在表示特殊含义方面上有着重要作用。

 在创建类的时候,也会较易发现def __init__():,只要有自动补全的IDE,就会默认出现下划线,前后都有。而不同的函数下划线长度还不同(长度为1或为2,前后出现表意也不同)。这边引起了我的好奇。

参考文章:《Python中下划线的5种含义》The Meaning of Underscores in Python

  • 单前导下划线: _var
  • 单末尾下划线:var_
  • 双前导下划线:__var
  • 双前导和末尾下划线:var
  • 单下划线:_

单前导下划线 _var

 Intended for internal use. It is defined there. 但是python并不强制要求,而且不像Java,Python对“公共”和“私有”的区分并不那么明显。就像一种提示:

嘿,这其实不是这个类的一个公共接口,最好别管它。
举例如下:

1
2
3
4
class Test:
def __init__(self):
self.foo = 11
self._bar = 23

实例化之后会发生啥呢?

1
2
3
4
5
>>> t = Test()
>>> t.foo
11
>>> t._bar
23

 所以单前导下划线并没有阻止我们访问到方法_bar
 但是如果使用通配符从模块中导入名称的话,python不会导入带有单前导下划线名称的方法。(我不确定在python中叫“方法”是否合适,欢迎指摘。)

比如,命名一个my_modelue有一下代码:

1
2
3
4
5
6
7
# This is my_module.py:

def external_func():
return 23

def _internal_func():
return 42

此时,如果用通配符*导入所有名称,则Python不会导入有单前导下划线的名称(除非模块定义了覆盖此行为的all列表)

1
2
3
4
5
>>> from my_module import *
>>> external_func()
23
>>> _internal_func()
NameError: "name '_internal_func' is not defined"

 而如果常规导入则不会受此影响:

1
2
3
4
5
>>> import my_module
>>> my_module.external_func()
23
>>> my_module._internal_func()
42

单末尾下划线 var_

 有时候名称被一个keyword占用了,命名的时候就不得不避开这些Python认定的关键字,下面的例子是用class_代替class来表示传递给方法的参数。

1
2
3
4
5
>>> def make_object(name, class):
SyntaxError: "invalid syntax"

>>> def make_object(name, class_):
... pass

 其实就是为了避免冲突而采用的。

双前导下划线: __var

A double underscore prefix causes the Python interpreter to rewrite the attribute name in order to avoid naming conflicts in subclasses.

 这将导致Python解释器重写属性名称避免在子类当中冲突。
 这其实也称作 name mangling. 解释器改变变量名称以使得后续扩展类的时候更不容易发生冲突。
例子:

1
2
3
4
5
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 23

使用内置的(built-in) dir() 来查看:

1
2
3
4
5
6
7
8
>>> t = Test()
>>> dir(t)
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_bar', 'foo']

 查看列表,查找我们的原始变量foo,_bar__baz我们注意到:

  • self.foo变量在属性列表中显示为未修改为foo
  • self._bar的行为方式相同 - 它以_bar的形式显示在类上。 就像我之前说过的,在这种情况下,前导下划线仅仅是一个约定。 给程序员一个提示而已。
  • 然而对于self.__baz而言,情况看来不同。列表中并没有__baz这个名字的变量

 仔细观察,发现有个_Test__baz的属性。这就是Python解释器所做的名称修饰。 它这样做是为了防止变量在子类中被重写。

 而如果创建另一个扩展Test类的类,并尝试重写构造函数中添加的现有属性:

1
2
3
4
5
6
class ExtendedTest(Test):
def __init__(self):
super().__init__()
self.foo = 'overridden'
self._bar = 'overridden'
self.__baz = 'overridden'

foo, _bar, 和 __baz的值会出现在这个ExtendedTest类的实例上吗? Let’s take a look.

1
2
3
4
5
6
7
>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"

得到的AttributeError? 名称修饰被再次触发了!事实上,这个对象没有__baz属性:

 我们可以看到_baz变成_ExtendedTest__baz以防止意外修改:

1
2
>>> t2._ExtendedTest__baz
'overridden'

 但原来的_Test__baz还在:

1
2
>>> t2._Test__baz
42

 双下划线名称修饰对程序员完全透明。下面例子证实:

1
2
3
4
5
6
7
8
9
10
11
class ManglingTest:
def __init__(self):
self.__mangled = 'hello'

def get_mangled(self):
return self.__mangled

>>> ManglingTest().get_mangled()
'hello'
>>> ManglingTest().__mangled
AttributeError: "'ManglingTest' object has no attribute '__mangled'"

还有另一个例子:

1
2
3
4
5
6
7
8

_MangledGlobal__mangled = 23

class MangledGlobal:
def test(self):
return __mangled

>>> MangledGlobal().test()

 在这里声明了一个名为_MangledGlobal__mangled的全局变量。然后在名为MangledGlobal的类的上下文中访问变量。由于名称修饰,我们能够在类的test()方法内,以mangled来引用_MangledGlobalmangled全局变量。

 Python解释器自动将名称mangled扩展为_MangledGlobalmangled,因为它以两个下划线字符开头。这表明名称修饰不是专门与类属性关联的。它适用于在类上下文中使用的两个下划线字符开头的任何名称。

双前导和双末尾下划线 var

 如果一个名字同时以双下划线开始和结束,则不会应用名称修饰。 由双下划线前缀和后缀包围的变量不会被Python解释器修改:

1
2
3
4
5
6
class PrefixPostfixTest:
def __init__(self):
self.__bam__ = 42

>>> PrefixPostfixTest().__bam__
42

 但是,Python保留了有双前导和双末尾下划线的名称,用于特殊用途。 这样的例子有,init对象构造函数,或call — 它使得一个对象可以被调用。

 但是,Python保留了有双前导和双末尾下划线的名称,用于特殊用途。 这样的例子有,init对象构造函数,或call — 它使得一个对象可以被调用。

单下划线 _

 有时候独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。在下面循环中,我们不需要访问正在运行的索引,可以使用_来表示它只是一个临时变量

1
2
3

>>> for _ in range(32):
... print('Hello, World.')

 也可以在拆分(unpacking)表达式中将单个下划线用作“不关心的”变量,以忽略特定的值。 同样,这个含义只是“依照约定”,并不会在Python解释器中触发特殊的行为。 单个下划线仅仅是一个有效的变量名称,会有这个用途而已。

 在下面的代码示例中,我们将汽车元组拆分为单独的变量,但我只对颜色和里程值感兴趣。 但是,为了使拆分表达式成功运行,需要将包含在元组中的所有值分配给变量。 在这种情况下,_作为占位符变量可以派上用场:

1
2
3
4
5
6
7
8
9
>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car

>>> color
'red'
>>> mileage
3812.4
>>> _
12

 除了用作临时变量之外,“_”是大多数Python REPL中的一个特殊变量,它表示由解释器评估的最近一个表达式的结果。

 这样比较方便,可以在一个解释器会话中访问先前计算的结果,或者,你是在动态构建多个对象并与它们交互,无需事先给这些对象分配名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 20 + 3
23
>>> _
23
>>> print(_)
23

>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]

CATALOG
  1. 1. 前言
  2. 2. 一 The aim of the project
    1. 2.1. 评估方式
      1. 2.1.1. Word-Overlap Metrics
      2. 2.1.2. ADEM
  3. 3. 二 train.py 中的小细节
    1. 3.1. argparse模块使用。
    2. 3.2. os.path模块
    3. 3.3. eval函数
  4. 4. 三 python中的下划线
    1. 4.1. 单前导下划线 _var
    2. 4.2. 单末尾下划线 var_
    3. 4.3. 双前导下划线: __var
    4. 4.4. 双前导和双末尾下划线 var
    5. 4.5. 单下划线 _