Автокодировщики в глубоком обучении без учителя

 Автокодировщики

Здравствуйте и вновь добро пожаловать на занятия по теме «Глубокое обучение без учителя на языке Python».

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

Я всегда говорил, что модели машинного обучения с учителем имеют две основные функции: обучение и прогноз. Поэтому обычно когда мы вызываем нейронную сеть, мы сначала для обучения используем условно некую функцию train(X,Y), а затем для прогнозирования – некую функцию predict(X). Но что, если мы заставим нейронную сеть давать прогноз по «самой себе», то есть используем, условно говоря, функцию train(X,X)? Тогда и получится автокодировщик.

Автокодировщики в глубоком обучении без учителя

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

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

Так, изображения вроде базы MNIST можно рассматривать как совокупность пикселей, каждый со своей яркостью, где 0 будет обозначать полное отсутствие яркости, а 1 – максимальную яркость. Промасштабировав данные по 255, чтобы исходящие данные были между нулём и единицей, мы затем можем использовать сигмоидную функцию и в скрытом, и в исходящем слоях.

Заметьте, что мы ещё не упоминали свободные члены. В автокодировщиках у нас есть различные свободные члены для скрытого и исходящего слоёв. Это легко реализовать в библиотеке Theano, поскольку градиенты будут вычисляться автоматически лишь с одним небольшим видоизменением.

Небольшая модификация, которая иногда используется и в автокодировщиках, и в ограниченных машинах Больцмана, состоит в использовании общих весовых коэффициентов. Так, вместо того чтобы использовать другие весовые коэффициенты в исходящем слое, мы используем транспонированные первоначальные весовые коэффициенты. С идеей общих весовых коэффициентов мы впервые столкнулись при рассмотрении свёрточных нейронных сетей – это был один и тот же фильтр, который скользил по каждой части изображения. При этом следует помнить, что наличие общих весовых коэффициентов является разновидностью регуляризации, поскольку мы уменьшаем количество параметров, тем самым уменьшая вероятность переобученности модели.

Далее, вернёмся теперь к целевой функции с квадратом ошибки. Записав её в терминах весовых коэффициентов и входящих данных, мы получим следующее:

 J=|X-X_{hat}|_{F}^{2}=|X-s(s(XW)W^T)|_{F}^{2}.

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

 J=|X-XQQ^T)|_{F}^{2}.

Действительно, автокодировщики можно рассматривать как своего рода нелинейный метод главных компонент, так как они создают нелинейное отображение, как и метод t-SNE. Но обратите внимание, что это будет неточным представлением, поскольку перед этим не рассматривались свободные члены, а также нет никаких требований о том, чтобы каждый столбец весовых коэффициентов имел единичную длину или чтобы каждый столбец был ортогональным, или чтобы они были упорядочены каким-либо определённым образом.

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

Шумоподавляющие автокодировщики

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

Другой способ видоизменения данных, отличный от явного перемещения и вращения предметов, состоит в добавлении шума. В частности, одним из вариантов является добавление гауссова шума к входным данным, другим – просто установить некоторые значения равными нулю. Делается это практически элементарно, поскольку, по сути, является простым добавлением битовой маски к входным данным. Другими словами, мы просто генерируем случайный вектор из нулей и единиц и поэлементно умножаем на первоначальное X для получения нового X. Как правило, вероятность генерации нуля устанавливается меньше 50%, например, оно может быть равно 30%, но исследователи обнаружили, что величина до 50% всё ещё обеспечивает хорошие результаты.

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

Вложенные автокодировщики

А что будет, когда мы возьмём группу автокодировщиков и вложим их в слои?

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

Обратите внимание, что вовсе не обязательно, чтобы всё было именно так. На самом деле мы могли бы установить огромное количество скрытых узлов в среднем слое. Теоретически, если у нас есть N учебных примеров и N скрытых узлов, то мы получили бы идеальное восстановление, поскольку каждый из N столбцов, состоящих из весовых коэффициентов, мог бы отвечать за восстановление различных примеров. Можно предположить, что это приведёт лишь к значительной переобученности модели, однако исследования показали, что модель не переобучается и может работать, даже если количество скрытых узлов превышает N.

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

class AutoEncoder:

  def fit(self,X):

    self.W=…, self.bh=…, delf.bo=…

    <градиентный спуск>

  def predict(self,X):

    return s(s(X.dot(self.W) + self.bh).dot(self.W.T) + self.bo)

deep_network = [список объектов AutoEncoder]

curretn_input = X

for ae in deep_network:

  current_input = ae.fit(current_input)

#теперь current_input на самом деле является выходом

Процедура, которую мы только что рассмотрели, называется «жадным послойным предварительным обучением». Если вы изучали алгоритмы, то, вероятно, понимаете, почему он называется «жадным». «Жадный» означает, что мы выбираем локально лучшее решение, что в нашем случае означает просто обучение одного слоя автокодировщика. Хотя теоретически оно может быть не лучшим, но на самом деле оно очень помогает в обучении с учителем, происходящим на следующем этапе. Именно поэтому оно и называется предварительным обучением.

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

И последнее, о чём я хотел бы упомянуть, прежде чем мы перейдём к коду, который будем писать прямо в библиотеке Theano. Я предлагаю вам попробовать написать этот код полностью самим. Обратите внимание, что здесь нет никакого нового материала, а лишь новая концепция, использующая старые понятия. Вы уже знаете всё необходимое – как создать нейронную сеть, как создать её с несколькими слоями и как делать градиентный спуск в библиотеке Theano. Я также уже показывал, как загружать данные, так что на данном этапе вы должны уметь всё сделать совершенно самостоятельно. Это очень хорошее упражнение – сделать всё это самому, а затем сравнить с предлагаемым мною кодом. Так вы увидите, где ваш код можно было бы улучшить, а где – он даже лучше, чем мой.

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: