上次優化完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)
速度還是挺慢的,還有待優化。
參考文獻:
連結: 參考連結.