keras使用總結及踩坑(持續中)

1 訓練調用函數

參考:https://blog.csdn.net/xovee/article/details/91357143
訓練調用函數共三個,分別爲fit,fit_generator和train_on_batch。
前面兩個可以通過callbacks實現學習率衰減、啓用tensorboard及記錄訓練過程中的模型,train_on_batch比較難以實現這些功能。

fit和fit_generator的區別在於,前者需要在訓練之前讀入全部的訓練數據,對內存/顯存的佔用很大。後者則是生成器模式,在需要的時候再去生成數據,內存/顯存佔用小,可能在某些情況下需要自己通過使用python 的yield去實現生成器。

2 自定義損失函數層訓練及推理

2.1 自定義損失函數

參考:https://blog.csdn.net/A_a_ron/article/details/79050204
分爲兩種形式,一是自定義損失函數,二是自定義損失函數層。兩者的區別在於後者可以在損失函數中添加可訓練的參數。

2.2 自定義損失函數層訓練

	input_layer = Input(shape=(None,None,3),name='input')

	x = conv_batch(input_layer, 16, 3)
	x = conv_batch(x, 16, 3)
	x = MaxPooling2D(pool_size=(2,2))(x)
	x = conv_batch(x, 32, 3)
	x = res_block(x, 32)
	x = MaxPooling2D(pool_size=(2,2))(x)
	x = conv_batch(x, 64, 3)
	x = res_block(x,64)
	x = res_block(x,64)
	x = MaxPooling2D(pool_size=(2,2))(x)
	x = conv_batch(x, 64, 3)
	x = res_block(x,64)
	x = res_block(x,64)
	x = MaxPooling2D(pool_size=(2,2))(x)
	x = conv_batch(x, 128, 3)
	x = res_block(x,128)
	x = res_block(x,128)
	x = res_block(x,128)
	x = res_block(x,128)

	x = end_block(x)

	Y_true = Input(shape=(None,None,9),name='Input1')
	out = CustomMultiLossLayer(nb_outputs=3)([Y_true,x])

	return Model(inputs=[input_layer,Y_true],outputs=out)

如代碼所示定義網絡,網絡的輸入是 x 和 Y_True,輸出是損失值,損失值由自定義損失函數層得到。這樣就得到了一個包含了損失層的網絡,訓練過程中也對包含損失層在內的所有層進行訓練。

2.3 自定義損失函數層推理

因爲訓練得到的網絡輸入是 x 和 Y_True,輸出是損失層。在推理的時候,並不需要計算損失值,因此不需要輸入Y_True,也不需要執行損失層。那麼對於訓練得到的包含了兩個輸入和損失層的網絡,該如何做推理呢?

解決方法是重新定義一個新的網絡,這個網絡的輸入只有x,輸出是原有網絡的損失層的前一層,可以根據想獲取層的名字將這一層取出來。如下面代碼所示:

wpod_net = keras.models.Model(inputs=wpod_net.inputs[0],outputs=wpod_net.get_layer(layer_name).output)

然後使用新生成的網絡進行預測。

3 使用多顯卡訓練

參考:https://blog.csdn.net/u010122972/article/details/84784245

os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2"

...

model=Model(...) #定義模型結構
model_parallel=multi_gpu_model(model,gpu=n) #使用幾張顯卡n等於幾
model_parallel.compile(...) #注意是model_parallel,不是model

4 訓練過程中學習率衰減

兩種方式:
方式一:

	#學習率衰減
	lr_reduction = keras.callbacks.ReduceLROnPlateau(monitor='loss',factor=0.5,patience=2)
	
	...
	model.fit(x = [train_x, train_y], y=None,batch_size=128, epochs=100, callbacks=[tensorboard,lr_reduction,MyCbk(model,model_path_backup)])

方式二:

def scheduler(epoch):
    # 每隔100個epoch,學習率減小爲原來的1/10
    if epoch % 40 == 0 and epoch != 0:
        lr = K.get_value(model.optimizer.lr)
        K.set_value(model.optimizer.lr, lr * 0.1)
        print("lr changed to {}".format(lr * 0.1))
    return K.get_value(model.optimizer.lr)
    
reduce_lr = keras.callbacks.LearningRateScheduler(scheduler)

train_loss = model.fit(x = [train_x, train_y], y=None,batch_size=128, epochs=100, callbacks=[tensorboard,reduce_lr,MyCbk(model,model_path_backup)])

方式一是自動衰減,這裏是在loss值在兩個epoch內沒有持續下降時,學習率變爲原來的0.5倍。方式二是自定義衰減策略,這裏實現的是每40個epoch學習率變爲原來的0.1倍。要注意第二種方式要設計好下降週期,如果過早的衰減學習率有可能造成損失值在一個較高位置開始進入微調狀態,最終收斂到了一個損失值較大的局部最優解。

5 訓練過程中保存模型

class MyCbk(keras.callbacks.Callback):
	def __init__(self,model,save_path):
		self.model = model
		self.save_path = save_path
		self.index = 0

	def on_epoch_end(self,epoch,logs = None):
		lr = K.get_value(model.optimizer.lr)
		print('epoch:%d,lr:%f'%(epoch,lr))

		for i,log_var in enumerate(model.layers[-1].log_vars):
			print("log_var%d:%f"%(i,np.exp(K.get_value(log_var[0]))**0.5))

		path = splitext(self.save_path+str(epoch))[0]
		model_json = self.model.to_json()
		with open('%s.json' % path,'w') as json_file:
			json_file.write(model_json)
		model.save_weights('%s.h5' % path)
		print('Saved to %s' % path)


