蟒蛇书学习笔记——Chapter 09 Section 02 使用类和实例 【第九章 2/3】

人工智能128

您可以使用类来模拟现实世界中的许多场景。

[En]

You can use classes to simulate many scenarios in the real world.

编写完类后,您将把大部分时间花在从类创建的实例上。您需要执行的一项重要任务是修改实例属性

[En]

After the class is written, you will spend most of your time on instances created from the class. One of the important tasks you need to perform is * modify the properties of the instance * .

您可以直接修改实例的属性,也可以编写方法以特定方式修改它。

[En]

You can modify the properties of an instance directly, or you can write a method to modify it in a specific way.

9.2.1 Car类

下面来准备一个Car类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:

class CarDemo:
    """一次模拟汽车的简单尝试。"""

    def __init__(self, make, model, year):
        """初始化描述车的属性(制造商、型号和生产年份)"""
        self.make = make
        self.model = model
        self.year = year

    def get_describtive_name(self):
        """返回简洁的描述性信息"""
        long_name = f"{self.year} {self.make} {self.model}."
        return long_name.title()

my_new_democar = CarDemo('Audi', 'a4', '2019')
print(my_new_democar.get_describtive_name())

output:

2019 Audi A4.

为了使这个类更有趣,让我们添加一个随时间变化的属性来存储汽车的总里程。

[En]

To make this class more interesting, let's add an attribute that changes over time to store the total mileage of the car.

9.2.2 给属性指定默认值

创建实例时,有些属性无须通过形参来定义,可在方法__init__()中为其指定默认值。

下面来添加一个名为odometer_reading的属性,其 初始值总是为0

我们还添加了一个名为read_odometer()的方法,用于读取汽车的里程表:

class Car:

    def __init__(self, make, model, year):
        """初始化描述车的属性(制造商、型号、生产年份和总里程)"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_describtive_name(self):
        """返回简洁的描述性信息"""
        long_name = f"{self.year} {self.make} {self.model}."
        return long_name.title()

    def read_odometer(self):
        """打印一条指出车总里程的信息"""
        print(f"This car has {self.odometer_reading} miles on it.")

my_new_car = Car('Audi', 'a4', '2019')
print(my_new_car.get_describtive_name())
my_new_car.read_odometer()

output:

2019 Audi A4.

This car has 0 miles on it.

此处里程的单位为英里(mile) , 1英里 ≈ 1.6千米。 ——蟒蛇书编者注
出售时里程表读数为0的汽车不多,因此需要一种方式来修改该属性的值。

9.2.3 修改属性的值

我们可以通过三种方式修改属性的值:直接通过实例、通过方法设置属性和通过方法递增属性(通过添加特定值)。

[En]

We can modify the value of a property in three ways: directly through the instance, by setting it by method, and by incrementing it by method (by adding a specific value).

my_new_car.odometer_reading = 23
my_new_car.read_odometer()

output:

This car has 23 miles on it.

如果有方法能替你更新属性,将大有裨益。这样就无须直接访问属性,而可将值传递给方法,由它在内部进行更新。

下面的示例演示了一个名为update_odometer()的方法:

class Car:

    def __init__(self, make, model, year):
        """初始化描述车的属性(制造商、型号、生产年份和总里程)"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_describtive_name(self):
        """返回简洁的描述性信息"""
        long_name = f"{self.year} {self.make} {self.model}."
        return long_name.title()

    def read_odometer(self):
        """打印一条指出车总里程的信息"""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
        """将里程表读数设置成任意值"""
        self.odometer_reading = mileage

my_new_car = Car('Audi', 'a4', '2019')
print(my_new_car.get_describtive_name())

my_new_car.update_odometer(23)
my_new_car.read_odometer()

output:

2019 Audi A4.

This car has 23 miles on it.

可对方法update_odometer()进行扩展,使其在修改里程表读数时做些额外的工作。

这里添加了一些逻辑,禁止任何人调整里程表读数:

[En]

Here's to add some logic that forbids anyone to adjust the odometer reading back:

class Car:

    def __init__(self, make, model, year):
        """初始化描述车的属性(制造商、型号、生产年份和总里程)"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_describtive_name(self):
        """返回简洁的描述性信息"""
        long_name = f"{self.year} {self.make} {self.model}."
        return long_name.title()

    def read_odometer(self):
        """打印一条指出车总里程的信息"""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
"""
        将里程表读数设置成任意值
        禁止将里程表读数往回调。
