MNIST上的神经网络——结果出乎意料



@IVlad给了我非常有用的反馈后,我试着修改我的代码,修改后的部分看起来像:

syn0 = (2*np.random.random((784,len(train_sample))) - 1)/8
syn1 = (2*np.random.random((len(train_sample),10)) - 1)/8

for i in xrange(10000):
    #forward propagation
    l0=train_sample
    l1=nonlin(np.dot(l0, syn0))
    l2=nonlin(np.dot(l1, syn1))
    #calculate error
    l2_error=train_tag_bool-l2
    if (i% 1000) == 0:
        print "Error:" + str(np.mean(np.abs(l2_error)))
    #apply sigmoid to the error 
    l2_delta = l2_error*nonlin(l2,deriv=True)
    l1_error = l2_delta.dot(syn1.T)
    l1_delta = l1_error * nonlin(l1,deriv=True)
    #update weights
    syn1 += alpha* (l1.T.dot(l2_delta) - beta*syn1)
    syn0 += alpha* (l0.T.dot(l1_delta) - beta*syn0)

注意标签(真标签)现在在<3000 x 10>的矩阵中,每一行是一个样本,十列描述每个样本代表的数字。(train_tag_bool,现在考虑一下它并不是布尔格式,所以命名有点糟糕,但为了讨论的缘故,我现在就保持这种方式。)

在这个项目中,我只在输入和输出层之间使用一个隐藏层,希望它足以完成这项工作。我已经应用了学习率和权重衰减,以及使初始权重更小。

我在计算错误率时使用了网站上的代码,即

np.mean(np.abs(l2_error))

,结果为0.1。我不知道该从这里拿什么。

同样,我进入了l2层(假设是给出预测的输出层),并且这些值都非常小(每个样本的最大值为10^-9,最小值可以达到10^-85)。虽然这只是在5次迭代之后,但我怀疑如果我运行1k或更多的循环,情况是否会有所不同。如果我返回每行的最大值,它总是第9个元素(代表数字'9'),这是完全错误的。

我又被这个问题难住了。溢出问题一直是我整个ML经验(当时是MATLAB,而不是Numpy)的最大挑战,我还没有找到一种方法来处理它.....

train_tag_bool代码:

train_tag_bool=np.array([[0]*10]*len(train_tag)).astype('float64')
for i in range(len(train_tag)):
    if train_tag[i]==0:
        train_tag_bool[i][0]=1
    elif train_tag[i]==1:
        train_tag_bool[i][1]=1
    elif train_tag[i]==2:
        train_tag_bool[i][2]=1
    elif train_tag[i]==3:
        train_tag_bool[i][3]=1
    elif train_tag[i]==4:
        train_tag_bool[i][4]=1
    elif train_tag[i]==5:
        train_tag_bool[i][5]=1
    elif train_tag[i]==6:
        train_tag_bool[i][6]=1
    elif train_tag[i]==7:
        train_tag_bool[i][7]=1
    elif train_tag[i]==8:
        train_tag_bool[i][8]=1
    elif train_tag[i]==9:
        train_tag_bool[i][9]=1
蛮力,我知道,但这是我现在最不关心的。结果是一个3000 x 10的矩阵,其中1对应于每个样本的数字。第一个元素表示数字0,最后一个元素表示9

交货。[0 0 0 0 0 0 1 0 0 0]表示6,[1 0 0 0 0 0 0 0 0]表示0,

原始代码:

import cPickle, gzip
import numpy as np
#from deeplearning.net
# Load the dataset
f = gzip.open('mnist.pkl.gz', 'rb')
train_set, valid_set, test_set = cPickle.load(f)
f.close()


#sigmoid function
def nonlin(x, deriv=False):
    if (deriv ==True):
        return x*(1-x)
    return 1/(1+np.exp(-x))
#seed random numbers to make calculation
#deterministic (just a good practice)
np.random.seed(1)


