Функциональный API Keras для глубокого обучения Python

глубокое обучение Python

Это 10-й день моего участия в августовском испытании обновлений. Узнайте подробности события:Испытание августовского обновления

Deep Learning with Python

Эта статья — одна из серии заметок, которые я написал, изучая Deep Learning with Python (второе издание, Франсуа Шолле). Содержимое статьи конвертировано из блокнотов Jupyter в Markdown, вы можете перейти наGitHubилиGiteeнайти оригинал.ipynbноутбук.

ты можешь идтиЧитайте оригинальный текст этой книги онлайн на этом сайте(Английский). Автор этой книги также дает соответствиеJupyter notebooks.

Эта статьяГлава 7. Передовые методы расширенного глубокого обучения (Chapter 7. Advanced deep-learning best practices) одной из записок.

7.1 Going beyond the Sequential model: the Keras functional API

Решение без последовательных моделей: функциональный API Keras

Последовательная модель, которую мы использовали ранее, является самой простой, но часто используемой моделью.У нее есть только один вход и один выход, а вся сеть образована линейным наложением слоев.

Sequential 模型:层的线性堆叠

Однако иногда наша сеть требует нескольких входов. Например, чтобы предсказать цену одежды, введите информацию о продукте, текстовое описание и изображения. Эти три типа информации должны быть обработаны Dense, RNN и CNN соответственно. После извлечения информации модуль слияния используется для объединения вся разнообразная информация, чтобы окончательно предсказать цену:

一个多输入模型

Также иногда нашей сети требуется несколько выходов (несколько головок). Например, введите роман, мы хотим получить классификацию романа и угадать время написания. Эта задача должна использовать общий модуль для обработки текста, извлечения информации, а затем передать ее новому классификатору и регрессору дат для прогнозирования классификации и времени написания:

一个多输出(或多头)模型

Кроме того, в некоторых сложных сетях используются нелинейные сетевые топологии. Например, что-то под названием Inception, вход обрабатывается несколькими параллельными ветвями свертки, а затем выходы этих ветвей объединяются в один тензор, также есть метод, называемый остаточным соединением (residual connection), который объединяет предыдущий вывод. тензор добавляется к последующему выходному тензору, тем самым повторно вводя предыдущее представление в нисходящий поток данных, предотвращая потерю информации в потоке обработки информации:

Inception 模块:层组成的子图,具有多个并行卷积分支;残差连接:通过特征图相加将前面的信息重新注入下游数据

Эти сети имеют графическую структуру, а не линейный стек, как Sequential. Реализация таких сложных моделей в Keras требует использования функционального API Keras.

Функциональный API

Функциональный API Keras рассматривает слои как функции, принимая тензоры и возвращая тензоры:

from tensorflow.keras import Input, layers

input_tensor = Input(shape=(32, ))    # 输入张量
dense = layers.Dense(32, activation='relu')    # 层函数
output_tensor = dense(input_tensor)   # 输出张量 

Давайте построим простую сеть с функциональным API и сравним ее с Sequential:

# Sequential 模型

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers

seq_model = Sequential()
seq_model.add(layers.Dense(32, activation='relu', input_shape=(64, )))
seq_model.add(layers.Dense(32, activation='relu'))
seq_model.add(layers.Dense(10, activation='softmax'))

seq_model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 32)                2080      
_________________________________________________________________
dense_2 (Dense)              (None, 32)                1056      
_________________________________________________________________
dense_3 (Dense)              (None, 10)                330       
=================================================================
Total params: 3,466
Trainable params: 3,466
Non-trainable params: 0
_________________________________________________________________
# 函数式 API 模型

from tensorflow.keras.models import Model
from tensorflow.keras import Input
from tensorflow.keras import layers

input_tensor = Input(shape=(64, ))
x = layers.Dense(32, activation='relu')(input_tensor)
x = layers.Dense(32, activation='relu')(x)
output_tensor = layers.Dense(10, activation='softmax')(x)

func_model = Model(input_tensor, output_tensor)

