keras框架下的深度學習(二)二分類和多分類問題

眼前有座山發表於2021-10-26

  本文第一部分是對資料處理中one-hot編碼的講解,第二部分是對二分類模型的程式碼講解,其模型的建立以及訓練過程與上篇文章一樣;在最後我們將訓練好的模型儲存下來,再用自己的資料放入儲存下來的模型中進行分類(在後面的文章中會詳細討論如何使用自己的資料去訓練模型,或者讓儲存下來的模型去處理自己的資料)。第三部分是多分類模型,多分類的過程和二分類很相似,只是在程式碼中有些地方需要做出調整。

  第二部分是本文的重點。 

一:one-hot編碼

  通過第一篇文章我們知道,對於使用keras來進行深度學習網路的搭建,因為keras中已經把深度學習的步驟都打包成函式或者檔案,我們只需要呼叫即可,所以我們需要在資料的處理(怎麼把資料處理好放入框架裡面)和引數的調整上用功夫。在本文中,我們討論一種資料處理的方法:ong-hot編碼,即根據一個矩陣生成一個只有0和1的矩陣,0表示之前的矩陣在這個索引處沒有數值,1表示之前的矩陣在這個索引處有數值。比如說有一條資料是a=[1,8,4,2],使用one-hot編碼生成一行10列的矩陣,那麼將會是[0,1,1,0,1,0,0,0,1,0],表示這個矩陣表示分別在索引1,2,3,8處有數值,而其他地方沒有數值(one-hot矩陣的列數要大於a矩陣中數值的最大數)。

  下面我們在pycharm中隨機生成一個[3,10],取值在0到20之間的矩陣,然後生成[3,20]的one-hot矩陣。

1.首先生成[3,10]的矩陣a:

import numpy as np
c1 = np.random.choice(20,10,replace=False)  #在0到20之間我們隨機選擇10不重複的數字,replace=False 表示選擇的數字不能有重複
c2 = np.random.choice(20,10,replace=False)
c3 = np.random.choice(20,10,replace=False)
a = np.array([c1,c2,c3])                    #把前面三個[1,10]矩陣合成[3,10]矩陣

  我們使用np.random.choice(20,10,replace=False)來生成10個在0到20之間不重複的數字,replace= False表示生成的數字不能有重複。

2.然後我們根據矩陣a生成one-hot矩陣b:

def one_hot(sample,dim=20):
    result = np.zeros((len(sample),dim))    
    for i,sample in enumerate(sample):      #enumerate會給i和sample自動編組   
        result[i,sample] = 1.          #索引在哪個位置有數值,就給賦值為1
    return result
b = ver(a)

  (1)在第一行程式碼中,我們建立一個one-hot函式。

  (2)第二行程式碼我們生成一個行是資料個數,列是20的全零矩陣result(列是矩陣a中數值最大的數)。

  (3)第三行程式碼中的enumerate函式表示將一個可遍歷的資料物件(如列表)組合為一個索引序列,同時列出資料和資料下標。比如說我們選擇隨機給定一個矩陣:

list_a = [[2,3,4,5],[4,6,2,4]]

  然後列印矩陣list_a和使用enumerate函式後的矩陣:

print('list_a為:',list_a)
print('enumerate函式生成的矩陣為:',list(enumerate(list_a)))

  輸出結果如下:

list_a為: [[2, 3, 4, 5], [4, 6, 2, 4]]
enumerate函式生成的矩陣為: [(0, [2, 3, 4, 5]), (1, [4, 6, 2, 4])]

  整個過程:

list_a = [[2,3,4,5],[4,6,2,4]]
print('list_a為:',list_a)
print('enumerate函式生成的矩陣為:',list(enumerate(list_a)))
輸出結果:
list_a為: [[2, 3, 4, 5], [4, 6, 2, 4]]
enumerate函式生成的矩陣為: [(0, [2, 3, 4, 5]), (1, [4, 6, 2, 4])]

  第四行程式碼表示在sample有數值的索引處賦值為1.

  最後返回result矩陣,並且根據資料a生成one-hot矩陣b。

 (4)列印矩陣a和b,顯示結果:

