Практикум на ЭВМ (317)/Autoencoder

Материал из MachineLearning.

(Различия между версиями)
Перейти к: навигация, поиск
Строка 3: Строка 3:
В этом задании вам предстоит реализовать нейросетевой алгоритм сокращения размерности данных, известный как автокодировщик ([http://en.wikipedia.org/wiki/Autoencoder autoencoder]). Его нужно настроить на базе изображений рукописных цифр [http://yann.lecun.com/exdb/mnist/ MNIST]. Выполненное задание состоит из кода на Matlab и отчёта в формате pdf, в который нужно поместить ответы на вопросы, сформулированные в тексте. Пункты, помеченные как бонусные, выполнять необязательно, но полезно.
В этом задании вам предстоит реализовать нейросетевой алгоритм сокращения размерности данных, известный как автокодировщик ([http://en.wikipedia.org/wiki/Autoencoder autoencoder]). Его нужно настроить на базе изображений рукописных цифр [http://yann.lecun.com/exdb/mnist/ MNIST]. Выполненное задание состоит из кода на Matlab и отчёта в формате pdf, в который нужно поместить ответы на вопросы, сформулированные в тексте. Пункты, помеченные как бонусные, выполнять необязательно, но полезно.
-
Задание необходимо сдать до XX марта, чтобы иметь возможность получить за него 5 баллов. После этого за каждый день опоздания начисляется штраф в 0.1 балл. Задание выполняется самостоятельно. Обмен кусками кода под любыми предлогами запрещён. При этом обсуждение методологии и результатов разрешается и даже приветствуется. Вопросы по заданию можно задавать в обсуждении к этой странице.
+
Задание необходимо сдать до 11 марта, чтобы иметь возможность получить за него 5 баллов. После этого за каждый день опоздания начисляется штраф в 0.1 балла. Задание выполняется самостоятельно. Обмен кусками кода под любыми предлогами запрещён. Вопросы по заданию можно задавать в обсуждении к этой странице.
== Цели задания ==
== Цели задания ==
Строка 12: Строка 12:
== Бэкграунд ==
== Бэкграунд ==
-
Искусственная нейронная сеть — достаточно мощная модель, используемая для решения различных задач машинного обучения. Классические алгоритмы машинного обучения (например, основанные на линейных моделях) пытаются отделить зависимости, действительно присутствующие в данных, от шума. При этом сами эти зависимости предполагаются достаточно простыми. В задачах, которые принято относить к искусственному интеллекту, данных значительно больше, поэтому шум представляет меньшую проблему, но зависимости в данных более сложные. Для решения подобных практических задач бывает полезно использовать нейросети.
+
Для обучения на данных со сложными зависимостями бывает полезно использовать искусственные нейронные сети, особенно если данных достаточно много, чтобы переобуение на шум не являлось проблемой. Функции, оптимизируемые при обучении многослойных нейронных сетей, невыпуклы, и как правило содержат плато и локальные минимумы. Найти глобальный минимум такой функции практически невозможно, поэтому на практике стремятся найти хороший локальный минимум. При этом результат обучения сильно зависит от инициализации и используемого метода оптимизации (в чём вам предстоит убедиться). Это делает результат трудновоспроизводимым. На практике при обучении приходится экспериментировать и применять эвристики.
-
Функции потерь, которые нужно оптимизировать при обучении многослойных нейронных сетей невыпуклы и как правило содержат плато и локальные минимумы, что делает их оптимизацию неприятной. Найти глобальный минимум такой функции практически невозможно. Однако на практике это и не нужно: достаточно найти хороший локальный минимум. При этом результат обучения сильно зависит от инициализации и используемого метода оптимизации (в чём вам предстоит убедиться), что делает его трудновоспроизводимым. На практике при обучении приходится экспериментировать и применять эвристики. Эти недостатки приходится приносить в жертву способности находить нелинейные зависимости в больших данных.
+
В течение последних нескольких лет в рамках парадигмы глубинного обучения ([http://en.wikipedia.org/wiki/Deep_learning deep learning]) появились методы стабильной инициализации весов. Другая идея из глубинного обучения заключается в следующем. Чтобы избежать переобучения, нужно сначала сократить размерность данных (игнорируя метки классов), а затем уже обучаться на данных низкой размерности. Первый этап может заменять ручное извлечение признаков, поэтому эту идею называют обучением признаков (feature learning). Автокодировщик — это нейросеть, которая преобразует данные высокой размерности в данные низкой размерности, как и [[метод главных компонент]] (PCA), но при этом преобразование является нелинейным.
-
 
+
-
В истории нейросетей всплески энтузиазма сменялись разочарованиями. Последнее разочаровние было во второй половине 90х и 00х годах, когда им на смену пришли модели, которые не требовали таких больших вычислительных ресурсов для оптимизации и аккуратной инициализации. Однако в конце 00х возрос интерес к [http://en.wikipedia.org/wiki/Big_data большим данным] и стали доступнее кластеры, что позволило обучать сложные модели. Также возникла парадигма глубинного обучения ([http://en.wikipedia.org/wiki/Deep_learning deep learning]), в рамках которой появились методы стабильной инициализации весов. Это возродило интерес к нейросетям.
+
-
 
+
-
Одна из идей глубинного обучения заключается в следующем. Чтобы избежать переобучения, нужно сначала сократить размерность данных (игнорируя метки классов), а затем уже обучаться на данных низкой размерности. Первый этап может заменять ручное извлечение признаков, поэтому эту идею называют обучением признаков (feature learning). Классические методы обучения без учителя, хотя и используются для предобработки выборки, либо линейные (например, [[метод главных компонент]], PCA), либо локальные ([[кластеризация]]). Автокодировщик преобразует данные, учитывая глобальные зависимости, как PCA, но при этом нелинейный. Он решает ту же задачу: преобразует данные высокой размерности в данные низкой размерности, пытаясь «нащупать» многообразие, в котором лежат данные. Восстановленные данные должны быть максимально близки к исходным данным большой размерности.
+
== Теория ==
== Теория ==
Строка 25: Строка 21:
:<tex>\mathbf{h}_i = \zeta_i(\mathbf{h}_{i-1} W_i + \mathbf{b}_{i}), \quad i \in \{1, \dots, n \},</tex>
:<tex>\mathbf{h}_i = \zeta_i(\mathbf{h}_{i-1} W_i + \mathbf{b}_{i}), \quad i \in \{1, \dots, n \},</tex>
-
где <tex>W_i</tex> — матрица весов <tex>i</tex>-го слоя, а <tex>\mathbf{b}_{i}</tex> — вектор смещений (bias), их необходимо настроить по обучающей выборке. <tex>\zeta_i(\cdot)</tex> — функция активации <tex>i</tex>-го слоя, которая применяется к векторам поэлементно и обеспечивает нелинейное преобразование. Мы предлагаем использовать архитектуру из статьи Хинтона и Салахутдинова ([http://www.lsv.uni-saarland.de/Seminar/ML_for_NLP_SS12/HinSal06.pdf 2006]), в которой было предложено глубинное обучение для автокодировщика. Размер слоёв следующий: <tex>[784, 1000, 500, 250, 30, 250, 500, 1000, 784]</tex>: на входном и выходном слоях размерность совпадает с числом пикселей в изображении символа (28×28). Средний слой — 30-мерный вектор, именно на нём получается сжатое представление символа. На всех слоях используются [http://en.wikipedia.org/wiki/Sigmoid_function сигмоидальные] функции активации: <tex>\sigma(x) = \frac{1}{1+\exp(-x)}</tex>, кроме среднего, где используется линейная активация: <tex>\zeta_4(x) = x</tex>.
+
где <tex>W_i</tex> — матрица весов <tex>i</tex>-го слоя, а <tex>\mathbf{b}_{i}</tex> — вектор смещений (bias), их необходимо настроить по обучающей выборке. <tex>\zeta_i(\cdot)</tex> — функция активации <tex>i</tex>-го слоя, которая применяется к векторам поэлементно и обеспечивает нелинейное преобразование. Мы предлагаем использовать архитектуру из статьи Хинтона и Салахутдинова ([http://www.lsv.uni-saarland.de/Seminar/ML_for_NLP_SS12/HinSal06.pdf 2006]), в которой было предложено глубинное обучение для автокодировщика. Размер слоёв следующий: [784, 1000, 500, 250, 30, 250, 500, 1000, 784]. На входном и выходном слоях размерность совпадает с числом пикселей в изображении символа (28×28). Средний слой — 30-мерный вектор, именно на нём получается сжатое представление символа. На всех слоях используются [http://en.wikipedia.org/wiki/Sigmoid_function сигмоидальные] функции активации: <tex>\sigma(x) = \frac{1}{1+\exp(-x)}</tex>, кроме среднего, где используется линейная активация: <tex>\zeta_4(x) = x</tex>.
При обучении необходимо настроить матрицы <tex>W_i</tex> и векторы <tex>\mathbf{b}_{i}</tex> так, чтобы минимизировать функцию потерь <tex>L(\mathbf{y},\mathbf{x})</tex>. Её можно задавать по-разному, лишь бы она отражала расстояние между ответами и желательно легко дифференцировалась. Мы предлагаем в этом задании использовать среднеквадратичную ошибку (mean squared error, MSE): <tex>L(\mathbf{y},\mathbf{x}) = || \mathbf{y} - \mathbf{x} ||_2^2</tex>.
При обучении необходимо настроить матрицы <tex>W_i</tex> и векторы <tex>\mathbf{b}_{i}</tex> так, чтобы минимизировать функцию потерь <tex>L(\mathbf{y},\mathbf{x})</tex>. Её можно задавать по-разному, лишь бы она отражала расстояние между ответами и желательно легко дифференцировалась. Мы предлагаем в этом задании использовать среднеквадратичную ошибку (mean squared error, MSE): <tex>L(\mathbf{y},\mathbf{x}) = || \mathbf{y} - \mathbf{x} ||_2^2</tex>.
Строка 31: Строка 27:
Параметры можно настроить методом градиентного спуска. Несмотря на большое количество параметров, градиент по ним можно оценивать довольно быстро благодаря «слоистой» структуре функционала. Метод последовательного вычисления градиентов с помощью применения правила производной сложной функции ([http://en.wikipedia.org/wiki/Chain_rule chain rule]) называется алгоритмом обратного распространения ошибки ([http://en.wikipedia.org/wiki/Backpropagation backpropagation]). При правильном выборе шага градиентного спуска, он сходится к локальному минимуму. Поэтому важно хорошо инициализировать параметры.
Параметры можно настроить методом градиентного спуска. Несмотря на большое количество параметров, градиент по ним можно оценивать довольно быстро благодаря «слоистой» структуре функционала. Метод последовательного вычисления градиентов с помощью применения правила производной сложной функции ([http://en.wikipedia.org/wiki/Chain_rule chain rule]) называется алгоритмом обратного распространения ошибки ([http://en.wikipedia.org/wiki/Backpropagation backpropagation]). При правильном выборе шага градиентного спуска, он сходится к локальному минимуму. Поэтому важно хорошо инициализировать параметры.
-
Хинтон и Салахутдинов предложили для инициализации параметров автокодировщика использовать этап ''предобучения'', на котором последовательно настраивать параметры отдельных слоёв. Для этого используется простая графическая вероятностная модель — [http://en.wikipedia.org/wiki/Restricted_Boltzmann_machine restricted Boltzmann machine] (RBM). Мы предлагаем Вам более простую альтернативу: настраивать параметры автокодировщиков с одним внутренним слоем с помощью алгоритма обратного распространения ошибки. Идея в том, что двуслойные нейросети более устойчивы к инициализации параметров, то есть, их проще настраивать. При этом помогает использовать т.н. связанные веса (tied weights), когда матрица весов на втором слое является транспонированной матрицей весов первого слоя: <tex>W_1 = W_2^{\mathrm{T}}</tex>. Смещения же настраиваются по отдельности. Функции активации используются те же, что использовались на этих слоях в исходном автокодировщике. Таким образом, на этапе предобучения Вам необходимо обучить 4 автокодировщика с одним внутренним слоем. Например, первый представляет собой нейросеть со слоями размерностей [784, 1000, 784], на выходах слоёв используются сигмоидальные функции активации. Его нужно обучить на исходных данных. Выход его скрытого слоя даёт данные для обучения второго автокодировщика. Четвёртый автокодировщик содержит слои размерностей [250, 30, 250], на скрытом слое используется линейная функция активации, на выходном — сигмоида. Полученные параметры используются для инициализации соответствующих слоёв «большого» автокодировщика, при его настройке ограничение на связанность весов снимается.
+
Хинтон и Салахутдинов предложили для инициализации параметров автокодировщика использовать этап ''предобучения'', на котором последовательно настраивать параметры отдельных слоёв. Для этого используется простая графическая вероятностная модель — [http://en.wikipedia.org/wiki/Restricted_Boltzmann_machine restricted Boltzmann machine] (RBM). Мы предлагаем Вам более простую альтернативу: настраивать параметры автокодировщиков с одним внутренним слоем с помощью алгоритма обратного распространения ошибки. Идея в том, что двуслойные нейросети более устойчивы к инициализации параметров, то есть, их проще настраивать. При этом помогает использовать т.н. связанные веса (tied weights), когда матрица весов на втором слое является транспонированной матрицей весов первого слоя: <tex>W_1 = W_2^{\mathrm{T}}</tex>. Смещения же настраиваются по отдельности. Функции активации используются те же, что использовались на этих слоях в исходном автокодировщике. Таким образом, на этапе предобучения вам необходимо обучить 4 автокодировщика с одним внутренним слоем. Например, первый представляет собой нейросеть со слоями размерностей [784, 1000, 784], на выходах слоёв используются сигмоидальные функции активации. Его нужно обучить на исходных данных. Выход его скрытого слоя даёт данные для обучения второго автокодировщика. Четвёртый автокодировщик содержит слои размерностей [250, 30, 250], на скрытом слое используется линейная функция активации, на выходном — сигмоида. Полученные параметры используются для инициализации соответствующих слоёв «большого» автокодировщика, при его настройке ограничение на связанность весов снимается.
== Практика ==
== Практика ==
Строка 55: Строка 51:
# Опишите вычисление функции потерь, в нашем случае MSE.
# Опишите вычисление функции потерь, в нашем случае MSE.
# Опишите вычисление градиентов функции потерь.
# Опишите вычисление градиентов функции потерь.
-
# Скорректируйте градиент для случая связанных весов. В этом случае для второй половины матриц весов градиенты должны остаться пустой матрицей.
+
# Скорректируйте градиент для случая связанных весов. В этом случае для второй половины матриц весов градиенты должны остаться пустыми матрицами.
В функции <code>minimize</code> опишите вычисление шага градиентного спуска на эпохе <tex>k</tex> как <tex>s_k = \frac{l}{k^{1/4}}</tex>, где <tex>l</tex> — параметр функции <code>learning_rate</code>. Остальные изменения нужно будет внести позже.
В функции <code>minimize</code> опишите вычисление шага градиентного спуска на эпохе <tex>k</tex> как <tex>s_k = \frac{l}{k^{1/4}}</tex>, где <tex>l</tex> — параметр функции <code>learning_rate</code>. Остальные изменения нужно будет внести позже.
Строка 65: Строка 61:
==== Подбор параметров на сокращённой выборке ====
==== Подбор параметров на сокращённой выборке ====
-
Для того чтобы подобрать параметры алгоритма оптимизации для этапа предобучения, будем использовать сокращённую выборку, а именно, обучающую выборку для нулевого класса. Тестировать будем на первом слое автокодировщика. Откройте файл <code>test_pretrain.m</code>.
+
Для того чтобы подобрать параметры алгоритма оптимизации для этапа предобучения, будем использовать сокращённую выборку, а именно, обучающую выборку для нулевого класса. Тестировать будем первый слой автокодировщика. Откройте файл <code>test_pretrain.m</code>.
1. Архитектура сети передаётся в <code>minimize</code> как массив структур <code>arch</code> длины на 1 больше, чем число слоёв, с полями <code>numw</code> — число весов на данном слое, <code>actfun</code> и <code>dactfun</code> — функция активации и её производная ''как функция выходного значения самой функции''. Опишите в начале <code>test_pretrain.m</code> вычисление сигмоиды и её производной: такие функции активации мы будем использовать для обоих слоёв. Запустите обучение из нулевого начального приближения на 20 эпох (это может занять пару минут). Какой результат вы наблюдаете визуально? Чему равна MSE в конце обучения?
1. Архитектура сети передаётся в <code>minimize</code> как массив структур <code>arch</code> длины на 1 больше, чем число слоёв, с полями <code>numw</code> — число весов на данном слое, <code>actfun</code> и <code>dactfun</code> — функция активации и её производная ''как функция выходного значения самой функции''. Опишите в начале <code>test_pretrain.m</code> вычисление сигмоиды и её производной: такие функции активации мы будем использовать для обоих слоёв. Запустите обучение из нулевого начального приближения на 20 эпох (это может занять пару минут). Какой результат вы наблюдаете визуально? Чему равна MSE в конце обучения?
-
2. Чтобы избежать такого эффекта, попробуйте инициализировать веса случайно, герерируя их из одномерных нормальных распределений с нулевым матожиданием и среднеквадратичным отклонением 0.3 независимо друг от друга. Чему равна MSE после 20 эпох? '''Бонус''': попробуйте довести обучение до сходимости? Сколько эпох и сколько времени это занимает, какая финальная ошибка?
+
2. Чтобы избежать такого эффекта, попробуйте инициализировать веса случайно, генерируя их из одномерных нормальных распределений с нулевым матожиданием и среднеквадратичным отклонением 0.3 независимо друг от друга. Чему равна MSE после 20 эпох? '''Бонус''': попробуйте довести обучение до сходимости. Сколько эпох и сколько времени это занимает, какая финальная ошибка?
3. Такая скорость обучения может быть приемлемой для сокращённой выборки, но перед переходом на полный набор желательно ускорить оптимизацию. Один из способов — использовать стохастический градиентный спуск. Его идея в том, чтобы оценивать градиент не по всей выборке, а по одному случайному объекту (on-line training) или небольшой группе объектов (mini-batch training). Несмотря на то что индивидуальные оценки градиента становятся менее точными, за счёт ускорения оценок градиента и компенсации ошибок удаётся увеличить скорость оптимизации. На практике при on-line оптимизации не выбирают каждый раз случайный объект, а в начале каждой эпохи упорядочивают их в случайном порядке и затем выбирают последовательно. В такой реализации on-line training является частным случаем mini-batch с размером порции равным 1.
3. Такая скорость обучения может быть приемлемой для сокращённой выборки, но перед переходом на полный набор желательно ускорить оптимизацию. Один из способов — использовать стохастический градиентный спуск. Его идея в том, чтобы оценивать градиент не по всей выборке, а по одному случайному объекту (on-line training) или небольшой группе объектов (mini-batch training). Несмотря на то что индивидуальные оценки градиента становятся менее точными, за счёт ускорения оценок градиента и компенсации ошибок удаётся увеличить скорость оптимизации. На практике при on-line оптимизации не выбирают каждый раз случайный объект, а в начале каждой эпохи упорядочивают их в случайном порядке и затем выбирают последовательно. В такой реализации on-line training является частным случаем mini-batch с размером порции равным 1.
-
Реализуйте в функции <code>minimize</code> поддержку параметров <code>shuffle</code> и <code>nbatches</code>. Если первый равен <code>true</code>, то в начале каждой эпохи нужно случайно переупорядочить объекты и поделить выборку на <code>nbatches</code> частей. Внутри каждой эпохи градиент должен оцениваться <code>nbatches</code> раз, при этом счётчик номера итерации (для вычисления шага) настёт каждый раз на 1. Раскомментируйте соответствующий цикл и перенесите туда вычисление градиента.
+
Реализуйте в функции <code>minimize</code> поддержку параметров <code>shuffle</code> и <code>nbatches</code>. Если первый равен <code>true</code>, то в начале каждой эпохи нужно случайно переупорядочить объекты и поделить выборку на <code>nbatches</code> частей. Внутри каждой эпохи градиент должен оцениваться <code>nbatches</code> раз, при этом счётчик номера итерации (для вычисления шага) растёт каждый раз на 1. Раскомментируйте соответствующий цикл и перенесите туда вычисление градиента.
-
Чем больше раз вычисляется градиент на каждой эпохе, тем она дольше, но для обучения нужно меньше эпох. Попробуйте запустить оптимизацию на минуту (установите достаточно большое число эпох и останавливайте внучную) при следующих значениниях <code>nbatches</code>: 1 (предыдущий эксперимент), 150, 1500, −1 (on-line оптимизация). Чему равна ошибка через минуту? При каком значении оптимизация идёт быстрее?
+
Чем больше раз вычисляется градиент на каждой эпохе, тем она дольше, но в этом случае для обучения нужно меньше эпох. Попробуйте запустить оптимизацию на минуту (установите достаточно большое число эпох и останавливайте внучную) при следующих значениниях <code>nbatches</code>: 1 (предыдущий эксперимент), 150, 1500, −1 (on-line оптимизация). Чему равна ошибка через минуту? При каком значении оптимизация идёт быстрее?
4. При маленьких размерах порций оценка градиента становится ненадёжной. Для её стабилизации иногда используют ''момент'' — метод усреднения градиента на последних итерациях (также называется damping). Вместо того, чтобы делать шаг по градиенту, на итерациях оптимизации поддерживается вектор <tex>\Delta</tex>. Если на <tex>k</tex>-й итерации оценен градиент <tex>\nabla^{k}L</tex>, то <tex>\Delta^k = (1-m)\nabla^{k}L + m\Delta^{k-1}</tex>, где константа <tex>m \in [0,1)</tex>. Этот вектор вычитается из вектора параметров на каждой итерации вместо градиента. Реализуйте эту возможность. Запустите 2 эпохи оптимизации на 1500 порциях при значениях коэффициента момента 0.0 (предыдущий эксперимент), 0.8, 0.9. Какие ошибки получаются после двух эпох?
4. При маленьких размерах порций оценка градиента становится ненадёжной. Для её стабилизации иногда используют ''момент'' — метод усреднения градиента на последних итерациях (также называется damping). Вместо того, чтобы делать шаг по градиенту, на итерациях оптимизации поддерживается вектор <tex>\Delta</tex>. Если на <tex>k</tex>-й итерации оценен градиент <tex>\nabla^{k}L</tex>, то <tex>\Delta^k = (1-m)\nabla^{k}L + m\Delta^{k-1}</tex>, где константа <tex>m \in [0,1)</tex>. Этот вектор вычитается из вектора параметров на каждой итерации вместо градиента. Реализуйте эту возможность. Запустите 2 эпохи оптимизации на 1500 порциях при значениях коэффициента момента 0.0 (предыдущий эксперимент), 0.8, 0.9. Какие ошибки получаются после двух эпох?
Строка 85: Строка 81:
1. В скрипте <code>test.m</code> опишите функции активации и их производные для всех слоёв. В функции <code>pretrain.m</code> внутри цикла опишите: 1) создание архитектуры двуслойного автокодировщика для данного слоя, 2) случайную инициализацию параметров, 3) преобразование данных в соответствии с обученными параметрами для получения обучающей выборки следующего слоя. Обратите внимание на используемые параметры: 3 эпохи по 1500 порций, коэффициент момента 0.97. Это эмпирически установленные значения, которые неплохо походят для предобучения всех слоёв. Вы можете добиться лучшего результата, установив индивидуальные параметры для каждого слоя, а также регулируя скорость обучения. Визуализация параметров включена только для первого слоя, так как только для него она имеет смысл. Запустите <code>test.m</code> и убедитесь, что обучение идёт.
1. В скрипте <code>test.m</code> опишите функции активации и их производные для всех слоёв. В функции <code>pretrain.m</code> внутри цикла опишите: 1) создание архитектуры двуслойного автокодировщика для данного слоя, 2) случайную инициализацию параметров, 3) преобразование данных в соответствии с обученными параметрами для получения обучающей выборки следующего слоя. Обратите внимание на используемые параметры: 3 эпохи по 1500 порций, коэффициент момента 0.97. Это эмпирически установленные значения, которые неплохо походят для предобучения всех слоёв. Вы можете добиться лучшего результата, установив индивидуальные параметры для каждого слоя, а также регулируя скорость обучения. Визуализация параметров включена только для первого слоя, так как только для него она имеет смысл. Запустите <code>test.m</code> и убедитесь, что обучение идёт.
-
2. Раскомментируйте в <code>test.m</code> тонкую подстройку весов градиентным спуском. Подождите 3–4 эпохи обучения, и остановите вручную. Чему равна ошибка на обучающей выборке? Сильно ли градиентный спуск улучшает результат предобучения? Загрузите тестовую выборку (файлы <code>testX.mat</code>) и протестируйте обученный автокодировщик на них. Сделайте вывод о степени переобучения.
+
2. Раскомментируйте в <code>test.m</code> тонкую подстройку весов градиентным спуском. Подождите 3–4 эпохи обучения, и остановите вручную. Чему равна ошибка на обучающей выборке? Сильно ли градиентный спуск улучшает результат предобучения? Загрузите тестовую выборку (файлы <code>testX.mat</code>) и протестируйте обученный автокодировщик на ней. Сделайте вывод о степени переобучения.
-
3. ('''Бонус''') Предлагается проверить, есть ли смысл в этапе предобучения. Отключите предобучение в начале <code>test.m</code>, сделайте инициализацию параметров из нормального распределения с центром в нуле и среднеквадратичным отклонением 0.3. Запустите градиентный спуск на час (или больше). Может ли он достигнуть той же точности, что и с предобучением? Попробуйте модифицировать инициализацию и параметры, может, дело в них?
+
3. Предлагается проверить, есть ли смысл в этапе предобучения. Отключите предобучение в начале <code>test.m</code>, сделайте инициализацию параметров из нормального распределения с центром в нуле и среднеквадратичным отклонением 0.3. Запустите градиентный спуск на час (или больше). Может ли он достигнуть той же точности, что и с предобучением? '''Бонус''': попробуйте модифицировать инициализацию и параметры, чтобы улучшить точность градиентного спуска без предобучения.
 +
4. ('''Бонус''') Реализуйте метод главных компонент (PCA) и сократите размерность данных до 30 главных компонент. Может ли автокодировщик извлечь пользу из нелинейности преобразования?
==== Обучение классификатора цифр на данных сокращённой размерности ====
==== Обучение классификатора цифр на данных сокращённой размерности ====
-
('''Бонус''') Получите для всех объектов обучающей выборки сокращённое представление на среднем слое. Используйте его, чтобы обучить нейросеть, у которой на выходе вероятности каждой из цифр. Заметьте, что функции <code>nnfunc</code> и <code>minimize</code> могут быть использованы для обучения без модификаций, необходимо только описать новую архитектуру и метки классов.
+
Получите для всех объектов обучающей выборки сокращённое представление на среднем слое. Используйте его, чтобы обучить нейросеть, у которой на выходе вероятности каждой из цифр. Заметьте, что функции <code>nnfunc</code> и <code>minimize</code> могут быть использованы для обучения без модификаций, необходимо только описать новую архитектуру и метки классов.
1. Попробуйте обучить однослойную нейросеть, на выходном слое которой используется функция активации [http://en.wikipedia.org/wiki/Softmax_activation_function softmax]. Это естественная функция для задачи многоклассовой классификации. Обратите внимание, что она применяется уже не поэлементно. Какую точность (доля верно распознанных цифр) удаётся получить?
1. Попробуйте обучить однослойную нейросеть, на выходном слое которой используется функция активации [http://en.wikipedia.org/wiki/Softmax_activation_function softmax]. Это естественная функция для задачи многоклассовой классификации. Обратите внимание, что она применяется уже не поэлементно. Какую точность (доля верно распознанных цифр) удаётся получить?
2. Добавьте скрытый слой размерности 20 с сигмоидальной функцией активации. Позволяет ли это поднять точность?
2. Добавьте скрытый слой размерности 20 с сигмоидальной функцией активации. Позволяет ли это поднять точность?
 +
 +
3. Сравните полученную точность с точностью SVM из первого задания. Опишите сравнение в отчёте.
[[Категория:МГУ]]
[[Категория:МГУ]]
[[Категория:Учебные курсы]]
[[Категория:Учебные курсы]]

Версия 15:50, 24 февраля 2013

Это черновик задания. Не сто́ит приступать к его выполнению до официального релиза.


В этом задании вам предстоит реализовать нейросетевой алгоритм сокращения размерности данных, известный как автокодировщик (autoencoder). Его нужно настроить на базе изображений рукописных цифр MNIST. Выполненное задание состоит из кода на Matlab и отчёта в формате pdf, в который нужно поместить ответы на вопросы, сформулированные в тексте. Пункты, помеченные как бонусные, выполнять необязательно, но полезно.

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

Содержание

Цели задания

  1. Познакомиться с задачей сокращения размерности данных (сжатия с потерями).
  2. Понять, какие практические проблемы возникают при обучении искусственных нейронных сетей.
  3. Усвоить некоторые принципы глубинного обучения (deep learning).
  4. Закрепить навыки манипуляции с матрицами.

Бэкграунд

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

В течение последних нескольких лет в рамках парадигмы глубинного обучения (deep learning) появились методы стабильной инициализации весов. Другая идея из глубинного обучения заключается в следующем. Чтобы избежать переобучения, нужно сначала сократить размерность данных (игнорируя метки классов), а затем уже обучаться на данных низкой размерности. Первый этап может заменять ручное извлечение признаков, поэтому эту идею называют обучением признаков (feature learning). Автокодировщик — это нейросеть, которая преобразует данные высокой размерности в данные низкой размерности, как и метод главных компонент (PCA), но при этом преобразование является нелинейным.

Теория

Этот раздел описывает архитектуру n-слойного нейросетевого автокодировщика. Пусть на вход подаётся вектор признаков \mathbf{x}, который в случае MNIST представляет собой вытянутое в строку изображение символа. На выходе автокодировщика имеем вектор \mathbf{y}, который должен быть близок к \mathbf{x}. Выход i-го слоя обозначим \mathbf{h}_i и положим \mathbf{h}_0 = \mathbf{x}, \mathbf{y} = \mathbf{h}_n. Формулы пересчёта значений на слоях следующие:

\mathbf{h}_i = \zeta_i(\mathbf{h}_{i-1} W_i + \mathbf{b}_{i}), \quad i \in \{1, \dots, n \},

где W_i — матрица весов i-го слоя, а \mathbf{b}_{i} — вектор смещений (bias), их необходимо настроить по обучающей выборке. \zeta_i(\cdot) — функция активации i-го слоя, которая применяется к векторам поэлементно и обеспечивает нелинейное преобразование. Мы предлагаем использовать архитектуру из статьи Хинтона и Салахутдинова (2006), в которой было предложено глубинное обучение для автокодировщика. Размер слоёв следующий: [784, 1000, 500, 250, 30, 250, 500, 1000, 784]. На входном и выходном слоях размерность совпадает с числом пикселей в изображении символа (28×28). Средний слой — 30-мерный вектор, именно на нём получается сжатое представление символа. На всех слоях используются сигмоидальные функции активации: \sigma(x) = \frac{1}{1+\exp(-x)}, кроме среднего, где используется линейная активация: \zeta_4(x) = x.

При обучении необходимо настроить матрицы W_i и векторы \mathbf{b}_{i} так, чтобы минимизировать функцию потерь L(\mathbf{y},\mathbf{x}). Её можно задавать по-разному, лишь бы она отражала расстояние между ответами и желательно легко дифференцировалась. Мы предлагаем в этом задании использовать среднеквадратичную ошибку (mean squared error, MSE): L(\mathbf{y},\mathbf{x}) = || \mathbf{y} - \mathbf{x} ||_2^2.

Параметры можно настроить методом градиентного спуска. Несмотря на большое количество параметров, градиент по ним можно оценивать довольно быстро благодаря «слоистой» структуре функционала. Метод последовательного вычисления градиентов с помощью применения правила производной сложной функции (chain rule) называется алгоритмом обратного распространения ошибки (backpropagation). При правильном выборе шага градиентного спуска, он сходится к локальному минимуму. Поэтому важно хорошо инициализировать параметры.

Хинтон и Салахутдинов предложили для инициализации параметров автокодировщика использовать этап предобучения, на котором последовательно настраивать параметры отдельных слоёв. Для этого используется простая графическая вероятностная модель — restricted Boltzmann machine (RBM). Мы предлагаем Вам более простую альтернативу: настраивать параметры автокодировщиков с одним внутренним слоем с помощью алгоритма обратного распространения ошибки. Идея в том, что двуслойные нейросети более устойчивы к инициализации параметров, то есть, их проще настраивать. При этом помогает использовать т.н. связанные веса (tied weights), когда матрица весов на втором слое является транспонированной матрицей весов первого слоя: W_1 = W_2^{\mathrm{T}}. Смещения же настраиваются по отдельности. Функции активации используются те же, что использовались на этих слоях в исходном автокодировщике. Таким образом, на этапе предобучения вам необходимо обучить 4 автокодировщика с одним внутренним слоем. Например, первый представляет собой нейросеть со слоями размерностей [784, 1000, 784], на выходах слоёв используются сигмоидальные функции активации. Его нужно обучить на исходных данных. Выход его скрытого слоя даёт данные для обучения второго автокодировщика. Четвёртый автокодировщик содержит слои размерностей [250, 30, 250], на скрытом слое используется линейная функция активации, на выходном — сигмоида. Полученные параметры используются для инициализации соответствующих слоёв «большого» автокодировщика, при его настройке ограничение на связанность весов снимается.

Практика

Вывод формул градиентного спуска

Выведите рекуррентные формулы градиентов по группам параметров. Обозначим линейный выход i-го слоя \mathbf{o}_i = \mathbf{h}_{i-1} W_i + \mathbf{b}_{i}, тогда \mathbf{h}_i = \zeta_i(\mathbf{o}_i). Вычислите и поместите в отчёт следующие функции:

  • \frac{dL}{d\mathbf{h}_n} как функцию от \mathbf{h}_n,
  • \frac{d\mathbf{h}_i}{d\mathbf{o}_i} как функцию от \mathbf{h}_i для поэлементных сигмоиды и линейной функции,
  • \frac{d\mathbf{o}_{i+1}}{d\mathbf{o}_{i}},
  • \frac{dL}{d\mathbf{o}_i} как функцию от \frac{dL}{d\mathbf{o}_{i+1}},
  • \frac{dL}{dW_i} и \frac{dL}{d\mathbf{b}_i} как функцию от \frac{dL}{d\mathbf{o}_{i}}.

Как изменятся формулы в случае связанных весов?

Реализация алгоритмов

TODO: залить код и вставить ссылку

Мы предоставляем каркас кода обучения автокодировщика, который должен упростить выполнение задания. Вы можете модифицировать его как угодно, интерфейс сохранять необязательно. [] Скачайте отсюда (30 Мб) данные MNIST в формате Matlab и распакуйте в ту же директорию, что и код.

Реализуйте вычисление выхода нейросети, функции потерь и её градиента в функции nnfunc. Следуйте инструкциям в коде:

  1. Опишите вычисление переменных val{layer}, аналогов h_{i+1}.
  2. Опишите вычисление функции потерь, в нашем случае MSE.
  3. Опишите вычисление градиентов функции потерь.
  4. Скорректируйте градиент для случая связанных весов. В этом случае для второй половины матриц весов градиенты должны остаться пустыми матрицами.

В функции minimize опишите вычисление шага градиентного спуска на эпохе k как s_k = \frac{l}{k^{1/4}}, где l — параметр функции learning_rate. Остальные изменения нужно будет внести позже.

В функции compute_error_and_plot опишите вычисление MSE между вычисленным ответом и верным. Эта функция вызывается в процессе оптимизации, чтобы отслеживать текущее качество модели. Если на вход подаются символы в формате MNIST, можно включить их отображение (вызывается код Руслана Салахутдинова).

Подбор параметров и обучение

Подбор параметров на сокращённой выборке

Для того чтобы подобрать параметры алгоритма оптимизации для этапа предобучения, будем использовать сокращённую выборку, а именно, обучающую выборку для нулевого класса. Тестировать будем первый слой автокодировщика. Откройте файл test_pretrain.m.

1. Архитектура сети передаётся в minimize как массив структур arch длины на 1 больше, чем число слоёв, с полями numw — число весов на данном слое, actfun и dactfun — функция активации и её производная как функция выходного значения самой функции. Опишите в начале test_pretrain.m вычисление сигмоиды и её производной: такие функции активации мы будем использовать для обоих слоёв. Запустите обучение из нулевого начального приближения на 20 эпох (это может занять пару минут). Какой результат вы наблюдаете визуально? Чему равна MSE в конце обучения?

2. Чтобы избежать такого эффекта, попробуйте инициализировать веса случайно, генерируя их из одномерных нормальных распределений с нулевым матожиданием и среднеквадратичным отклонением 0.3 независимо друг от друга. Чему равна MSE после 20 эпох? Бонус: попробуйте довести обучение до сходимости. Сколько эпох и сколько времени это занимает, какая финальная ошибка?

3. Такая скорость обучения может быть приемлемой для сокращённой выборки, но перед переходом на полный набор желательно ускорить оптимизацию. Один из способов — использовать стохастический градиентный спуск. Его идея в том, чтобы оценивать градиент не по всей выборке, а по одному случайному объекту (on-line training) или небольшой группе объектов (mini-batch training). Несмотря на то что индивидуальные оценки градиента становятся менее точными, за счёт ускорения оценок градиента и компенсации ошибок удаётся увеличить скорость оптимизации. На практике при on-line оптимизации не выбирают каждый раз случайный объект, а в начале каждой эпохи упорядочивают их в случайном порядке и затем выбирают последовательно. В такой реализации on-line training является частным случаем mini-batch с размером порции равным 1.

Реализуйте в функции minimize поддержку параметров shuffle и nbatches. Если первый равен true, то в начале каждой эпохи нужно случайно переупорядочить объекты и поделить выборку на nbatches частей. Внутри каждой эпохи градиент должен оцениваться nbatches раз, при этом счётчик номера итерации (для вычисления шага) растёт каждый раз на 1. Раскомментируйте соответствующий цикл и перенесите туда вычисление градиента.

Чем больше раз вычисляется градиент на каждой эпохе, тем она дольше, но в этом случае для обучения нужно меньше эпох. Попробуйте запустить оптимизацию на минуту (установите достаточно большое число эпох и останавливайте внучную) при следующих значениниях nbatches: 1 (предыдущий эксперимент), 150, 1500, −1 (on-line оптимизация). Чему равна ошибка через минуту? При каком значении оптимизация идёт быстрее?

4. При маленьких размерах порций оценка градиента становится ненадёжной. Для её стабилизации иногда используют момент — метод усреднения градиента на последних итерациях (также называется damping). Вместо того, чтобы делать шаг по градиенту, на итерациях оптимизации поддерживается вектор \Delta. Если на k-й итерации оценен градиент \nabla^{k}L, то \Delta^k = (1-m)\nabla^{k}L + m\Delta^{k-1}, где константа m \in [0,1). Этот вектор вычитается из вектора параметров на каждой итерации вместо градиента. Реализуйте эту возможность. Запустите 2 эпохи оптимизации на 1500 порциях при значениях коэффициента момента 0.0 (предыдущий эксперимент), 0.8, 0.9. Какие ошибки получаются после двух эпох?

5. (Бонус) попробуйте подобрать параметры алгоритма оптимизации, чтобы ошибка на обучении падала скорее всего.

Обучение многослойного автокодировщика

1. В скрипте test.m опишите функции активации и их производные для всех слоёв. В функции pretrain.m внутри цикла опишите: 1) создание архитектуры двуслойного автокодировщика для данного слоя, 2) случайную инициализацию параметров, 3) преобразование данных в соответствии с обученными параметрами для получения обучающей выборки следующего слоя. Обратите внимание на используемые параметры: 3 эпохи по 1500 порций, коэффициент момента 0.97. Это эмпирически установленные значения, которые неплохо походят для предобучения всех слоёв. Вы можете добиться лучшего результата, установив индивидуальные параметры для каждого слоя, а также регулируя скорость обучения. Визуализация параметров включена только для первого слоя, так как только для него она имеет смысл. Запустите test.m и убедитесь, что обучение идёт.

2. Раскомментируйте в test.m тонкую подстройку весов градиентным спуском. Подождите 3–4 эпохи обучения, и остановите вручную. Чему равна ошибка на обучающей выборке? Сильно ли градиентный спуск улучшает результат предобучения? Загрузите тестовую выборку (файлы testX.mat) и протестируйте обученный автокодировщик на ней. Сделайте вывод о степени переобучения.

3. Предлагается проверить, есть ли смысл в этапе предобучения. Отключите предобучение в начале test.m, сделайте инициализацию параметров из нормального распределения с центром в нуле и среднеквадратичным отклонением 0.3. Запустите градиентный спуск на час (или больше). Может ли он достигнуть той же точности, что и с предобучением? Бонус: попробуйте модифицировать инициализацию и параметры, чтобы улучшить точность градиентного спуска без предобучения.

4. (Бонус) Реализуйте метод главных компонент (PCA) и сократите размерность данных до 30 главных компонент. Может ли автокодировщик извлечь пользу из нелинейности преобразования?

Обучение классификатора цифр на данных сокращённой размерности

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

1. Попробуйте обучить однослойную нейросеть, на выходном слое которой используется функция активации softmax. Это естественная функция для задачи многоклассовой классификации. Обратите внимание, что она применяется уже не поэлементно. Какую точность (доля верно распознанных цифр) удаётся получить?

2. Добавьте скрытый слой размерности 20 с сигмоидальной функцией активации. Позволяет ли это поднять точность?

3. Сравните полученную точность с точностью SVM из первого задания. Опишите сравнение в отчёте.

Личные инструменты