func_model.summary()
Model: "functional_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         [(None, 64)]              0         
_________________________________________________________________
dense_4 (Dense)              (None, 32)                2080      
_________________________________________________________________
dense_5 (Dense)              (None, 32)                1056      
_________________________________________________________________
dense_6 (Dense)              (None, 10)                330       
=================================================================
Total params: 3,466
Trainable params: 3,466
Non-trainable params: 0
_________________________________________________________________

Когда создается экземпляр объекта Model, ему нужно только предоставить входной тензор и выходной тензор, полученный путем преобразования входного тензора (через различные слои). Keras автоматически найдет каждый слой от input_tensor до output_tensor и объединит эти слои в графоподобную структуру данных — модель.

Обратите внимание, что output_tensor должен быть преобразован соответствующим input_tensor. Если вы построите модель с несвязанными входными тензорами и выходными тензорами, она взорвет ValueError отключенного графика (keras, написанный в книге, — RuntimeError, tf.keras — ValueError):

>>> unrelated_input = Input(shape=(32,))
>>> bad_model = Model(unrelated_input, output_tensor)
... # Traceback
ValueError: Graph disconnected: cannot obtain value for tensor Tensor("input_2:0", shape=(None, 64), dtype=float32) at layer "dense_4". The following previous layers were accessed without issue: []

То есть невозможно соединить указанный вход с выходом, чтобы сформировать граф (граф, такая структура данных, такая сеть).

Компиляция, обучение или оценка сети, построенной с помощью этого функционального API, аналогична Sequential.

# 编译
func_model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

# 随机训练数据
import numpy as np
x_train = np.random.random((1000, 64))
y_train = np.random.random((1000, 10))

# 训练
func_model.fit(x_train, y_train, epochs=10, batch_size=128)

# 评估
score = func_model.evaluate(x_train, y_train)
Epoch 1/10
8/8 [==============================] - 0s 1ms/step - loss: 33.5245
...
Epoch 10/10
8/8 [==============================] - 0s 685us/step - loss: 74.5707
32/32 [==============================] - 0s 617us/step - loss: 78.1296

Модель с несколькими входами

Функциональный API можно использовать для построения моделей с несколькими входными данными. Модели с несколькими входными данными обычно в какой-то момент объединяют разные ветви со слоем, который может комбинировать тензоры. Объединение тензоров, которые можно добавлять, объединять и т. д., предоставляется в keras, таких какkeras.layers.add,keras.layers.concatenateи другие слои для выполнения этих операций.

Чтобы увидеть конкретный пример, создайте модель ответа на вопрос. Типичная модель ответа на вопрос использует два входа:

  • текст вопроса
  • Предоставляет информационный текст для ответов на вопросы (например, связанные новостные статьи)

Модель должна генерировать (выводить) ответ. Самый простой случай — ответить только одним словом, мы можем получить результат, софтмаксируя вывод для некоторого предопределенного словаря.

问答模型

Чтобы реализовать эту модель с помощью функционального API, мы сначала создаем две независимые ветви, представляющие справочный текст и вопрос в виде векторов соответственно, затем объединяем два вектора и добавляем к представлению классификатор softmax, пока объединение не будет завершено:

from tensorflow.keras.models import Model
from tensorflow.keras import layers
from tensorflow.keras import Input

text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500

# 参考
text_input = Input(shape=(None, ), dtype='int32', name='text')
embedded_text = layers.Embedding(text_vocabulary_size, 64)(text_input)
encoded_text = layers.LSTM(32)(embedded_text)

# 问题
question_input = Input(shape=(None, ), dtype='int32', name='question')
embedded_question = layers.Embedding(question_vocabulary_size, 32)(question_input)
encoded_question = layers.LSTM(16)(embedded_question)

# 合并参考、问题分支
concatenated = layers.concatenate([encoded_text, encoded_question], axis=-1)

# 顶层分类器
answer = layers.Dense(anser_vocabulary_size, activation='softmax')(concatenated)

model = Model([text_input, question_input], answer, name='QA')