#need to decrease the sample size or else computer dies
train_sample=train_set[0][0:3000]
train_tag=train_set[1][0:3000]
train_tag=train_tag.reshape(len(train_tag), 1)
#train_set's dimension for the pixels are 50000(samples) x 784 (28x28 for each sample)
#therefore the coefficients should be 784x50000 to make the hidden layer 50k x 50k
syn0 = 2*np.random.random((784,len(train_sample))) - 1
syn1 = 2*np.random.random((len(train_sample),1)) - 1

for i in xrange(10000):
    #forward propagation
    l0=train_sample
    l1=nonlin(np.dot(l0, syn0))
    l2=nonlin(np.dot(l1, syn1))
    #calculate error
    l2_error=train_tag-l2
    if (i% 1000) == 0:
        print "Error:" + str(np.mean(np.abs(l2_error)))
    #apply sigmoid to the error 
    l2_delta = l2_error*nonlin(l2,deriv=True)
    l1_error = l2_delta.dot(syn1.T)
    l1_delta = l1_error * nonlin(l1,deriv=True)
    #update weights
    syn1 += l1.T.dot(l2_delta)
    syn0 += l0.T.dot(l1_delta)
参考:

http://iamtrask.github.io/2015/07/12/basic-python-network/

http://yann.lecun.com/exdb/mnist/

我目前不能运行代码,但是有一些事情很突出。我很惊讶它竟然能很好地解决博客上使用的玩具问题。

在我们开始之前,你需要更多的输出神经元:确切地说是10个。

syn1 = 2*np.random.random((len(train_sample), 10)) - 1

你的标签(y)最好是一个长度为10的数组,1在正确数字的位置,0在其他位置。

首先,在默认情况下,我总是尝试使用float64。这几乎不会改变任何事情,所以我不确定你是否应该养成这个习惯。可能不会。

第二,代码没有你可以设置的学习率。这意味着学习率是隐含的1,这对于你的问题来说是巨大的,人们使用0.01甚至更少。要添加一个学习率alpha,执行:

syn1 += alpha * l1.T.dot(l2_delta)
syn0 += alpha * l0.T.dot(l1_delta)

最多设置为0.01。为了获得最好的效果,你必须摆弄它。

第三,通常用较小的权重初始化网络会更好。[0, 1)可能太大了。试一试:

syn0 = (np.random.random((784,len(train_sample))) - 0.5) / 4
syn1 = (np.random.random((len(train_sample),1)) - 0.5) / 4

如果你感兴趣,你可以搜索更多的初始化方案,但我已经得到了不错的结果。

第四,正规化。最容易实现的可能是权重衰减。实现权重衰减lambda可以这样做:

syn1 += alpha * l1.T.dot(l2_delta) - alpha * lambda * syn1
syn0 += alpha * l0.T.dot(l1_delta) - alpha * lambda * syn0

常见值也为< 0.1甚至< 0.01

Dropout也有帮助,但在我看来,如果你刚开始学习,它更难实现和理解。它对更深的网络也更有用。所以也许把这个留到最后。

第五,也许还可以使用动量(在权重衰减链接中解释),这应该会减少网络的学习时间。还要调整迭代的次数:您不希望太多,但也不希望太少。

第六,查看输出层的softmax。

第七,查看tanh而不是当前的nonlin sigmoid函数。

如果您增量地应用这些,您应该开始获得一些有意义的结果。我认为正则化和较小的初始权重应该有助于解决溢出错误。

更新:

我已经像这样改变了代码。经过100次训练后,准确率达到84.79%。还不错,几乎没有做什么调整。

我添加了偏见神经元,动量,重量衰减,使用更少的隐藏单位(与你拥有的东西相比太慢了),更改为tanh功能和其他一些。

你应该可以从这里调整它。我使用Python 3.4,所以我必须更改一些东西才能使它运行,但这不是什么大的变化。

