我是一名经验丰富的Python开发者,但在机器学习方面完全是新手。这是我的第一次尝试使用Keras。你能告诉我哪里做错了么?
我试图构建一个神经网络,它接受一个二进制形式的数字,并输出该数字除以7的模数。(我的目标是选择一个非常简单的任务,只是为了验证一切都正常运行。)
在下面的代码中,我定义了网络并在10,000个随机数字上训练它。然后我在500个随机数字上测试它。
不知为何,我得到的准确率大约是1/7,这正是你从一个完全随机的算法中期望得到的准确率,也就是说我的神经网络实际上什么也没做。
谁能帮我找出问题所在?
import keras.modelsimport numpy as npfrom python_toolbox import random_toolsRADIX = 7def _get_number(vector): return sum(x * 2 ** i for i, x in enumerate(vector))def _get_mod_result(vector): return _get_number(vector) % RADIXdef _number_to_vector(number): binary_string = bin(number)[2:] if len(binary_string) > 20: raise NotImplementedError bits = (((0,) * (20 - len(binary_string))) + tuple(map(int, binary_string)))[::-1] assert len(bits) == 20 return np.c_[bits]def get_mod_result_vector(vector): return _number_to_vector(_get_mod_result(vector))def main(): model = keras.models.Sequential( ( keras.layers.Dense( units=20, activation='relu', input_dim=20 ), keras.layers.Dense( units=20, activation='relu' ), keras.layers.Dense( units=20, activation='softmax' ) ) ) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) data = np.random.randint(2, size=(10000, 20)) labels = np.vstack(map(get_mod_result_vector, data)) model.fit(data, labels, epochs=10, batch_size=50) def predict(number): foo = model.predict(_number_to_vector(number)) return _get_number(tuple(map(round, foo[0]))) def is_correct_for_number(x): return bool(predict(x) == x % RADIX) predict(7) sample = random_tools.shuffled(range(2 ** 20))[:500] print('Total accuracy:') print(sum(map(is_correct_for_number, sample)) / len(sample)) print(f'(Accuracy of random algorithm is {1/RADIX:.2f}')if __name__ == '__main__': main()
回答:
更新
经过一些调整,我能够使用RNN得到一个相当不错的解决方案。它在所有可能的唯一输入的不到5%上进行训练,并在随机测试样本上提供了超过90%的准确率。你可以将批次数量从40增加到100,以使其更加准确(尽管在某些运行中,模型可能不会收敛到正确答案 – 这里的概率比通常高)。我在这里切换到了使用Adam优化器,并且不得不将样本数量增加到50K(10K对我来说导致了过拟合)。
请理解这个解决方案有点类似于开玩笑,因为它基于任务领域知识,即我们的目标函数可以通过输入比特序列上的简单递归公式来定义(如果你反转你的输入比特序列,公式会更简单,但在这里使用LSTM的go_backwards=True
没有帮助)。
如果你逆转输入比特的顺序(这样我们总是从最高有效位开始),那么目标函数的递归公式就是F_n = G(F_{n-1}, x_n)
,其中F_n = MOD([x_1,...,x_n], 7)
,并且G(x, y) = MOD(2*x+y, 7)
– 只有49个不同的输入和7个可能的输出。所以模型必须学习初始状态 + 这个G
更新函数。对于从最低有效位开始的序列,递归公式稍微复杂一些,因为它还需要在每一步跟踪当前的MOD(2**n, 7)
,但似乎这种难度对训练没有影响。
请注意 – 这些公式只是为了解释为什么RNN在这里有效。下面的网络只是一个简单的LSTM层 + softmax,原始的比特输入被视为一个序列。
使用RNN层的完整回答代码:
import keras.modelsimport numpy as npfrom python_toolbox import random_toolsRADIX = 7FEATURE_BITS = 20def _get_number(vector): return sum(x * 2 ** i for i, x in enumerate(vector))def _get_mod_result(vector): return _get_number(vector) % RADIXdef _number_to_vector(number): binary_string = bin(number)[2:] if len(binary_string) > FEATURE_BITS: raise NotImplementedError bits = (((0,) * (FEATURE_BITS - len(binary_string))) + tuple(map(int, binary_string)))[::-1] assert len(bits) == FEATURE_BITS return np.c_[bits]def get_mod_result_vector(vector): v = np.repeat(0, 7) v[_get_mod_result(vector)] = 1 return vdef main(): model = keras.models.Sequential( ( keras.layers.Reshape( (1, -1) ), keras.layers.LSTM( units=100, ), keras.layers.Dense( units=7, activation='softmax' ) ) ) model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.01), loss='categorical_crossentropy', metrics=['accuracy']) data = np.random.randint(2, size=(50000, FEATURE_BITS)) labels = np.vstack(map(get_mod_result_vector, data)) model.fit(data, labels, epochs=40, batch_size=50) def predict(number): foo = model.predict(_number_to_vector(number)) return np.argmax(foo) def is_correct_for_number(x): return bool(predict(x) == x % RADIX) sample = random_tools.shuffled(range(2 ** FEATURE_BITS))[:500] print('Total accuracy:') print(sum(map(is_correct_for_number, sample)) / len(sample)) print(f'(Accuracy of random algorithm is {1/RADIX:.2f}')if __name__ == '__main__': main()
原始回答
我不确定这是怎么发生的,但你选择检查代码的特定任务对于神经网络来说非常困难。我认为最好的解释是,神经网络在特征之间相互关联的情况下表现不佳,尤其是在改变一个特征总是完全改变目标输出的值时。一种看待它的方式是观察你期望某个答案时的特征集 – 在你的情况下,它们看起来像是20维空间中大量平行超平面的并集 – 而对于7个类别中的每一个,这些平面集是“很好地”交错在一起的,并留给神经网络来区分。
尽管如此 – 如果你的样本数量很大,比如10K,并且可能的输入数量较小,比如你的输入位数只有8位(因此只有256个独特的输入可能) – 网络应该能够“学习”正确的函数相当不错(通过“记住”每个输入的正确答案,而无需泛化)。在你的情况下,这没有发生,因为代码存在以下错误。
你的标签是20维向量,包含0-6的整数位(你实际期望的标签) – 所以我猜你基本上是在试图教神经网络将答案的位作为单独的分类器来学习(只有3位可能非零)。我将其改为我认为你实际想要的 – 长度为7的向量,其中只有一个值为1,其余为0(所谓的独热编码,根据这个,keras实际上期望用于categorical_crossentropy
)。如果你想尝试分别学习每个位,你绝对不应该在最后一层使用softmax 20,因为这种输出生成20个类别的概率,它们的总和为1(在这种情况下,你应该训练20个或更确切地说是3个二元分类器)。由于你的代码没有给keras正确的输入,你最终得到的模型有点随机,并且你应用的四舍五入旨在对95%-100%的输入输出相同的值。
下面的略有改动的代码训练了一个模型,它可以或多或少地正确猜测每个从0到255的数字的模7答案(再次,基本上是记住了每个输入的正确答案)。如果你尝试增加FEATURE_BITS
,你会看到结果大幅下降。如果你实际上想训练神经网络来学习这个任务,输入20位或更多位(并且不提供神经网络所有可能的输入和无限的时间来训练),你将需要应用一些特定于任务的特征变换和/或一些专门设计的层,准确地适合你想要实现的任务,正如其他人已经在你问题的评论中提到的。
...(代码内容与上文相同)...