机器学习:第六篇 Encoder-Decoder模型的简单应用

本文最后更新于:几秒前

Encoder-Decoder模型(编码-解码)在近几年有广泛的应用,例如nmt翻译框架,是一种模拟人类认知过程的模型。假如输入一句话,先让机器认知这句话的意思,再将这句话翻译成其他语言。

前言

encoder-decoder模型也被叫做序列到序列的学习模型(Sequence to Sequence,Seq2Seq),当然,实际上,它并不是一种具体的模型在实现形式上,更像是一种框架,一种方式。而Encoder和Decoder部分可以是任意的文字,信号,图像,视频数据。所以seq2seq的实现和应用也是非常多样的,例如cnn-rnn实现image caption的应用,lstm-lstm实现nmt翻译的应用。

序列的映射

输入一段序列,再输出一段我们想要的序列,可以简单的理解为给定一个字符串,然后得到另一个与之对应的字符串,这就是seq2seq,例如我们给定一段英语短语序列,输入之后,然后模型给出一个与之对应的法语翻译,当然,这里只是一个简单的一一对应的关系,nmt中还有单词补全,错位翻译等等技巧应用于翻译模型中。

应用

在LSTM中。模型的主要的结构是
结构

下面的列表突出了Encoder-Decoder LSTM结构的一些有趣的应用。

  • 机器翻译,如短语的英译法语;
  • 学习执行,例如小程序的计算结果;
  • 图像标题,例如用于生成图像;
  • 对话建模,例如对语篇产生的答案的问题;
  • 运动序列的分类,例如对一系列的手势生成一系列的命令。

简单实现

这里依然是跟之前的lstm模型一样,预测加法运算,不过这里是不定长输入,以及需要预测加号的位置,然后相加,例如输入’10+10+10’,然后输出’30’。
这里说一下我的理解的基本步骤,
1.首先将数据处理成序列,例如’10+10+10’处理成[1,0,+,1,0,+,1,0],长度为8的序列。
2.然后将序列转换为one hot编码类型,因为一共有12种字符’0123456789+ ‘,所以将所有的序列转换为长度12的one hot编码类型。
3.将one hot编码的数据传入Encoder-Decoder模型,编码器-解码器模型中。
4.输出两位one hot编码的数据,再转换为字符串。
输入结构

数据处理以及生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 生成一序列的数据
def random_sum_pairs(n_examples, n_numbers, largest):
X, y = list(), list()
for i in range(n_examples):
in_pattern = [randint(1, largest) for _ in range(n_numbers)]
out_pattern = sum(in_pattern)
X.append(in_pattern)
y.append(out_pattern)
return X, y

# 将数据转换为字符串
def to_string(X, y, n_numbers, largest):
max_length = n_numbers * math.ceil(math.log10(largest+1)) + n_numbers - 1
Xstr = list()
for pattern in X:
strp = '+' .join([str(n) for n in pattern])
strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp
Xstr.append(strp)
max_length = math.ceil(math.log10(n_numbers * (largest+1)))
ystr = list()

for pattern in y:
strp = str(pattern)
strp = ''.join([' ' for _ in range(max_length-len(strp))]) + strp
ystr.append(strp)
return Xstr, ystr

# 切割字符串
def integer_encode(X, y, alphabet):
char_to_int = dict((c, i) for i, c in enumerate(alphabet))
Xenc = list()
for pattern in X:
integer_encoded = [char_to_int[char] for char in pattern]
Xenc.append(integer_encoded)

yenc = list()
for pattern in y:
integer_encoded = [char_to_int[char] for char in pattern]
yenc.append(integer_encoded)
return Xenc, yenc

# 将字符串转换为one hot编码
def one_hot_encode(X, y, max_int):
Xenc = list()
for seq in X:
pattern = list()
for index in seq:
vector = [0 for _ in range(max_int)]
vector[index] = 1
pattern.append(vector)
Xenc.append(pattern)

yenc = list()
for seq in y:
pattern = list()
for index in seq:
vector = [0 for _ in range(max_int)]
vector[index] = 1
pattern.append(vector)
yenc.append(pattern)

return Xenc, yenc

# 生成数据
def generate_data(n_samples, n_numbers, largest, alphabet):
X, y = random_sum_pairs(n_samples, n_numbers, largest)
X, y = to_string(X, y, n_numbers, largest)
X, y = integer_encode(X, y, alphabet)
X, y = one_hot_encode(X, y, len(alphabet))
X, y = np.array(X), np.array(y)
return X, y

# 数据解码
def invert(seq, alphabet):
int_to_char = dict((i, c) for i, c in enumerate(alphabet))
strings = list()
for pattern in seq:
string = int_to_char[np.argmax(pattern)]
strings.append(string)
return ''.join(strings).lstrip('0')

以上就是生成数据,并且转换为one hot编码类型的数据

模型构建