import pickle, gzip
import numpy as np
#from deeplearning.net
# Load the dataset
f = gzip.open('mnist.pkl.gz', 'rb')
train_set, valid_set, test_set = pickle.load(f, encoding='latin1')
f.close()


#sigmoid function
def nonlin(x, deriv=False):
    if (deriv ==True):
        return 1-x*x
    return np.tanh(x)
#seed random numbers to make calculation
#deterministic (just a good practice)
np.random.seed(1)
def make_proper_pairs_from_set(data_set):
    data_set_x, data_set_y = data_set
    data_set_y = np.eye(10)[:, data_set_y].T
    return data_set_x, data_set_y

train_x, train_y = make_proper_pairs_from_set(train_set)
train_x = train_x
train_y = train_y
test_x, test_y = make_proper_pairs_from_set(test_set)
print(len(train_y))
#train_set's dimension for the pixels are 50000(samples) x 784 (28x28 for each sample)
#therefore the coefficients should be 784x50000 to make the hidden layer 50k x 50k
# changed to 200 hidden neurons, should be plenty
syn0 = (2*np.random.random((785,200)) - 1) / 10
syn1 = (2*np.random.random((201,10)) - 1) / 10
velocities0 = np.zeros(syn0.shape)
velocities1 = np.zeros(syn1.shape)
alpha = 0.01
beta = 0.0001
momentum = 0.99
m = len(train_x) # number of training samples
# moved the forward propagation to a function and added bias neurons
def forward_prop(set_x, m):
    l0 = np.c_[np.ones((m, 1)), set_x]
    l1 = nonlin(np.dot(l0, syn0))
    l1 = np.c_[np.ones((m, 1)), l1]
    l2 = nonlin(np.dot(l1, syn1))

    return l0, l1, l2, l2.argmax(axis=1)
num_epochs = 100
for i in range(num_epochs):
    # forward propagation
    l0, l1, l2, _ = forward_prop(train_x, m)
    # calculate error
    l2_error = l2 - train_y

    print("Error " + str(i) + ": " + str(np.mean(np.abs(l2_error))))
    # apply sigmoid to the error 
    l2_delta = l2_error * nonlin(l2,deriv=True)
    l1_error = l2_delta.dot(syn1.T)
    l1_delta = l1_error * nonlin(l1,deriv=True)
    l1_delta = l1_delta[:, 1:]
    # update weights
    # divide gradients by the number of samples
    grad0 = l0.T.dot(l1_delta) / m
    grad1 = l1.T.dot(l2_delta) / m
    v0 = velocities0
    v1 = velocities1
    velocities0 = velocities0 * momentum - alpha * grad0
    velocities1 = velocities1 * momentum - alpha * grad1

    # divide regularization by number of samples
    # because L2 regularization reduces to this
    syn1 += -v1 * momentum + (1 + momentum) * velocities1 - alpha * beta * syn1 / m
    syn0 += -v0 * momentum + (1 + momentum) * velocities0 - alpha * beta * syn0 / m

# find accuracy on test set
predictions = []
corrects = []
for i in range(len(test_x)): # you can eliminate this loop too with a bit of work, but this part is very fast anyway
    _, _, _, rez = forward_prop([test_x[i, :]], 1)
    predictions.append(rez[0])
    corrects.append(test_y[i].argmax())
predictions = np.array(predictions)
corrects = np.array(corrects)
print(np.sum(predictions == corrects) / len(test_x))
更新2:

如果将学习率增加到0.05,将epoch增加到1000,则可以得到95.43%的准确率。

用当前时间播种随机数生成器,添加更多隐藏神经元(或隐藏层)和更多参数调整可以使这个简单的模型达到大约98%的精度AFAIK。问题是它训练起来太慢了。

而且,这种方法并不真正可靠。我优化了参数以提高测试集的准确性,所以我可能会过度拟合测试集。您应该使用交叉验证或验证集。

无论如何,正如您所看到的,没有溢出错误。如果您想更详细地讨论事情,请随时给我发电子邮件(地址在个人资料中)。

最新更新