"""
        if mileage >= self.read_odometer:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

以下方法允许我们传递此增量并相应增加里程表读数:

[En]

The following method allows us to pass this increment and increase the odometer reading accordingly:

class Car:

    def __init__(self, make, model, year):
        """初始化描述车的属性(制造商、型号、生产年份和总里程)"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_describtive_name(self):
        """返回简洁的描述性信息"""
        long_name = f"{self.year} {self.make} {self.model}."
        return long_name.title()

    def read_odometer(self):
        """打印一条指出车总里程的信息"""
        print(f"This car has {self.odometer_reading} miles on it.")

    def update_odometer(self, mileage):
"""
        将里程表读数设置成任意值
        禁止将里程表读数往回调。
"""
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

    def increment_odometer(self, miles):
        """将里程表读数增加指定的量。 """
        self.odometer_reading += miles

my_used_car = Car('sabaru', 'outback', 2015)
print(my_used_car.get_describtive_name())

my_used_car.update_odometer(23_500)
my_used_car.read_odometer()

my_used_car.increment_odometer(100)
my_used_car.read_odometer()

output:

2015 Sabaru Outback.

This car has 23500 miles on it.

This car has 23600 miles on it.

习题:

新增的方法increment_odometer()接受一个单位为英里的数,并将其加入self.odometer_reading中。

创建一辆二手车my_used_car。调用方法update_odometer()并传入23_500,将这辆二手车的里程表读数设置为23 500。

调用increment_odometer()并传入100,以增加从购买到登记期间行驶的100英里:

Original: https://www.cnblogs.com/WarnerJDoe/p/15904075.html
Author: 姜子牙会更好
Title: 蟒蛇书学习笔记——Chapter 09 Section 02 使用类和实例 【第九章 2/3】



相关阅读

Title: MoCha——单调块注意力模型

MoCha——单调块注意力模型

1.概述

加了soft attention的seq2seq模型,在很多领域取得了广泛的应用,例如机器翻译、词性标注等NLP任务,因为它们都可以看成是序列到序列的问题。但是对于语音问题,这个模型存在很明显的弊端:

  • 时间复杂度很高: 因为对于soft attention的模型来说,decoder的每一个输出要计算encoder的每一个隐含状态的对应权重,所以时间复杂度为O(UT),其中U代表输出序列的长度,T代表输入序列的长度。对于语音任务来说,10ms的帧移就意味着1s的音频有100帧,随便读一段话可能就有成百上千帧的输入,这是非常耗时的。
  • 做不到online:
    同样因为计算soft-attention需要考虑encoder每一个隐含状态,所以要等到输入全部输入之后才能计算,因而无法做到实时解码。

不过与机器翻译等任务不同,语音识别和语音生成的任务中输入和输出是 单调对齐的,换句话说输入和输出享有通同一个自然的时间顺序,不涉及局部的颠倒。于是,Raffel等人在2017年的研究成果表明,对于这种单调对齐的seq2seq的问题,上述的两种弊端可以得到缓解。Raffel引入了一种叫hard monotonic attention的注意力机制,实现了 线性时间复杂度online解码

然而与soft attention相比,hard monotonic attention也限制了模型的表达性,因为soft attention理论可以学习任意方式的对齐,而这是单调对齐做不到的。实验结果也表明使用单调对齐的hard monotonic attention模型表现落后于soft attention。

所以,MoCha就这样应运而生,它不是摩卡咖啡,而是Monotonic Chunk Attention。该机制保留了hard monotonic attention线性时间复杂度和实时解码的优势,同时也允许软对齐。它是如何做到的呢,简要来说,它先确定一个滑动窗口,也就是所谓的chunk,然后在这个窗口里的几个时间步上做soft attention。窗口的移动是单调的(只能沿着时间从左向右),而窗口什么时候该滑动、滑动多少,是由模型自己决定的,或者说是学习得到的。
蟒蛇书学习笔记——Chapter 09 Section 02 使用类和实例 【第九章 2/3】

