Pull to refresh

Нейросетевое сжатие данных

Reading time 7 min
Views 23K
imageВ этой статье я хочу поведать о еще одном классе задач, решаемых нейронными сетями – сжатии данных. Алгоритм, описанный в статье, не претендует на использование в реальных боевых условиях по причине существования более эффективных алгоритмов. Сразу оговорюсь, что речь пойдет только о сжатии без потерь.
Большинство источников в Интернете утверждают, что есть 3 основных популярных архитектур нейронных сетей, решающих задачу сжатия данных.

1. Сеть Кохонена и ее вариации. Часто используется для сжатия изображений с потерей качества, не является алгоритмом сжатия без потерь.

2. Ассоциативная память (Сеть Хопфилда, Двунаправленная ассоциативная память и др.). «Сжатие данных» для этого класса сетей является «фичей», побочным явлением, так как главным их предназначением есть восстановление исходного сигнала/образа из зашумленных/поврежденных входных данных. Чаще всего, на вход этих сетей поступает зашумленный образ той же размерности, потому о сжатии данных речь не идет.

3. Метод «Бутылочного горлышка».

О последнем методе и пойдет речь в статье.

Суть метода


imageАрхитектура сети и алгоритм обучения таковы, что вектор большой размерности требуется передать со входа нейронной сети на ее выходы через относительно небольших размеров канал. Как аналогия – песочные часы, лейка, бутылочное горлышко, что кому ближе. Для реализации сжатия такого рода можно использовать многослойный персептрон следующей архитектуры: количество нейронов во входном и выходном слое одинаково, между ними размещаются несколько скрытых слоев меньшего размера.
Для обучения используется алгоритм обратного распространения ошибки, а в качестве активационной функции – сигмоид (сигмоидная функция, биполярная сигмоидная функция).
Сеть состоит из двух частей – одна часть работает на сжатие данных, другая – на восстановление. Время, затрачиваемое на обе процедуры, примерно одинаково. При практическом использовании полученную сеть разбивают на две. Вывод первой сети передают по каналу связи и подают на вход второй, которая осуществляет декомпрессию.

Что влияет на обучение?


Важным рычагом влияния на скорость обучения сети является умелое корректирование коэффициента скорости обучения. Самый простой вариант – когда ошибка сети изменяется на относительно небольшое значение (скажем, меньше, чем на 0.0000001) в сравнении с ошибкой сети на предыдущей эпохе — следует уменьшить коэффициент. Существуют и более умные алгоритмы изменения скорости обучения.
И еще очень важный элемент в обучении – всесильный рэндом. Принято при создании нейрона его весовым коэффициентам задавать небольшие случайные значения. Иногда получается так, что значения случайно задаются таковыми, что сеть становится необучаема, либо сходимость ошибки является очень малой.

Реализация


При реализации использовалась библиотека AForge.NET. Основные опыты я проводил над векторами длиной 10, потом над векторами длиной 20, суть это не изменило, потому увеличивать размерность не стал, но есть еще одна причина. Почему я не экспериментировал с более длинными векторами? Почему бы не генерировать большую обучающую выборку с длинными векторами? Это вызвано ленью тем, что доставляет некоторые проблемы и неудобства. В такой выборке могут быть противоречия. Например, во входном множестве векторов могут существовать два и более идентичных или схожих векторов, желаемый выход на которые будет абсолютно отличаться. Тогда сеть на такой обучающей выборке будет необучаема, а найти «конфликтующие» вектора довольно сложно.

Результаты и выводы


Для меня самым интересным вопросом являлось то, как же все-таки эффективно построить эту сеть? Сколько скрытых слоев она должна иметь? Как быстро проходит обучение? Как быстро проходит сжатие и восстановление?
Эксперименты проводились над двумя основными возможными вариантами построения этой сети.

«Быстрое сжатие»

Сетью с «быстрым сжатием» будем называть сеть, где количество нейронов в скрытых слоях намного меньше (в 3, 5 и т.п. раз), чем во входном/выходном слое, а сеть состоит из 4 слоев – входного, выходного и двух скрытых (например, 10-3-3-10). Подобная топология сети оказалась очень эффективной в некоторых случаях. Во-первых, такая сеть очень быстро обучается. Это связано с тем, что алгоритму обучения необходимо корректировать весовые коэффициенты относительно малого количества связей между соседними слоями. Во-вторых, скорость выдачи результата при функционировании сети тоже очень высока (по той же причине). Обратная сторона – сеть обучаема только на небольшой выборке относительно количества нейронов во входном (и, соответственно, выходном) слое (приемлемое количество векторов примерно равно половине нейронов в первом/последнем слое сети). Также, не стоит перегибать палку и делать слишком сильный перепад между слоями, оптимальный вариант – количество нейронов в скрытом слое должно быть примерно в 3 раза меньше, чем во входном/выходном слое.