model.summary()

model.compile(optimizer='rmsprop', 
              loss='categorical_crossentropy', 
              metrics=['acc'])

QA model summary

При обучении такой многовходовой модели можно передать список компонентов, а также можно передать словарь для ввода с заданным именем:

import numpy as np

num_samples = 1000
max_length = 100

text = np.random.randint(1, text_vocabulary_size, 
                         size=(num_samples, max_length))
question = np.random.randint(1, question_vocabulary_size, 
                             size=(num_samples, max_length))
answers = np.random.randint(0, 1, 
                            size=(num_samples, answer_vocabulary_size)) # one-hot 编码的

# 方法1. 传列表
model.fit([text, question], answers, epochs=2, batch_size=128)

# 方法2. 传字典
model.fit({'text': text, 'question': question}, answers, epochs=2, batch_size=128)

Несколько выходных моделей

Также удобно использовать функциональный API для построения моделей с несколькими выходами (несколько головок). Например, давайте создадим сеть, которая пытается предсказать различные свойства данных одновременно: введите несколько постов в социальных сетях и попытайтесь предсказать атрибуты возраста, пола и уровня дохода этого человека:

具有三个头的社交媒体模型

Конкретная реализация очень проста, просто напишите 3 разных вывода в конце:

from tensorflow.keras.layers import Conv1D, MaxPooling1D, GlobalMaxPool1D, Dense
from tensorflow.keras import Input
from tensorflow.keras.models import Model

vocabulary_size = 50000
num_income_groups = 10

posts_input = Input(shape=(None,), dtype='int32', name='posts')
embedded_post = layers.Embedding(256, vocabulary_size)(posts_input)
x = Conv1D(128, 5, activation='relu')(embedded_post)
x = MaxPooling1D(5)(x)
x = Conv1D(256, 5, activation="relu")(x)
x = Conv1D(256, 5, activation="relu")(x)
x = MaxPooling1D(5)(x)
x = Conv1D(256, 5, activation="relu")(x)
x = Conv1D(256, 5, activation="relu")(x)
x = GlobalMaxPool1D()(x)
x = Dense(128, activation='relu')(x)

# 定义多个头(输出)
age_prediction = Dense(1, name='age')(x)
income_prediction = Dense(num_income_groups, activation='softmax', name='income')(x)
gender_prediction = Dense(1, activation='sigmoid', name='gender')(x)

model = Model(posts_input, [age_prediction, income_prediction, gender_prediction])

model.summary()

functional_6 model summary

Подборка многоголовых моделей

При составлении такой модели из-за разных целей следует учитывать, что для каждой головки сети необходимо задавать разные функции потерь.

Но функция градиентного спуска может заключаться только в минимизации скаляра, поэтому в Keras будут добавлены разные потери, указанные для разных выходных данных во время компиляции, чтобы получить глобальную потерю во время обучения, а процесс обучения должен минимизировать это глобальное изменение потери.

В этом случае, если вклад потерь сильно несбалансирован, модель отдаст приоритет задаче с наибольшим значением потерь и проигнорирует другие задачи. Чтобы решить эту проблему, можно взвесить различные потери. Например, значение потерь mse обычно составляет 3 ~ 5, а значение потерь binary_crossentropy обычно не превышает 0,1. Чтобы сбалансировать вклад потерь, мы можем сделать вес binary_crossentropy равным 10, а вес mse потери берут 0,5.

Множественные потери и веса задаются с помощью списков или словарей:

model.compile(optimizer='rmsprop',
              loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'],
              loss_weights=[0.25, 1., 10.])

# 或者如果有对输出层命名的话,可以用字典:
model.compile(optimizer='rmsprop',
              loss={'age': 'mse',
                    'income': 'categorical_crossentropy',
                    'gender': 'binary_crossentropy'},
              loss_weights={'age': 0.25,
                            'income': 1.,
                            'gender': 10.})

Обучение многоголовой модели

При обучении такой модели просто передайте целевой вывод в виде списка или словаря:

