I have a solution however I use a densely connected layer at the output to simplify the reshaping. If you can manipulate the sizes of this model such that you have 4 output parameters this should work as well.
from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv1D, MaxPooling1D, Reshape
from keras.callbacks import ModelCheckpoint
from keras.models import model_from_json
from keras import backend as K
Preparing the data
We will generate some random lists containing integers between [0,49], we will take a random permutation of the list and then take the first 4 values. We will then set our targets $y$ as the sorted rows of $x$.
import numpy as np
n = 100000
x_train = np.zeros((n,4))
for i in range(n):
x_train[i,:] = np.random.permutation(50)[0:4]
x_train = x_train.reshape(n, 4, 1)
y_train = np.sort(x_train, axis=1).reshape(n, 4,)
n = 1000
x_test = np.zeros((n,4))
for i in range(n):
x_test[i,:] = np.random.permutation(50)[0:4]
x_test = x_test.reshape(n, 4, 1)
y_test = np.sort(x_test, axis=1).reshape(n, 4,)
print(x_test[0][0].T)
print(y_test[0])
[ 44. 36. 13. 0.]
[ 0. 13. 36. 44.]
The model
I tried different combinations of parameters. This worked out not bad.
input_shape = (4,1)
model = Sequential()
model.add(Conv1D(32, kernel_size=(2),
activation='relu',
input_shape=input_shape,
padding='same'))
model.add(Conv1D(64, (2), activation='relu', padding='same'))
model.add(MaxPooling1D(pool_size=(2)))
model.add(Reshape((64,2)))
model.add(Conv1D(32, (2), activation='relu', padding='same'))
model.add(MaxPooling1D(pool_size=(2)))
model.add(Flatten())
model.add(Dense(4))
model.compile(loss=keras.losses.mean_squared_error,
optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])
epochs = 10
batch_size = 128
# Fit the model weights.
history = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
Epoch 10/10
100000/100000 [==============================] - 6s
56us/step - loss: 0.9061 - acc: 0.9973 - val_loss: 0.5302 - val_acc:
0.9950
Results
So for a new list of values, I get the predicted output. Then I determine which value in the original list is closest to each of these and replace them. I could have just rounded the predicted values, however this caused so +/-1 errors due to rounding the wrong way.
test_list = [1,45,3,18]
pred = model.predict(np.asarray(test_list).reshape(1,4,1))
print(test_list)
print(pred)
print([np.asarray(test_list).reshape(4,)[np.abs(np.asarray(test_list).reshape(4,) - i).argmin()] for i in list(pred[0])])
[1, 45, 3, 18]
[[ 0.87599814 3.43058085 17.36335754 45.21624374]]
[1, 3, 18, 45]
And for the sequence you suggested as a test case
test_list = [5,3,6,2]
pred = model.predict(np.asarray(test_list).reshape(1,4,1))
print(test_list)
print(pred)
print([np.asarray(test_list).reshape(4,)[np.abs(np.asarray(test_list).reshape(4,) - i).argmin()] for i in list(pred[0])])
[5, 3, 6, 2]
[[ 1.85080266 2.95598722 4.92955017 5.88561296]]
[2,
3, 5, 6]