說起來自己還挺菜的,tensorflow很久之前就已經把keras合併到自己的生態裡,雖然早就會了keras,但是因為排期的原因自己一直沒有更新這塊的知識,這次系統地學習一下,並記錄下來。
當然,我不想寫成一個教程,也不想整成一個API,更希望這是一個導學,能給大家一些學習的思路,學這個之前,希望大家還是要對深度學習和tensorflow有些了解。下面講的實質性知識的東西,全部來源於tensorflow的源碼和官網版本的API文檔。給個連結吧:
tensorflow源碼:https://github.com/tensorflowtensorflow1.15.0文檔:https://tensorflow.google.cn/versions/r1.15/api_docs/python/tf/keras/wrapperstensorflow keras文檔:https://tensorflow.google.cn/guide/keras我的目標是學的1.14,但是1.14文檔因為一些原因我好像上不去,所以我主要參考的是1.15的文檔,在更新公告上沒有對keras進行太大幅度的更新,因此不用太擔心。
tf.keras生態keras本身是一套tensorflow老版本的優化方案,由於其便捷性一直受到大家歡迎,因此google將其收編也是大勢所趨,合併的具體版本並不清楚,但是在1.14版本已經成型,因此我以這個版本為基礎進行自己的學習,有API更新的我後續也會跟進。
接著說tf.keras,最簡單的了解一個包,其實就是用help了,我們來看看help(tensorflow.keras)會發生什麼,內容比較多,這裡我只想把這玩意下屬的包給列舉出來,特殊的我加點解釋:
PACKAGE CONTENTS
activations (package) # 激活函數
applications (package) # 應用,預裝的一些固定結構,例如mobilenet
backend (package) # 後端,底層函數,類似絕對值、點積之類的都有
callbacks (package) # 回調函數接口
constraints (package) # 訓練過程中對函數的約束,如MaxNorm
datasets (package) # 數據集操作
estimator (package) # 基於keras構造的estimator類
experimental (package) # 一些實驗模塊會放在這裡,例如學習率變化策略
initializers (package) # 初始化工具
layers (package) # 各種深度學習的層
losses (package) # 各種損失函數定義
metrics (package) # 各種評估指標,可用於訓練過程監測
mixed_precision (package)
models (package) # 就是keras裡的model類,組合各種層形成模型
optimizers (package) # 優化器
preprocessing (package) # 預處理工具
regularizers (package) # 正則化
utils (package) # 各種工具函數
wrappers (package) # 實現多個工具共通,目前實現了sklearn的可見tf.keras實現了大量的功能,形成了相對完備的深度學習生態,這個完整的框架能讓我們輕鬆實現深度學習。
當然看API學習本身缺少系統性,API更適合學完之後的深入學習或者是平時的詞典查閱,學習還是要系統性的。
建模框架如何寫模型應該是初學者最關心的問題,keras建模主要有兩種模式,分別是序列式和函數式。
序列式建模連結:https://tensorflow.google.cn/guide/keras/sequential_model
序列式建模是keras最基本的結構,簡單的理解就是和搭積木一樣一個接著一個的堆疊就好了,來看看例子:
# Define Sequential model with 3 layers
model = keras.Sequential(
[
layers.Dense(2, activation="relu", name="layer1"),
layers.Dense(3, activation="relu", name="layer2"),
layers.Dense(4, name="layer3"),
]
)
# Call model on a test input
x = tf.ones((3, 3))
y = model(x)通過keras.Sequential將每一個層堆疊起來,這個堆疊的實際上就是keras.layers的對象,如上圖所示就是3個全連接層,這樣子堆疊相比古老的tensorflow.nn就避免了計算每一層輸入輸出的維數的問題,很方便。
當然,除了直接構造一個keras.layers對象向量直接放入Sequential之外,還可以用add的方式進行放入。
model = keras.Sequential()
model.add(layers.Dense(2, activation="relu"))
model.add(layers.Dense(3, activation="relu"))
model.add(layers.Dense(4))構造完之後,如果想看看自己的建模內容,可以用``model.summary`查看並匯總,上面的模型執行後的效果如下:
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_7 (Dense) (1, 2) 10
_________________________________________________________________
dense_8 (Dense) (1, 3) 9
_________________________________________________________________
dense_9 (Dense) (1, 4) 16
=================================================================
Total params: 35
Trainable params: 35
Non-trainable params: 0函數式建模函數式建模是keras有一種建模框架,文檔:https://tensorflow.google.cn/guide/keras/functional。
函數式是一種更為靈活的模式,對於更複雜的網絡,就可以用它來整。來看一個完整的例子:
inputs = keras.Input(shape=(784,))
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")首先初始化了一個輸入點,然後後面構造的dense對象,這個對象是可調用的(內部其實就是有一個call函數),調用了這個inputs,這就代表了在inputs後接了一個dense層,一個接著一個,就能實現整體建模,我們用``model.summary`看看:
Model: "mnist_model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 784)] 0
_________________________________________________________________
dense (Dense) (None, 64) 50240
_________________________________________________________________
dense_1 (Dense) (None, 64) 4160
_________________________________________________________________
dense_2 (Dense) (None, 10) 650
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________如我們所料就是一個完整網絡。
有了網絡,就可以開始訓練和預測了,整個過程也不複雜:
# 加載數據
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# 數據預處理
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255
# 模型編譯
model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=keras.optimizers.RMSprop(),
metrics=["accuracy"],
)
# 訓練模型
history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2)
# 模型效果評估
test_scores = model.evaluate(x_test, y_test, verbose=2)
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])剛才說到,函數式能構造更為複雜的模型,我們來看一個例子,我們先看一張模型的結構圖:
可以清楚看到,模型有多個輸入和多個輸出,這種模型結構就只能用函數式來處理了。
num_tags = 12 # Number of unique issue tags
num_words = 10000 # Size of vocabulary obtained when preprocessing text data
num_departments = 4 # Number of departments for predictions
# 模型的輸入部分,一共3個輸入
title_input = keras.Input(
shape=(None,), name="title"
) # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body") # Variable-length sequence of ints
tags_input = keras.Input(
shape=(num_tags,), name="tags"
) # Binary vectors of size `num_tags`
# 對模型的輸入分別進行處理
# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)
# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)
# 處理以後將他們拼接起來
# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])
# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)
# 整理結果,完成輸出
# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
inputs=[title_input, body_input, tags_input],
outputs=[priority_pred, department_pred],
)當然的,構造好模型以後,就需要編譯模型,定義好訓練方式,針對這種多目標的問題還要設計好對應的權重,一般的有兩種方式:
# 向量形式的損失函數定製
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[
keras.losses.BinaryCrossentropy(from_logits=True),
keras.losses.CategoricalCrossentropy(from_logits=True),
],
loss_weights=[1.0, 0.2],
)
# 字典形式的損失函數定製
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={
"priority": keras.losses.BinaryCrossentropy(from_logits=True),
"department": keras.losses.CategoricalCrossentropy(from_logits=True),
},
loss_weights=[1.0, 0.2],
)並在訓練過程中按照要求灌入數據,完成訓練:
# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")
# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))
model.fit(
{"title": title_data, "body": body_data, "tags": tags_data},
{"priority": priority_targets, "department": dept_targets},
epochs=2,
batch_size=32,
)當然,還有一些諸如共享層、權重提取、重用之類的場景,keras都有實現,詳情可以看這個連結:https://tensorflow.google.cn/guide/keras/functional。
自定義層類似transformer之類的,都是對模型的創新,這時候我們需要自己去寫了,只需要按照keras給定的API去寫就行:
class CustomDense(layers.Layer):
def __init__(self, units=32):
super(CustomDense, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer="random_normal",
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,), initializer="random_normal", trainable=True
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
def get_config(self):
return {"units": self.units}
inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)
model = keras.Model(inputs, outputs)
config = model.get_config()
new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})類裡面有幾個需要注意的點:
繼承layers,並定義好對應的構造函數__init__。build中可以進一步對參數進行定義,在第一次使用該層的時候調用該部分代碼,在這裡創建變量可以使得變量的形狀自適應輸入的形狀,並且給裡面的參數進行初始化,可以看到裡面有個initializercall,在函數式下,執行這一層的時候會調用這個函數進行前向計算。像上面的例子就是做了一個線性變換。如果是需要支持序列化,則需要用get_config或者是from_config的方式把必要的超參返回出來。有關自定義新層的方法,詳見:https://tensorflow.google.cn/guide/keras/custom_layers_and_models
訓練、評估和預測上面講的都是模型的建立,現在我們就要談談怎麼怎麼折騰這個模型了。
首先是訓練,在進行建模和編譯以後,就可以開始訓練了:
# 模型編譯
model.compile(
optimizer=keras.optimizers.RMSprop(), # Optimizer
# Loss function to minimize
loss=keras.losses.SparseCategoricalCrossentropy(),
# List of metrics to monitor
metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
# 開始訓練
history = model.fit(
x_train,
y_train,
batch_size=64,
epochs=2,
# We pass some validation for
# monitoring validation loss and metrics
# at the end of each epoch
validation_data=(x_val, y_val),
)在進行訓練以後就可以進行評估和預測了:
results = model.evaluate(x_test, y_test, batch_size=128)
predictions = model.predict(x_test[:3])注意,這裡的predict其實只是把輸出層的預測結果給出,如果是分類問題,可能還要做類似篩選最大之類的後處理。
保存模型保存模型一共分3種:
這3種模式都有很明確的使用場景,例如最後一個權重的,就是在模型預測、遷移學習時使用。
保存和加載整個模型對於這種模式的保存,是最完整的,按照官方說法,一共覆蓋著4種東西:
API也非常簡單:
model.save() 或 tf.keras.models.save_model()tf.keras.models.load_model()保存架構保存架構是只保存模型的結構,而不保存權重。
get_config() 和 from_config()tf.keras.models.model_to_json() 和 tf.keras.models.model_from_json()僅保存權重get_config() 和 from_config()tf.keras.models.model_to_json() 和 tf.keras.models.model_from_json()到此,整個模型的核心流程已經完成,下面我們看一下keras還為我們提供了什麼有意思的功能供我們使用。
值得關注的API經過上面的詳細學習,其實大家已經對tensorflow.keras有足夠的了解,剩下就是在實踐過程中逐步學習成長,但這裡面,也有很多指的閱讀的API和源碼,有興趣的大家可以好好看看。
layers這應該是建模的關鍵,keras目前已經支持幾乎所有主流模型,全連接、卷積、池化、RNN等。有興趣的多去看看,包括裡面涉及的變量,都多了解,對後續自己寫自定義層有很大好處。
losses各種損失函數,常見的如BinaryCrossentropy、MeanSquaredError,也有一些比較有意思的Hinge、Poisson等。
activations激活函數,可以看看有些啥,煉丹的時候可以都嘗試嘗試,softmax、tanh、relu,也有elu之類的新東西。
preprocessing預處理,這裡分成了三個模塊,image、sequence、text,很多的預處理工作其實在這裡面都有實現。
metrics評估指標是評價模型好壞的核心,keras裡面也內置了大量的已經寫好的函數,AUC、Accuracy等。
backendbackend裡面有大量非常有用的基礎函數,熟悉一下,可以大大減輕自己手寫代碼的壓力。來看看幾個例子:
argmax,argmin,dot,各種簡單算子。manual_variable_initialization,人工特徵初始化。小結至今仍然感覺,看API、看源碼、看文檔是對一個工具了解的必經之路,看看博客之類的遠遠無法真正了解他們,即使是我們已經熟練使用了,回頭看看文檔,也會發現裡面有很多好東西,總之,學無止境吧。