@cleardusk
2015-11-27T19:39:08.000000Z
字数 4649
阅读 1622
GjzCVCode
仍然是 IO 部分,上一篇针对的是只有一个隐藏层的网络,这本在线书后两章介绍了 DNN,以及其中一种 model: ConvNet,本书中的 ConvNet 的 Python 实现与之前的有一个不同之处:Thenao 的使用。
Theano 可以借助 GPU 加速,在读取数据时多了一个将数据 copy 到 GPU 的过程,就是下面的 shared() 函数,这里有一些细节:
return shared_x, T.cast(shared_y, "int32")
以上这行代码的返回值是 tuple 形式,T.cast 应该是将 shared_y,就是表示 digit的 10 维向量转换为 int32 datatype,但 GPU 运算的时候还是使用 float32 格式。Theano 调用 GPU,只需调用 Theano.shared() 函数,很方便,当然,底层是通过 cuda 来运作的(具体的机制不清楚,只能靠大概的猜测,Theano 调用 cuda 库的函数)。自己的 mint 系统装不了 cuda,另一个 ubuntu 系统上还没花时间配置 theano 等库。目前只用 CPU 跑。
def load_data_shared(filename="../data/mnist.pkl.gz"):
f = gzip.open(filename, 'rb')
training_data, validation_data, test_data = cPickle.load(f)
f.close()
def shared(data):
"""Place the data into shared variables. This allows Theano to copy
the data to the GPU, if one is available.
"""
shared_x = theano.shared(
np.asarray(data[0], dtype=theano.config.floatX), borrow=True)
shared_y = theano.shared(
np.asarray(data[1], dtype=theano.config.floatX), borrow=True)
return shared_x, T.cast(shared_y, "int32")
return [shared(training_data), shared(validation_data), shared(test_data)]
仔细看了下代码,要开启 GPU mode,配置如下
theano.config.device = 'gpu'
IO 部分应该就是这么多内容,关于 Theano 写的很少,因为还没花时间研究。接下来会写网络具体实现的部分。
关于单个隐藏层的 NN (不知道用什么 term 来描述)的实现,我先花一点时间笼统地总结一下这个,然后总结下 CNN ,CNN 是以 NN 为基础的,如果我把这些全忘了(十年内应该不会全忘),我可能会先从基础部分的 NN 看起。
我想带着问题,对源码进行通读是个不错的选择。假设已经对 NN 有了一个大概的概念,比如知道 [input|hideen|output]layer, neuron(unit), weights, biases, activation function 这些词的意思。
Python 是 OO (面向对象)的,初始化网络的部分当然要放在 contruction (构造函数)部分中。
首先是网络的规模,由一个 list 中的变量表示,784×100×10 就用
sizes = [784, 100, 10]
表示,网络的规模是可调的,隐藏层的 unit
个数一般多多益善,但会增长训练的时间。
self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
self.weights = [np.random.randn(y, x)/np.sqrt(x)
for x, y in zip(self.sizes[:-1], self.sizes[1:])]
接下来是 weights, biases 的初始值问题,上面这段代码用的是高斯分布
网络的前向计算过程容易理解,但是在 Python 处理的时候,要特别注意整个符号体系以及下标。为了简化代码,乘积运算用的都是矩阵或向量的形式。代码很短。
def feedforward(self, a):
"""Return the output of the network if ``a`` is input."""
for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w, a)+b)
return a
调参似乎不涉及到 feedfoward 过程,在这个过程能下功夫的就是矩阵运算的优化了,Python 的 numpy 有一些优化,不过有专门处理 Linear Algebra 的库,比如 BLAS 等库,我曾在 C++ 中简单地用过 Armadillo 库。不过在训练过程中,运算的优化起到明显效果的,针对的应该不是 feedfoward,而是 backpropagate 过程。当然,拿一个训练好的模型来用的话,矩阵运算优化对 feedfoward 就很重要了,因为直接决定了效率。
网络的训练其实是 weights 和 biases 的调整过程,BP 算法提供梯度计算的方法,Stochastic Gradient Descent 算法(有变种,如 Hessian,Momemtum-based,我还没研究)其实是对求目标函数最小值算法的复杂度的一个优化(有点绕)。具体说一下,
但是 BP 算法是计算这个目标函数的梯度。
具体的训练过程,可以参考这个:BP 算法以及 BP+SGD 算法描述。这是核心的算法。关于 BP、SGD 的算法的推导证明的在此忽略(SGD 我还具体去看)。
至于代码部分,先定位到 SGD() 函数,我删除了部分代码,只留下核心算法。
def SGD(self, training_data, epochs, mini_batch_size, etalmbda = 0.0):
n = len(training_data)
for j in xrange(epochs):
random.shuffle(training_data)
mini_batches = [
training_data[k:k+mini_batch_size]
for k in xrange(0, n, mini_batch_size)]
for mini_batch in mini_batches:
self.update_mini_batch(
mini_batch, eta, lmbda, len(training_data))
print "Epoch %s training complete" % j
random.shuffle 随机排列 list 数据,即 training_data(是一个list),mini_batches list 存储了 mini_batch_size × mini_batch_size 张 images,,即 inputs。每一个 mini_batch 作为 update_mini_batch() 函数的参数,update_mini_batch() 会调用 backprop() 函数计算每一个 input
训练过程中涉及到的参数比较多,可调的参数,可能涉及的 trick,加上之前的,把我能想到的都列在这。
其中,数 regularization 和 dropout 比较黑科技,expand training
data 比较有趣。另外,书中有相当长的篇幅是介绍如何调参的,hyper parameter。