109,9
рейтинг
8 декабря 2012 в 18:24

Разработка → Реализация Restricted Boltzmann machine на c#

Привет. Закончился курс по нейронным сетям. Хороший курс, но мало практики. Так что в этом посте мы рассмотрим, напишем и протестим ограниченную машину Больцманастохастическую, генеративную модель нейронной сети. Обучим ее, используя алгоритм Contrastive Divergence (CD-k), разработанный профессором Джеффри Хинтоном, который кстати и ведет тот курс. Тестировать мы будем на наборе печатных английских букв. В следующем посте будет рассмотрен один из недостатков алгоритма обратного распространения ошибки и способ первоначальной инициализации весов с помощью машины Больцмана. Кто не боится формулок и простыней текста, прошу под кат.



Очень краткий экскурс в историю



Я не буду особо заострять внимание на истории происхождения ограниченной машины Больцмана (RBM), упомяну лишь, что началось все с рекуррентных нейронных сетей, которые представляют из себя сети с обратной связью, которые крайне трудно обучить. Вследствие этого небольшого затруднения в обучении, народ стал выдумывать более ограниченные рекуррентные модели, для которых можно было бы применить более простые алгоритмы обучения. Одной из таких моделей была нейронная сеть Хопфилда, и он же ввел понятие энергии сети, сравнив нейросетевую динамику с термодинамикой:


  • w — вес между нейронами
  • b — смещение нейрона
  • s — состояние нейрона


Следующим шагом на пути к RBM были обыкновенные машины Больцмана, они отличаются от сети Хопфилда тем, что имеют стохастическую природу, а нейроны поделены на две группы, описывающие обозреваемые и скрытые состояния (по аналогии со скрытыми моделями Маркова, кстати Хинтон признается в лекциях, что оттуда и позаимствовал слово «скрытый»). Энергия в машинах больцмана выражается следующим образом:



Машина Больцмана представляет из себя полносвязный неориентированный граф, и таким образом, любые две вершины из одной группы зависят друг от друга.
image

Но если убрать связи внутри группы, чтобы получился двудольный граф, мы получим структуру модели RBM.

image

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

Интерпретация и цель



RBM интерпретируются аналогично скрытым моделям Маркова. У нас есть ряд состояний, которые мы можем наблюдать (видимые нейроны, которые предоставляют интерфейс для общения с внешней средой) и ряд состояний, которые скрыты, и мы не можем напрямую увидеть их состояние (скрытые нейроны). Но мы можем сделать вероятностный вывод относительно скрытых состояний, опираясь на состояния, которые мы можем наблюдать. Обучив такую модель, мы так же получаем возможность делать выводы относительно видимых состояний, зная скрытые (теорему Байеса никто не отменял =), и тем самым генерировать данные из того вероятностного распределения, на котором обучена модель.

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

Теория



Введем следующие обозначения:
  • w_ij — веc между i-ым нейроном
  • a_i — смещение видимого нейрона
  • b_j — смещение скрытого нейрона
  • v_i — состояние видимого нейрона
  • h_j — состояние скрытого нейрона


Мы будем рассматривать обучающее множество, состоящее из бинарных векторов, но это легко обобщается до векторов из действительных чисел. Предположим, что у нас n видимых нейронов и m скрытых. Введем понятие энергии для RBM:



Нейросеть будет вычислять совместную вероятность всевозможных пар v и h следующим образом:



где Z — это статсумма или нормализатор (partition function) следующего вида (предположим, у нас имеется всего N образов v, и M образов h):



Очевидно, что полная вероятность вектора v будет вычисляться суммированием по всем h:



Рассмотрим вероятность того, что при данном v одно из скрытых состояний h_k = 1. Для этого представим один нейрон, тогда энергия системы при 1 будет E1, а при 0 будет E0, а вероятность 1 будет равна сигмоидной функции от линейной комбинации текущего нейрона с вектором состояний обозреваемого слоя вместе со смещением:



А так как при данном v все h_k не зависят друг от друга, то:



Аналогичный вывод делается и для вероятности v при данном h.

Как мы помним, цель обучения — это сделать так, чтобы восстановленный вектор был наиболее близок к оригиналу, или же, другими словами, максимизировать вероятность p(v). Для этого необходимо вычислить частные производные вероятности по параметрам модели. Начнем с дифференцирования энергии по параметрам модели:






