第三章 改进神经网络的学习方式(中下)
權重初始化
創建了神經網絡后,我們需要進行權重和偏差的初始化。到現在,我們一直是根據在第一章中介紹的那樣進行初始化。提醒你一下,之前的方式就是根據獨立的均值為 $$0$$,標準差為 $$1$$ 的高斯隨機變量隨機采樣作為權重和偏差的初始值。這個方法工作的還不錯,但是非常 ad hoc,所以我們需要尋找一些更好的方式來設置我們網絡的初始化權重和偏差,這對于幫助網絡學習速度的提升很有價值。
結果表明,我們可以比使用正規化的高斯分布效果更好。為什么?假設我們使用一個很多的輸入神經元,比如說 $$1000$$。假設,我們已經使用正規化的高斯分布初始化了連接第一隱藏層的權重。現在我將注意力集中在這一層的連接權重上,忽略網絡其他部分:
我們為了簡化,假設,我們使用訓練樣本 x 其中一半的神經元值為 $$0$$,另一半為 $$1$$。下面的觀點也是可以更加廣泛地應用,但是你可以從特例中獲得背后的思想。讓我們考慮帶權和 $$z=\sum_j w_j x_j + b$$ 的隱藏元輸入。其中 $$500$$ 個項消去了,因為對應的輸入 $$x_j=0$$。所以 $$z$$ 是 $$501$$ 個正規化的高斯隨機變量的和,包含 $$500$$ 個權重項和額外的 $$1$$ 個偏差項。因此 $$z$$ 本身是一個均值為 $$0$$ 標準差為 $$\sqrt{501}\approx 22.4$$ 的分布。$$z$$ 其實有一個非常寬的高斯分布,不是非常尖的形狀:
尤其是,我們可以從這幅圖中看出 $$|z|$$ 會變得非常的大,比如說 $$z\gg1$$ 或者 $$z\ll 1$$。如果是這樣,輸出 $$\sigma(z)$$ 就會接近 $$1$$ 或者 $$0$$。也就表示我們的隱藏元會飽和。所以當出現這樣的情況時,在權重中進行微小的調整僅僅會給隱藏元的激活值帶來極其微弱的改變。而這種微弱的改變也會影響網絡中剩下的神經元,然后會帶來相應的代價函數的改變。結果就是,這些權重在我們進行梯度下降算法時會學習得非常緩慢。這其實和我們前面討論的問題差不多,前面的情況是輸出神經元在錯誤的值上飽和導致學習的下降。我們之前通過代價函數的選擇解決了前面的問題。不幸的是,盡管那種方式在輸出神經元上有效,但對于隱藏元的飽和卻一點作用都沒有。
我已經研究了第一隱藏層的權重輸入。當然,類似的論斷也對后面的隱藏層有效:如果權重也是用正規化的高斯分布進行初始化,那么激活值將會接近 $$0$$ 或者 $$1$$,學習速度也會相當緩慢。
還有可以幫助我們進行更好地初始化么,能夠避免這種類型的飽和,最終避免學習速度的下降?假設我們有一個有 $$n_{in}$$ 個輸入權重的神經元。我們會使用均值為 $$0$$ 標準差為 $$1/\sqrt{n_{in}}$$ 的高斯分布初始化這些權重。也就是說,我們會向下擠壓高斯分布,讓我們的神經元更不可能飽和。我們會繼續使用均值為 $$0$$ 標準差為 $$1$$ 的高斯分布來對偏差進行初始化,后面會告訴你原因。有了這些設定,帶權和 $$z=\sum_j w_j x_j + b$$ 仍然是一個均值為 $$0$$ 不過有很陡的峰頂的高斯分布。假設,我們有 $$500$$ 個值為 $$0$$ 的輸入和$$500$$ 個值為 $$1$$ 的輸入。那么很容證明 $$z$$ 是服從均值為 $$0$$ 標準差為 $$\sqrt{3/2} = 1.22$$ 的高斯分布。這圖像要比以前陡得多,所以即使我已經對橫坐標進行壓縮為了進行更直觀的比較:
這樣的一個神經元更不可能飽和,因此也不大可能遇到學習速度下降的問題。
練習
- 驗證 $$z=\sum_j w_j x_j + b$$ 標準差為 $$\sqrt{3/2}$$。下面兩點可能會有幫助:(a) 獨立隨機變量的和的方差是每個獨立隨即便方差的和;(b)方差是標準差的平方。
我在上面提到,我們使用同樣的方式對偏差進行初始化,就是使用均值為 $$0$$ 標準差為 $$1$$ 的高斯分布來對偏差進行初始化。這其實是可行的,因為這樣并不會讓我們的神經網絡更容易飽和。實際上,其實已經避免了飽和的問題的話,如何初始化偏差影響不大。有些人將所有的偏差初始化為 $$0$$,依賴梯度下降來學習合適的偏差。但是因為差別不是很大,我們后面還會按照前面的方式來進行初始化。
讓我們在 MNIST 數字分類任務上比較一下新舊兩種權重初始化方式。同樣,還是使用 $$30$$ 個隱藏元,minibatch 的大小為 $$30$$,規范化參數 $$\lambda=5.0$$,然后是交叉熵代價函數。我們將學習率從 $$\eta=0.5$$ 調整到 $$0.1$$,因為這樣會讓結果在圖像中表現得更加明顯。我們先使用舊的初始化方法訓練:
>>> import mnist_loader >>> training_data, validation_data, test_data = \ ... mnist_loader.load_data_wrapper() >>> import network2 >>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost) >>> net.large_weight_initializer() >>> net.SGD(training_data, 30, 10, 0.1, lmbda = 5.0, ... evaluation_data=validation_data, ... monitor_evaluation_accuracy=True)我們也使用新方法來進行權重的初始化。這實際上還要更簡單,因為 network2's 默認方式就是使用新的方法。這意味著我們可以丟掉 net.large_weight_initializer() 調用:
>>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost) >>> net.SGD(training_data, 30, 10, 0.1, lmbda = 5.0, ... evaluation_data=validation_data, ... monitor_evaluation_accuracy=True)將結果用圖展示出來,就是:
兩種情形下,我們在 96% 的準確度上重合了。最終的分類準確度幾乎完全一樣。但是新的初始化技術帶來了速度的提升。在第一種初始化方式的分類準確度在 87% 一下,而新的方法已經幾乎達到了 93%。看起來的情況就是我們新的關于權重初始化的方式將訓練帶到了一個新的境界,讓我們能夠更加快速地得到好的結果。同樣的情況在 $$100$$ 個神經元的設定中也出現了:
在這個情況下,兩個曲線并沒有重合。然而,我做的實驗發現了其實就在一些額外的回合后(這里沒有展示)準確度其實也是幾乎相同的。所以,基于這些實驗,看起來提升的權重初始化僅僅會加快訓練,不會改變網絡的性能。然而,在第四張,我們會看到一些例子里面使用 $$1/\sqrt{n_{in}}$$ 權重初始化的長期運行的結果要顯著更優。因此,不僅僅能夠帶來訓練速度的加快,有時候在最終性能上也有很大的提升。
$$1/\sqrt{n_{in}}$$ 的權重初始化方法幫助我們提升了神經網絡學習的方式。其他的權重初始化技術同樣也有,很多都是基于這個基本的思想。我不會在這里給出其他的方法,因為 $$1/\sqrt{n_{in}}$$ 已經可以工作得很好了。如果你對另外的思想感興趣,我推薦你看看在 $$2012$$ 年的 Yoshua Bengio 的論文的 $$14$$ 和 $$15$$ 頁,以及相關的參考文獻。
Practical Recommendations for Gradient-Based Training of Deep Architectures, by Yoshua Bengio (2012).
問題
- 將規范化和改進的權重初始化方法結合使用 L2 規范化有時候會自動給我們一些類似于新的初始化方法的東西。假設我們使用舊的初始化權重的方法。考慮一個啟發式的觀點:(1)假設$$\lambda$$ 不太小,訓練的第一回合將會幾乎被權重下降統治。;(2)如果 $$\eta\lambda \ll n$$,權重會按照因子 $$exp(-\eta\lambda/m)$$ 每回合下降;(3)假設 $$\lambda$$ 不太大,權重下降會在權重降到 $$1/\sqrt{n}$$ 的時候保持住,其中 $$n$$ 是網絡中權重的個數。用論述這些條件都已經滿足本節給出的例子。
再看手寫識別問題:代碼
讓我們實現本章討論過的這些想法。我們將寫出一個新的程序,network2.py,這是一個對第一章中開發的 network.py 的改進版本。如果你沒有仔細看過 network.py,那你可能會需要重讀前面關于這段代碼的討論。僅僅 $$74$$ 行代碼,也很易懂。
和 network.py 一樣,主要部分就是 Network 類了,我們用這個來表示神經網絡。使用一個 sizes 的列表來對每個對應層進行初始化,默認使用交叉熵作為代價 cost 參數:
class Network(object):def __init__(self, sizes, cost=CrossEntropyCost):self.num_layers = len(sizes)self.sizes = sizesself.default_weight_initializer()self.cost=cost__init__ 方法的和 network.py 中一樣,可以輕易弄懂。但是下面兩行是新的,我們需要知道他們到底做了什么。
我們先看看 default_weight_initializer 方法,使用了我們新式改進后的初始化權重方法。如我們已經看到的,使用了均值為 $$0$$ 而標準差為 $$1/\sqrt{n}$$,$$n$$ 為對應的輸入連接個數。我們使用均值為 $$0$$ 而標準差為 $$1$$ 的高斯分布來初始化偏差。下面是代碼:
def default_weight_initializer(self):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:])]為了理解這段代碼,需要知道 np 就是進行線性代數運算的 Numpy 庫。我們在程序的開頭會 import Numpy。同樣我們沒有對第一層的神經元的偏差進行初始化。因為第一層其實是輸入層,所以不需要引入任何的偏差。我們在 network.py 中做了完全一樣的事情。
作為 default_weight_initializer 的補充,我們同樣包含了一個 large_weight_initializer 方法。這個方法使用了第一章中的觀點初始化了權重和偏差。代碼也就僅僅是和default_weight_initializer差了一點點了:
def large_weight_initializer(self):self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]self.weights = [np.random.randn(y, x) for x, y in zip(self.sizes[:-1], self.sizes[1:])]我將 larger_weight_initializer 方法包含進來的原因也就是使得跟第一章的結果更容易比較。我并沒有考慮太多的推薦使用這個方法的實際情景。
初始化方法 __init__ 中的第二個新的東西就是我們初始化了 cost 屬性。為了理解這個工作的原理,讓我們看一下用來表示交叉熵代價的類:
class CrossEntropyCost(object): @staticmethoddef fn(a, y):return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a))) @staticmethoddef delta(z, a, y):return (a-y)讓我們分解一下。第一個看到的是:即使使用的是交叉熵,數學上看,就是一個函數,這里我們用 Python 的類而不是 Python 函數實現了它。為什么這樣做呢?答案就是代價函數在我們的網絡中扮演了兩種不同的角色。明顯的角色就是代價是輸出激活值 $$a$$ 和目標輸出 $$y$$ 差距優劣的度量。這個角色通過 CrossEntropyCost.fn 方法來扮演。(注意,np.nan_to_num 調用確保了 Numpy 正確處理接近 $$0$$ 的對數值)但是代價函數其實還有另一個角色。回想第二章中運行反向傳播算法時,我們需要計算網絡輸出誤差,$$\delta^L$$。這種形式的輸出誤差依賴于代價函數的選擇:不同的代價函數,輸出誤差的形式就不同。對于交叉熵函數,輸出誤差就如公式(66)所示:
所以,我們定義了第二個方法,CrossEntropyCost.delta,目的就是讓網絡知道如何進行輸出誤差的計算。然后我們將這兩個組合在一個包含所有需要知道的有關代價函數信息的類中。
類似地,network2.py 還包含了一個表示二次代價函數的類。這個是用來和第一章的結果進行對比的,因為后面我們幾乎都在使用交叉函數。代碼如下。QuadraticCost.fn 方法是關于網絡輸出 $$a$$ 和目標輸出 $$y$$ 的二次代價函數的直接計算結果。由 QuadraticCost.delta 返回的值就是二次代價函數的誤差。
class QuadraticCost(object): @staticmethoddef fn(a, y):return 0.5*np.linalg.norm(a-y)**2 @staticmethoddef delta(z, a, y):return (a-y) * sigmoid_prime(z)現在,我們理解了 network2.py 和 network.py 兩個實現之間的主要差別。都是很簡單的東西。還有一些更小的變動,下面我們會進行介紹,包含 L2 規范化的實現。在講述規范化之前,我們看看 network2.py 完整的實現代碼。你不需要太仔細地讀遍這些代碼,但是對整個結構尤其是文檔中的內容的理解是非常重要的,這樣,你就可以理解每段程序所做的工作。當然,你也可以隨自己意愿去深入研究!如果你迷失了理解,那么請讀讀下面的講解,然后再回到代碼中。不多說了,給代碼:
"""network2.py ~~~~~~~~~~~~~~An improved version of network.py, implementing the stochastic gradient descent learning algorithm for a feedforward neural network. Improvements include the addition of the cross-entropy cost function, regularization, and better initialization of network weights. Note that I have focused on making the code simple, easily readable, and easily modifiable. It is not optimized, and omits many desirable features."""#### Libraries # Standard library import json import random import sys# Third-party libraries import numpy as np#### Define the quadratic and cross-entropy cost functionsclass QuadraticCost(object): @staticmethoddef fn(a, y):"""Return the cost associated with an output ``a`` and desired output``y``."""return 0.5*np.linalg.norm(a-y)**2 @staticmethoddef delta(z, a, y):"""Return the error delta from the output layer."""return (a-y) * sigmoid_prime(z)class CrossEntropyCost(object): @staticmethoddef fn(a, y):"""Return the cost associated with an output ``a`` and desired output``y``. Note that np.nan_to_num is used to ensure numericalstability. In particular, if both ``a`` and ``y`` have a 1.0in the same slot, then the expression (1-y)*np.log(1-a)returns nan. The np.nan_to_num ensures that that is convertedto the correct value (0.0)."""return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a))) @staticmethoddef delta(z, a, y):"""Return the error delta from the output layer. Note that theparameter ``z`` is not used by the method. It is included inthe method's parameters in order to make the interfaceconsistent with the delta method for other cost classes."""return (a-y)#### Main Network class class Network(object):def __init__(self, sizes, cost=CrossEntropyCost):"""The list ``sizes`` contains the number of neurons in the respectivelayers of the network. For example, if the list was [2, 3, 1]then it would be a three-layer network, with the first layercontaining 2 neurons, the second layer 3 neurons, and thethird layer 1 neuron. The biases and weights for the networkare initialized randomly, using``self.default_weight_initializer`` (see docstring for thatmethod)."""self.num_layers = len(sizes)self.sizes = sizesself.default_weight_initializer()self.cost=costdef default_weight_initializer(self):"""Initialize each weight using a Gaussian distribution with mean 0and standard deviation 1 over the square root of the number ofweights connecting to the same neuron. Initialize the biasesusing a Gaussian distribution with mean 0 and standarddeviation 1.Note that the first layer is assumed to be an input layer, andby convention we won't set any biases for those neurons, sincebiases are only ever used in computing the outputs from laterlayers."""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:])]def large_weight_initializer(self):"""Initialize the weights using a Gaussian distribution with mean 0and standard deviation 1. Initialize the biases using aGaussian distribution with mean 0 and standard deviation 1.Note that the first layer is assumed to be an input layer, andby convention we won't set any biases for those neurons, sincebiases are only ever used in computing the outputs from laterlayers.This weight and bias initializer uses the same approach as inChapter 1, and is included for purposes of comparison. Itwill usually be better to use the default weight initializerinstead."""self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]self.weights = [np.random.randn(y, x)for x, y in zip(self.sizes[:-1], self.sizes[1:])]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 adef SGD(self, training_data, epochs, mini_batch_size, eta,lmbda = 0.0,evaluation_data=None,monitor_evaluation_cost=False,monitor_evaluation_accuracy=False,monitor_training_cost=False,monitor_training_accuracy=False):"""Train the neural network using mini-batch stochastic gradientdescent. The ``training_data`` is a list of tuples ``(x, y)``representing the training inputs and the desired outputs. Theother non-optional parameters are self-explanatory, as is theregularization parameter ``lmbda``. The method also accepts``evaluation_data``, usually either the validation or testdata. We can monitor the cost and accuracy on either theevaluation data or the training data, by setting theappropriate flags. The method returns a tuple containing fourlists: the (per-epoch) costs on the evaluation data, theaccuracies on the evaluation data, the costs on the trainingdata, and the accuracies on the training data. All values areevaluated at the end of each training epoch. So, for example,if we train for 30 epochs, then the first element of the tuplewill be a 30-element list containing the cost on theevaluation data at the end of each epoch. Note that the listsare empty if the corresponding flag is not set."""if evaluation_data: n_data = len(evaluation_data)n = len(training_data)evaluation_cost, evaluation_accuracy = [], []training_cost, training_accuracy = [], []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" % jif monitor_training_cost:cost = self.total_cost(training_data, lmbda)training_cost.append(cost)print "Cost on training data: {}".format(cost)if monitor_training_accuracy:accuracy = self.accuracy(training_data, convert=True)training_accuracy.append(accuracy)print "Accuracy on training data: {} / {}".format(accuracy, n)if monitor_evaluation_cost:cost = self.total_cost(evaluation_data, lmbda, convert=True)evaluation_cost.append(cost)print "Cost on evaluation data: {}".format(cost)if monitor_evaluation_accuracy:accuracy = self.accuracy(evaluation_data)evaluation_accuracy.append(accuracy)print "Accuracy on evaluation data: {} / {}".format(self.accuracy(evaluation_data), n_data)printreturn evaluation_cost, evaluation_accuracy, \training_cost, training_accuracydef update_mini_batch(self, mini_batch, eta, lmbda, n):"""Update the network's weights and biases by applying gradientdescent using backpropagation to a single mini batch. The``mini_batch`` is a list of tuples ``(x, y)``, ``eta`` is thelearning rate, ``lmbda`` is the regularization parameter, and``n`` is the total size of the training data set."""nabla_b = [np.zeros(b.shape) for b in self.biases]nabla_w = [np.zeros(w.shape) for w in self.weights]for x, y in mini_batch:delta_nabla_b, delta_nabla_w = self.backprop(x, y)nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]self.weights = [(1-eta*(lmbda/n))*w-(eta/len(mini_batch))*nwfor w, nw in zip(self.weights, nabla_w)]self.biases = [b-(eta/len(mini_batch))*nbfor b, nb in zip(self.biases, nabla_b)]def backprop(self, x, y):"""Return a tuple ``(nabla_b, nabla_w)`` representing thegradient for the cost function C_x. ``nabla_b`` and``nabla_w`` are layer-by-layer lists of numpy arrays, similarto ``self.biases`` and ``self.weights``."""nabla_b = [np.zeros(b.shape) for b in self.biases]nabla_w = [np.zeros(w.shape) for w in self.weights]# feedforwardactivation = xactivations = [x] # list to store all the activations, layer by layerzs = [] # list to store all the z vectors, layer by layerfor b, w in zip(self.biases, self.weights):z = np.dot(w, activation)+bzs.append(z)activation = sigmoid(z)activations.append(activation)# backward passdelta = (self.cost).delta(zs[-1], activations[-1], y)nabla_b[-1] = deltanabla_w[-1] = np.dot(delta, activations[-2].transpose())# Note that the variable l in the loop below is used a little# differently to the notation in Chapter 2 of the book. Here,# l = 1 means the last layer of neurons, l = 2 is the# second-last layer, and so on. It's a renumbering of the# scheme in the book, used here to take advantage of the fact# that Python can use negative indices in lists.for l in xrange(2, self.num_layers):z = zs[-l]sp = sigmoid_prime(z)delta = np.dot(self.weights[-l+1].transpose(), delta) * spnabla_b[-l] = deltanabla_w[-l] = np.dot(delta, activations[-l-1].transpose())return (nabla_b, nabla_w)def accuracy(self, data, convert=False):"""Return the number of inputs in ``data`` for which the neuralnetwork outputs the correct result. The neural network'soutput is assumed to be the index of whichever neuron in thefinal layer has the highest activation.The flag ``convert`` should be set to False if the data set isvalidation or test data (the usual case), and to True if thedata set is the training data. The need for this flag arisesdue to differences in the way the results ``y`` arerepresented in the different data sets. In particular, itflags whether we need to convert between the differentrepresentations. It may seem strange to use differentrepresentations for the different data sets. Why not use thesame representation for all three data sets? It's done forefficiency reasons -- the program usually evaluates the coston the training data and the accuracy on other data sets.These are different types of computations, and using differentrepresentations speeds things up. More details on therepresentations can be found inmnist_loader.load_data_wrapper."""if convert:results = [(np.argmax(self.feedforward(x)), np.argmax(y))for (x, y) in data]else:results = [(np.argmax(self.feedforward(x)), y)for (x, y) in data]return sum(int(x == y) for (x, y) in results)def total_cost(self, data, lmbda, convert=False):"""Return the total cost for the data set ``data``. The flag``convert`` should be set to False if the data set is thetraining data (the usual case), and to True if the data set isthe validation or test data. See comments on the similar (butreversed) convention for the ``accuracy`` method, above."""cost = 0.0for x, y in data:a = self.feedforward(x)if convert: y = vectorized_result(y)cost += self.cost.fn(a, y)/len(data)cost += 0.5*(lmbda/len(data))*sum(np.linalg.norm(w)**2 for w in self.weights)return costdef save(self, filename):"""Save the neural network to the file ``filename``."""data = {"sizes": self.sizes,"weights": [w.tolist() for w in self.weights],"biases": [b.tolist() for b in self.biases],"cost": str(self.cost.__name__)}f = open(filename, "w")json.dump(data, f)f.close()#### Loading a Network def load(filename):"""Load a neural network from the file ``filename``. Returns aninstance of Network."""f = open(filename, "r")data = json.load(f)f.close()cost = getattr(sys.modules[__name__], data["cost"])net = Network(data["sizes"], cost=cost)net.weights = [np.array(w) for w in data["weights"]]net.biases = [np.array(b) for b in data["biases"]]return net#### Miscellaneous functions def vectorized_result(j):"""Return a 10-dimensional unit vector with a 1.0 in the j'th positionand zeroes elsewhere. This is used to convert a digit (0...9)into a corresponding desired output from the neural network."""e = np.zeros((10, 1))e[j] = 1.0return edef sigmoid(z):"""The sigmoid function."""return 1.0/(1.0+np.exp(-z))def sigmoid_prime(z):"""Derivative of the sigmoid function."""return sigmoid(z)*(1-sigmoid(z))有個更加有趣的變動就是在代碼中增加了 L2 規范化。盡管這是一個主要的概念上的變動,在實現中其實相當簡單。對大部分情況,僅僅需要傳遞參數 lmbda 到不同的方法中,主要是 Network.SGD 方法。實際上的工作就是一行代碼的事在 Network.update_mini_batch 的倒數第四行。這就是我們改動梯度下降規則來進行權重下降的地方。盡管改動很小,但其對結果影響卻很大!
其實這種情況在神經網絡中實現一些新技術的常見現象。我們花費了近千字的篇幅來討論規范化。概念的理解非常微妙困難。但是添加到程序中的時候卻如此簡單。精妙復雜的技術可以通過微小的代碼改動就可以實現了。
另一個微小卻重要的改動是隨機梯度下降方法的幾個標志位的增加。這些標志位讓我們可以對在代價和準確度的監控變得可能。這些標志位默認是 False 的,但是在我們例子中,已經被置為 True 來監控 Network 的性能。另外,network2.py 中的 Network.SGD 方法返回了一個四元組來表示監控的結果。我們可以這樣使用:
>>> evaluation_cost, evaluation_accuracy, ... training_cost, training_accuracy = net.SGD(training_data, 30, 10, 0.5, ... lmbda = 5.0, ... evaluation_data=validation_data, ... monitor_evaluation_accuracy=True, ... monitor_evaluation_cost=True, ... monitor_training_accuracy=True, ... monitor_training_cost=True)所以,比如 evaluation_cost 將會是一個 $$30$$ 個元素的列表其中包含了每個回合在驗證集合上的代價函數值。這種類型的信息在理解網絡行為的過程中特別有用。比如,它可以用來畫出展示網絡隨時間學習的狀態。其實,這也是我在前面的章節中展示性能的方式。然而要注意的是如果任何標志位都沒有設置的話,對應的元組中的元素就是空列表。
另一個增加項就是在 Network.save 方法中的代碼,用來將 Network 對象保存在磁盤上,還有一個載回內存的函數。這兩個方法都是使用 JSON 進行的,而非 Python 的 pickle 或者 cPickle 模塊——這些通常是 Python 中常見的保存和裝載對象的方法。使用 JSON 的原因是,假設在未來某天,我們想改變 Network 類來允許非 sigmoid 的神經元。對這個改變的實現,我們最可能是改變在 Network.__init__ 方法中定義的屬性。如果我們簡單地 pickle 對象,會導致 load 函數出錯。使用 JSON 進行序列化可以顯式地讓老的 Network 仍然能夠 load。
其他也還有一些微小的變動。但是那些只是 network.py 的微調。結果就是把程序從 $$74$$ 行增長到了 $$152$$ 行。
問題
- 更改上面的代碼來實現 L1 規范化,使用 L1 規范化使用 $$30$$ 個隱藏元的神經網絡對 MNIST 數字進行分類。你能夠找到一個規范化參數使得比無規范化效果更好么?
- 看看 network.py 中的 Network.cost_derivative 方法。這個方法是為二次代價函數寫的。怎樣修改可以用于交叉熵代價函數上?你能不能想到可能在交叉熵函數上遇到的問題?在 network2.py 中,我們已經去掉了 Network.cost_derivative 方法,將其集成進了 CrossEntropyCost.delta 方法中。請問,這樣是如何解決你已經發現的問題的?
文/Not_GOD(簡書作者)
原文鏈接:http://www.jianshu.com/p/c43198f5dae1
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”Not_GOD。
總結
以上是生活随笔為你收集整理的第三章 改进神经网络的学习方式(中下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第三章 改进神经网络的学习方式(上)
- 下一篇: 第二章 反向传播算法如何工作的?