实现一个用来执行加法的序列到序列学习模型

输入: "535+61"

输出: "596"

使用重复的标记字符(空格)处理填充。

输入可以选择性地反转,它被认为可以提高许多任务的性能,例如:Learning to Execute以及Sequence to Sequence Learning with Neural Networks

从理论上讲,它引入了源和目标之间的短期依赖关系。

两个反转的数字 + 一个 LSTM 层(128个隐藏单元),在 55 个 epochs 后,5k 的训练样本取得了 99% 的训练/测试准确率。

三个反转的数字 + 一个 LSTM 层(128个隐藏单元),在 100 个 epochs 后,50k 的训练样本取得了 99% 的训练/测试准确率。

四个反转的数字 + 一个 LSTM 层(128个隐藏单元),在 20 个 epochs 后,400k 的训练样本取得了 99% 的训练/测试准确率。

五个反转的数字 + 一个 LSTM 层(128个隐藏单元),在 30 个 epochs 后,550k 的训练样本取得了 99% 的训练/测试准确率。

  1. from __future__ import print_function
  2. from keras.models import Sequential
  3. from keras import layers
  4. import numpy as np
  5. from six.moves import range
  6. class CharacterTable(object):
  7. """给定一组字符:
  8. + 将它们编码为 one-hot 整数表示
  9. + 将 one-hot 或整数表示解码为字符输出
  10. + 将一个概率向量解码为字符输出
  11. """
  12. def __init__(self, chars):
  13. """初始化字符表。
  14. # 参数:
  15. chars: 可以出现在输入中的字符。
  16. """
  17. self.chars = sorted(set(chars))
  18. self.char_indices = dict((c, i) for i, c in enumerate(self.chars))
  19. self.indices_char = dict((i, c) for i, c in enumerate(self.chars))
  20. def encode(self, C, num_rows):
  21. """给定字符串 C 的 one-hot 编码。
  22. # 参数
  23. C: 需要编码的字符串。
  24. num_rows: 返回的 one-hot 编码的行数。
  25. 这用来保证每个数据的行数相同。
  26. """
  27. x = np.zeros((num_rows, len(self.chars)))
  28. for i, c in enumerate(C):
  29. x[i, self.char_indices[c]] = 1
  30. return x
  31. def decode(self, x, calc_argmax=True):
  32. """将给定的向量或 2D array 解码为它们的字符输出。
  33. # 参数
  34. x: 一个向量或 2D 概率数组或 one-hot 表示,
  35. 或 字符索引的向量(如果 `calc_argmax=False`)。
  36. calc_argmax: 是否根据最大概率来找到字符,默认为 `True`。
  37. """
  38. if calc_argmax:
  39. x = x.argmax(axis=-1)
  40. return ''.join(self.indices_char[x] for x in x)
  41. class colors:
  42. ok = '\033[92m'
  43. fail = '\033[91m'
  44. close = '\033[0m'
  45. # 模型和数据的参数
  46. TRAINING_SIZE = 50000
  47. DIGITS = 3
  48. REVERSE = True
  49. # 输入的最大长度是 'int+int' (例如, '345+678'). int 的最大长度为 DIGITS。
  50. MAXLEN = DIGITS + 1 + DIGITS
  51. # 所有的数字,加上符号,以及用于填充的空格。
  52. chars = '0123456789+ '
  53. ctable = CharacterTable(chars)
  54. questions = []
  55. expected = []
  56. seen = set()
  57. print('Generating data...')
  58. while len(questions) < TRAINING_SIZE:
  59. f = lambda: int(''.join(np.random.choice(list('0123456789'))
  60. for i in range(np.random.randint(1, DIGITS + 1))))
  61. a, b = f(), f()
  62. # 跳过任何已有的加法问题
  63. # 同事跳过任何 x+Y == Y+x 的情况(即排序)。
  64. key = tuple(sorted((a, b)))
  65. if key in seen:
  66. continue
  67. seen.add(key)
  68. # 利用空格填充,是的长度始终为 MAXLEN。
  69. q = '{}+{}'.format(a, b)
  70. query = q + ' ' * (MAXLEN - len(q))
  71. ans = str(a + b)
  72. # 答案可能的最长长度为 DIGITS + 1。
  73. ans += ' ' * (DIGITS + 1 - len(ans))
  74. if REVERSE:
  75. # 反转查询,例如,'12+345 ' 变成 ' 543+21'.
  76. # (注意用于填充的空格)
  77. query = query[::-1]
  78. questions.append(query)
  79. expected.append(ans)
  80. print('Total addition questions:', len(questions))
  81. print('Vectorization...')
  82. x = np.zeros((len(questions), MAXLEN, len(chars)), dtype=np.bool)
  83. y = np.zeros((len(questions), DIGITS + 1, len(chars)), dtype=np.bool)
  84. for i, sentence in enumerate(questions):
  85. x[i] = ctable.encode(sentence, MAXLEN)
  86. for i, sentence in enumerate(expected):
  87. y[i] = ctable.encode(sentence, DIGITS + 1)
  88. # 混洗 (x, y),因为 x 的后半段几乎都是比较大的数字。
  89. indices = np.arange(len(y))
  90. np.random.shuffle(indices)
  91. x = x[indices]
  92. y = y[indices]
  93. # 显式地分离出 10% 的训练数据作为验证集。
  94. split_at = len(x) - len(x) // 10
  95. (x_train, x_val) = x[:split_at], x[split_at:]
  96. (y_train, y_val) = y[:split_at], y[split_at:]
  97. print('Training Data:')
  98. print(x_train.shape)
  99. print(y_train.shape)
  100. print('Validation Data:')
  101. print(x_val.shape)
  102. print(y_val.shape)
  103. # 可以尝试更改为 GRU, 或 SimpleRNN。
  104. RNN = layers.LSTM
  105. HIDDEN_SIZE = 128
  106. BATCH_SIZE = 128
  107. LAYERS = 1
  108. print('Build model...')
  109. model = Sequential()
  110. # 利用 RNN 将输入序列「编码」为一个 HIDDEN_SIZE 长度的输出向量。
  111. # 注意:在输入序列具有可变长度的情况下,
  112. # 使用 input_shape=(None, num_feature).
  113. model.add(RNN(HIDDEN_SIZE, input_shape=(MAXLEN, len(chars))))
  114. # 作为解码器 RNN 的输入,为每个时间步重复地提供 RNN 的最后输出。
  115. # 重复 'DIGITS + 1' 次,因为它是最大输出长度。
  116. # 例如,当 DIGITS=3, 最大输出为 999+999=1998。
  117. model.add(layers.RepeatVector(DIGITS + 1))
  118. # 解码器 RNN 可以是多个堆叠的层,或一个单独的层。
  119. for _ in range(LAYERS):
  120. # 通过设置 return_sequences 为 True, 将不仅返回最后一个输出,而是返回目前的所有输出,形式为(num_samples, timesteps, output_dim)。
  121. # 这是必须的,因为后面的 TimeDistributed 需要第一个维度是时间步。
  122. model.add(RNN(HIDDEN_SIZE, return_sequences=True))
  123. # 将全连接层应用于输入的每个时间片。
  124. # 对于输出序列的每一步,决定应选哪个字符。
  125. model.add(layers.TimeDistributed(layers.Dense(len(chars), activation='softmax')))
  126. model.compile(loss='categorical_crossentropy',
  127. optimizer='adam',
  128. metrics=['accuracy'])
  129. model.summary()
  130. # 训练模型,并在每一代显示验证数据的预测。
  131. for iteration in range(1, 200):
  132. print()
  133. print('-' * 50)
  134. print('Iteration', iteration)
  135. model.fit(x_train, y_train,
  136. batch_size=BATCH_SIZE,
  137. epochs=1,
  138. validation_data=(x_val, y_val))
  139. # 从随机验证集中选择 10 个样本,以便我们可以看到错误。
  140. for i in range(10):
  141. ind = np.random.randint(0, len(x_val))
  142. rowx, rowy = x_val[np.array([ind])], y_val[np.array([ind])]
  143. preds = model.predict_classes(rowx, verbose=0)
  144. q = ctable.decode(rowx[0])
  145. correct = ctable.decode(rowy[0])
  146. guess = ctable.decode(preds[0], calc_argmax=False)
  147. print('Q', q[::-1] if REVERSE else q, end=' ')
  148. print('T', correct, end=' ')
  149. if correct == guess:
  150. print(colors.ok + '☑' + colors.close, end=' ')
  151. else:
  152. print(colors.fail + '☒' + colors.close, end=' ')
  153. print(guess)