model.fit(posts, [age_targets, income_targets, gender_targets],
          epochs=10, batch_size=64)

# or

model.fit(posts, {'age': age_targets,
                  'income': income_targets,
                  'gender': gender_targets},
          epochs=10, batch_size=64)

Направленный ациклический граф слоев

Используя функциональный API, помимо построения моделей с несколькими входами и несколькими выходами, мы также можем реализовывать сети со сложной внутренней топологией.

На самом деле нейронной сетью в Керасе может быть любой ориентированный ациклический граф, состоящий из слоев. Наиболее известными компонентами структуры графа являются начальный модуль и остаточное соединение.

Начальный модуль

Начало — это стек модулей, каждый из которых выглядит как небольшая независимая сеть, разделенная на несколько параллельных ветвей, которые в конечном итоге объединяют полученные функции. Эта операция позволяет сети изучать пространственные и канальные особенности изображения по отдельности, что более эффективно, чем использование сети для одновременного изучения этих характеристик.

На следующем рисунке показан упрощенный модуль Inception V3:

Inception 模块

Примечание. Используемая здесь свертка 1x1, называемая точечной сверткой, является функцией модуля Inception. Он просматривает только один пиксель за раз, а вычисляемые признаки могут смешивать информацию из различных входных каналов, но не пространственную информацию. Таким образом, реализуется различие между изучением признаков канала и изучением пространственных признаков.

Его можно реализовать с помощью функционального API:

from tensorflow.keras import layers

x = Input(shape=(None, None, 3))    # RGB 图片

branch_a = layers.Conv2D(128, 1, activation='relu', strides=2, padding='same')(x)

branch_b = layers.Conv2D(128, 1, activation='relu', padding='same')(x)
branch_b = layers.Conv2D(128, 3, activation='relu', strides=2, padding='same')(branch_b)

branch_c = layers.AveragePooling2D(3, strides=2, padding='same')(x)
branch_c = layers.Conv2D(128, 3, activation='relu', padding='same')(branch_c)

branch_d = layers.Conv2D(128, 1, activation='relu', padding='same')(x)
branch_d = layers.Conv2D(128, 3, activation='relu', padding='same')(branch_d)
branch_d = layers.Conv2D(128, 3, activation='relu', strides=2, padding='same')(branch_d)

output = layers.concatenate([branch_a, branch_b, branch_c, branch_d], axis=-1)

Фактически, в Keras встроена полная архитектура Inception V3. в состоянии пройтиkeras.applications.inception_v3.InceptionV3звонить.

Связанный с «Началом», есть такжеXceptionс вещами. Слово Xception означает экстремальное начало, которое является относительно экстремальным началом, которое полностью отделяет изучение признаков канала от изучения пространственных признаков. Xception имеет примерно то же количество параметров, что и Inception V3, но использует параметры более эффективно, поэтому работает лучше и точнее на крупномасштабных наборах данных.

остаточное соединение

Остаточное соединение — это часто используемый компонент, который решает проблему исчезающих градиентов и узких мест представления в крупномасштабных моделях глубокого обучения. Как правило, может помочь добавление остаточных соединений к любой модели с более чем 10 слоями.

  • Градиент исчезает: пройдено слишком много слоев, и ранее изученное представление становится размытым или даже полностью теряется, что делает сеть неспособной к обучению.
  • Представляет собой узкое место: слои сложены друг в друга, и последний уровень может получить доступ только к тому, чему научился предыдущий уровень. Если определенный слой слишком мал (в активацию можно вставить меньше информации), информация застрянет, и возникнет узкое место.

Остаточное соединение заключается в том, чтобы сделать выход предыдущего слоя входом более позднего слоя (создать ярлык в сети). Выход предыдущего слоя не объединяется с активацией последнего слоя, а добавляется к активации последнего слоя (если форма отличается, активация первого слоя изменяется на целевую форму с помощью линейного преобразования ).

Примечание. Линейное преобразование можно выполнить с помощью плотных слоев без активации или свертки 1 × 1 без активации в CNN.

