天天看點

python深度學習之GA(遺傳算法)優化LSTM神經網絡

上次優化完bp神經網絡後,發現用matlab優化進階的神經網絡太慢了,于是用tensorflow繼續學習GA優化部分。

1.項目概述

本文采用的是python程式設計,使用的資料集是mnist手寫資料集,該資料集主要是對0-9的手寫數字型識别,雖然說圖像識别方面用CNN識别率較高,但這裡LSTM也可以擷取較高的準确率。

2.優化參數

本文優化的是LSTM的層數參數和各層神經元參數,其中包含了lstm層和Dense層,其中我們規定了神經網絡的層數不超過3層,每層的神經元個數在[32,256]之間。

3.注意事項

1.本文的遺傳算法編碼并非2進制編碼,而是由各個參數組成的一維數組。

2.在遺傳算法交叉、變異操作中,關于神經網絡層數的參數不進行交叉,隻對神經網神經元個數進行交叉、變異。

3.檔案為兩部分每一部分為lstm的部分,一部分為ga部分

4.項目如下(代碼已詳細注釋,不懂的可在評論區詢問)

LSTM部分

#本章節GA_LSTM是關于遺傳算法優化lstm算法的層數和全連接配接層數及每層神經元的個數
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib as plt
from tensorflow.keras.layers import Input,LSTM,Dropout,Dense,BatchNormalization
from tensorflow.keras import optimizers,losses,metrics,models,Sequential
'''
本文的主要内容如下:
1.本文章是對lstm網絡的優化,優化的參數主要有:lstm層的層數,lstm隐藏層的神經元個數,dense層的層數,dense層的神經元個數
2.本文章利用的是遺傳算法進行優化,其中編碼形式并未采用2進制編碼,隻是将2數組之間的元素交換位置。
3.本文的lstm和dense的層數都在1-3的範圍内,因為3層的網絡足以拟合非線性資料
4.程式主要分為2部分,第一部分是lstm網絡的設計,第二部分是遺傳算法的優化。
5.代碼的解釋已詳細寫在對應的部分,有問題的同學可以在評論區進行交流
'''
#導入資料集,本文用的是mnist手寫資料集,該資料主要是對手寫體進行識别0-9的數字
def load_data():
    #從tensorflow自帶的資料集中導入資料
    (x_train,y_train),(x_test,y_test)=tf.keras.datasets.mnist.load_data()
    #主要進行歸一化操作
    x_train, x_test = x_train / 255.0, x_test / 255.0
    return x_train,x_test,y_test,y_train


#定義LSTM模型
def lstm_mode(inputs, units_num, sequences_state):
    #input主要是用來定義lstm的輸入,input的一般是在第一層lstm層之前,units_num即是隐藏層神經元個數,sequence_state即是lstm層輸出的方式
    lstm=LSTM(units_num,return_sequences=sequences_state)(inputs)
    print("lstm:",lstm.shape)
    return lstm


#定義全連接配接層、BN層
def dense_mode(input,units_num):
    #這裡主要定義全連接配接層的輸入,input參數定義dense的第一次輸入,units_num代表隐藏層神經元個數
    #這裡定義全連接配接層,采用L2正則化來防止過拟合,激活函數為relu
    dense=Dense(units_num,kernel_regularizer=tf.keras.regularizers.l2(0.001),activation='relu')(input)
    print("dense:",dense.shape)
    #定義dropout層,機率為0.2
    drop_out=Dropout(rate=0.2)(dense)
    #定義BN層,可以了解為是隐藏層的标準化過程
    dense_bn=BatchNormalization()(drop_out)
    return dense,drop_out,dense_bn


