最近,在一次采访中,我得到了这样一个问题:
设计一个对数字进行排序的卷积网络。运算符有 ReLU、Conv 和 Pooling。例如输入:5、3、6、2;输出:2、3、5、6
我不确定如何使用 CNN 对数字列表进行排序。我知道使用 RNN 是可能的。甚至可能吗?
最近,在一次采访中,我得到了这样一个问题:
设计一个对数字进行排序的卷积网络。运算符有 ReLU、Conv 和 Pooling。例如输入:5、3、6、2;输出:2、3、5、6
我不确定如何使用 CNN 对数字列表进行排序。我知道使用 RNN 是可能的。甚至可能吗?
我有一个解决方案,但是我在输出端使用密集连接层来简化整形。如果您可以操纵此模型的大小以使您有 4 个输出参数,那么它也应该可以工作。
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
我们将生成一些包含 [0,49] 之间整数的随机列表,我们将对列表进行随机排列,然后取前 4 个值。然后我们将设定我们的目标 作为排序的行 .
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.]
我尝试了不同的参数组合。结果还不错。
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))
纪元 10/10
100000/100000 [===============================] - 6s 56us/步 - 损失:0.9061 - acc :0.9973 - val_loss:0.5302 - val_acc:0.9950
所以对于一个新的值列表,我得到了预测的输出。然后我确定原始列表中的哪个值最接近这些值并替换它们。我本可以对预测值进行四舍五入,但是由于四舍五入错误的方式导致了 +/-1 的错误。
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]
对于您建议作为测试用例的序列
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]
可以手动编码一个冒泡排序版本,可以显示正确排序数字。
冒泡排序通过翻转数组中被反转的相邻元素来进行。例如,
3 2 1
x
2 3 1
x
2 1 3
x
1 2 3
这可以通过双 for 循环来实现
for i in 0..n {
for j in i+1..n {
if arr[i] > arr[j] {
arr[i], arr[j] = arr[j], arr[i];
}
}
}
首先,我们将设计一个交换层,它可以交换相邻元素以纠正它们的顺序。
让 是编码为浮点数的输入向量。交换层具有与输入相同的大小。所有方程都写成 基于索引。
奇怪的情况需要一个步幅为 2 的最大池,然后是在左侧填充零的转置卷积。
3 2 1 5
\| \|
=> 3 5 (Max Pool)
=> 0 3 0 5 (Padding)
偶数情况只是一个步幅为 2 分钟的池(稍后解释),然后是一个转置卷积层,在右侧用 0 填充输入,
3 2 1 5
|/ |/
=> 2 1 (Min Pool)
=> 2 0 1 0 (Zero padding)
然后将两个池相加以产生交换层。求和可以通过一个步长为 1 的简单 1x1 卷积层来完成。
0 3 0 5
+ 2 0 1 0
-------
= 2 3 1 5
交换层的整体结构如下所示,
Input --> MinPool -- Zero pad-- + --> Swapped
| |
|----> MaxPool -- Zero pad --|
请注意,交换层在 1x2 感受野中本地工作。为了允许像冒泡排序一样跨字段移动,下一个交换层移动了 1 个位置,从而颠倒了上面的奇偶规则。
2 3 1 5 -> 0 3 0 5 (Max)
-> 2 0 1 0 (Min)
-------
2 3 1 5
再次使用非偏移交换层完成示例,
2 1 3 5 -> 1 0 3 0 (Min)
-> 0 2 0 5 (Max)
-------
1 2 3 5
堆叠足够多的交换层最终会对数组进行排序,因为每对交换层将至少修复一个反转,并且最多有倒置。
最小池可以用 2 个具有线性激活的 1x1 卷积层和介于两者之间的 1 个最大池来实现,因为,
ReLU 激活不能传递负数,但如果您假设已将一个大的正数添加到输入以使它们全部为正,然后在输出中减去,则可以使用 ReLU。
因此,排序可以用层与每个神经元。