from keras import layers

x = ...

y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
y = layers.MaxPooling2D(2, strides=2)(y)

# 形状不同,要做线性变换:
residual = layers.Conv2D(128, 1, strides=2, padding='same')(x)  # 使用 1×1 卷积,将 x 线性下采样为与 y 具有相同的形状

y = layers.add([y, residual])

Общий вес слоя

Используя функциональный API, выполняется еще одна операция.Многократное использование экземпляра слоя. Вызывается несколько раз для одного и того же экземпляра слоя, одни и те же веса могут использоваться повторно. Используя это свойство, можно строить модели с общими ветвями, т. е. несколько ветвей используют одни и те же знания и выполняют одни и те же операции.

Например, мы хотим оценить семантическое сходство между двумя предложениями. Эта модель принимает два предложения в качестве входных данных и выводит оценку в диапазоне от 0 до 1. Чем больше значение, тем больше похоже значение предложений.

В этой задаче два входных предложения взаимозаменяемы (сходство предложения A с B равно сходству B с A). Следовательно, две отдельные модели не следует обучать обработке двух входных предложений по отдельности. Вместо этого используйте один слой LSTM для обработки двух предложений. Представления (веса) этого слоя LSTM изучаются из обоих входов одновременно. Эта модель называется сиамской LSTM или общей LSTM.

Такая модель реализована с помощью разделения слоев в функциональном API Keras:

from tensorflow.keras import layers
from tensorflow.keras import Input
from tensorflow.keras.models import Model

lstm = layers.LSTM(32)  # 只示例化一个 LSTM

left_input = Input(shape=(None, 128))
left_output = lstm(left_input)

right_input = Input(shape=(None, 128))
right_output = lstm(right_input)

merged = layers.concatenate([left_output, right_output], axis=-1)
predictions = layers.Dense(1, activation='sigmoid')(merged)

model = Model([left_input, right_input], predictions)

model.summary()
Model: "functional_8"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_13 (InputLayer)           [(None, None, 128)]  0                                            
__________________________________________________________________________________________________
input_14 (InputLayer)           [(None, None, 128)]  0                                            
__________________________________________________________________________________________________
lstm_15 (LSTM)                  (None, 32)           20608       input_13[0][0]                   
                                                                 input_14[0][0]                   
__________________________________________________________________________________________________
concatenate_13 (Concatenate)    (None, 64)           0           lstm_15[0][0]                    
                                                                 lstm_15[1][0]                    
__________________________________________________________________________________________________
dense_15 (Dense)                (None, 1)            65          concatenate_13[0][0]             
==================================================================================================
Total params: 20,673
Trainable params: 20,673
Non-trainable params: 0
__________________________________________________________________________________________________

Модель как слой

В Keras мы можем использовать модель как слой (представляя модель как большой слой), а классы Sequential и Model можно использовать в качестве слоев. Просто назовите его функционально как слой:

y = model(x)
y1, y2 = model_with_multi_inputs_and_outputs([x1, x2])

Например, мы имеем дело с моделью зрения с двумя камерами в качестве входных данных (эта модель может воспринимать глубину). Мы реализуем эту сеть, используя модель application.Xception в качестве уровня и используя предыдущий подход с общим уровнем:

from tensorflow.keras import layers
from tensorflow.keras import Input
from tensorflow.keras import applications

xception_base = applications.Xception(weights=None, include_top=False)

left_input = Input(shape=(250, 250, 3))
right_input = Input(shape=(250, 250, 3))

left_features = xception_base(left_input)
right_input = xception_base(right_input)

merged_features = layers.concatenate([left_features, right_input], axis=-1)

Суммировать

Использование функционального API Keras может быть более гибким, основанным на ориентированном ациклическом графе, для построения нейронной сети со сложными структурами, такими как:

  • Модель с несколькими входами
  • Несколько выходных моделей
  • остаточное соединение
  • Общий вес слоя

Используя эти методы, можно получить более мощные модели, чем простые последовательные нейронные сети.