資料探勘入門系列教程(八)之使用神經網路(基於pybrain)識別數字手寫集MNIST
阿新 • • 發佈:2020-04-05
[TOC]
## 資料探勘入門系列教程(八)之使用神經網路(基於pybrain)識別數字手寫集MNIST
在本章節中,並不會對神經網路進行介紹,因此如果不瞭解神經網路的話,強烈推薦先去看《西瓜書》,或者看一下我的上一篇部落格:[資料探勘入門系列教程(七點五)之神經網路介紹](https://www.cnblogs.com/xiaohuiduan/p/12623925.html)
本來是打算按照《Python資料探勘入門與實踐》裡面的步驟使用神經網路來識別驗證碼,但是呢,驗證碼要自己生成,然後我又想了一下,不是有大名鼎鼎的MNIST資料集嗎,為什麼不使用它呢,他不香嗎?
MNIST(Mixed National Institute of Standards and Technology database)相信大家基本上都瞭解過他,大部分的機器學習入門專案就是它。它是一個非常龐大的手寫**數字**資料集([官網](http://yann.lecun.com/exdb/mnist/))。裡面包含了0~9的手寫的數字。部分資料如下:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013433144-779700135.png)
資料集分為兩個部分,訓練集和測試集。然後在不同的集合中分為兩個檔案,資料Images檔案和Labels檔案。在資料集中一個有60,000個訓練資料和10,000個測試資料。圖片的大小是28*28。
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013433481-1961266374.png)
### 下載資料集
萬物始於資料集,儘管官網提供了資料集供我們下載,但是在**sklearn**中提供了更方便方法讓我們下載資料集。程式碼如下:
```python
import numpy as np
from sklearn.datasets import fetch_openml
# X為image資料,y為標籤
X, y = fetch_openml('mnist_784', version=1, return_X_y=True)
```
其中X,y中間既包含了訓練集又包含了測試集。也就是說X或者y中有70,000條資料。 那麼資料是什麼呢?
在X中,每一條資料是一個長為$28 \times 28=784$的陣列,陣列的資料是圖片的畫素值。每一條y資料就是一個標籤,代表這張圖片表示哪一個數字(從0到9)。
然後我們將資料進行二值化,畫素值大於0的置為1,並將資料儲存到資料夾中:
```python
X[X > 0 ] = 1
np.save("./Data/dataset",X)
np.save("./Data/class",y)
```
然後在Data資料夾中就出現了以下兩個檔案:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013433699-1622145825.png)
我們取出dataset中間的一條資料,然後轉成28*28的格式,如下所示:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013433984-1513321167.png)
資料集既可以使用上面的方法得到,也可以從我的[Github](https://github.com/xiaohuiduan/data_mining/tree/master/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/Data)上面進行下載(其中dataset資料集因為GitHub檔案大小的限制所以進行了壓縮,需要解壓才能夠使用)。
### 載入資料集
前面的步驟我們下載好了資料集,現在我們就可以來載入資料了。
````python
import numpy as np
X = np.load("./Data/dataset.npy")
y = np.load("./Data/class.npy")
````
取出X中的一條資料如下所示:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013434316-1351106605.png)
取出y中的一條資料,如下所示:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013434620-1697272099.png)
一切都很完美,但是這裡有一個問題,在神經網路中,輸出層實際上是這樣的:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013435010-788298620.png)
它並不是直接輸出某一個結果,而是輸出$y_1,…,y_j,…,y_l$結果(在MNIST中$l=10$,因為只有10種數字)。以上面的5為例子,輸出層並不是單純的輸出只輸出一個數字,而是要輸出10個值。那麼如何將輸出5變成輸出10個數字呢?這裡我們使用”one hot Encoding“。
One-Hot編碼,又稱為一位有效編碼,主要是採用$N$位狀態暫存器來對$N$個狀態進行編碼,每個狀態都由他獨立的暫存器位,並且在任意時候只有一位有效。
以下面的資料為例,每一行代表一條資料,每一列代表一個屬性。其中第2個屬性只需要3個狀態碼,因為他只有0,1,2三種屬性。這樣我們就可以使用100代表0,010代表1,001代表2。
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013435547-1067813367.png)
那麼這個資料編碼後的資料長什麼樣呢?如下圖:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013435828-526596123.png)
現在我們就可以將前面載入的資料集標籤$y$進行“one hot Encoding”。
程式碼如下:
```python
from sklearn.preprocessing import OneHotEncoder
# False代表不生成稀疏矩陣
onehot = OneHotEncoder(sparse = False)
# 首先將y轉成行長為7000,列長為1的矩陣,然後再進行轉化。
y = onehot.fit_transform(y.reshape(y.shape[0],1))
```
接著就是切割資料集了。將資料集切割成訓練集和測試集。
```python
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(X,y,random_state=14)
```
在神經網路中,我們使用[pybrain](http://pybrain.org/)框架去構建一個神經網路。但是呢,對於pybrain庫,他很與眾不同,他要使用自己的資料集格式,因此,我們需要將資料轉成它規定的格式。
```python
from pybrain.datasets import SupervisedDataSet
train_data = SupervisedDataSet(x_train.shape[1],y.shape[1])
test_data = SupervisedDataSet(x_test.shape[1],y.shape[1])
for i in range(x_train.shape[0]):
train_data.addSample(x_train[i],y_train[i])
for i in range(x_test.shape[0]):
test_data.addSample(x_test[i],y_test[i])
```
終於,資料集的載入就到這裡結束了。接下來我們就可以開始構建一個神經網路了。
### 構建神經網路
首先我們來建立一個神經網路,網路中只含有輸入層,輸出層和一層隱層。
```python
from pybrain.tools.shortcuts import buildNetwork
# X.shape[1]代表屬性的個數,100代表隱層中神經元的個數,y.shape[1]代表輸出
net = buildNetwork(X.shape[1],100, y.shape[1], bias=True)
```
這裡說有以下“bias”的作用。bias代表的是偏置神經元,bias = True代表偏置神經元啟用,也就是在每一層都使用這個這個神經元。偏置神經元如下圖,實際上也就是閾值,只不過換一種說法而已。
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013436117-187317211.png)
現在我們已經構建好了一個比較簡單的神經網路,接下來我們就是使用BP演算法去得到合適的權重值了。
### 反向傳播(BP)演算法
具體的演算法步驟在上一篇[部落格](https://www.cnblogs.com/xiaohuiduan/p/12623925.html)已經介紹過了,很幸運的是在pybrain中間提供了BP演算法的庫供我們使用。這裡就直接上程式碼吧。關於BackpropTrainer更加細節的使用可以看[官網](http://pybrain.org/docs/api/supervised/trainers.html)
```python
from pybrain.supervised.trainers import BackpropTrainer
trainer = BackpropTrainer(net, train_data, learningrate=0.01,weightdecay=0.01)
```
這裡面有幾個引數稍微的說明下:
- net:神經網路
- train_data:訓練的資料集
- learningrate:學習率,也就是下面的$\eta$,同樣它可以使用*lrdecay*這個引數去控制衰減率,具體的就去看[官網文件](http://pybrain.org/docs/api/supervised/trainers.html)吧。
$$
\begin{equation}\begin{array}{l}
\Delta w_{h j}=\eta g_{j} b_{h} \\
\Delta \theta_{j}=-\eta g_{j} \\
\Delta v_{i h}=\eta e_{h} x_{i} \\
\Delta \gamma_{h}=-\eta e_{h} \\
\end{array}\end{equation}
$$
- weightdecay:權重衰減,權重衰減也就是下面的$\lambda$
$$
\begin{equation}
E=\lambda \frac{1}{m} \sum_{k=1}^{m} E_{k}+(1-\lambda) \sum_{i} w_{i}^{2} \\
\lambda \in(0,1)
\end{equation}
$$
然後我們就可以開始訓練了。
```python
trainer.trainEpochs(epochs=100)
```
`epochs`也就是訓練集被訓練遍歷的次數。
接下載便是等待的時間了。等待訓練集訓練成完成。訓練的時間跟訓練集的大小,隱層神經元的個數,電腦的效能,步數等等有關。
切記切記,這一次的程式就不要在阿里雲的學生機上面跑了,還是用自己的機器跑吧。儘管聯想小新pro13 i5版本效能還可以,但是還是跑了一個世紀這麼久,哎(耽誤了我打遊戲的時間)。
### 進行預測
通過前面的步驟以及等待一段時間後,我們就完成了模型的訓練。然後我們就可以使用測試集進行預測。
````python
predictions = trainer.testOnClassData(dataset=test_data)
````
`predictions`的部分資料,代表著測試集預測的結果:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013436461-477788722.png)
然後我們就可以開始驗證準確度了,這裡繼續使用F1評估。這個已經在前面介紹過了,就不再介紹了。
### F1驗證
這裡有個地方需要注意,因為之前的`y_test`資料我們使用`one-hot encoding`進行了編碼,因此我們需要先將`one-hot`編碼轉成正常的形式。
```python
# 取每一行最大值的索引。
y_test_arry = y_test.argmax(axis =1)
```
具體效果如下:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013436687-454155819.png)
然後使用`F1`值進行驗證。
```python
from sklearn.metrics import f1_score
print("F-score: {0:.2f}".format(f1_score(predictions,y_test_arry,average='micro')))
```
然後結果如下:
![](https://img2020.cnblogs.com/blog/1439869/202004/1439869-20200405013436923-1246887652.png)
結果只能說還行吧,不是特別的差,但是也不是特別的好。
### 總結
專案地址:[Github](https://github.com/xiaohuiduan/data_mining/tree/master/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C)。儘管上面的準確度不咋地,只有$86\%$,但是也還行吧,畢竟也是使用了一層隱層,然後隱層也只有100個神經元。
如果電腦的效能不夠的話,可是適當的減少步數和訓練集的大小,以及隱層神經元的個數。
#### 參考
- [wikipedia](https://en.wikipedia.org/wiki/MNIST_database)
- 《Python資料探勘入門與實踐》
- [pybrain](http://pybra