train_loss = model.fit(x = [train_x, train_y], y=None,batch_size=128, epochs=100, callbacks=[tensorboard,reduce_lr,MyCbk(model,model_path_backup)])

這裏實現了在每個epoch結束時,即on_epoch_end函數中顯示學習率、保存模型。

6 啓用tensorboard

代碼:

tensorboard = keras.callbacks.TensorBoard(log_dir=outdir+'/'+'log',write_images=True,write_graph=True)

train_loss = model.fit(x = [train_x, train_y], y=None,batch_size=128, epochs=100, callbacks=[tensorboard,reduce_lr,MyCbk(model,model_path_backup)])

操作:cmd窗口中打開日誌保存路徑,如這裏的outdir所指路徑。執行tensorboard --logdir=log,執行完成後給出了tensorboard的網址,一般是localhost:6006,將給出的地址輸入到瀏覽器地址欄,可以打開tensorboard,在scalars中可以看到loss值、acc值等的變化情況,graphs中可以看到模型的網絡結構。

7 使用自定義的多任務損失函數層訓練過程中取出各loss值

一般在使用fit函數訓練時,返回的是一個History對象,記錄了各epoch的loss值。這些值和tensorboard記錄到的loss值是相同的。但是若是定義的損失函數爲自定義的多任務損失,如下面代碼所示:

		self.defloss.append(l1(pts_true*flags,pts*flags,(b,h,w,4*2)))
        self.defloss.append(logloss(obj_probs_true,obj_probs_pred,(b,h,w,1)))
        self.defloss.append(logloss(non_obj_probs_true,non_obj_probs_pred,(b,h,w,1)))
	
        sumloss = 0
       
        for i in range(self.nb_outputs):
            precision = K.exp(-self.log_vars[i])
            if i != 0:
                scale = 0.5
            else:
                scale = 1.
            sumloss += precision * self.defloss[i] + scale * self.log_vars[i]

如果我們訓練過程中想要追蹤各子loss的變化情況,如self.defloss[0],self.defloss[1],self.defloss[2]。該如何做呢?

答案是:在自定義的loss層中,使用add_metric函數將各子loss添加進去,各子loss需要爲Tensor類型

完整的代碼是:

		self.defloss.append(l1(pts_true*flags,pts*flags,(b,h,w,4*2)))
        self.defloss.append(logloss(obj_probs_true,obj_probs_pred,(b,h,w,1)))
        self.defloss.append(logloss(non_obj_probs_true,non_obj_probs_pred,(b,h,w,1)))
	
        sumloss = 0
   
        for i in range(self.nb_outputs):
            precision = K.exp(-self.log_vars[i])
            if i != 0:
                scale = 0.5
            else:
                scale = 1.
            sumloss += precision * self.defloss[i] + scale * self.log_vars[i]
        
        self.add_metric(sumloss,'sumloss')
        self.add_metric(self.defloss[0],'loss1')
        self.add_metric(self.defloss[1],'loss2')
        self.add_metric(self.defloss[2],'loss3')
        self.add_metric(self.log_vars[0],'log_var1')
        self.add_metric(self.log_vars[1],'log_var2')
        self.add_metric(self.log_vars[2],'log_var3')

        self.add_loss(sumloss, inputs=inputs)

這樣使用tensorboard時會記錄到所有添加的對象。add_matric函數的第一個參數爲Tensor,第二個參數爲記錄的該matric的名字。

8 如何使用History記錄多個訓練過程中的變量

和第7個問題一致,一般在使用fit函數訓練時,返回的是一個History對象,記錄了各epoch的loss值。如果想要記錄多個變量的值,該怎麼做?

正確的答案是,在使用第7節介紹的add_metric函數的基礎上,自定義History對象,通過callbacks傳遞到fit函數中

代碼如下:

class LossHistory(keras.callbacks.Callback):
	def on_train_begin(self,logs={}):
		self.sumloss = []
		self.loss1 = []
		self.loss2 = []
		self.loss3 = []
		self.log_val1 = []
		self.log_val2 = []
		self.log_val3 = []

	'''
	def on_batch_end(self,batch,logs={}):
		self.losses.append(logs.get('sumloss'))
		self.mean1.append(logs.get('loss1'))
		self.mean2.append(logs.get('loss2'))
		self.mean3.append(logs.get('loss3'))
	'''

	def on_epoch_end(self,epoch,logs={}):
		self.sumloss.append(logs.get('sumloss'))
		self.loss1.append(logs.get('loss1'))
		self.loss2.append(logs.get('loss2'))
		self.loss3.append(logs.get('loss3'))
		self.log_val1.append(logs.get('log_var1'))
		self.log_val2.append(logs.get('log_var2'))
		self.log_val3.append(logs.get('log_var3'))

#fit函數將定義的LossHistory對象傳遞進去
history = LossHistory()
train_loss = model.fit(x = [train_x, train_y], y=None,batch_size=128, epochs=10, callbacks=[tensorboard,history,lr_reduction,MyCbk(model,model_path_backup)])

這樣在訓練完成後,history對象內部的各list記錄了各個自定義關注的變量的值。

兩點需要說明:

  • 自定義的LossHistory類繼承自keras.callbacks.Callback類。該類定義了很多函數,可以在on_train_begin中定義數據結構,在on_batch_end/on_epoch_end函數中記錄值,前者每一個batch訓練完成後記錄一次,後者每一個epoch訓練完成後記錄一次。該類還有其他函數,可以提供更多的功能;
  • 在on_batch_end/on_epoch_end中,獲取各變量值的時候,是根據變量名獲取的。如self.loss1.append(logs.get(‘sumloss’)),這個變量名和第7節中定義的變量名是一一對應的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章