print(b)

結果:
a= [[ 6 15 14  9 19 13  4  1  2  3]
 [ 9 11 14 17 13 12  2 10  4 18]
 [18  2 11  8  5 14 13 12 19 16]]
b= [[0. 1. 1. 1. 1. 0. 1. 0. 0. 1. 0. 0. 0. 1. 1. 1. 0. 0. 0. 1.]
 [0. 0. 1. 0. 1. 0. 0. 0. 0. 1. 1. 1. 1. 1. 1. 0. 0. 1. 1. 0.]
 [0. 0. 1. 0. 0. 1. 0. 0. 1. 0. 0. 1. 1. 1. 1. 0. 1. 0. 1. 1.]]

3.總程式碼:

import numpy as np
c1 = np.random.choice(20,10,replace=False)  #在0到20之間我們隨機選擇10不重複的數字,replace=False 表示選擇的數字不能有重複
c2 = np.random.choice(20,10,replace=False)
c3 = np.random.choice(20,10,replace=False)
a = np.array([c1,c2,c3])                    #把前面三個[1,10]矩陣合成[3,10]矩陣
def ver(sample,dim=20):
    result = np.zeros((len(sample),dim))    
    for i,sample in enumerate(sample):      #enumerate會給i和sample自動編組
        result[i,sample] = 1.    #索引在哪個位置有數值,就給賦值為1
    return result
b = ver(a)
print('a=',a)
print('b=',b)

   以上就是對資料處理的one-hot方法,下面我們詳細講解keras框架下的二分類問題。

 

二:二分類問題

  對於二分類問題,我們這裡採用的資料集是IMDB,它包含50000條嚴重兩極分化的評論,資料集分為用於訓練的25000跳評論和用於測試的25000條評論,兩者都包含50%的證明評論和50%的負面評論。

1, 對於資料的預處理

  首先我們使用keras匯入imdb資料集:

from keras.datasets import imdb
(train_data,train_label),(test_data,test_label)=imdb.load_data(num_words=10000)

  其過程和手寫體的匯入過程一樣,不過多了一個引數的匯入:num_woeds表示只保留訓練資料集前10000個最常出現的單詞,低頻率的單詞會被捨去(比如說人名)

       在train_data內的資料是1到10000的數字,表示每個數字出現的頻率;而train_label是0或1的單個數字,0表示負面評論,1表示正面評論。比如說我們列印第一個資料以及它的標籤(列印過程程式碼不算入最後的總程式碼,只是檢視資料):

print(train_data[0]) 
print(train_label[0])
[1, 14, 22, 16, …19, 178, 32]
1

       第一個資料一共包含218個單詞,而它的標籤是1。

       這裡,你很可能會有問題,為什麼要把單詞變成數字?這樣深度學習的框架是怎麼學習到評論的好壞的?我的理解是比如說給我們人類一些數字,當給你的是單數的時候就給你糖,當是偶數的時候就給你一個懲罰,次數多了之後我們就知道了單數表示這有糖,是好的。類似於這個,負面評論中可能有很多負面的單詞,因為它們是編號碼的,所以負面的單詞不太可能出現在正面評論中,又或者出現的次數很少。所以我們把單詞變成純數字,交給深度學習去處理,從而得到一個正面或者負面評價的結果(讓單純的數值賦予其不同的意義)。

       言歸正傳,在匯入資料後,我們需要對資料進行處理,在把資料給到深度學習的時候,我們需要把資料變成張量,把資料向量化(簡化資料)所以我們採用one-hot編碼的方法,把train_data和test_data中的資料變成元素只有0和1的矩陣,至於train_label和test_label,因為只有單獨的一個資料,我們用numpy把它進行one-hot編碼,具體程式碼如下:

def one_hot(sample,dim=10000):
    result = np.zeros((len(sample),dim))
    for i,sample in enumerate(sample):
        result[i,sample] = 1
    return result
