На этом шаге мы рассмотрим роль этого класса в построении нейронной сети.
Простой прикладной интерфейс (API) должен иметь единую абстракцию, лежащую в основе всего. В Keras такой абстракцией служит класс слоев Layer. Все в Keras является либо слоем Layer, либо чем-то еще, что тесно взаимодействует со слоем Layer.
Слой - это объект, инкапсулирующий некоторое состояние (веса) и некоторые вычисления (прямой проход). Веса обычно определяются с помощью метода build() (но также могут инициализироваться в конструкторе __init__()), а вычисления определяются в методе call(). На 57 шаге мы реализовали класс NaiveDense, содержавший два веса, W и b, и применили вычисления output = activation(dot(input, W) + b). Вот как тот же слой выглядел бы в Keras.
import tensorflow as tf from tensorflow import keras # Все классы слоев в Keras наследуют базовый класс Layer class SimpleDense(keras.layers.Layer): def __init__(self, units, activation=None): super().__init__() self.units = units self.activation = activation # Веса создаются в методе build() def build(self, input_shape): input_dim = input_shape[-1] # add_weight() - это вспомогательный метод для создания весов. # Также имеется возможность создать отдельные # переменные и связать их с атрибутами слоя, например: # self.W = tf.Variable(tf.random.uniform(w_shape)) self.W = self.add_weight(shape=(input_dim, self.units), initializer="random_normal") self.b = self.add_weight(shape=(self.units,), initializer="zeros") # Вычисления, выполняемые во время прямого прохода, # определяются в методе call() def call(self, inputs): y = tf.matmul(inputs, self.W) + self.b if self.activation is not None: y = self.activation(y) return y
Мы еще вернемся к методам build() и call() в следующих шагах и рассмотрим их подробнее.
Создав такой слой, его можно использовать как функцию, принимающую на входе тензор TensorFlow:
# Создать экземпляр слоя, который мы определили выше my_dense = SimpleDense(units=32, activation=tf.nn.relu) # Сформировать некоторые входные данные input_tensor = tf.ones(shape=(2, 784)) # Вызвать слой подобно функции и передать ему входные данные output_tensor = my_dense(input_tensor) print(output_tensor.shape)
Вам, наверное, интересно узнать, зачем нужно было реализовать методы call() и build(), если в итоге мы использовали наш слой, просто вызвав его как функцию, то есть с помощью его метода __call__()? Причина проста: нам нужно, чтобы состояние создавалось динамически, на лету. Давайте посмотрим, как это работает.
На следующем шаге мы рассмотрим построение слоев на лету.