我正在按照书籍Grokking Deep Learning(第8章,代码在这里)中的指导,构建一个Numpy神经网络,用于对MNIST数字进行分类,测试准确率约为82%。但当我修改神经网络以适应一个合成数据集时,它会从训练开始就达到一个特定的训练准确率(取决于隐藏层的维度和alpha),并停留在那里。请检查以下内容:
import numpy as npimport sysfrom sklearn import datasetsX, y = datasets.make_classification(n_samples=10000, n_features=5, n_classes=4, n_clusters_per_class=1, shuffle=True, random_state=1)from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)def relu(x): return (x >= 0) * x # returns x if x > 0 # returns 0 otherwisedef relu2deriv(output): return output >= 0 #returns 1 for input > 0def onehot(arr): one_hot_labels = np.zeros((len(arr),4)) for i,l in enumerate(arr): one_hot_labels[i][l] = 1 return one_hot_labelsy_train = onehot(y_train)y_test = onehot(y_test)alpha, iterations, hidden_size = (0.002, 300, 10)weights_0_1 = 0.2*np.random.random((5, hidden_size)) - 0.1weights_1_2 = 0.2*np.random.random((hidden_size, 4)) - 0.1for j in range(iterations): error, correct_cnt = (0.0,0) for i in range(len(X_train)): layer_0 = X_train[i:i+1] layer_1 = relu(np.dot(layer_0,weights_0_1)) dropout_mask = np.random.randint(2, size=layer_1.shape) layer_1 *= dropout_mask * 2 layer_2 = np.dot(layer_1,weights_1_2) error += np.sum((y_train[i:i+1] - layer_2) ** 2) correct_cnt += int(np.argmax(layer_2) == np.argmax(y_train[i:i+1])) layer_2_delta = (y_train[i:i+1] - layer_2) layer_1_delta = layer_2_delta.dot(weights_1_2.T) * relu2deriv(layer_1) layer_1_delta *= dropout_mask weights_1_2 += alpha * layer_1.T.dot(layer_2_delta) weights_0_1 += alpha * layer_0.T.dot(layer_1_delta) if(j%1 == 0): # can be set for any interval test_error = 0.0 test_correct_cnt = 0 for i in range(len(X_test)): layer_0 = X_test[i:i+1] layer_1 = relu(np.dot(layer_0, weights_0_1)) layer_2 = np.dot(layer_1, weights_1_2) test_error += np.sum((y_test[i:i+1] - layer_2) ** 2) test_correct_cnt += int(np.argmax(layer_2) == np.argmax(y_test[i:i+1])) sys.stdout.write("\n" + \ "I:" + str(j) + \ " Test-Err:" + str(test_error/ float(len(X_test)))[0:5] +\ " Test-Acc:" + str(test_correct_cnt/ float(len(X_test)))+\ " Train-Err:" + str(error/ float(len(X_train)))[0:5] +\ " Train-Acc:" + str(correct_cnt/ float(len(X_train))))
输出:
I:0 Test-Err:0.470 Test-Acc:0.812 Train-Err:0.704 Train-Acc:0.572I:1 Test-Err:0.452 Test-Acc:0.811 Train-Err:0.574 Train-Acc:0.626625I:2 Test-Err:0.445 Test-Acc:0.814 Train-Err:0.571 Train-Acc:0.61425 . . .I:297 Test-Err:0.470 Test-Acc:0.7685 Train-Err:0.613 Train-Acc:0.6045I:298 Test-Err:0.492 Test-Acc:0.785 Train-Err:0.612 Train-Acc:0.60525I:299 Test-Err:0.478 Test-Acc:0.778 Train-Err:0.614 Train-Acc:0.60725
这是怎么回事?为什么这个神经网络能在MNIST数据集上表现良好,而在这个数据集上却不行?
回答:
我认为问题在于你没有使用偏置项(bias terms)。
例如,
layer_1 = relu(np.dot(layer_0,weights_0_1))
从几何角度来看,这意味着第1层的输出(以及其余层的输出)没有平移项,这使得决策边界被迫通过原点。
因此,对于不围绕0的数据,可能无法学习到决策边界。想象一下,对于二元分类,数据紧密聚集在(0, 1)
和(0, 2)
周围。通过(0, 0)
的任何线性边界都无法分离这些聚类。
关于为什么需要偏置项,这里有一个很好的解释:查看这里。
我认为(但没有验证),添加偏置项应该可以实现收敛。
layer_1 = relu(np.dot(layer_0,weights_0_1) + layer_0_bias)
以此类推。
关于偏置的导数,在这里有讨论:查看这里。
还有更多可能的原因。
layer_2
的输出是输出,计算的是均方误差损失(MSELoss),而不是使用负对数似然损失(NLL)或交叉熵损失(CrossEntropy Loss)。- 没有对输入进行归一化,这可能导致网络无法学习。对于来自超立方的合成数据,这不太可能,但对于其他一般数据来说,很可能如此。