#這裡定義的即是評價lstm效果的函數——也是遺傳算法的适應度函數
def aim_function(x_train,y_train,x_test,y_test,num):
    #這裡傳入資料和參數數組num,num儲存了需要優化的參數
    #這裡我們設定num數組中num[0]代表lstm的層數。
    lstm_layers=num[0]
    #num[2:2 + lstm_layers]分别為lstm各層的神經元個數,有同學不知道num(1)去哪了(num(1)為全連接配接層的層數)
    lstm_units = num[2:2 + lstm_layers]
    #将num
    lstm_name = list(np.zeros((lstm_layers,)))
    # 設定全連接配接層的參數
    #num(1)為全連接配接的參數
    lstm_dense_layers = num[1]
    #将lstm層之後的地方作為全連接配接層各層的參數
    lstm_dense_units = num[2 + lstm_layers: 2 + lstm_layers + lstm_dense_layers]
    #
    lstm_dense_name = list(np.zeros((lstm_dense_layers,)))
    lstm_dense_dropout_name = list(np.zeros((lstm_dense_layers,)))
    lstm_dense_batch_name = list(np.zeros((lstm_dense_layers,)))
    #這主要是定義lstm的第一層輸入,形狀為訓練集資料的形狀
    inputs_lstm = Input(shape=(x_train.shape[1], x_train.shape[2]))


    #這裡定義lstm層的輸入(如果為第一層lstm層,則将初始化的input輸入,如果不是第一層,則接受上一層輸出的結果)
    for i in range(lstm_layers):
        if i == 0:
            inputs = inputs_lstm
        else:
            inputs = lstm_name[i - 1]
        if i == lstm_layers - 1:
            sequences_state = False
        else:
            sequences_state = True
        #通過循環,我們将每層lstm的參數都設計完成
        lstm_name[i] = lstm_mode(inputs, lstm_units[i], sequences_state=sequences_state)

    #同理設計全連接配接層神經網絡的參數
    for i in range(lstm_dense_layers):
        if i == 0:
            inputs = lstm_name[lstm_layers - 1]
        else:
            inputs = lstm_dense_name[i - 1]

        lstm_dense_name[i], lstm_dense_dropout_name[i], lstm_dense_batch_name[i] = dense_mode(inputs,units_num=lstm_dense_units[i])

    #這裡是最後一層:分類層,softmax
    outputs_lstm = Dense(10, activation='softmax')(lstm_dense_batch_name[lstm_dense_layers - 1])
    print("last_dense",outputs_lstm.shape)

    # 利用函數式調試神經網絡,調用inputs和outputs之間的神經網絡
    LSTM_model =tf.keras.Model(inputs=inputs_lstm, outputs=outputs_lstm)
    # 編譯模型
    LSTM_model.compile(optimizer=optimizers.Adam(),
                       loss='sparse_categorical_crossentropy',
                       metrics=['accuracy'])
    print("訓練集形狀",x_train.shape)

    history = LSTM_model.fit(x_train, y_train,batch_size=32, epochs=1, validation_split=0.1, verbose=1)
    # 驗證模型,model.evaluate傳回的值是一個數組,其中score[0]為loss,score[1]為準确度
    acc = LSTM_model.evaluate(x_test, y_test, verbose=0)
    return acc[1]


           

GA優化部分

#GA優化lstm的遺傳算法部分
import GA_LSTM_lstm as ga
import numpy as np
import pandas as pd
import matplotlib as plt
import os
#不顯示警告資訊
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
#設定遺傳算法的參數
DNA_size = 2
DNA_size_max = 8    # 每條染色體的長度
POP_size = 20       # 種群數量
CROSS_RATE = 0.5    # 交叉率
MUTATION_RATE = 0.01# 變異率
N_GENERATIONS = 40  # 疊代次數


#接收資料
x_train,x_test,y_test,y_train = ga.load_data()

# 定義适用度函數,即aim_function函數,接收傳回值
def get_fitness(x):
    return ga.aim_function(x_train,y_train,x_test,y_test,num=x)

# 生成新的種群
def select(pop,fitness):
    #這裡主要是進行選擇操作,即從20個種群中随機選取重複随機采樣出20個種群進行種群初始化操作,p代表被選擇的機率,這裡采用的是輪盤賭的方式
    idx = np.random.choice(np.arange(POP_size),size=POP_size,replace=True,p=fitness/fitness.sum())
    #将選擇的種群組成初始種群pop
    return pop[idx]

# 交叉函數
def crossover(parent,pop):
    #這裡主要進行交叉操作,随機數小于交叉機率則發生交叉
    if np.random.rand() < CROSS_RATE:
        #從20個種群中選擇一個種群進行交叉
        i_ = np.random.randint(0,POP_size,size=1)   # 染色體的序号
        #這裡将生成一個8維的2進制數,并轉換層成bool類型,true表示該位置交叉,False表示不交叉
        cross_points = np.random.randint(0,2,size=DNA_size_max).astype(np.bool)     # 用True、False表示是否置換

        # 這一部分主要是對針對不做變異的部分
        for i,point in enumerate(cross_points):
            '''
            第一部分:這裡是指該位點為神經元個數的位點,本來該交換,但其中位點為0,
            什麼意思呢?即[2,3,32,43,34,230,43,46,67]和[2,2,32,54,55,76,74,26,0],末尾的0位置就
            不應該交叉,因為交叉完後,會對前兩位的參數産生影響。
            
            第二部分:即對前兩位不進行交叉操作,因為前兩位代表的是層數,層數交叉後會對神經元的個數産生影響
            '''
            #第一部分
            if point == True and pop[i_,i] * parent[i] == 0:
                cross_points[i] = False
            #第二部分
            if point == True and i < 2:
                cross_points[i] = False
        # 将第i_條染色體上對應位置的基因置換到parent染色體上
        parent[cross_points] = pop[i_,cross_points]
    return parent