«Последовательное сжатие»

Сетью с «последовательным» или «неспешным» сжатием будем называть сеть, где количество нейронов в соседних слоях не сильно отличается (до 25%), а сеть содержит 4 и более скрытых слоев (например, 10-8-5-3-3-5-8-10). Такие сети не оправдали мои ожидания. Даже при малой обучающей выборке и длине вектора они очень долго обучаются. Ну и функционируют, конечно же, тоже медленней. Хотя поговаривают, что в реальных боевых условиях они более стабильны. Но из-за очень долгого время обучения проверить это не удалось.

О эффективности и времени работы

С практической точки зрения интересны два факта – коэффициент сжатия и время сжатия/восстановления. Сеть почти 100%-во обучается и отлично функционирует, сжимая данные в 3 раза. Скорость работы тоже радует. Для архитектуры (10-3-3-10) время выполнения полного цикла (сжатия и восстановления) – 00:00:00.0000050. Для (20-6-6-20) — 00:00:00.0000065. Смело можем делить на 2 и получим 00:00:00.0000025 и 00:00:00.0000033 для сжатия либо восстановления одного вектора.
Испытания проводились на процессоре AMD Athlon II x240 2.80 Ghz.

Пример


В качестве примера — небольшой прокомментированный кусочек кода (на C#), описывающий примитивный пример создания, обучения и функционирования сети. Сеть сжимает вектор в 3 раза, обучаема в 98% случаев. Для функционирования необходимо подключить библиотеку AForge.NET.

using System;
using AForge.Neuro;
using AForge.Neuro.Learning;

namespace FANN
{
  class Program
  {
    static void Main(string[] args)
    {
      // Создаем сеть с сигмодиной активацонной функцией и 4 слоями (с 10,3,3,10 нейронами в каждом слое, соответственно)
      ActivationNetwork net = new ActivationNetwork((IActivationFunction)new SigmoidFunction(), 10,3,3,10);
      // Обучение - алгоритм с обратным распространением ошибки
      BackPropagationLearning trainer = new BackPropagationLearning(net);
      
      // Формируем множество входных векторов
      double[][] input = new double[][] {
          new double[]{1,1,1,1,1,1,1,1,1,1},
          new double[]{0,0,0,0,0,0,0,0,0,0},
          new double[]{1,1,1,1,1,0,0,0,0,0},
          new double[]{0,0,0,0,0,1,1,1,1,1},
        };

      // Формируем множество желаемых выходных векторов
      double[][] output= new double[][] {
          new double[]{1,1,1,1,1,1,1,1,1,1},
          new double[]{0,0,0,0,0,0,0,0,0,0},
          new double[]{1,1,1,1,1,0,0,0,0,0},
          new double[]{0,0,0,0,0,1,1,1,1,1},
        };

      // Переменная, сохраняющая значение ошибки сети на предыдущем шаге
      double prErr = 10000000;
      // Ошибка сети
      double error = 100;
      // Сначала скорость обучения должна быть высока
      trainer.LearningRate = 1;
      // Обучаем сеть пока ошибка сети станет небольшой
      while (error > 0.001)
      {
        // Получаем ошибку сети
        error = trainer.RunEpoch(input, output);
        // Если ошибка сети изменилась на небольшое значения, в сравнении ошибкой предыдущей эпохи
        if (Math.Abs(error - prErr) < 0.000000001)
        {
          // Уменьшаем коэффициент скорости обучения на 2
          trainer.LearningRate /= 2;
          if (trainer.LearningRate < 0.001)
            trainer.LearningRate = 0.001;
        }

        prErr = error;
      }

      // Любуемся полученными результатами
      double[] result;
      result = net.Compute(new double[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
      result = net.Compute(new double[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
      result = net.Compute(new double[] { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 });
      result = net.Compute(new double[] { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 });
    }
  }
}


* This source code was highlighted with Source Code Highlighter.


Литература и примечания


  • Википедия
  • Страница библиотеки — www.aforgenet.com
  • Страница загрузки библиотеки — www.aforgenet.com/framework/downloads.html
  • Из всей библиотеки AForge.NET нужна только ее часть — AForge.Neuro.
  • Библиотека AForge.NET работает с типом double, потому выход сети является дробным, но очень близким к необходимым нам значениям – 0 и 1. Поэтому, для получения полноценного выхода можно прогнать каждое значение массива через «пороговую» функцию, возвращающую 0, если значение меньше 0.5 и 1 в противном случае.
Tags:
Hubs:
+35
Comments 25
Comments Comments 25

Articles