Также нам понадобятся производные экспоненты от отрицательной энергии:





Продифференцируем статсумму:






Мы почти у цели -) Рассмотрим частную производную вероятности v по весам:



Очевидно, что максимизация вероятности это тоже самое, что максимизация логарифма вероятности, этот трюк поможет прийти к красивому решению. Рассмотрим производную натурального логарифма по весу:



Объединяя две последние формулы, получаем следующее выражение:





Домножив числитель и знаменатель каждого слагаемого на Z и, упростив до вероятностей, получим следующее выражение:



А по определению математического ожидания мы получаем итоговое выражение (M перед квадратной скобкой — это знак мат. ожидания):



Аналогично выводятся и производные по смещениям нейронов:





И в итоге получаются три правила обновления параметров модели, где эта — это скорость обучения:





PS мог ошибиться малька в индексах, на хабре же нет встроенного редактора =)

Алгоритм обучения Contrastive Divergence


Этот алгоритм придуман профессором Хинтоном в 2002 году, и он отличается своей простотой. Главная идея в том, что математические ожидания заменяются вполне определенными значениями. Вводится понятие процесса сэмплирования (Gibbs sampling, чтобы увидеть ассоциацию, можно заглянуть сюда). Процесс CD-k выглядит следующим образом:
  1. состояние видимых нейронов приравнивается к входному образу
  2. выводятся вероятности состояний скрытого слоя
  3. каждому нейрону скрытого слоя ставится в соответствие состояние «1» с вероятностью, равной его текущему состоянию
  4. выводятся вероятности видимого слоя на основании скрытого
  5. если текущая итерация меньше k, то возврат к шагу 2
  6. выводятся вероятности состояний скрытого слоя


В лекциях у Хинтона это выглядит так:




Т.е. чем дольше мы делаем сэмплинг, тем точнее будет наш градиент. В то же время профессор утверждает, что даже для CD-1 (всего одна итерация сэмплинга) уже вполне хороший результат получается. Первое слагаемое называется положительной фазой, а второе со знаком минус называется отрицательной фазой.

Talk is cheap. Show me the code.



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

public void Train(IMultilayerNeuralNetwork network, IList<DataItem> data)

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

LearningAlgorithmConfig config = new LearningAlgorithmConfig() { BatchSize = 10, //размер батча MaxEpoches = 1000, //максимальное количество эпох до остановки алгоритма GibbsSamplingChainLength = 30, //как раз параметр k из CD-k LearningRate = 0.01, //скорость обучения CostFunctionRecalculationStep = 1, //как часто вычислять ошибку обучения Momentum = 0.9, //параметр момента MinError = 0, // минимальная ошибка для остановки алгоритма MinErrorChange = 0, //минимальное изменение ошибки для остановки алгоритма UseBiases = true //использовать ли смещения в процессе обучения };


Перед основным циклом обучения инициализируются различные параметры.

Инициализация

ILayer visibleLayer = network.Layers[0]; //сеть RBM состоит из двух слоев, один это видимые состояния 
ILayer hiddenLayer = network.Layers[1];  //и один скрытые состояния

if (!_config.UseBiases) //случай когда смещения учавствуют в обучении
{
    for (int i = 0; i < visibleLayer.Neurons.Length; i++)
    {
        visibleLayer.Neurons[i].Bias = 0d;
    }
    for (int i = 0; i < hiddenLayer.Neurons.Length; i++)
    {
        hiddenLayer.Neurons[i].Bias = 0d;
    }
}

//init momentum
//формулу для момента я приведу далее при обновлении весов
double[,] momentumSpeedWeights = new double[visibleLayer.Neurons.Length, hiddenLayer.Neurons.Length];
double[] momentumSpeedVisibleBiases = new double[visibleLayer.Neurons.Length];
double[] momentumSpeedHiddenBiases = new double[hiddenLayer.Neurons.Length];

//init stop factors
bool stopFlag = false;
double lastError = Double.MaxValue; //многие переменные имеют говорящие имена, так что пропущу их описание
double lastErrorChange = double.MaxValue;
double learningRate = _config.LearningRate;

int currentEpoche = 0;
BatchEnumerator<DataItem<double>> batchEnumerator = new BatchEnumerator<DataItem<double>>(data, _config.BatchSize, true); 



Текст BatchEnumerator'а можно посмотреть тут, а про батчи описано в предыдущем посте.

Далее начинается основной цикл по эпохам:

Основной цикл

do
{

    DateTime dtStart = DateTime.Now;

                
    //start batch processing
    foreach (IList<DataItem<double>> batch in batchEnumerator)
    {

        //batch gradient
        //как и в реализации алгоритма обратного распространения из предыдущей статьи, в набле хранится изменения весов/смещений на один батч
        double[,] nablaWeights = new double[visibleLayer.Neurons.Length, hiddenLayer.Neurons.Length];
        double[] nablaHiddenBiases = new double[hiddenLayer.Neurons.Length];
        double[] nablaVisibleBiases = new double[visibleLayer.Neurons.Length];

        #region iterate through batch
        //...
        #endregion
   
        #region compute mean of wights nabla, and update them
        //...
        #endregion
    }

    #region Logging and error calculation
    //...
    #endregion

    //условия остановки алгоритма
    currentEpoche++;                
    if (currentEpoche >= _config.MaxEpoches)
    {
        stopFlag = true;
        Logger.Instance.Log("Stop: currentEpoche:" + currentEpoche + " >= _config.MaxEpoches:" + _config.MaxEpoches);
    }
    else if (_config.MinError >= lastError)
    {
        stopFlag = true;
        Logger.Instance.Log("Stop: _config.MinError:" + _config.MinError + " >= lastError:" + lastError);
    }
    else if (_config.MinErrorChange >= lastErrorChange)
    {
        stopFlag = true;
        Logger.Instance.Log("Stop: _config.MinErrorChange:" + _config.MinErrorChange + " >= lastErrorChange:" + lastErrorChange);
    }
} while (!stopFlag);




Рассмотрим основную часть алгоритма, это как раз Gibbs sampling, вычисление и аккумулирование градиента по параметрам.

#region iterate through batch

//проходим по каждому обучающему примеру из одного батча
foreach (DataItem<double> dataItem in batch)
{
    //init visible layer states
    //присваиваем обозримым состояниям значения из одного/текущего примера из батча
    for (int i = 0; i < dataItem.Input.Length; i++)
    {
        visibleLayer.Neurons[i].LastState = dataItem.Input[i];
    }

    #region Gibbs sampling
    for (int k = 0; k <= _config.GibbsSamplingChainLength; k++)
    {
        //calculate hidden states probabilities
        //скрытый слой обновляет свои состояния используя состояния обозримого слоя и параметры модели, вычисляются пока только вероятности
        hiddenLayer.Compute();

        #region accumulate negative phase
        //в случае если текущая итерация сэмплинга - это последняя итерация, то необходимо собрать отрицательную часть градиента
        if (k == _config.GibbsSamplingChainLength)
        {
            for (int i = 0; i < visibleLayer.Neurons.Length; i++)
            {
                for (int j = 0; j < hiddenLayer.Neurons.Length; j++)
                {
                    //умножение видимых состояний на скрытые
                    nablaWeights[i, j] -= visibleLayer.Neurons[i].LastState *
                                            hiddenLayer.Neurons[j].LastState;
                }
            }
            if (_config.UseBiases)
            {
                for (int i = 0; i < hiddenLayer.Neurons.Length; i++)
                {
                    nablaHiddenBiases[i] -= hiddenLayer.Neurons[i].LastState;
                }
                for (int i = 0; i < visibleLayer.Neurons.Length; i++)
                {
                    nablaVisibleBiases[i] -= visibleLayer.Neurons[i].LastState;
                }
            }

            break;
        }
        #endregion

        //sample hidden states
        //после того как вычислены вероятности, необходимо сделать сэмплирование скрытого слоя, но только если это не последняя итерация, иначе мы бы уже вышли из цикла Gibbs sampling
        for (int i = 0; i < hiddenLayer.Neurons.Length; i++)
        {
            hiddenLayer.Neurons[i].LastState = _r.NextDouble() <= hiddenLayer.Neurons[i].LastState ? 1d : 0d;
        }

        #region accumulate positive phase
        //если это первая итерация, то собираем положительную часть градиента
        if (k == 0)
        {
            for (int i = 0; i < visibleLayer.Neurons.Length; i++)
            {
                for (int j = 0; j < hiddenLayer.Neurons.Length; j++)
                {
                    nablaWeights[i, j] += visibleLayer.Neurons[i].LastState*
                                            hiddenLayer.Neurons[j].LastState;
                }
            }
            if (_config.UseBiases)
            {
                for (int i = 0; i < hiddenLayer.Neurons.Length; i++)
                {
                    nablaHiddenBiases[i] += hiddenLayer.Neurons[i].LastState;
                }
                for (int i = 0; i < visibleLayer.Neurons.Length; i++)
                {
                    nablaVisibleBiases[i] += visibleLayer.Neurons[i].LastState;
                }
            }
        }
        #endregion

        //calculate visible probs
        //вычисляем вероятности видимого слоя
        visibleLayer.Compute();

        //сэмплирование видимого слоя, в статье у Хинтона написано что его сэмплировать вообще говоря не обязательно, от этого только увеличивается дисперсия, но мат. ожидание остается на месте; мои опыты показали что без этого блока работает действительно лучше
        //todo: may be not do sampling, like in 3.2 of http://www.cs.toronto.edu/~hinton/absps/guideTR.pdf
        //sample visible
        //for (int i = 0; i < visibleLayer.Neurons.Length; i++)
        //{
        //    visibleLayer.Neurons[i].LastState = _r.NextDouble() <= visibleLayer.Neurons[i].LastState ? 1d : 0d;
        //}

    }
    #endregion

}



Мы вышли из цикла по обучающим примерам из батча, и нужно обновить веса. Момент используется следующим образом: прошлое изменение параметра модели умножается на постоянную момента (мю) и складывается с текущим градиентом. Затем сумма умножается на скорость обучения.



#region compute mean of wights nabla, and update them

for (int i = 0; i < visibleLayer.Neurons.Length; i++)
{
    for (int j = 0; j < hiddenLayer.Neurons.Length; j++)
    {
        //запись текущего изменения веса с учетом прошлого, для использования на следующей итерации
        momentumSpeedWeights[i, j] = _config.Momentum*momentumSpeedWeights[i, j] +
                                        nablaWeights[i, j]/batch.Count;
        //использование текущего изменения веса и скорости обучения
        visibleLayer.Neurons[i].Weights[j] += learningRate * momentumSpeedWeights[i, j];
        hiddenLayer.Neurons[j].Weights[i] = visibleLayer.Neurons[i].Weights[j];
    }
}
if (_config.UseBiases)
{
    for (int i = 0; i < hiddenLayer.Neurons.Length; i++)
    {
        momentumSpeedHiddenBiases[i] = _config.Momentum*momentumSpeedHiddenBiases[i] +
                                        nablaHiddenBiases[i]/batch.Count;
        hiddenLayer.Neurons[i].Bias += learningRate * momentumSpeedHiddenBiases[i];
    }
    for (int i = 0; i < visibleLayer.Neurons.Length; i++)
    {
        momentumSpeedVisibleBiases[i] = _config.Momentum*momentumSpeedVisibleBiases[i] +
                                        nablaVisibleBiases[i]/batch.Count;
        visibleLayer.Neurons[i].Bias += learningRate * momentumSpeedVisibleBiases[i];
    }
}



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

#region Logging and error calculation

string msg = "Epoche #" + currentEpoche;

#region calculate error

if (currentEpoche % _config.CostFunctionRecalculationStep == 0)
{
                    
    #region calculating squared error with reconstruction

    IMetrics<double> sed = MetricsCreator.SquareEuclideanDistance();
    double d = 0;
    foreach (DataItem<double> dataItem in data)
    {
        d += sed.Calculate(dataItem.Input, network.ComputeOutput(dataItem.Input));
    }
    msg += "; SqDist is " + d;

    lastErrorChange = Math.Abs(lastError - d);
    lastError = d;

    #endregion

}

#endregion

msg += "; Time: " + (DateTime.Now - dtStart).Duration().ToString();
Logger.Instance.Log(msg);



Исходник можно посмотреть тут.

Результаты работы RBM


Свои эксперименты я делал на распознавании текста. Для этого поста я выбрал простое, небольшого размера множество больших английских букв, что бы ускорить процесс обучения, в котором присутствуют немного шумов (всего 260 картинок 29 на 29 пикселей): скачать для просмотра можно тут.

Обучение производилось со следующими параметрами:

LearningAlgorithmConfig:
LearningRate = 0.01
BatchSize = 10
RegularizationFactor = 0
MaxEpoches = 1000
MinError = 0
MinErrorChange = 0
CostFunctionRecalculationStep = 1
ErrorFunction =
Momentum = 0.9
NeuronLocalGainLimit: not setted
GibbsSamplingChainLength = 30
UseBiases = True

Как видно, единственным критерием остановки было 1000 эпох. Т.к. батч равен 10, то за одну эпоху было сделано 26 обновлений весов, а в общем 26000 обновлений. Лог можно скачать тут.

По логу можно увидеть, что после первой эпохи (26 обновлений параметров) среднеквадратичное Евклидово расстояние равно 13128, а на последней уже 76.

Давайте глянем на несколько восстановленных картинок (слева оригинал, справа восстановленное):


А теперь давайте посмотрим на одно из изображений покрупнее:


Как видно по картинке, даже шумы восстанавливаются правильно (мы сейчас не говорим о понятии переобучения для RBM).

Теперь зададимся вопросом, можно ли как-то визуализировать паттерны, которые были найдены сетью. Оказывается, это просто. Для каждого скрытого состояния есть 841 (29*29) вес и одно смещение. Если проигнорировать смещение, то из 841 веса можно составить картинку 29 на 29 пикселей, и это как раз будут нужные нам шаблоны, автоматически найденные сетью. А ведь линейная комбинация шаблона и картинки плюс смещение, и взятая от этого числа сигмоидная функция, и будет вероятность того, активен нейрон или нет. Вот полученная карта:



А вот как выглядит эта же карта у крутых пацанов на множестве рукописных символов MNIST: http://deeplearning.net/tutorial/_images/filters_at_epoch_14.png. Так что мне еще подбирать и подбирать параметры обучения, но вроде как я на правильном пути =)

В следующем посте мы рассмотрим применение RBM для того, чтобы инициализировать веса сети прямого распространения, которая будет обучаться по алгоритму обратного распространения ошибки.

Ссылки




PS респект и уважуха пользователю ambikontur за регулярную помощь в проверке формул и текста!

UPD:
вот кстати паттерны полученные после более тонкой настройки и на множестве состоящем из нескольких шрифтов

и если немного увеличить
Павел Нестеров @mephistopheies
карма
87,0
рейтинг 109,9
data scientist
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (20)

  • 0
    Отличная статья, спасибо.
    Подумываю о реализации на Python.
  • +1
    Ого. Как много всего интересного, оказывается, в нейросетеводстве.

    Спасибо за статью. Для себя сделал вывод, что следующий курс по нейронным сетям пропустить нельзя.
  • 0
    Узнал из статьи действительно много нового. Понимание принципов работы таких сетей здорово помогло при работе с библиотекой Accord.NET. В своём проекте использую инициализацию по PCA, проведенному на начальном этапе, т.к. обрабатываемые данные принципиально далеки от изображений. Скажите, какие реализации/подвиды данных сетей хорошо применимы для действительных, не бинарных данных?
    Спасибо, жду продолжения!
    • 0
      для моделирования действительных значений используется немного другая функция энергии сети
      image
      в данном случае видимые нейроны действительны, а скрытые — бинарные (gaussian-binary rbm), аналогично можно ввести и энегрию для gaussian-gaussian rbm.

      про это можно почитать как у самого Хинтона www.cs.toronto.edu/~hinton/absps/guideTR.pdf
      так и тут на хабре habrahabr.ru/post/186368/

      проблема тут в другом, пока нет достаточно эффективного алгоритма обучения таких сетей, хотя за не имением чего то крутого, сам же Хинтон использует свой же contrastive-divergence, хотя и говорит что вопрос открытый и следует разработать более эффективный алгоритм
      • 0
        Интересна такая ситуация. Допустим, есть обычная сеть со следующими количествами скрытых слоёв: 7-4-2 (взято наобум). Вопрос, эффективно ли будет предобучение, если мы сначала обучим сеть типа бутылочного горла вида 7-4-7 и перенесем веса из слоя с 4 нейронами в исходную сеть, ну и так далее.
        • 0
          да вполне, такая сеть называется автоэнкодером
          image
          • 0
            Еще один вопрос. Насколько я понимаю, автоэнкодер осуществляет сжатие данных до меньшей размерности. И если центральный слой сделать состоящим из одного нейрона, можно ли будет использовать такую сеть для вычисления «рейтинга» чего-либо, к примеру, банка, по его финансовым показателям?

            Или для этого (т.е. для получения первой нелинейной главной компоненты, простите, не знаю, как это точно называется) лучше использовать другие методы, отбросив нейронные сети.
            • 0
              ну почему бы нет, можно (за адекватность не отвечаю =)

              но тут как я понимаю вот какая ситуация, когда мы говорим о рейтинге, то это уже задача регрессии в рамках обучения с учителем (есть множество пар вход-выход, где выход это одно число-рейтинг), тогда будет иметь структуру n1-n2-...-nk-1, т.е. на выходе один нейрон и мы ее обучаем

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

              не могу ответить какими методами лучше или хуже использовать при уменьшении размерности, с линейными компонентами Пирсона все ясно, там можно расчитать variance explained, но с нелинейными я не в курсе (думаю есть какие нибудь аналогичные способы оценки, ноя не в курсе), я как практик скажу что все сильно зависитот данных, и необходимо тестировать методы на конкретных данных
              • 0
                Ну я исхожу из предположения, что если финансовые характеристики преобразовать таким образом, что большие входные значения будут соответствовать тем банкам, которые лучше, то главная компонента (линейна она или нет) должна будет идти в многомерном пространстве от банков с наихудшими показателями к банкам с наилучшими. И, по идее, выход единичного нейрона в центре автоэнкодера должен максимально совпасть с главной компонентой, т.к. из нее и именно из нее можно с наименьшими потерями восстановить исходные данные. Т.о. обучение с учителем нам не нужно, мы просто обучим автоэнкодер, а потом «обрежем» его, взяв первую половину слоев.
                • 0
                  звучит вроде ок, нада тестить
  • 0
    Добрый день. Заранее скажу, что вполне возможно, что я не прав. Тем не менее, считаю, что вы не совсем правильно реализовали алгоритм. У Хинтона в статье написано, что для градиента берутся «expectations under the distribution...», а исходя из определения ожидания, — это значение, умноженное на его вероятность. Таким образом, как на deeplearning.net (ссылка у вас в статье) и написано, обе части градиента вычисляются как вероятность * значение. У вас же в коде, если я не ошибаюсь, перемножпются значения самих юнитов без вероятностей.
    • 0
      так а значение юнита (сигмоид от линейной комбинации весов и входа) при бинарной рбм это не есть значение вероятности активации нейрона?
      • 0
        Я еще и написал не туда… Ну, с телефона не очень удобно ((
  • 0
    А как же hiddenLayer.Neurons[i].LastState = _r.NextDouble() <= hiddenLayer.Neurons[i].LastState? 1d: 0d;? (Прошу прощения, что как текст написал — с телефона пишу)

    Судя по этой строчке, в значение записывается не сигмоид, а либо 1, либо 0.
    • 0
      вот тут в практических советах http://www.cs.toronto.edu/~hinton/absps/guideTR.pdf он пишет и объясняет почему лучше семплировать скрытый, но не семплировать видимый
      • 0
        Я вам не про это говорю. Вы говорите, что перемножается у вас сигмоида (условная вероятность скрытого нейрона) и значение соответствующего нейрона видимого слоя. Я говорю, что из кода этого не видно, так как есть строчка, в которой последним значениям, в зависимости от вероятности, присваивается 0 или 1. А потом мы перемножаем значения видимого и скрытого нейронов. Возможно, я просто не увидел этот код, тогда, пожалуйста, укажите мне на него.
        • 0
          так а в чем ошибка то? перемножаем значение видимого и скрытого, вроде как так и надо; у видимого значение — это вероятность, у скрытого засемплированное значение из бернулли распределения
          • 0
            Ммм. Тогда ок)) просто из кода не видно, что видимом слое стоят вероятности, а по логике понятно, что вход вы не меняете. Видимо вся эта логика находится в Compute()?
            • 0
              ага, compute просто вычисляет значение функции активации от линейной комбинации, класс используется тот же что и в предыдущих постах описан

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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.