为什么我的 2 层神经网络的二元分类准确率仅为 50%?

Why does my 2-layer neural network achieve just 50% accuracy for binary classification?

review post 之后,我构建了这个用于二进制分类的数据集,其中包含 Fashion MNIST T 恤与衬衫。

import tensorflow as tf
import numpy as np

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()

# generate the indices
idx_digit_train = np.argwhere((y_train == 0) | (y_train == 6)).flatten()
idx_digit_test = np.argwhere((y_test == 0) | (y_test == 6)).flatten()

# construct the training set
y_train_mnist = y_train[idx_digit_train].reshape(-1,1)
x_train_mnist = x_train[idx_digit_train].reshape(-1,28*28)

# construct the test set
y_test_mnist = y_test[idx_digit_test].reshape(-1,1)
x_test_mnist = x_test[idx_digit_test].reshape(-1,28*28)

x_train_mnist = x_train_mnist/255.
x_test_mnist = x_test_mnist/255.

y_train_mnist[y_train_mnist==6]=1
y_test_mnist[y_test_mnist==6]=1

y_train_mnist = np.array(y_train_mnist, dtype=np.float32)
y_test_mnist = np.array(y_test_mnist, dtype=np.float32)

改编自另一个 review post,我用 NumPy 从头开始​​构建了一个 2 层神经网络。

import numpy as np

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

class MLPClassifier:
    def __init__(self, eta=.05, n_epoch=10, n_0=2, n_1=2, n_2=1,
          model_w1=[], model_b1=[], model_w2=[], model_b2=[]):
        self.eta = eta
        self.n_epoch = n_epoch
        self.model_w1 = model_w1
        self.model_b1 = model_b1
        self.model_w2 = model_w2
        self.model_b2 = model_b2
        self.n_1 = n_1
        self.n_2 = n_2
        
    def initialize_params(self, n_0, n_1, n_2):
        if len(self.model_w1) == 0:
            self.model_w1 = np.random.random(size=(n_0, n_1))
        if len(self.model_w2) == 0:
            self.model_w2 = np.random.random(size=(n_1, n_2))
        if len(self.model_b1) == 0:
            self.model_b1 = np.random.random(size=(1, n_1))
        if len(self.model_b2) == 0:
            self.model_b2 = np.random.random(size=(1, n_2))
        
    def predict(self, x):
        _, a2 = self.forward_propagation(x)
        yhat = a2 >= 0.5
        return 1*yhat

    def forward_propagation(self, x):
        z1 = np.dot(x, self.model_w1) + self.model_b1
        a1 = sigmoid(z1)
        z2 = np.dot(a1, self.model_w2) + self.model_b2
        a2 = sigmoid(z2)
        return a1, a2
    
    def backward_propagation(self, x, y, a1, a2):
        m = len(x)
        n_1 = self.n_1
        n_2 = self.n_2
        a1 = a1.reshape(m, -1)
        dz2 = a2 - y
        dw2 = np.dot(a1.T, dz2)/m
        dw2 = dw2.reshape(n_1, n_2)
        db2 = np.mean(dz2, keepdims = True)
        dz1 = np.dot(dz2, self.model_w2.T) * (a1*(1-a1))
        dw1 = np.dot(x.T, dz1)/m
        db1 = np.mean(dz1, axis=0)
        return dw2, db2, dw1, db1

    def update_params(self, dw2, db2, dw1, db1):
        self.model_w2 -= self.eta * dw2
        self.model_b2 -= self.eta * db2
        self.model_w1 -= self.eta * dw1
        self.model_b1 -= self.eta * db1

    def fit(self, x, y, verbose=False):
        n_0 = x.shape[-1]
        n_1 = self.n_1
        n_2 = self.n_2
        self.initialize_params(n_0, n_1, n_2)        
        for i in range(self.n_epoch):
            a1, a2 = self.forward_propagation(x)
            dw2, db2, dw1, db1 = self.backward_propagation(x, y, a1, a2)
            self.update_params(dw2, db2, dw1, db1)

根据第一个post,一个逻辑回归模型可以实现

train accuracy: 0.828583