上图是一个形象的展示,纵轴代表decoder输出序列的顺序,横轴代表encoder输出的隐含状态序列,颜色代表概率,每一横行概率相加应该是1。从图中我们可以看出soft attention可以实现任意的对齐;hard monotonic attention每一步决定向右移动几步,当然也可以不移动;而MoCha移动的是一个固定长度的窗口,窗口内部实现了软注意力。

MoCha的论文中还介绍了它的反向传播训练方式,该训练方式可以直接应用于现有的seq2seq模型。实验表明,MoCha使得单调注意力模型赶上了软注意力模型的性能,代价是参数量和计算成本的适度增加。

下面我们将详细介绍这一模型的模型结构以及训练过程。

; 2.MoCha的定义

上一小节中谈到的三种模型,实际上是依次被提出的,hard monotonic attention的提出是为了解决soft attention存在的问题,但是牺牲了模型的准确度。MoCha的提出既保留了hard monotonic attention的优点,也弥补了它在准确度上的劣势。

所以我们这一小节,按照顺序,先来回顾一下soft attention,再来讲一下hard monotonic attention与soft attention的不同,最后再讲一下MoCha在前两者的基础上做了哪些改进。

2.1 Soft Attention

蟒蛇书学习笔记——Chapter 09 Section 02 使用类和实例 【第九章 2/3】

对于一个由RNN encoder和RNN deconder组成的seq2seq模型结构来说:
x = { x 1 , x 2 , . . . , x T } h = { h 1 , h 2 , . . . , h T } s = { s 1 , s 2 , . . . , s U } y = { y 1 , y 2 , . . . , y U } x={x_1,x_2,...,x_T}\ h={h_1,h_2,...,h_T}\ s={s_1,s_2,...,s_U}\ y={y_1,y_2,...,y_U}\x ={x 1 ​,x 2 ​,...,x T ​}h ={h 1 ​,h 2 ​,...,h T ​}s ={s 1 ​,s 2 ​,...,s U ​}y ={y 1 ​,y 2 ​,...,y U ​}
其中h h h为encoder的隐含状态,s s s为decoder的隐含状态,T为输入序列的长度(在语音识别任务中就是帧数),U为输出序列的长度(在语音识别任务中是token的个数)。
h j = E n c o d e r R N N ( x j , h j − 1 ) s i = D e c o d e r R N N ( y i − 1 , s i − 1 , c i ) y i = O u t p u t ( s i , c i ) h_j=EncoderRNN(x_j,h_{j-1})\ s_i=DecoderRNN(y_{i-1},s_{i-1},c_i)\ y_i=Output(s_i,c_i)h j ​=E n c o d e r R N N (x j ​,h j −1 ​)s i ​=D e c o d e r R N N (y i −1 ​,s i −1 ​,c i ​)y i ​=O u t p u t (s i ​,c i ​)
Attention可以就将其看做是一系列操作,经过这一系列操作之后Encoder的T个隐含状态h h h就变成了U个context向量c c c。这个c c c就是将输入信息传给decoder的唯一通道。对于不同的attention实际上就是不同的操作,但是最终的效果就是将h h h变成c c c。

对于soft attention来说,对应的操作如下所示:
e i , j = E n e r g y ( h j , s i − 1 ) = v ⊤ t a n h ( W h h j + W s s i − 1 + b ) α i , j = exp ⁡ ( e i , j ) ∑ k = 1 T exp ⁡ ( e i , k ) c i = ∑ j = 1 T α i , j h j e_{i,j}=Energy(h_j,s_{i-1})=v^\top tanh(W_hh_j+W_ss_{i-1}+b)\ \alpha_{i,j}=\frac{\exp(e_{i,j})}{\sum_{k=1}^T{\exp(e_{i,k})}}\ c_i=\sum_{j=1}^T{\alpha_{i,j}h_j}e i ,j ​=E n e r g y (h j ​,s i −1 ​)=v ⊤t a n h (W h ​h j ​+W s ​s i −1 ​+b )αi ,j ​=∑k =1 T ​exp (e i ,k ​)exp (e i ,j ​)​c i ​=j =1 ∑T ​αi ,j ​h j ​
计算第i i i个时间步的c i c_i c i ​时候,首先对前一个时间步的s i − 1 s_{i-1}s i −1 ​计算和每一个h h h的energy,然后经过softmax后得到当前时间步的输出对于每一个h h h的权重系数α \alpha α,然后加权相加,得到当前时间步的c i c_i c i ​,这样decoder依次得到每一个c i c_i c i ​,再结合前一步的隐含状态s i − 1 s_{i-1}s i −1 ​以及前一步的输出y i − 1 y_{i-1}y i −1 ​就可以得到当前时间步的输出y i y_i y i ​。因为计算每一个c i c_i c i ​的时候要考虑每一个h h h,所以soft attention的时空复杂度是O ( T U ) O(TU)O (T U )。

