基於Python的卷積神經網路和特徵提取
基於Python的卷積神經網路和特徵提取
發表於2015-08-27 21:39| 4577次閱讀| 來源blog.christianperone.com/| 13 條評論| 作者Christian S.Peron
深度學習特徵提取神經網路Pythonnolearntheano
摘要:本文展示瞭如何基於nolearn使用一些卷積層和池化層來建立一個簡單的ConvNet體系結構,以及如何使用ConvNet去訓練一個特徵提取器,然後在使用如SVM、Logistic迴歸等不同的模型之前使用它來進行特徵提取。
卷積神經網路(ConvNets)是受生物啟發的MLPs(多層感知器),它們有著不同類別的層,並且每層的工作方式與普通的MLP層也有所差異。如果你對ConvNets感興趣,這裡有個很好的教程
常規的神經網路(來自CS231n網站)
ConvNet網路體系結構(來自CS231n網站)
如你所見,ConvNets工作時伴隨著3D卷積並且在不斷轉變著這些3D卷積。我在這篇文章中不會再重複整個CS231n的教程,所以如果你真的感興趣,請在繼續閱讀之前先花點時間去學習一下。
Lasagne 和 nolearn
Lasagne和nolearn是我最喜歡使用的深度學習Python包。Lasagne是基於Theano的,所以GPU的加速將大有不同,並且其對神經網路建立的宣告方法也很有幫助。nolearn庫是一個神經網路軟體包實用程式集(包含Lasagne),它在神經網路體系結構的建立過程上、各層的檢驗等都能夠給我們很大的幫助。
在這篇文章中我要展示的是,如何使用一些卷積層和池化層來建立一個簡單的ConvNet體系結構。我還將向你展示如何使用ConvNet去訓練一個特徵提取器,在使用如SVM、Logistic迴歸等不同的模型之前使用它來進行特徵提取。大多數人使用的是預訓練ConvNet模型,然後刪除最後一個輸出層,接著從ImageNets資料集上訓練的ConvNets網路提取特徵。這通常被稱為是遷移學習,因為對於不同的問題你可以使用來自其它的ConvNets層,由於ConvNets的第一層過濾器被當做是一個邊緣探測器,所以它們可以用來作為其它問題的普通特徵探測器。
載入MNIST資料集
MNIST資料集是用於數字識別最傳統的資料集之一。我們使用的是一個面向Python的版本,但先讓我們匯入需要使用的包:
[py] view plaincopy
- import matplotlib
- import matplotlib.pyplot as plt
- import matplotlib.cm as cm
- from urllib import urlretrieve
- import cPickle as pickle
- import os
- import gzip
- import numpy as np
- import theano
- import lasagne
- from lasagne import layers
- from lasagne.updates import nesterov_momentum
- from nolearn.lasagne import NeuralNet
- from nolearn.lasagne import visualize
- from sklearn.metrics import classification_report
- from sklearn.metrics import confusion_matrix
[py] view plain copy
- import matplotlib
- import matplotlib.pyplot as plt
- import matplotlib.cm as cm
- from urllib import urlretrieve
- import cPickle as pickle
- import os
- import gzip
- import numpy as np
- import theano
- import lasagne
- from lasagne import layers
- from lasagne.updates import nesterov_momentum
- from nolearn.lasagne import NeuralNet
- from nolearn.lasagne import visualize
- from sklearn.metrics import classification_report
- from sklearn.metrics import confusion_matrix
正如你所看到的,我們匯入了用於繪圖的matplotlib包,一些用於下載MNIST資料集的原生Python模組,numpy, theano,lasagne,nolearn 以及 scikit-learn庫中用於模型評估的一些函式。
然後,我們定義一個載入MNIST資料集的函式(這個功能與Lasagne教程上使用的非常相似)
[py] view plaincopy
- def load_dataset():
- url = 'http://deeplearning.net/data/mnist/mnist.pkl.gz'
- filename = 'mnist.pkl.gz'
- if not os.path.exists(filename):
- print("Downloading MNIST dataset...")
- urlretrieve(url, filename)
- with gzip.open(filename, 'rb') as f:
- data = pickle.load(f)
- X_train, y_train = data[0]
- X_val, y_val = data[1]
- X_test, y_test = data[2]
- X_train = X_train.reshape((-1, 1, 28, 28))
- X_val = X_val.reshape((-1, 1, 28, 28))
- X_test = X_test.reshape((-1, 1, 28, 28))
- y_train = y_train.astype(np.uint8)
- y_val = y_val.astype(np.uint8)
- y_test = y_test.astype(np.uint8)
- return X_train, y_train, X_val, y_val, X_test, y_test
[py] view plain copy
- def load_dataset():
- url = 'http://deeplearning.net/data/mnist/mnist.pkl.gz'
- filename = 'mnist.pkl.gz'
- if not os.path.exists(filename):
- print("Downloading MNIST dataset...")
- urlretrieve(url, filename)
- with gzip.open(filename, 'rb') as f:
- data = pickle.load(f)
- X_train, y_train = data[0]
- X_val, y_val = data[1]
- X_test, y_test = data[2]
- X_train = X_train.reshape((-1, 1, 28, 28))
- X_val = X_val.reshape((-1, 1, 28, 28))
- X_test = X_test.reshape((-1, 1, 28, 28))
- y_train = y_train.astype(np.uint8)
- y_val = y_val.astype(np.uint8)
- y_test = y_test.astype(np.uint8)
- return X_train, y_train, X_val, y_val, X_test, y_test
正如你看到的,我們正在下載處理過的MNIST資料集,接著把它拆分為三個不同的資料集,分別是:訓練集、驗證集和測試集。然後重置影象內容,為之後的Lasagne輸入層做準備,與此同時,由於GPU/theano資料型別的限制,我們還把numpy的資料型別轉換成了uint8。
隨後,我們準備載入MNIST資料集並檢驗它:
[py] view plaincopy
- X_train, y_train, X_val, y_val, X_test, y_test = load_dataset()
- plt.imshow(X_train[0][0], cmap=cm.binary)
[py] view plain copy
- X_train, y_train, X_val, y_val, X_test, y_test = load_dataset()
- plt.imshow(X_train[0][0], cmap=cm.binary)
這個程式碼將輸出下面的影象(我用的是IPython Notebook)
一個MNIST資料集的數字例項(該例項是5)
ConvNet體系結構與訓練
現在,定義我們的ConvNet體系結構,然後使用單GPU/CPU來訓練它(我有一個非常廉價的GPU,但它很有用)
[py] view plaincopy
- net1 = NeuralNet(
- layers=[('input', layers.InputLayer),
- ('conv2d1', layers.Conv2DLayer),
- ('maxpool1', layers.MaxPool2DLayer),
- ('conv2d2', layers.Conv2DLayer),
- ('maxpool2', layers.MaxPool2DLayer),
- ('dropout1', layers.DropoutLayer),
- ('dense', layers.DenseLayer),
- ('dropout2', layers.DropoutLayer),
- ('output', layers.DenseLayer),
- ],
- # input layer
- input_shape=(None, 1, 28, 28),
- # layer conv2d1
- conv2d1_num_filters=32,
- conv2d1_filter_size=(5, 5),
- conv2d1_nonlinearity=lasagne.nonlinearities.rectify,
- conv2d1_W=lasagne.init.GlorotUniform(),
- # layer maxpool1
- maxpool1_pool_size=(2, 2),
- # layer conv2d2
- conv2d2_num_filters=32,
- conv2d2_filter_size=(5, 5),
- conv2d2_nonlinearity=lasagne.nonlinearities.rectify,
- # layer maxpool2
- maxpool2_pool_size=(2, 2),
- # dropout1
- dropout1_p=0.5,
- # dense
- dense_num_units=256,
- dense_nonlinearity=lasagne.nonlinearities.rectify,
- # dropout2
- dropout2_p=0.5,
- # output
- output_nonlinearity=lasagne.nonlinearities.softmax,
- output_num_units=10,
- # optimization method params
- update=nesterov_momentum,
- update_learning_rate=0.01,
- update_momentum=0.9,
- max_epochs=10,
- verbose=1,
- )
- # Train the network
- nn = net1.fit(X_train, y_train)
[py] view plain copy
- net1 = NeuralNet(
- layers=[('input', layers.InputLayer),
- ('conv2d1', layers.Conv2DLayer),
- ('maxpool1', layers.MaxPool2DLayer),
- ('conv2d2', layers.Conv2DLayer),
- ('maxpool2', layers.MaxPool2DLayer),
- ('dropout1', layers.DropoutLayer),
- ('dense', layers.DenseLayer),
- ('dropout2', layers.DropoutLayer),
- ('output', layers.DenseLayer),
- ],
- # input layer
- input_shape=(None, 1, 28, 28),
- # layer conv2d1
- conv2d1_num_filters=32,
- conv2d1_filter_size=(5, 5),
- conv2d1_nonlinearity=lasagne.nonlinearities.rectify,
- conv2d1_W=lasagne.init.GlorotUniform(),
- # layer maxpool1
- maxpool1_pool_size=(2, 2),
- # layer conv2d2
- conv2d2_num_filters=32,
- conv2d2_filter_size=(5, 5),
- conv2d2_nonlinearity=lasagne.nonlinearities.rectify,
- # layer maxpool2
- maxpool2_pool_size=(2, 2),
- # dropout1
- dropout1_p=0.5,
- # dense
- dense_num_units=256,
- dense_nonlinearity=lasagne.nonlinearities.rectify,
- # dropout2
- dropout2_p=0.5,
- # output
- output_nonlinearity=lasagne.nonlinearities.softmax,
- output_num_units=10,
- # optimization method params
- update=nesterov_momentum,
- update_learning_rate=0.01,
- update_momentum=0.9,
- max_epochs=10,
- verbose=1,
- )
- # Train the network
- nn = net1.fit(X_train, y_train)
如你所視,在layers的引數中,我們定義了一個有層名稱/型別的元組字典,然後定義了這些層的引數。在這裡,我們的體系結構使用的是兩個卷積層,兩個池化層,一個全連線層(稠密層,dense layer)和一個輸出層。在一些層之間也會有dropout層,dropout層是一個正則化矩陣,隨機的設定輸入值為零來避免過擬合(見下圖)。
Dropout層效果(來自CS231n網站)
呼叫訓練方法後,nolearn包將會顯示學習過程的狀態,我的機器使用的是低端的的GPU,得到的結果如下:
[py] view plaincopy
- # Neural Network with 160362 learnable parameters
- ## Layer information
- # name size
- --- -------- --------
- 0 input 1x28x28
- 1 conv2d1 32x24x24
- 2 maxpool1 32x12x12
- 3 conv2d2 32x8x8
- 4 maxpool2 32x4x4
- 5 dropout1 32x4x4
- 6 dense 256
- 7 dropout2 256
- 8 output 10
- epoch train loss valid loss train/val valid acc dur
- ------- ------------ ------------ ----------- --------- ---
- 1 0.85204 0.16707 5.09977 0.95174 33.71s
- 2 0.27571 0.10732 2.56896 0.96825 33.34s
- 3 0.20262 0.08567 2.36524 0.97488 33.51s
- 4 0.16551 0.07695 2.15081 0.97705 33.50s
- 5 0.14173 0.06803 2.08322 0.98061 34.38s
- 6 0.12519 0.06067 2.06352 0.98239 34.02s
- 7 0.11077 0.05532 2.00254 0.98427 33.78s
- 8 0.10497 0.05771 1.81898 0.98248 34.17s
- 9 0.09881 0.05159 1.91509 0.98407 33.80s
- 10 0.09264 0.04958 1.86864 0.98526 33.40s
[py] view plain copy
- # Neural Network with 160362 learnable parameters
- ## Layer information
- # name size
- --- -------- --------
- 0 input 1x28x28
- 1 conv2d1 32x24x24
- 2 maxpool1 32x12x12
- 3 conv2d2 32x8x8
- 4 maxpool2 32x4x4
- 5 dropout1 32x4x4
- 6 dense 256
- 7 dropout2 256
- 8 output 10
- epoch train loss valid loss train/val valid acc dur
- ------- ------------ ------------ ----------- --------- ---
- 1 0.85204 0.16707 5.09977 0.95174 33.71s
- 2 0.27571 0.10732 2.56896 0.96825 33.34s
- 3 0.20262 0.08567 2.36524 0.97488 33.51s
- 4 0.16551 0.07695 2.15081 0.97705 33.50s
- 5 0.14173 0.06803 2.08322 0.98061 34.38s
- 6 0.12519 0.06067 2.06352 0.98239 34.02s
- 7 0.11077 0.05532 2.00254 0.98427 33.78s
- 8 0.10497 0.05771 1.81898 0.98248 34.17s
- 9 0.09881 0.05159 1.91509 0.98407 33.80s
- 10 0.09264 0.04958 1.86864 0.98526 33.40s
正如你看到的,最後一次的精度可以達到0.98526,是這10個單元訓練中的一個相當不錯的效能。
預測和混淆矩陣
現在,我們使用這個模型來預測整個測試集:
[py] view plaincopy
- preds = net1.predict(X_test)
[py] view plain copy
- preds = net1.predict(X_test)
我們還可以繪製一個混淆矩陣來檢查神經網路的分類效能:
[py] view plaincopy
- cm = confusion_matrix(y_test, preds)
- plt.matshow(cm)
- plt.title('Confusion matrix')
- plt.colorbar()
- plt.ylabel('True label')
- plt.xlabel('Predicted label')
- plt.show()
[py] view plain copy
- cm = confusion_matrix(y_test, preds)
- plt.matshow(cm)
- plt.title('Confusion matrix')
- plt.colorbar()
- plt.ylabel('True label')
- plt.xlabel('Predicted label')
- plt.show()
上面的程式碼將繪製下面的混淆矩陣:
混淆矩陣
如你所視,對角線上的分類更密集,表明我們的分類器有一個良好的效能。
過濾器的視覺化
我們還可以從第一個卷積層中視覺化32個過濾器:
[py] view plaincopy
- visualize.plot_conv_weights(net1.layers_['conv2d1'])
[py] view plain copy
- visualize.plot_conv_weights(net1.layers_['conv2d1'])
上面的程式碼將繪製下面的過濾器:
第一層的5x5x32過濾器
如你所視,nolearn的plot_conv_weights函式在我們指定的層中繪製出了所有的過濾器。
Theano層的功能和特徵提取
現在可以建立theano編譯的函數了,它將前饋輸入資料輸送到結構體系中,甚至是你感興趣的某一層中。接著,我會得到輸出層的函式和輸出層前面的稠密層函式。
[py] view plaincopy
- dense_layer = layers.get_output(net1.layers_['dense'], deterministic=True)
- output_layer = layers.get_output(net1.layers_['output'], deterministic=True)
- input_var = net1.layers_['input'].input_var
- f_output = theano.function([input_var], output_layer)
- f_dense = theano.function([input_var], dense_layer)
[py] view plain copy
- dense_layer = layers.get_output(net1.layers_['dense'], deterministic=True)
- output_layer = layers.get_output(net1.layers_['output'], deterministic=True)
- input_var = net1.layers_['input'].input_var
- f_output = theano.function([input_var], output_layer)
- f_dense = theano.function([input_var], dense_layer)
如你所視,我們現在有兩個theano函式,分別是f_output和f_dense(用於輸出層和稠密層)。請注意,在這裡為了得到這些層,我們使用了一個額外的叫做“deterministic”的引數,這是為了避免dropout層影響我們的前饋操作。
現在,我們可以把例項轉換為輸入格式,然後輸入到theano函式輸出層中:
[py] view plaincopy
- instance = X_test[0][None, :, :]
- %timeit -n 500 f_output(instance)
- 500 loops, best of 3: 858 µs per loop
[py] view plain copy
- instance = X_test[0][None, :, :]
- %timeit -n 500 f_output(instance)
- 500 loops, best of 3: 858 µs per loop
如你所視,f_output函式平均需要858µs。我們同樣可以為這個例項繪製輸出層啟用值結果:
[py] view plaincopy
- pred = f_output(instance)
- N = pred.shape[1]
- plt.bar(range(N), pred.ravel())
[py] view plain copy
- pred = f_output(instance)
- N = pred.shape[1]
- plt.bar(range(N), pred.ravel())
上面的程式碼將繪製出下面的圖:
輸出層啟用值
正如你所看到的,數字被認為是7。事實是為任何網路層建立theano函式都是非常有用的,因為你可以建立一個函式(像我們以前一樣)得到稠密層(輸出層前一個)的啟用值,然後你可以使用這些啟用值作為特徵,並且使用你的神經網路作為特徵提取器而不是分類器。現在,讓我們為稠密層繪製256個啟用單元:
[py] view plaincopy
- pred = f_dense(instance)
- N = pred.shape[1]
- plt.bar(range(N), pred.ravel())
[py] view plain copy
- pred = f_dense(instance)
- N = pred.shape[1]
- plt.bar(range(N), pred.ravel())
上面的程式碼將繪製下面的圖:
稠密層啟用值
現在,你可以使用輸出的這256個啟用值作為線性分類器如Logistic迴歸或支援向量機的特徵了。
最後,我希望你會喜歡這個教程。