Theano3.2-练习之数据集及目标函数介绍
來自http://deeplearning.net/tutorial/gettingstarted.html#gettingstarted
一、下載
? ?在后續的每個學習算法上,都需要下載對應的文檔,如果想要一次全部下好,那么可以復制git上面的這個教程的資料:
git clone git://github.com/lisa-lab/DeepLearningTutorials.git二、數據集
? MNIST 數據集(mnist.pkl.gz)(現在這個數據集除了教學,好像已經沒什么人關注了)
? ? 這個MNIST?數據集包含的是手寫數字圖像,其中有6w張訓練樣本和1w張測試樣本,不過在幾乎許多論文和本教程中,都是將這6w張訓練樣本劃分成5w張訓練樣本和1w張驗證集樣本,所有的圖片都已經中心化而且是固定的大小28×28,其中是灰度圖,白色為255,黑色為0.為了方面本教程,是需要在python下使用的,可以下載?here.也就是已經劃分好了三個list:訓練集、驗證集和測試集。每一個list都是由圖像和相應的標簽組成的。其中圖像是numpy的784(28*28)的一維數組(就是把2維的圖像拉成一條向量),標簽則是一個0-9之間的數字。下面的代碼演示了如何使用這個數據集:
import cPickle, gzip, numpy# Load the dataset f = gzip.open('mnist.pkl.gz', 'rb') train_set, valid_set, test_set = cPickle.load(f) f.close()? ? ? ? 當使用這個數據集的時候,通常是將它劃分成minibatches(?Stochastic Gradient Descent)(還有http://blog.csdn.net/shouhuxianjian/article/details/41040245,這是hinton的視頻的第6課)。我們建議你可以將這個數據集放入到共享變量(shared variables)中,并通過基于minibatch索引、給定一個固定和已知的batch size來訪問它。這樣做的好處就是共享變量之后可以用在gpu上,因為當將數據復制到GPU內存上的時候會有較大的開銷,如果按照代碼的執行(每個minibatch都是獨立的)來進行傳輸數據的時候,如果不是用共享變量的方法,結果反而比只使用CPU的速度還慢。如果使用Theano 共享變量,那么就是讓Theano將整個數據在共享變量構造的時候通過一個單一的調用都復制到GPU上。之后,GPU可以通過在這個共享變量上進行切片slice來訪問任何minibatch,而不需要從cpu的內存上復制到GPU上,所以避免了很多的數據傳輸的開銷。因為這些數據點和他們的標簽通常都是不同的(標簽通常是整數,而數據點通常是實數),我們建議使用不同的變量來表示數據和標簽。同樣我們推薦使用對這三個不同的集合也采用不同的變量來使得代碼更具有可讀性(會生成6個不同的共享變量)。因為當前的數據是一個變量,而且一個minibatch可以被定為這個變量的一個切片,所以很自然的可以通過指定索引和尺寸來定義一個minibatch。在我們的步驟中,batch size在代碼執行過程中?一直是一個常量,所以一個函數實際上需要的只是索引來指定哪個數據點被使用了。下面的代碼來表示如何存儲數據并且如何訪問一個minibatch:
def shared_dataset(data_xy):""" Function that loads the dataset into shared variablesThe reason we store our dataset in shared variables is to allowTheano to copy it into the GPU memory (when code is run on GPU).Since copying data into the GPU is slow, copying a minibatch everytimeis needed (the default behaviour if the data is not in a sharedvariable) would lead to a large decrease in performance."""data_x, data_y = data_xyshared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX))#因為GPU只接受float類型shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX))#因為GPU只接受float類型# When storing data on the GPU it has to be stored as floats# therefore we will store the labels as ``floatX`` as well# (``shared_y`` does exactly that). But during our computations# we need them as ints (we use labels as index, and if they are# floats it doesn't make sense) therefore instead of returning# ``shared_y`` we will have to cast it to int. This little hack# lets us get around this issuereturn shared_x, T.cast(shared_y, 'int32') #返回的時候強制標簽為int類型test_set_x, test_set_y = shared_dataset(test_set) valid_set_x, valid_set_y = shared_dataset(valid_set) train_set_x, train_set_y = shared_dataset(train_set)batch_size = 500 # size of the minibatch# accessing the third minibatch of the training set 訪問訓練集的第三個minibatchdata = train_set_x[2 * 500: 3 * 500] label = train_set_y[2 * 500: 3 * 500]? ? ? ? 在GPU上存儲的數據只能是floats類型的,(存儲在GPu上時,右邊的dtype被賦值為theano.config.floatX).為了繞過這個標簽上的問題,通過將其存儲為float,然后返回的時候強制為int類型。
note:如果你想要在GPU上運行代碼,而你使用的數據集太大而無法放入GPU內存中,這種情況下,你可能會將數據存儲到一個共享變量中。然而你可以存儲一個足夠小的數據塊(幾個minibatches)放到一個共享變量中,然后使用這個來進行訓練,當這個數據塊訓練完成之后,更新存儲的數據塊換下一部分。這個方法是為了最小化CPU和GPU之間數據傳輸的次數的折衷方法。
三、符號
?數據集符號
? ? 我們將數據集表示成,當需要區別對待的時候,將訓練集,驗證集和測試集表示成::?,??和。驗證集是用來執行模型選取和超參數選擇的,而且測試集是用來驗證最后的 泛化誤差和以無偏的方式來對比不同的算法。該教程基本上處理的是分類問題,這里每個數據集是有關?對的索引集合,使用上標來區分不同的訓練集樣本:?是第 i 個維度為的訓練樣本。相似的,是第 i 個指派給輸入的標簽。這樣就可以簡單的擴展這些例子,使得能夠有其他類型(例如,高斯回歸,或者能夠預測多個符號的多項式組(也就是多分類))。
數學約定
- : 大寫符號用來表示一個矩陣,除非有其他的特指?
- : 矩陣的第i 行第j 列的元素
- : 向量, 表示矩陣第i 行
- : 向量, 表示矩陣第j 列
- : 小寫符號用來表示一個向量,除非有其他的特指
- : 向量的第i 個元素
符號和函數的列表
- : 輸入的維度數.
- : 第i 層隱藏單元的個數.
- ,?: 和模型?相關的分類函數, 定義成?. 注意到這里通常將下標?丟棄掉.
- L:標簽的個數.
- : 由參數定義模型?的的log似然?.
- ?在數據集上由參數決定的預測函數 f 的期望損失.
- NLL: 負似然log(negative log-likelihood,NLL)
- : 對于一個給定模型的所有參數集合
python的命名空間
本教程的代碼通常使用下面的命名空間:
import theano import theano.tensor as T import numpy
四、在DL上的有監督優化的入門
深度學習最讓人興奮的主要是利用深網絡的無監督學習。不過有監督學習同樣也扮演著很重要的角色。無監督預訓練的使用通常是基于當使用有監督微調之后獲得效果的基礎上來進行評估的。本部分回顧下作為分類模型的有監督的基礎部分,然后介紹下作為在許多dl教程中模型上使用的微調的minibatch隨機梯度下降算法。詳細的可以看看introductory course notes on gradient-based learning?,來了解更多關于使用梯度來優化訓練標準的基本概念。
學習一個分類器
zero-one 損失
? ? 本教程中呈現的模型大多是用來做分類的。訓練一個分類器的目標是為了最小化在不可見樣本上的誤差(0-1損失)的數量。如果?是預測函數,那么損失函數可以寫成:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
這里是?表示的是訓練集合(在訓練的時候)或者?(為了避免驗證集或者測試誤差的有偏估計)。?是指示函數,可以被定義成:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
在這個教程中,f 被定義成:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
在python中,使用Theano的話,可以寫成如下形式:
# zero_one_loss is a Theano variable representing a symbolic # expression of the zero one loss ; to get the actual value this # symbolic expression has to be compiled into a Theano function (see # the Theano tutorial for more details) zero_one_loss = T.sum(T.neq(T.argmax(p_y_given_x), y))
負log似然損失
? ? 因為0-1損失不能微分,對于大型模型(成千上百萬的參數)的優化來說,代價是非常高昂的(計算量)。所以我們在訓練集給定所有的標簽的基礎上讓我們的分類器的log似然最大化:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? 正確類別的似然和正確預測的數量是不相同的,不過從一個隨機初始化的分類器的觀點上看,它們相當接近。不過提醒下,0-1損失和似然是不同的目標;你需要看見它們是在驗證集上是正相關的,不過有時候卻是負相關的。因為我們通常說要最小化一個損失函數,所以學習其實就是為了最小化這個負log似然函數,定義為:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
我們分類器的NLL是可微分的,所以可以用來代替0-1損失,而且我們在基于訓練數據上使用這個函數的梯度作為 一個分類器的dl 有監督學習信號(其實就是用梯度來訓練分類器的意思,我直譯的而已)。這可以通過使用下面的代碼來計算得到:
# NLL is a symbolic variable ; to get the actual value of NLL, this symbolic # expression has to be compiled into a Theano function (see the Theano # tutorial for more details) NLL = -T.sum(T.log(p_y_given_x)[T.arange(y.shape[0]), y]) # note on syntax: T.arange(y.shape[0]) is a vector of integers [0,1,2,...,len(y)]. # Indexing a matrix M by the two vectors [0,1,...,K], [a,b,...,k] returns the # elements M[0,a], M[1,b], ..., M[K,k] as a vector. Here, we use this # syntax to retrieve the log-probability of the correct labels, y.
隨機梯度下降
? ? 普通的梯度下降是什么?它是一個簡單的算法,在這個算法中首先有由一些參數定義的損失函數表示的錯誤表面,然后在這個表面上重復的使用很小的步長進行下降的算法。針對于普通的梯度下降法的目的來說,訓練數據是需要放入到這個損失函數中的。然后這個算法的偽代碼可以寫成如下形式:
# GRADIENT DESCENTwhile True:loss = f(params)d_loss_wrt_params = ... # compute gradientparams -= learning_rate * d_loss_wrt_paramsif <stopping condition is met>:return params
? ? 隨機梯度下降法是和用普通梯度下降法一樣的原則來work的,不過可以通過每次一點樣本來計算梯度從而更快速的進行處理,所以不需要一次放入整個訓練樣本了。對應的偽代碼如下:
# STOCHASTIC GRADIENT DESCENT for (x_i,y_i) in training_set:# imagine an infinite generator# that may repeat examples (if there is only a finite training set)loss = f(params, x_i, y_i)d_loss_wrt_params = ... # compute gradientparams -= learning_rate * d_loss_wrt_paramsif <stopping condition is met>:return params? ? ?在dl 上我們推薦使用在隨機梯度上的進一步變體,叫做“minibatches”。minibatch sgd的工作規則是和sgd一樣的,只是我們在每次的梯度估計上使用不止一個訓練樣本來訓練。這個技術可以梯度估計中間的方差,而且通常在現代計算機中可以更好地利用層級存儲的組織方式:
for (x_batch,y_batch) in train_batches:# imagine an infinite generator# that may repeat examplesloss = f(params, x_batch, y_batch)d_loss_wrt_params = ... # compute gradient using theanoparams -= learning_rate * d_loss_wrt_paramsif <stopping condition is met>:return params? ? 這是在minibatch size ?的選擇上的權衡考慮。方差的減小和SIMD指令的使用在當從1增加到2的時候通常是很有幫助的,不過這個很小的提升卻會很快的回歸虛無。使用更大的,時間會消耗在減少梯度估計器的方差減少上,本來這些時間是應該更好的用在額外的梯度步長上的。一個最優的是基于模型、數據集、和硬件考慮的,同時可以在任何地方從1上升到甚至好幾百。在這個教程中,我們將它設置成20,不過這個選擇通常是任意的。
note:如果你訓練的時候使用的是固定數量的epochs,那么這個minibatch size就變得很重要了,因為它控制著你的參數的更新次數。使用batch size 為1 的10次epochs來訓練相同的模型得到的結果完全不同于訓練batch size 為20的而且也是10個epochs的結果。記得,在不同的batch sizes之間轉換的時候,記得按照使用過的這個batch size 來調整所有的其他參數。
? ? 上面所有的演示該算法的偽代碼塊,在theano中執行同樣的算法的代碼如下:
# Minibatch Stochastic Gradient Descent# assume loss is a symbolic description of the loss function given # the symbolic variables params (shared variable), x_batch, y_batch;# compute gradient of loss with respect to params d_loss_wrt_params = T.grad(loss, params)# compile the MSGD step into a theano function updates = [(params, params - learning_rate * d_loss_wrt_params)] MSGD = theano.function([x_batch,y_batch], loss, updates=updates)for (x_batch, y_batch) in train_batches:# here x_batch and y_batch are elements of train_batches and# therefore numpy arrays; function MSGD also updates the paramsprint('Current loss is ', MSGD(x_batch, y_batch))if stopping_condition_is_met:return params
正則化
? ? 除了優化,在機器學習中還有更重要的部分。當我們從數據中訓練我們的模型的時候,我們是將它準備用在新樣本上的,而不是那些我們已經見過的樣本。上面的MSGD的訓練循環如果沒有考慮到這一點,也許就會過擬合訓練樣本。一個對應過你的方法就是正則化。這里有好幾種正則化的方法,這里會介紹L1/L2正則化和早期停止。
L1/L2 正則化
? ? L2和L2正則化涉及到在損失函數上增加額外的項,用來懲罰某一個參數組合。形式上,如果我們的損失函數是:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
那么正則化損失就該是:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
或者,在我們的情況中:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
這里
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
這是?的?范數。是一個超參數,用來控制正則化參數的相關重要性。通常 p的值是1和2,所以命名為L1/L2,如果p=2,那么這個正則化叫做“權重衰減”。原則上說,對損失函數增加一個正則化項將會使得在NN中網絡更加的平滑(通過懲罰值較大的參數,這些值較大的參數會降低網絡模型的非線性程度,所以需要懲罰)。更直觀的說,這兩項(NLL和)對應于很好的對數據進行建模(NLL)和有著“簡單”或“平滑”的解決方法。因此,最小化這兩個項的和,從理論上來說,就是為了在擬合訓練數據和解決方法的“泛化”之間找到正確的平衡點。為了遵循Occam的razor原則,這個最小化應該讓我們找到最簡單的解決方法(通過我們簡單的標準來測量的)來擬合訓練數據。注意到這樣一個事實,一個所謂的“簡單”的解決方法不是意味著能夠很好的泛化。經驗上來說,通常是在NN的背景下這樣的正則化的添加有助于泛化,特別是對于小的數據集來說。下面的代碼塊用來表示當包含由來權重化的L1正則化項和由來權重化的L2正則化項的時候如何在python中計算損失的:
# symbolic Theano variable that represents the L1 regularization term L1 = T.sum(abs(param))# symbolic Theano variable that represents the squared L2 term L2_sqr = T.sum(param ** 2)# the loss loss = NLL + lambda_1 * L1 + lambda_2 * L2
早期停止
? ? 用早期停止來解決過擬合是通過在驗證集合上監測模型的執行結果來完成的。驗證集就是我們在梯度下降的時候未使用的樣本集,不過這同樣也不是測試集的一部分。驗證集樣本是被認為作為未來測試集樣本的代表的。我們可以在訓練的時候使用時因為它們不是測試集的一部分。如果模型的效果在驗證集上已經停止了提升,或者甚至在后面的優化上還有下降,那么這里需要做的就是停止使用更多的優化。選擇什么時候停止是一個主觀判斷而且是存在啟發式的,不過這些教程將會在基于會具有幾何增長的patience數量上使用一些策略:
# early-stopping parameters patience = 5000 # look as this many examples regardless patience_increase = 2 # wait this much longer when a new best is# found improvement_threshold = 0.995 # a relative improvement of this much is# considered significant validation_frequency = min(n_train_batches, patience/2)# go through this many# minibatches before checking the network# on the validation set; in this case we# check every epochbest_params = None best_validation_loss = numpy.inf test_score = 0. start_time = time.clock()done_looping = False epoch = 0 while (epoch < n_epochs) and (not done_looping):# Report "1" for first epoch, "n_epochs" for last epochepoch = epoch + 1for minibatch_index in xrange(n_train_batches):d_loss_wrt_params = ... # compute gradientparams -= learning_rate * d_loss_wrt_params # gradient descent# iteration number. We want it to start at 0.iter = (epoch - 1) * n_train_batches + minibatch_index# note that if we do `iter % validation_frequency` it will be# true for iter = 0 which we do not want. We want it true for# iter = validation_frequency - 1.if (iter + 1) % validation_frequency == 0:this_validation_loss = ... # compute zero-one loss on validation setif this_validation_loss < best_validation_loss:# improve patience if loss improvement is good enoughif this_validation_loss < best_validation_loss * improvement_threshold:patience = max(patience, iter * patience_increase)best_params = copy.deepcopy(params)best_validation_loss = this_validation_lossif patience <= iter:done_looping = Truebreak# POSTCONDITION: # best_params refers to the best out-of-sample parameters observed during the optimization
? ? 如果我們在跑完patience之前跑完了所有的訓練數據,那么我們只需要回到訓練數據的開始部分,然后再來一次。
note:validation_frequency應該總是要小于patience的。在跑完patience之前代碼需要檢查至少兩次。這是因為我們使用的公式validation_frequency = min( value,patience/2)。
note:當決定什么時候需要增大patience的時候,算法可以通過使用統計測試的方法來明顯的提升,而不是簡單的使用對比。
測試
? ? 在現有的循環之后,best_params變量表示在驗證集上best-performing的模型。如果我們給另一個模型類別重復這個過程,或者甚至使用另一個隨機初始化,我們應該也要對數據使用相同的train/valid/test劃分,然后得到其他best-performing模型。如果我們不得不需要選擇最好的模型類別或者最好的初始化,我們需要對每個模型進行對比best_validation_loss。當我們選擇我們認為的最好的模型(基于驗證集)的時候,我們會將這個模型用在測試集上,并報告結果。
回顧
? ? 這是為了優化部分準備的。早期停止的技術需要我們將樣本集合劃分成三個不同的集合(訓練集、驗證集、測試集)。訓練集用來作為目標函數的可微分的近似函數的minibatch sgd上。當我們執行梯度下降的時候,我們定期的使用驗證集來觀察我們在真正的目標函數上模型的結果(或者至少從經驗上分析)。當我們在驗證集上看到一個好的模型的時候,我們需要保存下來,當我們發現從看到一個好模型已經過去了很久,那么我們就放棄我們的研究,回頭去找到那些最好的參數,然后在測試集上進行評估。
五、theano/python的提示
裝載和保存模型
? ? 當你做實驗的時候,會花費好幾個小時(或者幾天)來做梯度下降然后找到最好的參數。一旦你找到了它們,你將會需要保存這些權重。隨著研究的開展,你也許同樣會想要保存你當前最好的結果。
從共享變量中pickle這個numpy ndarrays
? ? 最好的保存/存檔你的模型的參數的方法是使用pickle或者深度復制ndarray對象。例如,如果你的參數都放在共享變量w,v,u中,那么你可以像下面的命令來保存:
>>> import cPickle >>> save_file = open('path', 'wb') # this will overwrite current contents >>> cPickle.dump(w.get_value(borrow=True), save_file, -1) # the -1 is for HIGHEST_PROTOCOL >>> cPickle.dump(v.get_value(borrow=True), save_file, -1) # .. and it triggers much more efficient >>> cPickle.dump(u.get_value(borrow=True), save_file, -1) # .. storage than numpy's default >>> save_file.close()然后,你可以像這樣裝載你的數據:
>>> save_file = open('path') >>> w.set_value(cPickle.load(save_file), borrow=True) >>> v.set_value(cPickle.load(save_file), borrow=True) >>> u.set_value(cPickle.load(save_file), borrow=True)這些技術是有一點過于詳盡了,不過試過都是正確的。你可以在matplotlib中毫無問題的裝載你的數據然后對它進行加工。
不要為需要長期存儲的目的而pickle你的訓練或測試函數
? ? theano函數是兼容python的深度復制和pickle機制的,不過你沒必要一定pickle一個theano函數。如果你更新你的theano文件夾或者說其內部有一些改變,那么你也許沒法unpickle你的模型。theano仍然是一個動態的開發項目,內部的APIs可能會改變。所以從安全角度上來說,不要為了長期的存儲而pickle你的整個訓練或者測試函數(也就是如果save幾天或者幾個禮拜估計還ok,就是怕幾個月之后更新了theano,之前的保存的就沒法讀取了)。pickle機智是為了短期存儲而準備的,例如一個臨時文件,或者在一個分布式工作中從一個機器上復制到另一個機器上。
了解更多可以看看?serialization in Theano, 或者 Python的?pickling.
顯示中間的結果
? ? 可視化對于理解你的模型或者訓練算法在干什么是很有幫助的工具。你可能需要視圖鍵入matplotlib畫圖命令,或者PIL 圖像呈現命令到你的模型訓練腳本中。然而,之后你可能想要從這些預呈現的圖形中顯示一些你感興趣的或者想要看看從圖形中得到的是否清晰,那么你需要保存原始的模型。
如果你有足夠的磁盤空間,你的訓練腳本應該保存中間模型并且一個可視化腳本應該用來處理這些保存的模型。
你已經有了一個模型保存函數了嗎?可以再次使用它來保存這些中間模型
你可能會想要了解的庫:python圖像庫?(PIL),?matplotlib.
參考資料:
[1] 官網:http://deeplearning.net/tutorial/gettingstarted.html#gettingstarted
[2]?theano學習指南1:http://www.cnblogs.com/xueliangliu/archive/2013/04/03/2997437.html
轉載于:https://www.cnblogs.com/shouhuxianjian/p/4564627.html
總結
以上是生活随笔為你收集整理的Theano3.2-练习之数据集及目标函数介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hdu 4493 Tutor (水 精度
- 下一篇: Redis: Useful comman