而且soft attention部分的计算无非是矩阵的乘法、加法、tanh激活函数,这些操作都是可以求导的,也就支持了反向传播。所以引入soft attention机制并不需要额外做什么事情便可以直接训练。

; 2.2 Hard Monotonic Attention

在hard monotonic attention中,我们依然来看一下,U个context向量c c c是如何由T个隐含状态h h h得到。首先对于decoder的第i i i的时间步的输出来说,这种注意力机制从第i − 1 i-1 i −1的时间步对齐的h h h开始依次遍历每一个h h h,而不是像soft attention一样从头开始。假设decoder第i − 1 i-1 i −1个时间步对齐的h h h的索引是t i − 1 t_{i-1}t i −1 ​,也就是说c i − 1 c_{i-1}c i −1 ​对应h t i − 1 h_{t_{i-1}}h t i −1 ​​,那么计算c i c_i c i ​的时候,我们对于所有的j = t i − 1 , t i − 1 + 1 , . . . j=t_{i-1},t_{i-1}+1,...j =t i −1 ​,t i −1 ​+1 ,...依次计算e i , j e_{i,j}e i ,j ​,然后再通过一个sigmoid激活函数,计算出一个p i , j p_{i,j}p i ,j ​,它可以理解成"c i c_i c i ​选择第h j h_j h j ​的概率"。这样,由这个p i , j p_{i,j}p i ,j ​就可以得到一个伯努利随机变量,对个随机变量来采样得到z i , j z_{i,j}z i ,j ​用来表征"对齐到这一帧\跳过这一帧",z i , j z_{i,j}z i ,j ​为1就表示对齐到这一帧,为0就表示跳过这一帧。z i , j z_{i,j}z i ,j ​一直为0就一直向后寻找,直到z i , j z_{i,j}z i ,j ​为1时,c i = h j c_i=h_j c i ​=h j ​
e i , j = M o n o t o n i c E n e r g y ( s i − 1 , h j ) p i , j = σ ( e i , j ) z i , j ∼ B e r n u o l l i ( p i , j ) e_{i,j}=MonotonicEnergy(s_{i-1},h_j)\ p_{i,j}=\sigma(e_{i,j})\ z_{i,j}\sim Bernuolli(p_{i,j})e i ,j ​=M o n o t o n i c E n e r g y (s i −1 ​,h j ​)p i ,j ​=σ(e i ,j ​)z i ,j ​∼B e r n u o l l i (p i ,j ​)
对于解码而言,从T个h h h中得到U个c c c的方式就是根据上述的这个模型输出0或1,来选择出一部分h h h作为c c c。这个算法在解码的时候是没有任何问题的,但是因为这个的选择的操作并不能求导,所以就没有办法通过反向传播去训练。

为了解决训练的问题,在论文中,作者提出了一个方法,可以支持训练。推导比较繁琐,但是实际并不难,我们一起来看一下。