包括输入参数以及模型构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#生成训练集数量
n_numbers = 75000
#相加的数的个数
n_terms = 4
#最大的相加数字
largest = 30
#涵盖所有的可能字符范围
alphabet = [str(x) for x in range(10)] + ['+', ' ']
# 训练次数
epochs=2
# 训练步长
batch_size=64

# 字符范围的大小(one hot编码的长度)
n_chars = len(alphabet)
# 输入字符的长度,例如(10+10+10长度是8)
n_in_seq_length = n_terms * math.ceil(math.log10(largest+1)) + n_terms - 1
# 输出字符的长度,例如(30的长度就是2)
n_out_seq_length = math.ceil(math.log10(n_terms * (largest+1)))

# 定义模型
model = Sequential()
model.add(LSTM(75, input_shape=(n_in_seq_length, n_chars)))
model.add(RepeatVector(n_out_seq_length))
model.add(LSTM(50, return_sequences=True))
model.add(TimeDistributed(Dense(n_chars, activation='softmax')))
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
print(model.summary())

解析:
首先是编码器,一个或多个LSTM层可用于显示编码器模型。这个模型的输出时一个固定大小的向量,表示输入许的内部表示。这个层中的存储单元的数目与这个大小的向量长度无关。

然后是解码器,解码器必须将所学习的输入序列的内部表示转换成正确的输出序列。还可以使用一个或多个LSTM层来实现编解码模型。这个模型是从编码器模型的大小输出中读取的。与Vanilla LSTM一样,一个Dense层可以被用做网络的输出。通过将Dense层包裹在TimeDistributed层中,同样的权重可以被用来在每个输出序列中输出每个时间步长。

但是有一个问题。我们必须把编码器和解码器连接起来,但是它们不适合。也就是说,编码器将产生输出的二维矩阵,其中长度由层中的存储单元的数目决定。解码器是一个LSTM层,它期望3D输入(样本、时间步长、特征),以产生由该问题产生的不同长度的解码序列。

如果你试图强迫这些碎片在一起,你会得到一个错误,表明解码器的输出是2D,需要3D解码器。我们可以用重复向量层来解决这个问题。该层简单地多次重复所提出的2D输入以创建3D输出。

RepeatVector层可以像适配器一样使用,以将网络的编码器和解码器部分适配在一起。我们可以配置重复向量以在输出序列中的每个时间步长中重复一个固定长度向量。

训练和验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 训练
X, y = generate_data(n_numbers, n_terms, largest, alphabet)
model.fit(X, y, epochs=epochs, batch_size=batch_size)

# 评估
X, y = generate_data(1000, n_terms, largest, alphabet)
loss, acc = model.evaluate(X, y, verbose=0)
print('Loss: %f, Accuracy: %f' % (loss, acc*100))

# 预测
for _ in range(10):
# generate an input-output pair
X, y = generate_data(1, n_terms, largest, alphabet)
# make prediction
yhat = model.predict(X, verbose=0)
# decode input, expected and predicted
in_seq = invert(X[0], alphabet)
out_seq = invert(y[0], alphabet)
predicted = invert(yhat[0], alphabet)
print('%s = %s (expect %s)' % (in_seq, predicted, out_seq))

先是生成n_numbers数量的序列对,通过函数生成和填充之后的数据。然后fit数据。之后再生成100个验证数据,计算网络的准确率。再是预测。

训练结果

可以简单的看一下结果,这里是n_terms = 4 ,largest = 10的结果,可以自己修改一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1172/1172 [==============================] - 20s 13ms/step - loss: 1.1085 - accuracy: 0.6363
Epoch 2/3
1172/1172 [==============================] - 15s 13ms/step - loss: 0.2582 - accuracy: 0.9624
Epoch 3/3
1172/1172 [==============================] - 16s 13ms/step - loss: 0.1399 - accuracy: 0.9738
Loss: 0.074328, Accuracy: 98.449999
3+10+4+3 = 20 (expect 20)
6+2+2+10 = 20 (expect 20)
2+9+1+10 = 22 (expect 22)
2+6+1+9 = 18 (expect 18)
3+3+2+9 = 17 (expect 17)
6+10+3+10 = 29 (expect 29)
7+3+8+5 = 23 (expect 23)
5+7+9+10 = 31 (expect 31)
1+2+6+6 = 15 (expect 15)
4+9+10+9 = 32 (expect 32)

说实话,准确率还挺高。

相关文献

1
2
3
4
5
6
文章中代码来源[机器之心](https://www.jiqizhixin.com/articles/2019-02-18-13)
Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation, 2014.
[Sequence to Sequence Learning with Neural Networks, 2014. https://arxiv.org/abs/1409.3215
Show and Tell: A Neural Image Caption Generator, 2014.
Learning to Execute, 2015.
{A Neural Conversational Model, 2015.](https://arxiv.org/abs/1506.05869)

之所以写这个是因为nmt框架需要这个东西。