入門指南
這些教材不會試圖去彌補(bǔ)研究生或者本科生的機(jī)器學(xué)習(xí)課程,而是概述一些重要的概念(和記法),以確保我們擁有相同的進(jìn)度。為了運(yùn)行接下來的教程例子代碼,你還需要下載這節(jié)提到的數(shù)據(jù)集。
下載
在每個學(xué)習(xí)算法的頁面,你可以下載相應(yīng)的文件。如果希望一次性下載所有文件,你可以復(fù)制教程的Git倉庫:
git clone git://github.com/lisa-lab/DeepLearningTutorials.git
數(shù)據(jù)集
MNIST數(shù)據(jù)集
(mnist.pkl.gz)
MNIST數(shù)據(jù)集由手寫數(shù)字圖像組成,分成60000個訓(xùn)練樣本和10000測試樣本。在許多論文,與這個教程一樣,官方的60000訓(xùn)練樣本分成50000個訓(xùn)練樣本和10000個驗(yàn)證樣本(為了選擇像學(xué)習(xí)率和模型大小的超參數(shù))。所有數(shù)字圖像大小都?xì)w一化在固定的2828大小的圖像中。在原始的數(shù)據(jù)集,圖像的每個像素表示為一個[0,255]的值,0表示黑色,255表示白色,(0,255)之間的是不同的灰度。 這里有一些MNIST數(shù)字的例子:
為了方便,我處理了一下數(shù)據(jù)集,使得更容易在Python使用??梢栽?a href="http:///data/mnist/mnist.pkl.gz">這里下載處理好的樣本。處理過的文件表示為3個列表的元組:訓(xùn)練集、驗(yàn)證集和測試集。每個都是一對一系列圖像和一系列類標(biāo)識組成。一個圖像表示為numpy的一維784(2828)的范圍為[0,1](0表示為黑色,1表示為白色)的浮點(diǎn)數(shù)組。標(biāo)識為[0,9]的表示數(shù)字的標(biāo)識。下面的代碼展示如何加載數(shù)據(jù)集:
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()
當(dāng)我們使用數(shù)據(jù)集時,我們經(jīng)常把它分成多批(查看隨機(jī)梯度下降)。你應(yīng)該把數(shù)據(jù)集存儲到共享變量里,基于批的索引號訪問數(shù)據(jù)集,批的大小是固定已知的。使用共享變量的理由與使用GPU有關(guān)。當(dāng)拷貝數(shù)據(jù)到GPU內(nèi)存時,從CPU拷貝數(shù)據(jù)到GPU是個大的瓶頸。如果代碼要做拷貝數(shù)據(jù),如果沒有使用共享變量,因?yàn)檫@個瓶頸,GPU代碼不會比CPU代碼快(可能更慢)。雖然你把數(shù)據(jù)放到Theano共享變量,當(dāng)共享變量創(chuàng)建時,單次調(diào)用時,GPU也會把整個數(shù)據(jù)拷貝到GPU內(nèi)存里。然后GPU可以使用切片共享變量訪問任何一批數(shù)據(jù)。不需要從CPU內(nèi)存里拷貝任何信息,因此繞過了瓶頸。因?yàn)闃颖竞蜆?biāo)識通常與自然的有區(qū)別(標(biāo)識一般為整形而樣本一般為實(shí)數(shù)),建議為標(biāo)識和數(shù)據(jù)使用不同的變量。我們也推薦為訓(xùn)練集、驗(yàn)證集合測試集使用不同的變量,使得代碼更具可讀性(從而使用6個不同的變量)。 由于現(xiàn)在數(shù)據(jù)在一個變量里,一批定義為變量的切片,這樣可以更自然的使用索引和大小來定義一批。在我們的設(shè)置中,整個代碼中批的大小保持不變,因此一個函數(shù)實(shí)際上只需要僅僅表示哪個樣本的的索引。下面的代碼顯示如何存儲數(shù)據(jù)和訪問每批樣本:
def shared_dataset(data_xy):
""" Function that loads the dataset into shared variables
The reason we store our dataset in shared variables is to allow
Theano to copy it into the GPU memory (when code is run on GPU).
Since copying data into the GPU is slow, copying a minibatch everytime
is needed (the default behaviour if the data is not in a shared
variable) would lead to a large decrease in performance.
"""
data_x, data_y = data_xy
shared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX))
shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX))
# 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 issue
return shared_x, T.cast(shared_y, 'int32')
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
data = train_set_x[2 * 500: 3 * 500]
label = train_set_y[2 * 500: 3 * 500]
在GPU,數(shù)據(jù)被保存成浮點(diǎn)(正確的類型dtype 由theano.config.floatX 給定)。為了回避標(biāo)識的短處,我們把它們保存為浮點(diǎn),然后轉(zhuǎn)換為整形。
注意:如果你在GPU上運(yùn)行代碼,使用的數(shù)據(jù)集超出內(nèi)存的大小,代碼就會崩潰。這種情況下,需要把數(shù)據(jù)保存到共享變量。在訓(xùn)練時使用,你可以保存為足夠小的數(shù)據(jù)塊到共享變量。一旦你通過數(shù)據(jù)塊獲取數(shù)據(jù),可以更新保存的值。這個方法可以最小化CPU和GPU內(nèi)存之間的數(shù)據(jù)傳輸。
記法
數(shù)據(jù)集記法
我們標(biāo)記數(shù)據(jù)集為D。區(qū)別是很重要的,我表示訓(xùn)練、驗(yàn)證和測試數(shù)據(jù)集為Dtrain、Dvalid和Dtest。驗(yàn)證集用于執(zhí)行模型選擇和超參數(shù)選擇,而測試集用于評估最終的泛化錯誤,并公平地與其他不同算法比較。 教材大多數(shù)處理分類問題,每個數(shù)據(jù)集D是一組索引對(x(i),y(i))。我們使用上標(biāo)區(qū)分訓(xùn)練集的樣本:x(i)∈RD是因?yàn)榈趇個訓(xùn)練樣本是D維的。同樣地,y(i)∈0,...,L是第i個與輸入樣本x(i)的標(biāo)識??梢院芎唵蔚財U(kuò)展這些樣本,比如y(i)可以有其它的類型(比如,高斯回歸或者預(yù)測多個符號的多項(xiàng)式組)。
數(shù)學(xué)約定
- W:除非特定說明,引用矩陣的大寫符號。
- Wij:矩陣W的第i行第j列的元素。
- Wi:矩陣的第i行向量。
- Wj:矩陣的第j列向量。
- b:除非特定說明,引用向量的小寫符號。
- bi:向量b的第i個元素。
符號和縮略詞列表
- D:輸入維度大小
- D(i)h:第i層的隱層單元數(shù)
- fθ(x),f(x):模型P(Y|x,θ)對應(yīng)的分類函數(shù),定義為argmaxkP(Y=k|x,θ)。注意我們一般會丟掉下標(biāo)θ。
- L:標(biāo)識數(shù)目
- L(θ,D):在樣本集D上的以參數(shù)θ定義的模型的對數(shù)似然函數(shù)。
- (θ,D):在數(shù)據(jù)集D上的以θ為參數(shù)的預(yù)測函數(shù)f的經(jīng)驗(yàn)代價函數(shù)。
- NLL:負(fù)對數(shù)似然函數(shù)。
- θ:給定模型的所有參數(shù)集。
Python命名空間
教程代碼經(jīng)常使用下面的命名空間:
import theano
import theano.tensor as T
import numpy
一個深度學(xué)習(xí)的監(jiān)督優(yōu)化的初級教程
關(guān)于深度學(xué)習(xí)的令人興奮的是主要使用無監(jiān)督的深度學(xué)習(xí)網(wǎng)絡(luò)。但是,監(jiān)督學(xué)習(xí)也扮演著重要的角色。無監(jiān)督預(yù)訓(xùn)練的作用經(jīng)常估計(jì)在監(jiān)督微調(diào)(fine-tuning)之后可以達(dá)到的性能。這節(jié)復(fù)習(xí)用于分類模型的監(jiān)督學(xué)習(xí),包括用于微調(diào)深度學(xué)習(xí)入門的多個模型的批量隨機(jī)梯度下降??纯?a >基于梯度學(xué)習(xí)的基礎(chǔ)入門課程筆記中更多使用梯度優(yōu)化訓(xùn)練準(zhǔn)則的基本概念。
學(xué)習(xí)分類器
0-1代價
在這些深度學(xué)習(xí)教程提出的模型主要用于分類。訓(xùn)練分類器的目標(biāo)是最小化測試樣本集的錯誤率(0-1代價)。如果f:RD→0,...,L是預(yù)測函數(shù),則代價可以寫為成: 0,1=∑|D|i=0If(x(i))≠y(i) 其中D為訓(xùn)練集或者D∩Dtrain=(避免估計(jì)驗(yàn)證集或者測試集的錯誤率的偏差)。I是一個指示函數(shù),定義為: I_x=\left{\begin{array}{ccc}1& \mbox{ if x is True} \ 0& \mbox{ otherwise}\end{array}\right 在這個教程,f定義為: f(x)=argmaxkP(Y=k|x,θ) 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))
負(fù)對數(shù)似然代價
由于0-1代價不可微,優(yōu)化大的模型(數(shù)千或者數(shù)百萬的參數(shù)的)的代價非常高。因此最大化訓(xùn)練集給定所有標(biāo)識的分類器的對數(shù)似然函數(shù)。 L(θ,D)=∑|D|i=0logP(Y=y(i)|x(i),θ) 預(yù)測正確的類的概率不是正確預(yù)測的數(shù)目,但是從隨機(jī)初始化的分類器看,它們相當(dāng)相似的。記住似然函數(shù)和0-1代價函數(shù)的目的是不同的。你應(yīng)該看到在驗(yàn)證集它們是相關(guān)的,但是有時一個會上升另一個會下降。 由于我們通常會說最小化代價函數(shù),因此學(xué)習(xí)將嘗試最小化負(fù)對數(shù)似然函數(shù)(NLL),定義為:
NLL(θ,D)=∑|D|i=0logP(Y=y(i)|x(i),θ)
我們分類器的NLL是一個0-1代價的可微的替代,我們使用在訓(xùn)練數(shù)據(jù)集上的函數(shù)的梯度作為深度學(xué)習(xí)的分類器的監(jiān)督學(xué)習(xí)信號。 可以使用下面的代碼計(jì)算:
# 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.
隨機(jī)梯度下降
什么是普通的梯度下降?就是一個簡單的算法-重復(fù)的小步驟的從由代價的一些參數(shù)定義的錯誤曲面往下移動。為了普通梯度下降的目的,我考慮訓(xùn)練數(shù)據(jù)輸入代價函數(shù)。算法的偽代碼可以描述為:
# GRADIENT DESCENT
while True:
loss = f(params)
d_loss_wrt_params = ... # compute gradient
params -= learning_rate * d_loss_wrt_params
if <stopping condition is met>:
return params
隨機(jī)梯度下降根據(jù)普通梯度下降相同的原則工作,但是每次只通過使用單個樣本(而不是整個訓(xùn)練集)估計(jì)梯度,從而處理更快。用這種存粹的形式,我們每次只使用單個樣本估計(jì)梯度。
# 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 gradient
params -= learning_rate * d_loss_wrt_params
if <stopping condition is met>:
return params
深度學(xué)習(xí)中推薦是一個隨機(jī)梯度下降更加優(yōu)化的變種,叫批量隨機(jī)梯度下降。批量隨機(jī)梯度下降跟隨機(jī)梯度下降一樣地工作,除了每次使用更多的訓(xùn)練樣本估計(jì)梯度。這個技術(shù)減少估計(jì)的梯度的方差,常更好的利用計(jì)算機(jī)的分層內(nèi)存組織。
for (x_batch,y_batch) in train_batches:
# imagine an infinite generator
# that may repeat examples
loss = f(params, x_batch, y_batch)
d_loss_wrt_params = ... # compute gradient using theano
params -= learning_rate * d_loss_wrt_params
if <stopping condition is met>:
return params
批量的大小B是一個權(quán)衡的選擇。B從1增加到2時,減少方差(估計(jì)的梯度更準(zhǔn))和SIMD指令的使用,但是邊界的改善就迅速消失了。越大的B,就越成倍減少估計(jì)的梯度的方差,在額外的梯度估計(jì)步驟就會花費(fèi)更多的時間。最佳的B是模型、數(shù)據(jù)集和硬件獨(dú)立的,可以為1到幾百。在這教程,設(shè)置為20,但是這個選擇幾乎是任意的(即使無害的)。 注意:如果訓(xùn)練固定次數(shù),批量的大小就變得更加重要,因?yàn)檫@個參數(shù)控制更新的數(shù)量。訓(xùn)練同樣的模型10次,批量大小為1和訓(xùn)練10,批量大小為20的會產(chǎn)生完全不同的結(jié)果。記住當(dāng)轉(zhuǎn)換批量大小,準(zhǔn)備好使用這個大小作用于所有的參數(shù)。 下面的所有代碼塊顯示算法的偽代碼是怎么樣的。實(shí)現(xiàn)這樣的算法,在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 params
print('Current loss is ', MSGD(x_batch, y_batch))
if stopping_condition_is_met:
return params
正規(guī)化
因?yàn)殡S機(jī)梯度下降是在線更新的算法,所以可以使用新的樣本訓(xùn)練已有的模型。不過這個容易過擬合。一個防止過擬合的方法是正規(guī)化。還有一個是訓(xùn)練時一定條件下提早結(jié)束。
L1和L2正規(guī)化
L1和L2正規(guī)化包括添加額外的項(xiàng)到代價函數(shù)里,用于懲罰某些參數(shù)配置。形式上,如果代價函數(shù)是: NLL(θ,D)=∑|D|i=0logP(Y=y(i)|x(i),θ) 則正規(guī)化的代價函數(shù)為: E(θ,D)=NLL(θ,D)+λR(θ)$或者,對我們的例子而言為:E(\theta, \mathcal{D}) = NLL(\theta, \mathcal{D}) + \lambda||\theta||p^p其中,||\theta||p = \left(\sum_{j=0}^{|\theta|}{|\theta_j|^p}\right)^{\frac{1}{p}}這是參數(shù)\theta的L_p的正規(guī)化。\lambda是控制正規(guī)化參數(shù)的相關(guān)重要性的超參數(shù)。通常p的值為1和2,所以叫L1和L2正規(guī)化。如果p等于2,那么正規(guī)化也叫做權(quán)值衰減。原則上,添加一個正規(guī)項(xiàng)到代價函數(shù)會使得神經(jīng)網(wǎng)絡(luò)的映射更加平滑(通過懲罰值大的參數(shù),這個將減少網(wǎng)絡(luò)模型的非線性)。更直觀地,兩項(xiàng)(NLL和R(\theta))與建立好模型(NLL),并且擁有簡單或者平滑的解決方案(R(\theta)$)一致。因此,最小化兩個的和,理論上,與找到匹配訓(xùn)練數(shù)據(jù)和解決方案的一般性的平衡。遵循奧卡姆剃刀原則,最小化可以讓我們找到匹配訓(xùn)練數(shù)據(jù)的最簡單的方法。 注意一個事實(shí)是簡單的方案不一定意味著好的泛化能力。但是一般在神經(jīng)網(wǎng)絡(luò)的背景里,正規(guī)化可以提高泛化能力,尤其在小的集合上。下面的代碼顯示怎樣在Python里計(jì)算帶權(quán)值L1和L2的代價函數(shù)。
# 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
提早結(jié)束
通過監(jiān)視模型在驗(yàn)證集的性能,在批量梯度下降中提早結(jié)束可以防止過擬合。驗(yàn)證集是那些沒被用于梯度下降的訓(xùn)練的樣本,但不是測試集的一部分。驗(yàn)證集被認(rèn)為是測試的代表。因?yàn)椴皇菧y試集的一部分,所以可以在訓(xùn)練中使用驗(yàn)證集。如果模型的性能在驗(yàn)證集沒有明顯的提升,或者之后的優(yōu)化甚至下降,則這里可以實(shí)現(xiàn)啟發(fā)式停止優(yōu)化。 什么時候停止需要做一些啟發(fā)式的判斷,但是這里的教程使用基于幾何式增長的容忍的策略。
# 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 epoch
best_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 epoch
epoch = epoch + 1
for minibatch_index in xrange(n_train_batches):
d_loss_wrt_params = ... # compute gradient
params -= 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 set
if this_validation_loss < best_validation_loss:
# improve patience if loss improvement is good enough
if this_validation_loss < best_validation_loss * improvement_threshold:
patience = max(patience, iter * patience_increase)
best_params = copy.deepcopy(params)
best_validation_loss = this_validation_loss
if patience <= iter:
done_looping = True
break
# POSTCONDITION:
# best_params refers to the best out-of-sample parameters observed during the optimization
如果先于容忍跑完所有的批次的訓(xùn)練數(shù)據(jù),則只是簡單的跳到數(shù)據(jù)集的開頭,然后重復(fù)。 注意:
validation_frequency 總是小于patience 。在跑出容忍的次數(shù)之前,代碼至少應(yīng)該兩次檢測模型的性能。這是我們使用validation_frequency = min( value, patience/2.) 公式的理由。
- 在決定是否增加容忍次數(shù)時,算法可以使用一個統(tǒng)計(jì)意義上的測試而不是簡單的比較,這能提高性能。
測試
所有循環(huán)結(jié)束之后,最好的參數(shù)指得是在驗(yàn)證集上的最好性能的模型。如果我們對另外的模型重復(fù)這樣的過程,或者甚至另一個隨機(jī)的初始化,應(yīng)該使用相同劃分的訓(xùn)練、驗(yàn)證和測試集,得到其它更好的模型。如果我們不得不選擇哪個是最好的模型或者最好的初始化,可以比較每個模型最好的驗(yàn)證性能。如果我們最終選擇一個我們認(rèn)為最好的模型(在驗(yàn)證集上的),可以報告模型的測試集的性能。這個我們期望的性能可以認(rèn)為是未見的樣本的性能。
摘要
這就是優(yōu)化的部分。提早結(jié)束的技術(shù)要求劃分樣本為三個集合(訓(xùn)練集、驗(yàn)證集和測試集)。訓(xùn)練集用于批量梯度下降算法近似估計(jì)目標(biāo)函數(shù)。在訓(xùn)練時,可以定期獲取在驗(yàn)證集的性能以查看模型的性能(或者依靠經(jīng)驗(yàn)估計(jì))。當(dāng)看到一個在驗(yàn)證集性能好的模型,可以保存它,當(dāng)長時間看到一個好的模型,可以停止搜索,返回在測試集上的估計(jì)最好的參數(shù)。
Theano/Python竅門
加載和保存模型
當(dāng)實(shí)驗(yàn)時,使用梯度下降可能會花費(fèi)幾個小時(有時幾天)尋找好的參數(shù)。一旦你找到它們,可以保存那些權(quán)值。在搜索過程中,你也可能想要保存當(dāng)前最好的估計(jì)。
pickle保存共享變量中的numpy的n維數(shù)組
保存或者歸檔模型的參數(shù)的最好的方法是使用pickle或者deepcopy保存n維數(shù)組。比如,如果你的參數(shù)在共享變量w、v、u中,則保存的命令應(yīng)該看起來像這樣:
>>> 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()
然后,可以像這樣子加載數(shù)據(jù):
>>> 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)
這個技術(shù)有點(diǎn)繁瑣,但是可靠并且正確的。你能加載之前保存的數(shù)據(jù),并不用折騰就可以在matplotlib中顯示。
不要使用pickle長期存儲訓(xùn)練和測試函數(shù)
Theano兼容Python的deepcopy和pickle機(jī)理,但是不應(yīng)該使用pickle保存一個Theano函數(shù)。如果你更新了Theano目錄,其中一個改變了,則你可能不能un-pickle你的模型了。Theano仍然在積極的開發(fā)中,內(nèi)部的APIs可能會被修改。所以為了安全起見,比如一個臨時文件,或者在分布式工作中的額外的機(jī)器的拷貝。
顯示中間結(jié)果
可視化是一個理解模型怎么樣的或者判斷訓(xùn)練的算法的好壞的有用的工具。你可能會插入matplotlib 畫圖指令,或者PIL 圖像顯示指令到你的模型訓(xùn)練腳本中。之后你能觀察到一些在預(yù)先顯示的圖像中感興趣的地方,檢查與圖像不對的地方。你可能想要你已經(jīng)保存原始模型。
如果有足夠的硬盤空間,訓(xùn)練腳本應(yīng)該保存中間模型,可視化的腳本應(yīng)該處理這些保存的模型。
你應(yīng)該有一個模型保存函數(shù),對吧?僅僅再次使用并保存中間模型。你可能想要知道的庫:Python圖像庫(PIL),matplotlib。
|