首先,为了attention的计算能够求导,我们依然想办法让每一个c c c都由h h h加权求和得到,即:
c i = ∑ j = 1 T α i , j h j c_i=\sum_{j=1}^T{\alpha_{i,j}h_j}c i ​=j =1 ∑T ​αi ,j ​h j ​
那么,这里的α i , j \alpha_{i,j}αi ,j ​如何设计呢,作者的思路是,将α i , j \alpha_{i,j}αi ,j ​理解成对于整个序列c i c_i c i ​对齐到h j h_j h j ​的概率。c i c_i c i ​对齐到h j h_j h j ​的概率可以分为两部分,一部分是c i − 1 c_{i-1}c i −1 ​也对齐到了h j h_j h j ​的概率α i − 1 , j \alpha_{i-1,j}αi −1 ,j ​乘上p i , j p_{i,j}p i ,j ​,另一部分是c i − 1 c_{i-1}c i −1 ​对齐到了h k ( k < j ) h_k(k然后从k k k到j − 1 j-1 j −1帧都输出0的概率,即:
α i , j = p i , j ∑ k = 1 j ( α i − 1 , k ∏ l = k j − 1 ( 1 − p i , l ) ) = p i , j ( ∑ k = 1 j − 1 ( α i − 1 , k ∏ l = k j − 1 ( 1 − p i , l ) ) + α i − 1 , j ) = p i , j ( ( 1 − p i , j − 1 ) ∑ k = 1 j − 1 ( α i − 1 , k ∏ l = k j − 2 ( 1 − p i , l ) ) + α i − 1 , j ) = p i , j ( ( 1 − p i , j − 1 ) α i , j − 1 p i , j − 1 + α i − 1 , j ) . \begin{aligned}\alpha_{i,j}&=p_{i,j}\sum_{k=1}^{j}\left(\alpha_{i-1,k}\prod_{l=k}^{j-1}{(1-p_{i,l})}\right)\ &=p_{i,j}\left(\sum_{k=1}^{j-1}\left(\alpha_{i-1,k}\prod_{l=k}^{j-1}{(1-p_{i,l})}\right)+\alpha_{i-1,j}\right)\ &=p_{i,j}\left((1-p_{i,j-1})\sum_{k=1}^{j-1}\left(\alpha_{i-1,k}\prod_{l=k}^{j-2}{(1-p_{i,l})}\right)+\alpha_{i-1,j}\right)\ &=p_{i,j}\left((1-p_{i,j-1})\frac{\alpha_{i,j-1}}{p_{i,j-1}}+\alpha_{i-1,j}\right)\ \end{aligned} .αi ,j ​​=p i ,j ​k =1 ∑j ​(αi −1 ,k ​l =k ∏j −1 ​(1 −p i ,l ​))=p i ,j ​(k =1 ∑j −1 ​(αi −1 ,k ​l =k ∏j −1 ​(1 −p i ,l ​))+αi −1 ,j ​)=p i ,j ​((1 −p i ,j −1 ​)k =1 ∑j −1 ​(αi −1 ,k ​l =k ∏j −2 ​(1 −p i ,l ​))+αi −1 ,j ​)=p i ,j ​((1 −p i ,j −1 ​)p i ,j −1 ​αi ,j −1 ​​+αi −1 ,j ​)​.

令 q i , j = α i , j p i , j 带 入 上 式 得 q i , j = ( 1 − p i , j − 1 ) q i , j − 1 + α i − 1 , j q i , j ∏ k = 1 j ( 1 − p i , k − 1 ) − ( 1 − p i , j − 1 ) q i , j − 1 ∏ k = 1 j ( 1 − p i , k − 1 ) = α i − 1 , j ∏ k = 1 j ( 1 − p i , k − 1 ) q i , j ∏ k = 1 j ( 1 − p i , k − 1 ) − q i , j − 1 ∏ k = 1 j − 1 ( 1 − p i , k − 1 ) = α i − 1 , j ∏ k = 1 j ( 1 − p i , k − 1 ) ∑ l = 1 j ( q i , l ∏ k = 1 l ( 1 − p i , k − 1 ) − q i , l − 1 ∏ k = 1 l − 1 ( 1 − p i , k − 1 ) ) = ∑ l = 1 j α i − 1 , l ∏ k = 1 l ( 1 − p i , k − 1 ) q i , j ∏ k = 1 j ( 1 − p i , k − 1 ) − q i , 0 = ∑ l = 1 j α i − 1 , l ∏ k = 1 l ( 1 − p i , k − 1 ) q i , j = ( ∏ k = 1 j ( 1 − p i , k − 1 ) ) ( ∑ l = 1 j α i − 1 , l ∏ k = 1 l ( 1 − p i , k − 1 ) ) 所 以 q i = c u m p r o d ( 1 − p i ) c u m s u m ( α i − 1 c u m p r o d ( 1 − p i ) ) 令 q_{i,j}=\frac{\alpha_{i,j}}{p_{i,j}}\ 带入上式得\ \begin{aligned} q_{i,j}&=(1-p_{i,j-1})q_{i,j-1}+\alpha_{i-1,j}\ \frac{q_{i,j}}{\prod_{k=1}^j(1-p_{i,k-1})}-\frac{(1-p_{i,j-1})q_{i,j-1}}{\prod_{k=1}^j(1-p_{i,k-1})}&=\frac{\alpha_{i-1,j}}{\prod_{k=1}^j(1-p_{i,k-1})}\ \frac{q_{i,j}}{\prod_{k=1}^j(1-p_{i,k-1})}-\frac{q_{i,j-1}}{\prod_{k=1}^{j-1}(1-p_{i,k-1})}&=\frac{\alpha_{i-1,j}}{\prod_{k=1}^j(1-p_{i,k-1})}\ \sum_{l=1}^j\left(\frac{q_{i,l}}{\prod_{k=1}^l(1-p_{i,k-1})}-\frac{q_{i,l-1}}{\prod_{k=1}^{l-1}(1-p_{i,k-1})}\right)&=\sum_{l=1}^j\frac{\alpha_{i-1,l}}{\prod_{k=1}^l(1-p_{i,k-1})}\ \frac{q_{i,j}}{\prod_{k=1}^j(1-p_{i,k-1})}-q_{i,0}&=\sum_{l=1}^j\frac{\alpha_{i-1,l}}{\prod_{k=1}^l(1-p_{i,k-1})}\ q_{i,j}&=\left(\prod_{k=1}^j(1-p_{i,k-1})\right)\left(\sum_{l=1}^j\frac{\alpha_{i-1,l}}{\prod_{k=1}^l(1-p_{i,k-1})}\right)\ 所以\ q_i&=cumprod(1-p_i)cumsum\left(\frac{\alpha_{i-1}}{cumprod(1-p_i)}\right) \end{aligned}令q i ,j ​=p i ,j ​αi ,j ​​带入上式得q i ,j ​∏k =1 j ​(1 −p i ,k −1 ​)q i ,j ​​−∏k =1 j ​(1 −p i ,k −1 ​)(1 −p i ,j −1 ​)q i ,j −1 ​​∏k =1 j ​(1 −p i ,k −1 ​)q i ,j ​​−∏k =1 j −1 ​(1 −p i ,k −1 ​)q i ,j −1 ​​l =1 ∑j ​(∏k =1 l ​(1 −p i ,k −1 ​)q i ,l ​​−∏k =1 l −1 ​(1 −p i ,k −1 ​)q i ,l −1 ​​)∏k =1 j ​(1 −p i ,k −1 ​)q i ,j ​​−q i ,0 ​q i ,j ​所以q i ​​=(1 −p i ,j −1 ​)q i ,j −1 ​+αi −1 ,j ​=∏k =1 j ​(1 −p i ,k −1 ​)αi −1 ,j ​​=∏k =1 j ​(1 −p i ,k −1 ​)αi −1 ,j ​​=l =1 ∑j ​∏k =1 l ​(1 −p i ,k −1 ​)αi −1 ,l ​​=l =1 ∑j ​∏k =1 l ​(1 −p i ,k −1 ​)αi −1 ,l ​​=(k =1 ∏j ​(1 −p i ,k −1 ​))(l =1 ∑j ​∏k =1 l ​(1 −p i ,k −1 ​)αi −1 ,l ​​)=c u m p r o d (1 −p i ​)c u m s u m (c u m p r o d (1 −p i ​)αi −1 ​​)​
其中cumprod和cumsum是连乘和累加的意思,只不过是对向量操作,例如对于x = [ x 1 , x 2 , x 3 , . . . x n ] x=[x_1,x_2,x_3,...x_n]x =[x 1 ​,x 2 ​,x 3 ​,...x n ​],c u m p r o d ( x ) = [ 1 , x 1 , x 1 x 2 , . . . , ∏ i = 1 n − 1 x i ] cumprod(x)=[1,x_1,x_1x_2,...,\prod_{i=1}^{n-1}x_i]c u m p r o d (x )=[1 ,x 1 ​,x 1 ​x 2 ​,...,∏i =1 n −1 ​x i ​],c u m s u m ( x ) = [ x 1 , x 1 + x 2 , . . . , ∑ i = 1 n x i ] cumsum(x)=[x_1,x_1+x_2,...,\sum_{i=1}^nx_i]c u m s u m (x )=[x 1 ​,x 1 ​+x 2 ​,...,∑i =1 n ​x i ​]。对于这两个操作,早在1980年就提出了并行计算的高速算法,计算量是可以接受的。

现在我们就有了一个可以求导的过程,对于每个h j h_j h j ​,依次计算每一个p i , j p_{i,j}p i ,j ​,然后依次计算q i q_i q i ​,再通过q i q_i q i ​计算α i \alpha_i αi ​,这样把α i \alpha_i αi ​当做权重乘上h h h加权相加得到了c i c_i c i ​。整个过程无论是p i , j p_{i,j}p i ,j ​的计算还是累加连乘等操作都是可以求导的,这样就能够训练了。

你可能会疑惑,这样时间复杂度不依然是O ( T U ) O(TU)O (T U )吗,因为对于每一个c i c_i c i ​还是要依次计算每一个h j h_j h j ​。实际上在训练的时候确实是这样的,但是在解码的时候还是按照0,1采样的方式去决定每一个c i c_i c i ​取哪一个h j h_j h j ​,也就是说该模型在训练和解码的时候采用了不同的计算公式。作者为了弥补训练和推理时的差异,在经过计算p i , j p_{i,j}p i ,j ​的sigmoid函数之前加入了一个0均值1方差的高斯噪声来让训练出来的p i , j p_{i,j}p i ,j ​变得sharp,也就是概率要么接近0,要么接近1。在推理的时候并没有这个高斯噪声,p i , j > 0.5 p_{i,j}>0.5 p i ,j ​>0 .5就输出1,p i , j < 0.5 p_{i,j}就输出0,这样推理的时候就可以得到确定的结果。

另外,作者对attention的计算也做了改进:
e i , j = M o n o t o n i c E n e r g y ( s i − 1 , h j ) = g v ⊤ ∣ ∣ v ∣ ∣ t a n h ( W s i − 1 + V h j + b ) + r e_{i,j}=MonotonicEnergy(s_{i-1},h_j)=g\frac{v^\top}{||v||}tanh(Ws_{i-1}+Vh_j+b)+r e i ,j ​=M o n o t o n i c E n e r g y (s i −1 ​,h j ​)=g ∣∣v ∣∣v ⊤​t a n h (W s i −1 ​+V h j ​+b )+r
g g g和r r r都是可训练的参数,

在soft attention中,e i , j e_{i,j}e i ,j ​计算出来是会送到softmax中去,softmax只关心相对大小,所以e i , j e_{i,j}e i ,j ​如果整体的数值有偏移的话,分子分母是会抵消掉的。monotonic attention做不到这一点,因为它的e i , j e_{i,j}e i ,j ​是会送到sigmoid函数中,所以为了防止输入sigmoid前的数值偏移影响结果,加了可训练的参数r r r来调整。而且,q i , j = ( 1 − p i , j − 1 ) q i , j − 1 + α i − 1 , j q_{i,j}=(1-p_{i,j-1})q_{i,j-1}+\alpha_{i-1,j}q i ,j ​=(1 −p i ,j −1 ​)q i ,j −1 ​+αi −1 ,j ​在连乘的时候随着i i i的增大,会出现指数衰减的情况,加了r r r之后,r可以为负值,这样经过sigmoid之后的概率值就趋近于0,( 1 − p i , j − 1 ) → 1 (1-p_{i,j-1})\to1 (1 −p i ,j −1 ​)→1,解决了q i , j q_{i,j}q i ,j ​会变得很小的问题,也使得概率更加的sharp。而比上∣ ∣ v ∣ ∣ ||v||∣∣v ∣∣在乘上g g g是为了消除v v v的scale的影响。

2.3 Monotonic Chunkwise Attention

接下来我们再来看一下Monotonic Chunkwise Attention,也就是MoCha。依然还是推理和训练分别介绍,首先是推理。

蟒蛇书学习笔记——Chapter 09 Section 02 使用类和实例 【第九章 2/3】

和hard monotonic attention类似,MoCha只是将对到固定的一帧改为对齐到一个滑动窗口。推理时的步骤如上图所示:首先给decoder输入一个开始信号,在输出第i个输出y i y_i y i ​时,先从y i − 1 y_{i-1}y i −1 ​对齐到的滑动窗口的位置开始遍历每一个h j h_j h j ​计算一个概率p i , j p_{i,j}p i ,j ​:
e i , j = M o n o t o n i c E n e r g y ( s i − 1 , h j ) p i , j = σ ( e i , j ) e_{i,j}=MonotonicEnergy(s_{i-1},h_j)\ p_{i,j}=\sigma(e_{i,j})\e i ,j ​=M o n o t o n i c E n e r g y (s i −1 ​,h j ​)p i ,j ​=σ(e i ,j ​)
如果这个概率大于0.5就意味着将长度为w w w的滑动窗口放到这一帧(这一帧作为滑动窗口的最后一帧)。然后对在滑动窗口内的w w w帧计算soft attention:
u i , k = C h u n k E n e r g y ( s i − 1 , h k ) c i = ∑ k = j − w + 1 j exp ⁡ ( u i , k ) ∑ l = j − w + 1 j exp ⁡ ( u i , l ) h k u_{i,k}=ChunkEnergy(s_{i-1},h_k)\ c_i=\sum_{k=j-w+1}^j\frac{\exp(u_{i,k})}{\sum_{l=j-w+1}^j\exp(u_{i,l})}h_k u i ,k ​=C h u n k E n e r g y (s i −1 ​,h k ​)c i ​=k =j −w +1 ∑j ​∑l =j −w +1 j ​exp (u i ,l ​)exp (u i ,k ​)​h k ​
这样就得到了c i c_i c i ​,如果遍历到最后一帧模型一直没有输出大于0.5的概率,那么c i c_i c i ​就取零向量。最终依然按照同样的方式,将c i c_i c i ​送入decoder计算输出y i y_i y i ​:
s i = D e c o d e r R N N ( y i − 1 , s i − 1 , c i ) y i = O u t p u t ( s i , c i ) s_i=DecoderRNN(y_{i-1},s_{i-1},c_i)\ y_i=Output(s_i,c_i)s i ​=D e c o d e r R N N (y i −1 ​,s i −1 ​,c i ​)y i ​=O u t p u t (s i ​,c i ​)
接下来说一下训练过程,MoCha训练的时候同样无法反向传播,所以依然仿照hard monotonic 的方式,先计算α i , j \alpha_{i,j}αi ,j ​,这里的α i , j \alpha_{i,j}αi ,j ​理解为c i c_i c i ​对应的滑动窗口的位置在第j j j帧的概率。但是不同的是,c i c_i c i ​对于h j h_j h j ​的权重系数并不只是α i , j \alpha_{i,j}αi ,j ​,因为c i c_i c i ​的滑动窗口包含h j h_j h j ​的情况有w w w种(h j h_j h j ​作为滑动窗口中的第1,2,3...w帧),要把这些情况都考虑进去,而且在每一种情况中,h j h_j h j ​对c i c_i c i ​的贡献是softmax算出来的相应的权重,所以要把这个权重也乘上。
β i , j = ∑ k = 1 j + w − 1 ( α i , k exp ⁡ ( u i , j ) ∑ l = k − w + 1 k exp ⁡ ( u i , l ) ) c i = ∑ j = 1 T β i , j h j \beta_{i,j}=\sum_{k=1}^{j+w-1}\left(\alpha_{i,k}\frac{\exp(u_{i,j})}{\sum_{l=k-w+1}^{k}\exp(u_{i,l})}\right)\ c_i=\sum_{j=1}^T\beta_{i,j}h_j βi ,j ​=k =1 ∑j +w −1 ​(αi ,k ​∑l =k −w +1 k ​exp (u i ,l ​)exp (u i ,j ​)​)c i ​=j =1 ∑T ​βi ,j ​h j ​

这里的β \beta β看起来计算量很大,不过幸运的是,计算这种嵌套累加也有并行的高效算法,这里就不做展开了。其他训练的操作和hard monotonic attention相同。

Original: https://blog.csdn.net/hengyi_chang/article/details/118035617
Author: 常恒毅
Title: MoCha——单调块注意力模型