In [47]:
import numpy as np
from random import seed, randint
from math import ceil, log10

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

In [11]:
seed(1)
n_samples = 1
n_numbers = 2
largest = 10
# generate pairs
X, y = random_sum_pairs(n_samples, n_numbers, largest)
print(X, y)


[[3, 10]] [13]

In [19]:
# convert data to strings
def to_string(X, y, n_numbers, largest):
    max_length = int(n_numbers * ceil(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 = int(ceil(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

In [33]:
n_samples = 1
n_numbers = 2
largest = 10

# generate pairs
X, y = random_sum_pairs(n_samples, n_numbers, largest)
print(X, y)

X, y = to_string(X, y, n_numbers, largest)
print(X, y)


[[4, 8]] [12]
['  4+8'] ['12']

In [34]:
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

seed(1)
n_samples = 1
n_numbers = 2
largest = 10

X, y = random_sum_pairs(n_samples, n_numbers, largest)
print(X, y)

X, y = to_string(X, y, n_numbers, largest)
print(X, y)

alphabet = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', ' ']
X, y = integer_encode(X, y, alphabet)
print(X, y)


[[3, 10]] [13]
[' 3+10'] ['13']
[[11, 3, 10, 1, 0]] [[1, 3]]

In [36]:
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

In [38]:
print(X, y)
X, y = one_hot_encode(X, y, len(alphabet))
print(X, y)


[[11, 3, 10, 1, 0]] [[1, 3]]
[[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]] [[[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]]]

In [39]:
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

In [53]:
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)

In [41]:
# configulation
n_terms = 3
largest = 10
alphabet = [str(x) for x in range(10)] + ['+', ' ']

In [42]:
n_chars = len(alphabet)
n_in_seq_length = int(n_terms * ceil(log10(largest + 1)) + n_terms - 1)
n_out_seq_length = int(ceil(log10(n_terms * (largest + 1))))

In [49]:
from keras.models import Sequential
from keras.layers import LSTM, Dense, TimeDistributed, RepeatVector

model = Sequential()
# (timesteps, features)
model.add(LSTM(75, input_shape=(n_in_seq_length, n_chars)))
model.add(RepeatVector(n_out_seq_length))  # Decoderへの入力のため出力系列長と同じ長さだけコピー
model.add(LSTM(50, return_sequences=True))
model.add(TimeDistributed(Dense(n_chars, activation='softmax')))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()


_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm_5 (LSTM)                (None, 75)                26400     
_________________________________________________________________
repeat_vector_2 (RepeatVecto (None, 2, 75)             0         
_________________________________________________________________
lstm_6 (LSTM)                (None, 2, 50)             25200     
_________________________________________________________________
time_distributed_2 (TimeDist (None, 2, 12)             612       
=================================================================
Total params: 52,212
Trainable params: 52,212
Non-trainable params: 0
_________________________________________________________________

In [50]:
# fit LSTM
X, y = generate_data(75000, n_terms, largest, alphabet)
model.fit(X, y, epochs=1, batch_size=32)


Epoch 1/1
75000/75000 [==============================] - 84s - loss: 0.6521 - acc: 0.8086    
Out[50]:
<keras.callbacks.History at 0x317427128>

In [51]:
# evaluate LSTM
X, y = generate_data(100, n_terms, largest, alphabet)
loss, acc = model.evaluate(X, y, verbose=0)
print('Loss: %f, Accuracy: %f' % (loss, acc * 100))


Loss: 0.075868, Accuracy: 99.500000

In [54]:
# predict
for _ in range(10):
    X, y = generate_data(1, n_terms, largest, alphabet)
    yhat = model.predict(X, verbose=0)
    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))


   4+1+9 = 14 (expect 14)
   9+6+5 = 20 (expect 20)
   1+3+7 = 11 (expect 11)
   7+5+9 = 21 (expect 21)
  3+10+3 = 16 (expect 16)
   6+6+4 = 16 (expect 16)
   1+6+1 =  8 (expect  8)
   2+5+5 = 12 (expect 12)
   6+3+9 = 18 (expect 18)
   3+9+3 = 15 (expect 15)