train_data = one_hot(train_data)
test_data = one_hot(test_data)

train_label = np.asarray(train_label).astype('float32')
test_label = np.asarray(test_label).astype('float32')

       one-hot的方法在上面已經詳細講述過了,所以這裡不詳細講解;在這裡我們定義一個one_hot函式,然後把train_data和test_data的資料放進去處理。我們列印train_data[0]和train_lable[0]看看資料是什麼樣的:

print(train_data[0])
print(test_label[0])

[0. 1. 1. ... 0. 0. 0.]
0.0

2.搭建網路框架

       在這裡我們採用三層全連線層,其中前兩層是16個隱藏層,採用的函式時relu啟用函式;最後是一層輸出一個標量,表示評論的情感,採用sigmid啟用函式。程式碼如下所示:

from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(16,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(16,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

  對於全連線層(Dense)來說,16個隱藏單元,表示將輸入的資料投影到16維表示空間中。Relu函式是一個非線性的啟用函式,其函式為:。假設輸入的資料是X(單列矩陣),那麼假設經過16個隱藏單元的全連線層後的輸出為Z,則可以得到:Z=Relu(dot(W,X)+b)。其中W表示權重矩陣,其形狀是(X,16),b是偏執向量,含有16個數值。當隱藏單元個數越大時,網路越能學到複雜的表示,但是其計算代價也會更高。

3.編譯

model.compile(
    optimizer='rmsprop', #可以匯入包,自己定義
    loss='binary_crossentropy',
    metrics=['accuracy']
)

       在編譯中,我們可以自定義損失函式loss,通過匯入keras中的優化器,對其引數進行修改,從而使模型得到更好的效果。下面是匯入損失函式的程式碼(替換上面的程式碼):

from keras import optimizers
model.copile(
optimizer= optimizers.RMSprop(lr=0.001), #可以匯入包,自己定義
    loss='binary_crossentropy',
    metrics=['accuracy']
)

     為了更好的理解優化器的內容,我們可以在keras的檔案裡面找到optimizers.py檔案,裡面包含著很多個類,每個類都是一種優化器的具體過程。我們找到RMSprop優化函式:

    然後檢視lr,如圖所示:

4.迴圈訓練

  訓練開始前,我們需要說明一點,就是在訓練階段(先不去管測試集),我們需要在訓練集中分出一部分資料來進行檢測,檢測我們的模型泛化能力,也就是檢測模型遇到新資料時的表現(有時候雖然準確率高,其實是模型把資料和對應的標籤背了下來,對新資料的處理會十分差勁)所以,在我們開始迴圈時,我們在訓練集中抽一部分做驗證集,具體程式碼如下:

train_data_val = train_data[:10000] 
train_data = train_data[10000:]

train_label_val = trian_label[:10000]
train_label = trian_label[10000:]

    其中在train_data和train_label中抽出10000個資料和標籤作為驗證集(validation),剩下的作為訓練集。注:[:10000]表示取0到9999索引的資料(也就是取10000個資料,但是不要10000)

    下面我們把資料丟入網路中進行訓練以及驗證:

history=model.fit(train_data,train_label,epochs=100,batch_size=512,validation_data=(train_data_val,train_lable_val ))

    其中,我們把模型訓練以及驗證得到的結果賦值給history(model.fit()中得到的結果是以字典的形式返回)這樣方便我們後面畫圖檢視模型訓練以及驗證的情況;上述函式fit中的引數於上文一致,不過多了一個驗證的過程,即:validation_data=(train_data_val,train_lable_val )

    下面我們引用history中的引數,畫圖檢視訓練以及驗證的情況,程式碼如下:

history_dict=history.history
loss_values = history_dict['loss']
val_loss_values=history_dict['val_loss']
epochs=range(1,len(loss_values)+1)

plt.plot(epochs,loss_values,'bo',label='training loss')
plt.plot(epochs,val_loss_values,'b',label='validation loss')
plt.title('training and validation')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

    第一行程式碼表示,我們使用history函式呼叫history中的引數賦值給history_dict,第二行第三行表示取出訓練損失值和驗證損失值(在這裡,若是不知道history中有什麼值,可以在執行時檢視頁面,history會有四個key以及他們返回的value,其中key分別是:loss,val_loss,acc,val_acc;分別表示損失值,驗證損失值,準確率,驗證準確率)

    第四行表示畫圖的橫座標.

 第五到十一行是畫圖,執行以上程式碼,將會得到的圖形如圖所示:

  圖中訓練集的損失值是不斷下降,逼近於零,但是當模型執行驗證集的時候,也就是遇見新的資料時,在大概第4個epoch的地方,其驗證集的損失值直線上升,出現過擬合現象。對於過擬合的處理有很多方法,會在以後的文章中闡述,這裡我們讓epochs為4,總共執行4論就結束,讓其處於在驗證集最佳的位置。(這裡只畫了損失值的影像,對於準確率(acc)的影像可以自行編寫程式碼檢視)我們最後更改把histor=model.fit()這句程式碼更改成如下程式碼:

history=model.fit(train_data,train_label,epochs=4,batch_size=512,validation_data=(train_data_val,train_lable_val ))

5.測試訓練的網路模型

result = model.evaluate(test_data,test_label)
print(result)

  最後使用evaluate函式進行模型的最後測試,並且把數值列印出來,檢視測試的結果。結果:[0.32555921449184416, 0.8685600161552429],準確率可到達86%。

7.總程式碼:

from keras.datasets import imdb
from keras import models
from keras import layers

(train_data,train_label),(test_data,test_label)=imdb.load_data(num_words=10000)
def one_hot(sample,dim=10000):
    result = np.zeros((len(sample),dim))
    for i,sample in enumerate(sample):
        result[i,sample] = 1
    return result
train_data = one_hot(train_data)
test_data = one_hot(test_data)

train_label = np.asarray(train_label).astype('float32')
test_label = np.asarray(test_label).astype('float32')

model = models.Sequential()
model.add(layers.Dense(16,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(16,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

model.compile(
    optimizer='rmsprop', #可以匯入包,自己定義
    loss='binary_crossentropy',
    metrics=['accuracy']
)

train_data_val = train_data[:10000] #[:10000] 
train_data = train_data[10000:]


train_label_val = trian_label[:10000]
train_label = trian_label[10000:]

history=model.fit(train_data,train_label,epochs=4,batch_size=512,validation_data=(train_data_val,train_lable_val ))

history_dict=history.history
loss_values = history_dict['loss']
val_loss_values=history_dict['val_loss']
epochs=range(1,len(loss_values)+1)

plt.plot(epochs,loss_values,'bo',label='training loss')
plt.plot(epochs,val_loss_values,'b',label='validation loss')
plt.title('training and validation')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

result = model.evaluate(test_data,test_label)
print(result)

8.用訓練好的模型處理自己的資料

  在我們使用自己的資料識別的時候,我們先把自己的模型儲存下來,在程式碼的最後我們加上下面的程式碼儲存模型:

model.save('tow_classes.h5')

 (類似的,我們可以在網上下載別人訓練好的模型然後進行自己資料的識別)我們會在旁邊檔案目錄裡面看到儲存的該模型:

 

 

 然後我們在儲存的模型下檔案的同目錄下,重新建立一個.py檔案,進行自己資料的處理。

 首先我們使用keras匯入自己的模型檔案:

from keras import models
model=models.load_model('tow_classes.h5')

 然後我們隨機制造一組資料放入模型中進行預測(可以套用到之前手寫體數字識別的模板中,不過得對圖片進行處理)

 隨機生成資料a(這裡只做示範,資料沒有包含任何資訊):

a0 = np.random.choice(2000,200,replace=False)
a1 = np.random.choice(2000,200,replace=False)
a2 = np.random.choice(2000,200,replace=False)
a3 = np.random.choice(2000,200,replace=False)
a4 = np.random.choice(2000,200,replace=False)
a = np.array([[a1],[a2],[a3],[a4]])

    隨機生成4組資料,組成a矩陣

    然後把矩陣a用one-hot編碼:

def one_hot(sample,dim=10000):
    result=np.zeros((len(sample),dim))
    for i,sample in enumerate(sample):
        result[i,sample] = 1
    return result

b=one_hot(a)

    最後把b矩陣丟入模型得到一個分類的概率值以及最後分類的結果:

predict = model.predict(b) 
predict_class = model.predict_classes(b) 

    列印結果檢視:

print(predict)
print(predict_class)
[[0.02564826]
 [0.46361473]
 [0.98723143]
 [0.07622854]]
[[0]
 [0]
 [1]
 [0]]

 模型把第一、二、四組的資料分成第一類,把第三組資料分成第二類。

 總程式碼:

from keras import models
import numpy as np
model=models.load_model('tow_classes.h5')
a0 = np.random.choice(2000,200,replace=False)
a1 = np.random.choice(2000,200,replace=False)
a2 = np.random.choice(2000,200,replace=False)
a3 = np.random.choice(2000,200,replace=False)
a4 = np.random.choice(2000,200,replace=False)
a = np.array([[a1],[a2],[a3],[a4]])
def one_hot(sample,dim=10000):
    result=np.zeros((len(sample),dim))
    for i in enumerate(sample):
        result[i] = 1
    return result

b=one_hot(a)

predict = model.predict(b)
predict_class = model.predict_classes(b)
print(predict)
print(predict_class)

 

三、多分類問題

    對於多分類問題,其模型框架與二分類十分類似,只是有幾個個地方稍有不同,在多分類模型中,我們採用路透社資料集,它包含許多短新聞以及其對應的主題。一共包含46個不同的主題,每組至少有10組樣本(資料集詳情,參考reuters中的原始碼)。

 這裡直接上總程式碼:

from keras.datasets import reuters
from keras import layers
from keras import models
from keras.utils import to_categorical
import numpy as np
#1對資料的處理
(train_data,train_labels),(test_data,test_labels) = reuters.load_data(num_words=10000)

def ver(sequence,dim=46):
    resules=np.zeros((len(sequence),dim)) 
    for i,sequence in enumerate(sequence): #編號  屬於哪個新聞社
        resules[i,sequence]=1  #有資料的地方標1
    return resules

x_trian=ver(train_data)
x_test=ver(test_data)


one_hot_trian_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
x_val=x_trian[:1000]
partial_x_train=x_trian[1000:]

y_val=one_hot_trian_labels[:1000]
partial_y_train=one_hot_trian_labels[1000:]

#2.搭建網路框架
model=models.Sequential()
model.add(layers.Dense(64,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(64,activation='relu'))
model.add(layers.Dense(46,activation='softmax'))

#3.編譯
model.compile(
    optimizer='rmsprop',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)
#4.迴圈訓練
history=model.fit(partial_x_train,partial_x_train,epochs=9,batch_size=512,validation_data=(x_val,y_val))
#5.測試訓練的網路模型
result = model.evaluate(x_test,one_hot_test_labels)
print(result)

  多分類的資料處理和二分類一樣,但是有幾點不同:1.我們把驗證集的劃分移到了前面(dim變成了46,表示有46個報社)。2.在搭建網路框架中,第一二層全連線層的隱藏元個數變成了64,最後輸出變成了46,其啟用函式變成了softmax啟用函式。(注意:以上沒有寫出畫圖的程式碼,畫圖程式碼與二分類一樣,最後得到結果是模型到了第九次左右的效果為最佳;在畫圖前,可以先讓模型執行幾十次,如,讓epochs=30,最後得到影像後,根據影像獲得合適的epochs)

  在最後我們可以與二分類模型一樣,儲存訓練好的模型,自己模擬一組資料放入模型中進行分類預測(在自己模擬資料放入模型預測之前,需要檢視資料的格式,如檢視訓練集資料的第一個資料格式:print(trian_data[0].size))。(在後續的文章中,我們會從零開始製作自己的資料集,然後利用深度學習模型來進行處理)

相關文章