# 定義變異函數
def mutate(child):
    #變異操作也隻是針對後6位參數
    for point in range(DNA_size_max):
        if np.random.rand() < MUTATION_RATE:
            #2位參數之後的參數才才參與變異
            if point >= 2:
                if child[point] != 0:
                    child[point] = np.random.randint(32,257)
    return child

#初始化2列層數參數
pop_layers = np.zeros((POP_size,DNA_size),np.int32)
pop_layers[:,0] = np.random.randint(1,4,size=(POP_size,))
pop_layers[:,1] = np.random.randint(1,4,size=(POP_size,))

# 種群
#初始化20x8的種群
pop = np.zeros((POP_size,DNA_size_max))
# 将初始化的種群指派,前兩列為層數參數,後6列為神經元個數參數
for i in range(POP_size):
    #随機從[32,256]中抽取随機數組組成神經元個數資訊
    pop_neurons = np.random.randint(32,257,size=(pop_layers[i].sum(),))
    #将2列層數資訊和6列神經元個數資訊合并乘8維種群資訊
    pop_stack = np.hstack((pop_layers[i],pop_neurons))
    #将這些資訊指派給pop種群進行初始化種群
    for j,gene in enumerate(pop_stack):
        pop[i][j] = gene

# 在疊代次數内,計算種群的适應度函數
for each_generation in range(N_GENERATIONS):
    # 初始化适應度
    fitness = np.zeros([POP_size,])
    # 周遊20個種群,對基因進行操作
    for i in range(POP_size):
        pop_list = list(pop[i])
        # 第i個染色體上的基因
        #對指派為0的基因進行删除
        for j,each in enumerate(pop_list):
            if each == 0.0:
                index = j
                pop_list = pop_list[:j]
        #将基因進行轉換為int類型
        for k,each in enumerate(pop_list):
            each_int = int(each)
            pop_list[k] = each_int
        #将計算出來的适應度填寫在适應度數組中
        fitness[i] = get_fitness(pop_list)
        #輸出結果
        print('第%d代第%d個染色體的适應度為%f'%(each_generation+1,i+1,fitness[i]))
        print('此染色體為:',pop_list)
    print('Generation:',each_generation+1,'Most fitted DNA:',pop[np.argmax(fitness),:],'适應度為:',fitness[np.argmax(fitness)])

    # 生成新的種群
    pop = select(pop,fitness)

    # 複制一遍種群
    pop_copy = pop.copy()
    #周遊pop中的每一個種群,進行交叉,變異,遺傳操作
    for parent in pop:
        child = crossover(parent,pop_copy)
        child = mutate(child)
        parent = child

           

5.運作結果如下

1688/1688 [==============================] - 21s 10ms/step - loss: 0.7340 - accuracy: 0.8076 - val_loss: 0.1616 - val_accuracy: 0.9730
第1代第1個染色體的适應度為0.966900
此染色體為: [1, 1, 151, 248]
lstm: (None, 83)
dense: (None, 200)
last_dense (None, 10)
訓練集形狀 (60000, 28, 28)
1688/1688 [==============================] - 19s 10ms/step - loss: 0.7532 - accuracy: 0.7855 - val_loss: 0.1744 - val_accuracy: 0.9672
第1代第2個染色體的适應度為0.961300
此染色體為: [1, 1, 83, 200]
lstm: (None, 28, 135)
lstm: (None, 28, 41)
lstm: (None, 126)
dense: (None, 47)
last_dense (None, 10)
訓練集形狀 (60000, 28, 28)
1688/1688 [==============================] - 33s 17ms/step - loss: 0.7534 - accuracy: 0.7755 - val_loss: 0.1258 - val_accuracy: 0.9717
第1代第3個染色體的适應度為0.967800
此染色體為: [3, 1, 135, 41, 126, 47]
lstm: (None, 28, 247)
lstm: (None, 28, 82)
lstm: (None, 71)
dense: (None, 190)
dense: (None, 161)
dense: (None, 124)
last_dense (None, 10)
訓練集形狀 (60000, 28, 28)

           

速度還是挺慢的,還有待優化。

參考文獻:

連結: 參考連結.

繼續閱讀