Документирование моего пути к fast.ai: ОБЗОР КОДА. SWIFT ДЛЯ TENSORFLOW (S4TF) ПРОЕКТ ГЛУБОКОГО ПОГРУЖЕНИЯ. СВЕТОЧНЫЕ НЕЙРОННЫЕ СЕТИ.

Для проекта «Урок 14.2» я решил глубже погрузиться в реализацию простой сверточной нейронной сети с использованием Swift For TensorFlow (S4TF).

Мы будем использовать Официальную документацию API Swift для библиотеки глубокого обучения TensorFlow и Пример модели классификации MNIST, представленный в Официальном репозитории S4TF Models Github, в качестве руководств. Здесь мы сосредоточимся на слое классификации и на том, как он построен.

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

1. Простой классификатор.

Ниже приведен исходный код, с которым мы будем работать.

/// A classifier.
struct Classifier: Layer {
typealias Input = Tensor<Float> 
typealias Output = Tensor<Float>
var conv1a = Conv2D<Float>(filterShape: (3, 3, 1, 32), activation: relu) 
var conv1b = Conv2D<Float>(filterShape: (3, 3, 32, 64), activation: relu)
var pool1 = MaxPool2D<Float>(poolSize: (2, 2), strides: (2, 2)) 
var dropout1a = Dropout<Float>(probability: 0.25) 
var flatten = Flatten<Float>() 
var layer1a = Dense<Float>(inputSize: 9216, outputSize: 128, activation: relu) 
var dropout1b = Dropout<Float>(probability: 0.5) 
var layer1b = Dense<Float>(inputSize: 128, outputSize: 10, activation: softmax)
@differentiable 
func call(_ input: Input) -> Output {
  let convolved = input.sequenced(through: conv1a, conv1b, pool1) 
  return convolved.sequenced(through: dropout1a, flatten, layer1a,             dropout1b, layer1b)
  }
}

Как вы можете заметить, он начинается со следующего объявления:

struct Classifier: Layer {}

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

applied(to:in:)

Этот метод в основном сопоставляет входные тензоры с выходными тензорами, упорядочивая ввод через подслои, которые мы вскоре определим.

Далее у нас есть следующие строки:

typealias Input = Tensor<Float> 
typealias Output = Tensor<Float>

которые объявляют псевдонимы 2 типов. Ссылка Эта более подробно объясняет псевдонимы типов в Swift, но основная идея заключается в том, что мы даем пользовательское имя уже существующему типу, например строке, целому числу или, в данном случае, тензору с плавающей запятой.

2. Входные слои.

После этого у нас есть следующее объявление:

var conv1a = Conv2D<Float>(filterShape: (3, 3, 1, 32), activation: relu) 
var conv1b = Conv2D<Float>(filterShape: (3, 3, 32, 64), activation: relu)

Здесь мы создаем 2 сверточных слоя, используя Conv2D. Чтобы объявить Conv2D Layer, мы указываем форму фильтра и функцию активации. Если вы посмотрите официальную документацию по Conv2D, вы увидите, что вы также можете указать смещение, шаг, а также отступы.

Здесь у нас есть четырехмерное ядро ​​размера (3, 3, 1, 32) и функция активации ReLU.

Затем мы строим последние слои сети, так что давайте пройдемся по ним один за другим.

var pool1 = MaxPool2D<Float>(poolSize: (2, 2), strides: (2, 2)) 

Здесь мы используем MaxPool2D, чтобы объявить, как следует из названия, слой Max Pooling. Мы используем классический размер пула 2×2, что позволит отбросить 3/4 наших активаций. Мы также добавляем шаг размером 2 × 2.

3. Выходные слои.

Затем мы используем Dropout, чтобы объявить, как следует из названия, наш Dropout Layer.

var dropout1a = Dropout<Float>(probability: 0.25)

Здесь с вероятностью 0,25 мы преобразуем наши входные единицы в 0.

После этого мы используем Flatten, чтобы создать Flatten Layer, который, как следует из названия, сгладит или свернет наш ввод.

var flatten = Flatten<Float>()

И теперь мы используем Dense, чтобы создать наш первый Dense Layer.

var layer1a = Dense<Float>(inputSize: 9216, outputSize: 128, activation: relu)

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

Обратите внимание, здесь мы указываем входной размер и выходной размер. Это способ указать форму весовой матрицы, которая будет инициализирована с помощью Единой инициализации Glorot.

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

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

Затем мы объявляем еще один Dropout Layer, но с вероятностью 0,5.

var dropout1b = Dropout<Float>(probability: 0.5)

И, наконец, мы создаем еще один плотный слой, но с входным размером 128 и выходным размером 10, потому что мы используем MNIST. Набор данных. Мы также меняем нашу функцию активации на softmax.

var layer1b = Dense<Float>(inputSize: 128, outputSize: 10, activation: softmax)

4. Давайте дифференцироваться.

Чтобы различать в S4TF, мы используем атрибут @дифференцируемый. Идея атрибутов состоит в том, чтобы предоставить дополнительную информацию о функции, которую мы объявляем. Ссылка Эта предоставит дополнительную информацию об атрибутах Swift.

@differentiable 

Когда мы применяем атрибут к функции, мы в основном помечаем функцию как дифференцируемую по отношению к различным параметрам. Для получения дополнительной информации о дифференцируемых функциях и API-интерфейсах дифференциации перейдите по этой ссылке.

Напомним, что в первом разделе мы говорили, что после принятия Layer Protocol мы должны определить метод в форме:

applied(to:in:)

По этой причине мы определяем следующую функцию:

func call(_ input: Input) -> Output {}

Функция вызова в основном дает нам выходные данные, которые мы получим, применяя ранее определенные слои к нашим входным данным.

Чтобы применить слои к входным данным, мы используем sequenced(through:_:_:), который является методом расширения Differentiable Protocol. В частности, метод возвращает результат, применяя слой к выводу предыдущего слоя.

let convolved = input.sequenced(through: conv1a, conv1b, pool1) return convolved.sequenced(through: dropout1a, flatten, layer1a,             dropout1b, layer1b)

Как мы видим, сначала мы применяем наши первые 2 сверточных слоя и слой максимального пула к нашему входу.

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