為什么加載之前保存的Keras模型得出不一樣的結果:經驗和教訓
譯文現在,機器學習模型在生產環境中的使用比以往都要廣泛。Keras就是這樣一種流行的庫,用于創建強大的機器學習和深度學習模型。然而,這些模型的訓練過程常常計算開銷大,還費時,具體取決于實際處理的數據和模型架構。一些模型需要數周到數月的時間來訓練。因此,能夠在本地存儲模型、需要進行預測時再次檢索它們變得至關重要。但如果由于某種原因保存的模型沒有正確加載,該怎么辦?我會根據本人的經驗試著給出答案。
我不會詳細介紹如何使用和保存Keras模型,只是假設讀者熟悉該過程,直接介紹如何處理加載時意外的模型行為。也就是說,在訓練存儲在Model變量中的Keras模型之后,我們希望將其保存為原樣,那樣下次加載時我們可以跳過訓練,就進行預測。
我首選的方法是保存模型的權重,權重在模型創建開始時是隨機的,隨著模型的訓練而加以更新。于是我點擊了model.save_weights(“model.h5”)。創建了“model.h5”文件,含有模型學習到的權重。接下來,在另一個會話中,我使用與以前相同的架構重建模型,并使用 new_model.load_weights(“model.h5”)加載我保存的訓練權重。一切似乎都很好。只是我點擊 new_model.predict(test_data)后,得到的準確性為零,不知道為什么。
事實證明,模型無法做出正確的預測有諸多原因。我在本文試著總結最常見的原因,并介紹如何解決。
1. 先仔細檢查數據。
我知道這似乎很明顯,但是從磁盤重新加載模型時,一有疏忽就會導致性能下降。比如說,如果您在構建語言模型,應確保在每個新會話中,您執行以下操作:
- 重新檢查類標簽的順序。如果您將它們映射到數字,重新檢查在每個會話中每個類標簽都有相同的數字。如果您使用list(set())函數來檢索,可能會發生這種情況,該函數每次都會以不同的順序返回您的標簽。這最終可能會搞亂您的標簽預測。
- 檢查數據集。如果您的測試數據還沒有在另一個文件中,檢查訓練-測試拆分不是隨機的,以便每次進行預測時,您根據不同的數據進行預測,因此您的預測準確性最終會不一致。
當然,您可能會遇到其他與數據相關的問題,具體取決于您從事的領域。然而,請始終檢查數據表示的一致性。
2. 度量指標問題
導致錯誤或結果不一致的另一個原因是,準確性度量指標的選擇。在構建模型并保存其權重時,我們通常執行以下操作:
def build_model(max_len, n_tags):
input_layer = Input(shape=(max_len, ))
output_layer = Dense(n_tags, activation = 'softmax')(input_layer)
model = Model(input_layer, output_layer)
return model
model = build_model()
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy", metrics=["accuracy"])
model.fit(..)
model.save_weights("model.h5")
如果我們需要在新的會話/腳本中打開它,需要執行以下操作:
def build_model(max_len, n_tags):
input_layer = Input(shape=(max_len, ))
output_layer = Dense(n_tags, activation = 'softmax')(input_layer)
model = Model(input_layer, output_layer)
return model
model = build_model()
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
model.load_weights("model.h5")
model.evaluate()
這可能拋出錯誤,具體視所使用的特定的Keras/Tensorflow版本而定。編譯模型并選擇“準確性”作為指標時,會出現問題。Keras識別準確性的各種定義:“稀疏分類準確性”、“分類準確性”等;視您使用的數據而定,不同的定義是優選的解決方案。這是由于如果我們將度量指標設為“準確性”,Keras將試著分配其中一種特定的準確性類型,具體取決于它認為哪一種最適合數據分布。它可能會在不同的運行中推斷出不同的準確性指標。這里最好的解決方法是,始終明確設置準確性指標,而不是讓Keras自行選擇。比如說,把
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy", metrics=["accuracy"])
換成:
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy", metrics=["sparse_categorical_accuracy"])
3. 隨機性
在與以前相同的數據上重新訓練Keras神經網絡時,您很少兩次獲得同樣的結果。這是由于Keras中的神經網絡在初始化權重時使用隨機性,因此每次運行時權重的初始化方式都不同,因此在學習過程中這些權重會以不同方式更新,于是在進行預測時不太可能獲得相同的準確性結果。
如果出于某種原因,您需要在訓練之前使權重相等,可以在代碼前面設置隨機數生成器:
from numpy.random import seed
seed(42)
from tensorflow import set_random_seed
set_random_seed(42)
numpy隨機種子用于Keras,而至于Tensorflow后端,我們需要將其自己的隨機數生成器設置為相等的種子。該代碼片段將確保每次運行代碼時,您的神經網絡權重都會被同等地初始化。
4. 留意自定義層的使用
Keras提供了眾多層(Dense、LSTM、Dropout和BatchNormalizaton等),但有時我們希望對模型中的數據采取某種特定的操作,但又沒有為它定義的特定層。一般來說,Keras提供了兩種類型的層:Lambda和基礎層類。但對這兩種層要很小心,如果您將模型架構保存為json格式更要小心。Lambda層的棘手地方在于序列化限制。由于它與Python字節碼的序列化一同保存,它只能加載到保存它的同一個環境中,即它不可移植。遇到該問題時,通常建議覆蓋keras.layers.Layer層,或者只保存其權重,從頭開始重建模型,而不是保存整個模型。
5. 自定義對象
很多時候,您會想要使用自定義函數應用于數據,或計算損失/準確性等指標的函數。
Keras允許這種使用,為此讓我們可以在保存/加載模型時指定額外的參數。假設我們想要將我們自行創建的特殊的損失函數與之前保存的模型一并加載:
model = load_model("model.h5", custom_objects=
{"custom_loss":custom_loss})
如果我們在新環境中加載該模型,必須在新環境中小心定義custom_loss函數,因為默認情況下,保存模型時不會記住這些函數。即使我們保存了模型的整個架構,它也會保存該自定義函數的名稱,但函數體是我們需要額外提供的東西。
6. 全局變量初始化器
如果您使用Tensorflow 1.x作為后端——您可能仍然需要該后端用于許多應用程序,這點尤為重要。運行tf 1.x會話時,您需要運行tf.global_variables_initializer(),它隨機初始化所有變量。這么做的副作用是,當您嘗試保存模型時,它可能重新初始化所有權重。您可以手動停止該行為,只需運行:
from keras.backend import manual_variable_initialization manual_variable_initialization(True)
結語
本文列出了最常導致您的Keras模型無法在新環境中正確加載的幾個因素。有時這些問題導致不可預測的結果,而在其他情況下,它們只會拋出錯誤。它們何時發生、如何發生,在很大程度上也取決于您使用的Python版本以及Tensorflow和Keras版本,因為其中一些版本不相兼容,從而導致意外的行為。但愿讀完本文后,下次遇到此類問題時您知道從何處入手。
原文標題:Why Loading a Previously Saved Keras Model Gives Different Results: Lessons Learned,作者:Kristina Popova