
Как построить мозг с керасом и несколькими линиями кода
12 июня 2025 г.Обзор контента
- Настраивать
- Введение
- Обучение, оценка и вывод
- Сохранить и сериализовать
- Используйте один и тот же график слоев для определения нескольких моделей
- Все модели поддаются выходу, как слои
- Манипулировать сложными топологиями графика
- Модели с несколькими входами и выходами
- Модель игрушечного режима
Настраивать
import numpy as np
import tensorflow as tf
from tensorflow import keras
from keras import layers
Введение
КерасФункциональный APIэто способ создать модели, которые являются более гибкими, чемkeras.Sequential
API. Функциональный API может обрабатывать модели с нелинейной топологией, общими слоями и даже несколькими входами или выходами.
Основная идея заключается в том, что модель глубокого обучения обычно представляет собой направленный ациклический график (DAG) слоев. Таким образом, функциональный API - это способ построитьГрафики слоевПолем
Рассмотрим следующую модель:
`` `(вход: 784-мерные векторы) ↧ [плотные (64 единицы, активация RELU)] ↧ [Плотный (64 единицы, активация Rel
Это базовый график с тремя слоями. Чтобы построить эту модель, используя функциональный API, начните с создания входного узла:
inputs = keras.Input(shape=(784,))
Форма данных установлена как 784-мерный вектор. Размер партии всегда пропускается, поскольку указана только форма каждого образца.
Если, например, у вас есть ввод изображения с формой(32, 32, 3)
, вы бы использовали:
# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))
Аinputs
возвращается, содержит информацию о форме иdtype
из входных данных, которые вы подаете в свою модель. Вот форма:
inputs.shape
TensorShape([None, 784])
Вот dtype:
inputs.dtype
tf.float32
Вы создаете новый узел на графике слоев, вызывая слой на этоinputs
объект:
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
Действие «Вызов слоя» - это все равно, что привлекать стрелку из «входов» к этому слою, который вы создаете. Вы "передаете" входные данныеdense
слой, и вы получаетеx
как выход.
Давайте добавим еще несколько слоев в график слоев:
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
На этом этапе вы можете создатьModel
Указав его входы и выходы на графике слоев:
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
Давайте посмотрим, как выглядит резюме модели:
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: 55050 (215.04 KB)
Trainable params: 55050 (215.04 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Вы также можете построить модель как график:
keras.utils.plot_model(model, "my_first_model.png")
И, необязательно, отобразите входные и выходные формы каждого слоя на графике с графиком:
keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)
Эта цифра и код практически идентичны. В кодовой версии стрелки подключения заменяются операцией вызова.
«График слоев» - это интуитивно понятный ментальный образ для модели глубокого обучения, а функциональный API - это способ создания моделей, которые внимательно отражают это.
Обучение, оценка и вывод
Обучение, оценка и вывод работают точно так же для моделей, созданных с использованием функционального API, как и дляSequential
модели.
АModel
класс предлагает встроенную петлю обучения (fit()
метод) и встроенный цикл оценки (evaluate()
метод). Обратите внимание, что вы можете легкоНастройте эти петлиДля реализации процедур обучения за пределами контролируемого обучения (например,Ган)
Здесь загрузите данные изображения MNIST, превратите их в векторы, соответствуют модели на данные (при мониторинге производительности при разделении валидации), затем оцените модель на тестовых данных:
(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=[keras.metrics.SparseCategoricalAccuracy()],
)
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])
Epoch 1/2
750/750 [==============================] - 4s 3ms/step - loss: 0.3556 - sparse_categorical_accuracy: 0.8971 - val_loss: 0.1962 - val_sparse_categorical_accuracy: 0.9422
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1612 - sparse_categorical_accuracy: 0.9527 - val_loss: 0.1461 - val_sparse_categorical_accuracy: 0.9592
313/313 - 0s - loss: 0.1492 - sparse_categorical_accuracy: 0.9556 - 463ms/epoch - 1ms/step
Test loss: 0.14915992319583893
Test accuracy: 0.9556000232696533
Для дальнейшего чтения см. ВОбучение и оценкагид.
Сохранить и сериализовать
Сохранение модели и сериализации работает так же для моделей, созданных с использованием функционального API, как и дляSequential
модели. Стандартный способ сохранения функциональной модели - вызовmodel.save()
Сохранить всю модель как единый файл. Позже вы можете воссоздать ту же модель из этого файла, даже если код, который создал модель, больше не доступен.
Этот сохраненный файл включает в себя:
- модель архитектура
- Значения веса модели (которые были изучены во время тренировки)
- конфигурация обучения модели, если таковые имеются (как передано в
compile
) - Оптимизатор и его состояние, если таковые имеются (для перезапуска обучения, где вы остановились)
model.save("path_to_my_model.keras")
del model
# Recreate the exact same model purely from the file:
model = keras.models.load_model("path_to_my_model.keras")
Для получения подробной информации прочитайте модельсериализация и сохранениегид.
Используйте один и тот же график слоев для определения нескольких моделей
В функциональном API модели создаются путем указания их входов и выходов на графике слоев. Это означает, что один график слоев может использоваться для генерации нескольких моделей.
В приведенном ниже примере вы используете одну и ту же стек слоев для создания двух моделей:encoder
модель, которая превращает входы изображения в 16-мерные векторы и сквознякautoencoder
модель для обучения.
encoder_input = keras.Input(shape=(28, 28, 1), name="img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()
x = layers.Reshape((4, 4, 1))(encoder_output)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)
autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder")
autoencoder.summary()
Model: "encoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
img (InputLayer) [(None, 28, 28, 1)] 0
conv2d (Conv2D) (None, 26, 26, 16) 160
conv2d_1 (Conv2D) (None, 24, 24, 32) 4640
max_pooling2d (MaxPooling2 (None, 8, 8, 32) 0
D)
conv2d_2 (Conv2D) (None, 6, 6, 32) 9248
conv2d_3 (Conv2D) (None, 4, 4, 16) 4624
global_max_pooling2d (Glob (None, 16) 0
alMaxPooling2D)
=================================================================
Total params: 18672 (72.94 KB)
Trainable params: 18672 (72.94 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
img (InputLayer) [(None, 28, 28, 1)] 0
conv2d (Conv2D) (None, 26, 26, 16) 160
conv2d_1 (Conv2D) (None, 24, 24, 32) 4640
max_pooling2d (MaxPooling2 (None, 8, 8, 32) 0
D)
conv2d_2 (Conv2D) (None, 6, 6, 32) 9248
conv2d_3 (Conv2D) (None, 4, 4, 16) 4624
global_max_pooling2d (Glob (None, 16) 0
alMaxPooling2D)
reshape (Reshape) (None, 4, 4, 1) 0
conv2d_transpose (Conv2DTr (None, 6, 6, 16) 160
anspose)
conv2d_transpose_1 (Conv2D (None, 8, 8, 32) 4640
Transpose)
up_sampling2d (UpSampling2 (None, 24, 24, 32) 0
D)
conv2d_transpose_2 (Conv2D (None, 26, 26, 16) 4624
Transpose)
conv2d_transpose_3 (Conv2D (None, 28, 28, 1) 145
Transpose)
=================================================================
Total params: 28241 (110.32 KB)
Trainable params: 28241 (110.32 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Здесь архитектура декодирования строго симметрична по отношению к архитектуре кодирования, поэтому форма вывода такая же, как форма ввода(28, 28, 1)
Полем
ОбратнаяConv2D
слой - этоConv2DTranspose
слой и обратныйMaxPooling2D
слой являетсяUpSampling2D
слой.
Все модели можно вызовать, как слои
Вы можете относиться к любой модели, как если бы это был слой, вызывая ее наInput
или на выходе другого слоя. Называя модель, вы не просто повторно используете архитектуру модели, вы также повторно используете ее веса.
Чтобы увидеть это в действии, вот другой взгляд на пример AutoEncoder, который создает модель кодера, модель декодера и цепол их в двух вызовах, чтобы получить модель AutoEncoder:
encoder_input = keras.Input(shape=(28, 28, 1), name="original_img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()
decoder_input = keras.Input(shape=(16,), name="encoded_img")
x = layers.Reshape((4, 4, 1))(decoder_input)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)
decoder = keras.Model(decoder_input, decoder_output, name="decoder")
decoder.summary()
autoencoder_input = keras.Input(shape=(28, 28, 1), name="img")
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder")
autoencoder.summary()
Model: "encoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
original_img (InputLayer) [(None, 28, 28, 1)] 0
conv2d_4 (Conv2D) (None, 26, 26, 16) 160
conv2d_5 (Conv2D) (None, 24, 24, 32) 4640
max_pooling2d_1 (MaxPoolin (None, 8, 8, 32) 0
g2D)
conv2d_6 (Conv2D) (None, 6, 6, 32) 9248
conv2d_7 (Conv2D) (None, 4, 4, 16) 4624
global_max_pooling2d_1 (Gl (None, 16) 0
obalMaxPooling2D)
=================================================================
Total params: 18672 (72.94 KB)
Trainable params: 18672 (72.94 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Model: "decoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
encoded_img (InputLayer) [(None, 16)] 0
reshape_1 (Reshape) (None, 4, 4, 1) 0
conv2d_transpose_4 (Conv2D (None, 6, 6, 16) 160
Transpose)
conv2d_transpose_5 (Conv2D (None, 8, 8, 32) 4640
Transpose)
up_sampling2d_1 (UpSamplin (None, 24, 24, 32) 0
g2D)
conv2d_transpose_6 (Conv2D (None, 26, 26, 16) 4624
Transpose)
conv2d_transpose_7 (Conv2D (None, 28, 28, 1) 145
Transpose)
=================================================================
Total params: 9569 (37.38 KB)
Trainable params: 9569 (37.38 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
img (InputLayer) [(None, 28, 28, 1)] 0
encoder (Functional) (None, 16) 18672
decoder (Functional) (None, 28, 28, 1) 9569
=================================================================
Total params: 28241 (110.32 KB)
Trainable params: 28241 (110.32 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Как видите, модель может быть вложена: модель может содержать субмодели (поскольку модель похожа на слой). Общий вариант использования для модельного гнездованияансамблированиеПолем Например, вот как ансамбль набора моделей в одну модель, которая усредняет их прогнозы:
def get_model():
inputs = keras.Input(shape=(128,))
outputs = layers.Dense(1)(inputs)
return keras.Model(inputs, outputs)
model1 = get_model()
model2 = get_model()
model3 = get_model()
inputs = keras.Input(shape=(128,))
y1 = model1(inputs)
y2 = model2(inputs)
y3 = model3(inputs)
outputs = layers.average([y1, y2, y3])
ensemble_model = keras.Model(inputs=inputs, outputs=outputs)
Манипулировать сложными топологиями графика
Модели с несколькими входами и выходами
Функциональный API позволяет легко манипулировать несколькими входами и выходами. С этим нельзя обращаться сSequential
API.
Например, если вы создаете систему для ранжирования билетов на выпуск клиентов по приоритету и маршрутируют их в правильный отдел, то у модели будут три входных данных:
- заголовок билета (текстовый ввод),
- текстовый корпус билета (текстовый ввод) и
- Любые теги, добавленные пользователем (категорический ввод)
Эта модель будет иметь два выхода:
- приоритетный балл от 0 до 1 (скалярная сигмоидальная выход) и
- Департамент, который должен обрабатывать билет (вывод Softmax через набор отделов).
Вы можете построить эту модель в нескольких линиях с функциональным API:
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
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],
)
Теперь постройте модель:
keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)
При составлении этой модели вы можете назначить различные потери на каждый вывод. Вы даже можете назначить различные веса для каждой потери - для модуляции их вклада в общую потерю обучения.
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={"priority": 1.0, "department": 0.2},
)
Обучите модель, передавая списки Numpy Arrays of Points and Targets:
# 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,
)
Epoch 1/2
40/40 [==============================] - 8s 112ms/step - loss: 1.2982 - priority_loss: 0.6991 - department_loss: 2.9958
Epoch 2/2
40/40 [==============================] - 3s 64ms/step - loss: 1.3110 - priority_loss: 0.6977 - department_loss: 3.0666
<keras.src.callbacks.History at 0x7f08d51fab80>
При назывании подходитDataset
объект, он должен дать либо кортеж из списков, таких как([title_data, body_data, tags_data], [priority_targets, dept_targets])
или кортеж из словарей, как({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})
Полем
Для получения более подробного объяснения, обратитесь кОбучение и оценкагид.
Модель игрушечного режима
В дополнение к моделям с несколькими входами и выходами, функциональный API позволяет легко манипулировать нелинейными топологиями связности-это модели со слоями, которые не подключены последовательно, которыеSequential
API не может справиться.
Общий вариант использования для этого - остаточные соединения. Давайте создадим модель игрушечного Resnet для Cifar10, чтобы продемонстрировать это:
inputs = keras.Input(shape=(32, 32, 3), name="img")
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.Conv2D(64, 3, activation="relu")(x)
block_1_output = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_2_output = layers.add([x, block_1_output])
x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_3_output = layers.add([x, block_2_output])
x = layers.Conv2D(64, 3, activation="relu")(block_3_output)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs, outputs, name="toy_resnet")
model.summary()
Model: "toy_resnet"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
img (InputLayer) [(None, 32, 32, 3)] 0 []
conv2d_8 (Conv2D) (None, 30, 30, 32) 896 ['img[0][0]']
conv2d_9 (Conv2D) (None, 28, 28, 64) 18496 ['conv2d_8[0][0]']
max_pooling2d_2 (MaxPoolin (None, 9, 9, 64) 0 ['conv2d_9[0][0]']
g2D)
conv2d_10 (Conv2D) (None, 9, 9, 64) 36928 ['max_pooling2d_2[0][0]']
conv2d_11 (Conv2D) (None, 9, 9, 64) 36928 ['conv2d_10[0][0]']
add (Add) (None, 9, 9, 64) 0 ['conv2d_11[0][0]',
'max_pooling2d_2[0][0]']
conv2d_12 (Conv2D) (None, 9, 9, 64) 36928 ['add[0][0]']
conv2d_13 (Conv2D) (None, 9, 9, 64) 36928 ['conv2d_12[0][0]']
add_1 (Add) (None, 9, 9, 64) 0 ['conv2d_13[0][0]',
'add[0][0]']
conv2d_14 (Conv2D) (None, 7, 7, 64) 36928 ['add_1[0][0]']
global_average_pooling2d ( (None, 64) 0 ['conv2d_14[0][0]']
GlobalAveragePooling2D)
dense_6 (Dense) (None, 256) 16640 ['global_average_pooling2d[0][
0]']
dropout (Dropout) (None, 256) 0 ['dense_6[0][0]']
dense_7 (Dense) (None, 10) 2570 ['dropout[0][0]']
==================================================================================================
Total params: 223242 (872.04 KB)
Trainable params: 223242 (872.04 KB)
Non-trainable params: 0 (0.00 Byte)
__________________________________________________________________________________________________
Постройте модель:
keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)
Теперь тренируйте модель:
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=keras.losses.CategoricalCrossentropy(from_logits=True),
metrics=["acc"],
)
# We restrict the data to the first 1000 samples so as to limit execution time
# on Colab. Try to train on the entire dataset until convergence!
model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)
13/13 [==============================] - 4s 39ms/step - loss: 2.3086 - acc: 0.0988 - val_loss: 2.3020 - val_acc: 0.0850
<keras.src.callbacks.History at 0x7f078810c880>
Первоначально опубликовано на
Оригинал