然而,当我训练我的手工艺模型 1、2 时,经过 9 个 epoch 甚至 9999 个 epoch,它在训练集和测试集上的精度都达到了 0.5。

classifier = MLPClassifier(.1, 9999)
classifier.fit(x_train_mnist, y_train_mnist, verbose=False)
acc = np.count_nonzero(np.squeeze(classifier.predict(x_train_mnist)) == np.squeeze(y_train_mnist))

我还尝试了隐藏单元的一系列数字,其中 none 效果更好。

acc_list=[]
for i in np.arange(2, 99):
  for j in range(9):
    classifier = MLPClassifier(.1, j, n_1=i)
    classifier.fit(x_train_mnist, y_train_mnist, verbose=False)
    acc = np.count_nonzero(np.squeeze(classifier.predict(x_train_mnist)) == np.squeeze(y_train_mnist))
    acc_list.append(acc)

与此相反,tensorflow 版本

model = tf.keras.Sequential([
  tf.keras.layers.Dense(2, activation=tf.nn.sigmoid),
  tf.keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

model.compile(loss=tf.keras.losses.binary_crossentropy,
              optimizer='sgd',
              metrics=['accuracy'])

model.fit(x_train_mnist, y_train_mnist, epochs=20, verbose=1)

在 30 个纪元后达到 0.85

时代 30/50 375/375 [==============================] - 1 秒 1 毫秒/步 - 损失:0.3326 - 精度:0.8525

我错过了什么?

如何在不添加更多层的情况下改进我的 2 层手工艺模型?

注意:我的scratch版本也是用的CrossEntropy。

单击 the colab link 以 运行 在线。

您的代码中存在几个问题。

  1. 由于您可能想要实施随机梯度下降 (SGD),其背后的想法是执行 在线 训练,这基本上意味着您更新每个训练示例之后的模型参数(相对地,在 batch-SGD 中的每批样本之后)。因此,您的训练循环应如下所示:

    for i in trange(self.n_epoch):
        for j in range(0, len(x)):
            a1, a2 = self.forward_propagation(x[i])
            dw2, db2, dw1, db1 = self.backward_propagation(x[i], y[i], a1, a2)
            self.update_params(dw2, db2, dw1, db1)
    
  2. 您似乎没有在 backward_propagation.
    的网络输出端包含 sigmoid 函数的导数 此外,通过编写 db2 = np.mean(dz2, keepdims = True),您正在减少对标量值的偏差更新(这可能不是您想要的)。
    此外,您的代码中的数组形状存在一些问题(请注意 np.reshape - 它可能会输出您预期之外的其他内容,我的谦虚建议是 - 除非您真的想重塑数组,否则不要使用它) .
    最终,反向传递应该如何工作:

    def backward_propagation(self, x, y, a1, a2):
        dz2 = (a2 - y) * (a2*(1-a2))
        dw2 = np.dot(a1.T, dz2) / len(a1)
        db2 = dz2/len(dz2)
        dz1 = np.dot(dz2, self.model_w2.T) * (a1 * (1 - a1))
        dw1 = np.dot(np.expand_dims(x, 1), dz1) / len(x)
        db1 = dz1/len(dz1)
        return dw2, db2, dw1, db1
    
  3. 最后,仅用正值初始化权重以形成均匀分布并不是最好的主意。一个好的起点是使用标准法线:

    def initialize_params(self, n_0, n_1, n_2):
        if len(self.model_w1) == 0:
            self.model_w1 = np.random.randn(n_0, n_1)
        if len(self.model_w2) == 0:
            self.model_w2 = np.random.randn(n_1, n_2)
        if len(self.model_b1) == 0:
            self.model_b1 = np.random.randn(1, n_1)
        if len(self.model_b2) == 0:
            self.model_b2 = np.random.randn(1, n_2)
    

您可以进一步尝试减少正态分布的标准偏差参数(但不要用零初始化!)。如果您想深入挖掘,请搜索 'Kaming He initialization' 和 'Xavier initialization'.

最后,您可以尝试看看开源深度学习库(即tensorflow或pytorch)的代码。一个人真的可以从中学到很多东西!下面是线性层如何在 torch 中实现的例子:torch.nn.modules.linear