Сигмоидальная функция

Сигмоидальная функция

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

Сигмоидальная функция

в котором первый член соответствует обычному методу наискорейшего спуска, тогда как второй член, называемый моментом, отражает последнее изменение весов и не зависит от фактического значения градиента. Значение коэффициента момента а, как правило, выбирается из интервала Сигмоидальная функция Следует обратить внимание, что влияние момента на подбор весов увеличивается с ростом значения а. Такое влияние существенным образом усиливается при непосредственной близости локального минимума, где значение градиента стремится к нулю. В этом случае возможны такие изменения весов, которые приводят к возрастанию значения целевой функции и выходу за пределы области локального минимума. Такая ситуация применительно к аппроксимирующей сети (выполняющей аппроксимацию входных данных) иллюстрируется на рис. 2.4. Отмеченные на графике точки соответствуют значениям целевой функции, получаемым на каждом шаге обучения. Локальный минимум
Сигмоидальная функция был покинут благодаря действию момента. Это позволило найти в точке Сигмоидальная функция новый минимум с меньшим значением целевой функции, который оказался более подходящим с позиций приближения фактического значения Сигмоидальная функция к ожидаемому значению Сигмоидальная функция

Следует отметить, что показатель момента не должен доминировать в процессе обучения, так как это приведет к нестабильности (расходимости) алгоритма. Как правило, в процессе обучения отслеживается значение погрешности Сигмоидальная функция с тем, чтобы не допустить его возрастания сверх некоторого допустимого предела, например 5%. В подобном случае, если

Сигмоидальная функция

Рис. 2.4. Иллюстрация влияния момента на процесс обучения нейронной сети

stu.scask.ru

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

Функция softmax — это способ нормализации ваших данных, делая выбросы «менее интересными». Кроме того, он «сквозит» ваш входной вектор таким образом, что он гарантирует, что сумма вектора равна 1.

В вашем примере:

   
> np.sum([ 0.09003057, 0.24472847, 0.66524096]) > 1.0

Это просто обобщение логистической функции с дополнительным «ограничением» для получения каждого элемента вектора в интервале (0, 1) и его суммы до 1.0.

Функция sigmoid — еще один частный случай логистических функций. Это просто реальная, дифференцируемая функция с колоколообразной формой. Он интересен для нейронных сетей, потому что его довольно легко вычислить, нелинейно и имеет отрицательные и положительные границы, поэтому ваша активация не может расходиться, но набирает насыщенность, если она становится «слишком высокой».

Однако сигмоидальная функция не гарантирует, что входной вектор суммируется до 1.0.

В нейронных сетях сигмоидные функции часто используются в качестве функции активации для одиночных нейронов, тогда как функция нормировки сигмоида /softmax довольно используется на выходном уровне, чтобы весь слой добавлялся до 1. Вы просто перепутали сигмоидной функции (для одиночных нейронов) по сравнению с функциями нормировки сигмоида/мягкого макса (для всего слоя).

РЕДАКТИРОВАТЬ: Чтобы это сделать для вас, я дам вам простой пример с выбросами, это демонстрирует поведение двух разных функций для вас.

Пусть реализуется сигмоидальная функция:

import numpy as np  def s(x):  return 1.0 / (1.0 + np.exp(-x))     

И нормализованная версия (небольшими шагами, что упрощает чтение):

def sn(x):  numerator = x - np.mean(x)  denominator = np.std(x)  fraction = numerator / denominator   return 1.0 / (1.0 + np.exp(-fraction)) 

Теперь мы определяем некоторые измерения чего-то с огромными выбросами:

measure = np.array([0.01, 0.2, 0.5, 0.6, 0.7, 1.0, 2.5, 5.0, 50.0, 5000.0]) 

Теперь посмотрим на результаты, что s (сигмоид) и sn (нормализованный сигмоид) дают:

> s(measure) > array([ 0.50249998, 0.549834 , 0.62245933, 0.64565631, 0.66818777,  0.73105858, 0.92414182, 0.99330715, 1. , 1. ])  > sn(measure) > array([ 0.41634425, 0.41637507, 0.41642373, 0.41643996, 0.41645618,  0.41650485, 0.41674821, 0.41715391, 0.42447515, 0.9525677 ])     

Как вы можете видеть, s только переводит значения «один за другим» через логистическую функцию, поэтому выбросы полностью зависают с 0.999, 1.0, 1.0. Расстояние между другими значениями изменяется.

Когда мы смотрим на sn, мы видим, что функция фактически нормализовала наши значения. Все теперь абсолютно идентично, кроме 0,95, что было 5000.0.

Для чего это полезно или как это интерпретировать?

Подумайте о выходном уровне в нейронной сети: активация 5000.0 в одном классе на уровне вывода (по сравнению с другими другими значениями) означает, что сеть действительно уверена, что это «правильный» класс для вашего данного вход. Если бы вы использовали s там, вы получили бы 0.99, 1.0 и 1.0 и не могли бы отличить, какой класс является правильной догадкой для вашего ввода.

qaru.site

Упрощение

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


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

Биологическая структура → схема

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

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

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

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

Сигмоидальная функция

Электрические сигналы → числа

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

Сигмоидальная функция

Электрический сигнал всегда будет электрическим сигналом. Концептуально ничего не изменяется. Но что же тогда меняется? Меняется величина этого электрического сигнала (сильнее/слабее). А любую величину всегда можно выразить числом (больше/меньше).


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

На входы сети мы будем подавать какие-то числа, символизирующие величины электрического сигнала, если бы он был. Эти числа будут продвигаться по сети и каким-то образом меняться. На выходе сети мы получим какое-то результирующее число, являющееся откликом сети.

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

Синапсы → веса связей

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

Сигмоидальная функция

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

Это ключевой момент в концепции искусственных нейронных сетей, я объясню его подробнее. Посмотрите на картинку ниже. Теперь каждой черной стрелке (связи) на этой картинке соответствует некоторое число ​( w_i )​ (вес связи). И когда сигнал проходит по этой связи, его величина умножается на вес этой связи.


Сигмоидальная функция

На приведенном выше рисунке вес стоит не у каждой связи лишь потому, что там нет места для обозначений. В реальности у каждой ​( i )​-ой связи свой собственный ​( w_i )​-ый вес.

Искусственный нейрон

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

На рисунке ниже представлена полная модель искусственного нейрона.

Сигмоидальная функция

Не пугайтесь, ничего сложного здесь нет. Давайте рассмотрим все подробно слева направо.

Входы, веса и сумматор

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

Поступившие на входы сигналы умножаются на свои веса. Сигнал первого входа ​( x_1 )​ умножается на соответствующий этому входу вес ​( w_1 )​. В итоге получаем ​( x_1w_1 )​. И так до ​( n )​-ого входа. В итоге на последнем входе получаем ​( x_nw_n )​.


Теперь все произведения передаются в сумматор. Уже исходя из его названия можно понять, что он делает. Он просто суммирует все входные сигналы, умноженные на соответствующие веса:

[ x_1w_1+x_2w_2+cdots+x_nw_n = sumlimits^n_{i=1}x_iw_i ]

Результатом работы сумматора является число, называемое взвешенной суммой.

Роль сумматора очевидна – он агрегирует все входные сигналы (которых может быть много) в какое-то одно число – взвешенную сумму, которая характеризует поступивший на нейрон сигнал в целом. Еще взвешенную сумму можно представить как степень общего возбуждения нейрона.

Пример

Для понимания роли последнего компонента искусственного нейрона – функции активации – я приведу аналогию.

Давайте рассмотрим один искусственный нейрон. Его задача – решить, ехать ли отдыхать на море. Для этого на его входы мы подаем различные данные. Пусть у нашего нейрона будет 4 входа:

  1. Стоимость поездки
  2. Какая на море погода
  3. Текущая обстановка с работой
  4. Будет ли на пляже закусочная

Все эти параметры будем характеризовать 0 или 1. Соответственно, если погода на море хорошая, то на этот вход подаем 1. И так со всеми остальными параметрами.

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

  1. 5
  2. 4
  3. 1
  4. 1

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

Пусть на входы нашего нейрона мы подаем следующие сигналы:

  1. 1
  2. 0
  3. 0
  4. 1

Умножаем веса входов на сигналы соответствующих входов:

  1. 5
  2. 0
  3. 0
  4. 1

Взвешенная сумма для такого набора входных сигналов равна 6:

[ net=sumlimits^4_{i=1}x_iw_i = 5 + 0 + 0 + 1 =6 ]

Все классно, но что делать дальше? Как нейрон должен решить, ехать на море или нет? Очевидно, нам нужно как-то преобразовать нашу взвешенную сумму и получить ответ.

Вот на сцену выходит функция активации.

Функция активации

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

Она преобразует взвешенную сумму в какое-то число, которое и является выходом нейрона (выход нейрона обозначим переменной ​( out )​).

Для разных типов искусственных нейронов используют самые разные функции активации. В общем случае их обозначают символом ​( phi(net) )​. Указание взвешенного сигнала в скобках означает, что функция активации принимает взвешенную сумму как параметр.


Далее мы подробно рассмотрим самые известные функции активации.

Функция единичного скачка

Самый простой вид функции активации. Выход нейрона может быть равен только 0 или 1. Если взвешенная сумма больше определенного порога ​( b )​, то выход нейрона равен 1. Если ниже, то 0.

Как ее можно использовать? Предположим, что мы поедем на море только тогда, когда взвешенная сумма больше или равна 5. Значит наш порог равен 5:

[ b=5 ]

В нашем примере взвешенная сумма равнялась 6, а значит выходной сигнал нашего нейрона равен 1. Итак, мы едем на море.

Однако если бы погода на море была бы плохой, а также поездка была бы очень дорогой, но имелась бы закусочная и обстановка с работой нормальная (входы: 0011), то взвешенная сумма равнялась бы 2, а значит выход нейрона равнялся бы 0. Итак, мы никуда не едем.

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

Графически эту функцию активации можно изобразить следующим образом.

Сигмоидальная функция

На горизонтальной оси расположены величины взвешенной суммы. На вертикальной оси — значения выходного сигнала. Как легко видеть, возможны только два значения выходного сигнала: 0 или 1. Причем 0 будет выдаваться всегда от минус бесконечности и вплоть до некоторого значения взвешенной суммы, называемого порогом. Если взвешенная сумма равна порогу или больше него, то функция выдает 1. Все предельно просто.

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

[ out(net) = begin{cases} 0, net < b \ 1, net geq b end{cases} ]

В этой записи нет ничего сложного. Выход нейрона (​( out )​) зависит от взвешенной суммы (​( net )​) следующим образом: если ​( net )​ (взвешенная сумма) меньше какого-то порога (​( b )​), то ​( out )​ (выход нейрона) равен 0. А если ​( net )​ больше или равен порогу ​( b )​, то ​( out )​ равен 1.

Сигмоидальная функция

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

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

Итак… самая часто используемая в нейронных сетях сигмоида — логистическая функция.

Сигмоидальная функция

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

А вот так она записывается аналитически:

[ out(net)=frac{1}{1+exp(-a cdot net)} ]

Что за параметр ​( a )​? Это какое-то число, которое характеризует степень крутизны функции. Ниже представлены логистические функции с разным параметром ​( a )​.

Сигмоидальная функция

Вспомним наш искусственный нейрон, определяющий, надо ли ехать на море. В случае с функцией единичного скачка все было очевидно. Мы либо едем на море (1), либо нет (0).

Здесь же случай более приближенный к реальности. Мы до конца полностью не уверены (в особенности, если вы параноик) – стоит ли ехать? Тогда использование логистической функции в качестве функции активации приведет к тому, что вы будете получать цифру между 0 и 1. Причем чем больше взвешенная сумма, тем ближе выход будет к 1 (но никогда не будет точно ей равен). И наоборот, чем меньше взвешенная сумма, тем ближе выход нейрона будет к 0.

Например, выход нашего нейрона равен 0.8. Это значит, что он считает, что поехать на море все-таки стоит. Если бы его выход был бы равен 0.2, то это означает, что он почти наверняка против поездки на море.

Какие же замечательные свойства имеет логистическая функция?

  • она является «сжимающей» функцией, то есть вне зависимости от аргумента (взвешенной суммы), выходной сигнал всегда будет в пределах от 0 до 1
  • она более гибкая, чем функция единичного скачка – ее результатом может быть не только 0 и 1, но и любое число между ними
  • во всех точках она имеет производную, и эта производная может быть выражена через эту же функцию

Именно из-за этих свойств логистическая функция чаще всего используются в качестве функции активации в искусственных нейронах.

Гиперболический тангенс

Однако есть и еще одна сигмоида – гиперболический тангенс. Он применяется в качестве функции активации биологами для более реалистичной модели нервной клетки.

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

Функция записывается следующим образом:

[ out(net) = tanhleft(frac{net}{a}right) ]

В данной выше формуле параметр ​( a )​ также определяет степень крутизны графика этой функции.

А вот так выглядит график этой функции.

Сигмоидальная функция

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

Что мы узнали?

Теперь вы получили полное представление о внутренней структуре искусственного нейрона. Я еще раз приведу краткое описание его работы.

У нейрона есть входы. На них подаются сигналы в виде чисел. Каждый вход имеет свой вес (тоже число). Сигналы на входе умножаются на соответствующие веса. Получаем набор «взвешенных» входных сигналов.

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

Затем взвешенная сумма преобразуется функцией активации и мы получаем выход нейрона.

Сформулируем теперь самое короткое описание работы нейрона – его математическую модель:

Виды ИНС

Мы разобрались со структурой искусственного нейрона. Искусственные нейронные сети состоят из совокупности искусственных нейронов. Возникает логичный вопрос – а как располагать/соединять друг с другом эти самые искусственные нейроны?

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

А дальше начинаются различия…

Однослойные нейронные сети

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

Выглядит однослойная нейронная сеть следующим образом:

Сигмоидальная функция

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

Нейроны соединены друг с другом стрелками. Над стрелками расположены веса соответствующих связей (весовые коэффициенты).

Многослойные нейронные сети

Такие сети, помимо входного и выходного слоев нейронов, характеризуются еще и скрытым слоем (слоями). Понять их расположение просто – эти слои находятся между входным и выходным слоями.

Сигмоидальная функция

Такая структура нейронных сетей копирует многослойную структуру определенных отделов мозга.

Сигмоидальная функция

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

Многослойные нейронные сети обладают гораздо большими возможностями, чем однослойные.

Работу скрытых слоев нейронов можно сравнить с работой большого завода. Продукт (выходной сигнал) на заводе собирается по стадиям. После каждого станка получается какой-то промежуточный результат. Скрытые слои тоже преобразуют входные сигналы в некоторые промежуточные результаты.

Сети прямого распространения

Можно заметить одну очень интересную деталь на картинках нейросетей в примерах выше.

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

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

Однако никто не запрещает сигналу идти и в обратную сторону.

Сети с обратными связями

В сетях такого типа сигнал может идти и в обратную сторону. В чем преимущество?

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

А в сетях с обратными связями выходы нейронов могут возвращаться на входы. Это означает, что выход какого-нибудь нейрона определяется не только его весами и входным сигналом, но еще и предыдущими выходами (так как они снова вернулись на входы).

Сигмоидальная функция

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

Обучение нейронной сети

Теперь давайте чуть более подробно рассмотрим вопрос обучения нейронной сети. Что это такое? И каким образом это происходит?

Что такое обучение сети?

Искусственная нейронная сеть – это совокупность искусственных нейронов. Теперь давайте возьмем, например, 100 нейронов и соединим их друг с другом. Ясно, что при подаче сигнала на вход, мы получим что-то бессмысленное на выходе.

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

Что мы можем менять в нейронной сети?

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

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

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

Остается только один вариант – менять веса связей.

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

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

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

Именно с этой целью и создаются обучающие выборки.

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

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

Мы поняли, что такое «обучение сети» – подбор правильного набора весов. Теперь возникает вопрос – а как можно обучать сеть? В самом общем случае есть два подхода, приводящие к разным результатам: обучение с учителем и обучение без учителя.

Обучение с учителем

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

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

Затем, с помощью специальных алгоритмов, вы меняете веса связей нейронной сети и снова даете ей входной сигнал. Сравниваете ее ответ с правильным и повторяете этот процесс до тех пор, пока сеть не начнет отвечать с приемлемой точностью (как я говорил в 1 главе, однозначно точных ответов сеть давать не может).

Где взять правильные ответы?

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

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

И так далее…

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

Обучение без учителя

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

Что же происходит при таком обучении сети? Оказывается, что при таком «обучении» сеть начинает выделять классы подаваемых на вход сигналов. Короче говоря – сеть начинает кластеризацию.

Например, вы демонстрируете сети конфеты, пирожные и торты. Вы никак не регулируете работу сети. Вы просто подаете на ее входы данные о данном объекте. Со временем сеть начнет выдавать сигналы трех разных типов, которые и отвечают за объекты на входе.

Выводы

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

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

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

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

neuralnet.info

Введение

Наилучшие результаты в области распознавания лиц показала Convolutional Neural Network или сверточная нейронная сеть (далее – СНС), которая является логическим развитием идей таких архитектур НС как когнитрона и неокогнитрона. Успех обусловлен возможностью учета двумерной топологии изображения, в отличие от многослойного персептрона.

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

  • локальные рецепторные поля (обеспечивают локальную двумерную связность нейронов);
  • общие синаптические коэффициенты (обеспечивают детектирование некоторых черт в любом месте изображения и уменьшают общее число весовых коэффициентов);
  • иерархическая организация с пространственными подвыборками.


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

Сигмоидальная функция

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

Структура сверточной нейронной сети

СНС состоит из разных видов слоев: сверточные (convolutional) слои, субдискретизирующие (subsampling, подвыборка) слои и слои «обычной» нейронной сети – персептрона, в соответствии с рисунком 1.

Сигмоидальная функция
Рисунок 1 – топология сверточной нейронной сети

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

Свое название сверточная сеть получила по названию операции – свертка, суть которой будет описана дальше.

Сверточные сети являются удачной серединой между биологически правдоподобными сетями и обычным многослойным персептроном. На сегодняшний день лучшие результаты в распознавании изображений получают с их помощью. В среднем точность распознавания таких сетей превосходит обычные ИНС на 10-15%. СНС – это ключевая технология Deep Learning.

Основной причиной успеха СНС стало концепция общих весов. Несмотря на большой размер, эти сети имеют небольшое количество настраиваемых параметров по сравнению с их предком – неокогнитроном. Имеются варианты СНС (Tiled Convolutional Neural Network), похожие на неокогнитрон, в таких сетях происходит, частичный отказ от связанных весов, но алгоритм обучения остается тем же и основывается на обратном распространении ошибки. СНС могут быстро работать на последовательной машине и быстро обучаться за счет чистого распараллеливания процесса свертки по каждой карте, а также обратной свертки при распространении ошибки по сети.

На рисунке ниже продемонстрирована визуализация свертки и подвыборки:

Сигмоидальная функция

Топология сверточной нейросети

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

Можно выделить следующие этапы влияющие на выбор топологии:

  • определить решаемую задачу нейросетью (классификация, прогнозирование, модификация);
  • определить ограничения в решаемой задаче (скорость, точность ответа);
  • определить входные (тип: изображение, звук, размер: 100×100, 30×30, формат: RGB, в градациях серого) и выходных данные (количество классов).

Решаемая моей нейросетью задача – классификация изображений, конкретно лиц. Накладываемые ограничения на сеть — это скорость ответа – не более 1 секунды и точность распознавания не менее 70%. Общая топология сети в соответствии с рисунком 2.

Сигмоидальная функция
Рисунок 2 — Топология сверточной нейросети

Входной слой

Входные данные представляют из себя цветные изображения типа JPEG, размера 48х48 пикселей. Если размер будет слишком велик, то вычислительная сложность повысится, соответственно ограничения на скорость ответа будут нарушены, определение размера в данной задаче решается методом подбора. Если выбрать размер слишком маленький, то сеть не сможет выявить ключевые признаки лиц. Каждое изображение разбивается на 3 канала: красный, синий, зеленый. Таким образом получается 3 изображения размера 48х48 пикселей.

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

Входные данные каждого конкретного значения пикселя нормализуются в диапазон от 0 до 1, по формуле:

Сигмоидальная функция

Сверточный слой

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

Количество карт определяется требованиями к задаче, если взять большое количество карт, то повысится качество распознавания, но увеличится вычислительная сложность. Исходя из анализа научных статей, в большинстве случаев предлагается брать соотношение один к двум, то есть каждая карта предыдущего слоя (например, у первого сверточного слоя, предыдущим является входной) связана с двумя картами сверточного слоя, в соответствии с рисунком 3. Количество карт – 6.

Сигмоидальная функция
Рисунок 3 — Организация связей между картами сверточного слоя и предыдущего

Размер у всех карт сверточного слоя – одинаковы и вычисляются по формуле 2:

Сигмоидальная функция

Ядро представляет из себя фильтр или окно, которое скользит по всей области предыдущей карты и находит определенные признаки объектов. Например, если сеть обучали на множестве лиц, то одно из ядер могло бы в процессе обучения выдавать наибольший сигнал в области глаза, рта, брови или носа, другое ядро могло бы выявлять другие признаки. Размер ядра обычно берут в пределах от 3х3 до 7х7. Если размер ядра маленький, то оно не сможет выделить какие-либо признаки, если слишком большое, то увеличивается количество связей между нейронами. Также размер ядра выбирается таким, чтобы размер карт сверточного слоя был четным, это позволяет не терять информацию при уменьшении размерности в подвыборочном слое, описанном ниже.

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

Сигмоидальная функция

Изначально значения каждой карты сверточного слоя равны 0. Значения весов ядер задаются случайным образом в области от -0.5 до 0.5. Ядро скользит по предыдущей карте и производит операцию свертка, которая часто используется для обработки изображений, формула:

Сигмоидальная функция

Неформально эту операцию можно описать следующим образом — окном размера ядра g проходим с заданным шагом (обычно 1) все изображение f, на каждом шаге поэлементно умножаем содержимое окна на ядро g, результат суммируется и записывается в матрицу результата, как на рисунке 4.

Сигмоидальная функция
Рисунок 4 — Операция свертки и получение значений сверточной карты (valid)

Сигмоидальная функция
Операция свертки и получение значений сверточной карты. Ядро смещено, новая карта получается того же размера, что и предыдущая (same)

При этом в зависимости от метода обработки краев исходной матрицы результат может быть меньше исходного изображения (valid), такого же размера (same) или большего размера (full), в соответствии с рисунком 5.

Сигмоидальная функция
Рисунок 5 — Три вида свертки исходной матрицы

В упрощенном виде этот слой можно описать формулой:

Сигмоидальная функция

При этом за счет краевых эффектов размер исходных матриц уменьшается, формула:

Сигмоидальная функция

Подвыборочный слой

Подвыборочный слой также, как и сверточный имеет карты, но их количество совпадает с предыдущим (сверточным) слоем, их 6. Цель слоя – уменьшение размерности карт предыдущего слоя. Если на предыдущей операции свертки уже были выявлены некоторые признаки, то для дальнейшей обработки настолько подробное изображение уже не нужно, и оно уплотняется до менее подробного. К тому же фильтрация уже ненужных деталей помогает не переобучаться.
В процессе сканирования ядром подвыборочного слоя (фильтром) карты предыдущего слоя, сканирующее ядро не пересекается в отличие от сверточного слоя. Обычно, каждая карта имеет ядро размером 2×2, что позволяет уменьшить предыдущие карты сверточного слоя в 2 раза. Вся карта признаков разделяется на ячейки 2х2 элемента, из которых выбираются максимальные по значению.

Обычно в подвыборочном слое применяется функция активации RelU. Операция подвыборки (или MaxPooling – выбор максимального) в соответствии с рисунком 6.

Сигмоидальная функция
Рисунок 6 — Формирование новой карты подвыборочного слоя на основе предыдущей карты сверточного слоя. Операция подвыборки (Max Pooling)

Формально слой может быть описан формулой:

Сигмоидальная функция

Полносвязный слой

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

Сигмоидальная функция

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

Сигмоидальная функция

Выходной слой

Выходной слой связан со всеми нейронами предыдущего слоя. Количество нейронов соответствует количеству распознаваемых классов, то есть 2 – лицо и не лицо. Но для уменьшения количества связей и вычислений для бинарного случая можно использовать один нейрон и при использовании в качестве функции активации гиперболический тангенс, выход нейрона со значением -1 означает принадлежность к классу “не лица”, напротив выход нейрона со значением 1 – означает принадлежность к классу лиц.

Выбор функции активации

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

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

Сигмоидальная функция

Функция активации сигмоиды

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

Сигмоидальная функция

График сигмоидальной функции в соответствии с рисунком ниже:

Сигмоидальная функция

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

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

Сигмоидальная функция является:

  • непрерывной;
  • монотонно возрастающей;
  • дифференцируемой.

Функция активации гиперболический тангенс

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

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

График функции гиперболического тангенса показан на рисунке:

Сигмоидальная функция

Функция активации ReLU

Известно, что нейронные сети способны приблизить сколь угодно сложную функцию, если в них достаточно слоев и функция активации является нелинейной. Функции активации вроде сигмоидной или тангенциальной являются нелинейными, но приводят к проблемам с затуханием или увеличением градиентов. Однако можно использовать и гораздо более простой вариант — выпрямленную линейную функцию активации (rectified linear unit, ReLU), которая выражается формулой:

Сигмоидальная функция

График функции ReLU в соответствии с рисунком ниже:

Сигмоидальная функция

Преимущества использования ReLU:

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

Из недостатков можно отметить, что ReLU не всегда достаточно надежна и в процессе обучения может выходить из строя («умирать»). Например, большой градиент, проходящий через ReLU, может привести к такому обновлению весов, что данный нейрон никогда больше не активируется. Если это произойдет, то, начиная с данного момента, градиент, проходящий через этот нейрон, всегда будет равен нулю. Соответственно, данный нейрон будет необратимо выведен из строя. Например, при слишком большой скорости обучения (learning rate), может оказаться, что до 40% ReLU «мертвы» (то есть, никогда не активируются). Эта проблема решается посредством выбора надлежащей скорости обучения.

Обучающие выборки использующиеся в экспериментах

Обучающая выборка состоит из положительных и отрицательных примеров. В данном случае из лиц и “не лиц”. Соотношение положительных к отрицательным примерам 4 к 1, 8000 положительных и 2000 отрицательных.

В качестве положительной обучающей выборки использовалась база данных LFW3D [7]. Она содержит цветные изображения фронтальных лиц типа JPEG, размером 90×90 пикселей, в количестве 13000. База данных предоставляется по FTP, доступ осуществляется по паролю. Для получения пароля необходимо заполнить на главной странице сайта простую форму, где указать свое имя и электронную почту. Пример лиц из базы данных показан в соответствии с рисунком ниже:

Сигмоидальная функция

В качестве отрицательных обучающих примеров использовалась база данных SUN397 [8], она содержит огромное количество всевозможных сцен, которые разбиты по категориям. Всего 130000 изображений, 908 сцен, 313000 объектов сцены. Общий вес этой базы составляет 37 GB. Категории изображений весьма различны и позволяют выбирать более конкретную среду, где будет использоваться конечное ПС. Например, если априори известно, что детектор лиц предназначен только для распознавания внутри помещения, то нет смысла использовать обучающую выборку природы, неба, гор и т.д. По этой причине автором работы были выбраны следующие категории изображений: жилая комната, кабинет, классная комната, компьютерная комната. Примеры изображений из обучающей выборки SUN397 показаны в соответствии с рисунком ниже:

Сигмоидальная функция

Результаты

Прямое распространение сигнала от входного изображения размером 90х90 пикселей занимает 20 мс (на ПК), 3000 мс в мобильном приложении. При детектировании лица в видеопотоке в разрешении 640х480 пикселей, возможно детектировать 50 не перекрытых областей размером 90х90 пикселей. Полученные результаты с выбранной топологией сети хуже по сравнению с алгоритмом Виолы-Джонса.

Выводы

Сверточные нейронные сети обеспечивают частичную устойчивость к изменениям масштаба, смещениям, поворотам, смене ракурса и прочим искажениям.

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

Подвыборочный слой дает:

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

Последние слои – слои обычного многослойного персептрона. Два полносвязных и один выходной. Этот слой отвечает за классификацию, с математической точки зрения моделирует сложную нелинейную функцию, оптимизируя которую улучшается качество распознавания. Число нейронов в слое 6 по числу карт признаков подвыборочного слоя.

Возможные улучшения

  • рассмотреть нейросети Fast-RCNN, YOLO;
  • распараллеливание процесса обучения на графические процессоры;
  • использование Android NDK (C++) для улучшения производительности

Обучение сверточной нейронной сети описано во второй части.

habr.com

Семейство функций класса сигмоид

В семейство функций класса сигмоид также входят такие функции как арктангенс, гиперболический тангенс и другие функции подобного вида.

Функция Ферми (экспоненциальная сигмоида): <math>f(s)= frac{1}{1+e^{-2 alpha s}}</math>

Рациональная сигмоида: <math>f(s)= frac{s}{|s|+ alpha}</math>

Гиперболический тангенс: <math>f(s)= th frac{s}{alpha} = frac{ e^{ frac{s}{alpha} } — e^{ — frac{s}{alpha}} } {e^{ frac{s}{alpha} } + e^{ — frac{s}{alpha}}} </math>

Модифицированный гиперболический тангенс: <math>f(s)= frac {e^{as} — e^{-bs}} {e^{cs} + e^{-ds}}</math>

Применение

Нейронные сети

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

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

<math>sigma'(x) = (1 + sigma(x)) cdot (1 — sigma(x))</math> — для гиперболического тангенса
<math>sigma'(x) = sigma(x) cdot (1 — sigma(x))</math> — для логистической функции

Логистическая регрессия

Логистическая функция <math>f(x) = frac{1}{1 + e^{-x}}</math> используется в логистической регрессии следующим образом. В ней решается задача классификации с двумя классами (<math>y=0</math> и <math>y=1</math>, где <math>y</math> — переменная, указывающая класс объекта), и делается предположение о том, что вероятность принадлежности объекта к одному из классов выражается через значения признаков этого объекта <math>x_1, x_2, …, x_n</math> (действительные числа):

<math>mathbb{P}{y=0mid x_1,ldots,x_n} = f(a_1 x_1 + ldots + a_n x_n) = frac{1}{1 + exp(-a_1 x_1 — ldots — a_n x_n)},</math>

где <math>a_1, …, a_n</math> — некоторые коэффициенты, требующие подбора, обычно, методом наибольшего правдоподобия.

Выбор именно этой функции <math>f(x)</math> можно обосновать, рассматривая логистическую регрессию, как обобщённую линейную модель в предположении, что зависимая переменная <math>y</math> распределена по закону Бернулли.

См. также

  • Искусственная нейронная сеть
  • Перцептрон
  • Модифицированный гиперболический тангенс

Литература

  • Mitchell, Tom M. Machine Learning. — WCB–McGraw–Hill, 1997. — ISBN 0-07-042807-7.

Ссылки

  • [www.neuropro.ru/memo312.shtml Сравнение быстроты нескольких программных реализаций гиперболического тангенса]
  • [www.computing.dcu.ie/~humphrys/Notes/Neural/sigmoid.html Continuous output, the sigmoid function] (англ.).

Отрывок, характеризующий Сигмоида

Наташа и княжна Марья плакали тоже теперь, но они плакали не от своего личного горя; они плакали от благоговейного умиления, охватившего их души перед сознанием простого и торжественного таинства смерти, совершившегося перед ними.

Для человеческого ума недоступна совокупность причин явлений. Но потребность отыскивать причины вложена в душу человека. И человеческий ум, не вникнувши в бесчисленность и сложность условий явлений, из которых каждое отдельно может представляться причиною, хватается за первое, самое понятное сближение и говорит: вот причина. В исторических событиях (где предметом наблюдения суть действия людей) самым первобытным сближением представляется воля богов, потом воля тех людей, которые стоят на самом видном историческом месте, – исторических героев. Но стоит только вникнуть в сущность каждого исторического события, то есть в деятельность всей массы людей, участвовавших в событии, чтобы убедиться, что воля исторического героя не только не руководит действиями масс, но сама постоянно руководима. Казалось бы, все равно понимать значение исторического события так или иначе. Но между человеком, который говорит, что народы Запада пошли на Восток, потому что Наполеон захотел этого, и человеком, который говорит, что это совершилось, потому что должно было совершиться, существует то же различие, которое существовало между людьми, утверждавшими, что земля стоит твердо и планеты движутся вокруг нее, и теми, которые говорили, что они не знают, на чем держится земля, но знают, что есть законы, управляющие движением и ее, и других планет. Причин исторического события – нет и не может быть, кроме единственной причины всех причин. Но есть законы, управляющие событиями, отчасти неизвестные, отчасти нащупываемые нами. Открытие этих законов возможно только тогда, когда мы вполне отрешимся от отыскиванья причин в воле одного человека, точно так же, как открытие законов движения планет стало возможно только тогда, когда люди отрешились от представления утвержденности земли.

После Бородинского сражения, занятия неприятелем Москвы и сожжения ее, важнейшим эпизодом войны 1812 года историки признают движение русской армии с Рязанской на Калужскую дорогу и к Тарутинскому лагерю – так называемый фланговый марш за Красной Пахрой. Историки приписывают славу этого гениального подвига различным лицам и спорят о том, кому, собственно, она принадлежит. Даже иностранные, даже французские историки признают гениальность русских полководцев, говоря об этом фланговом марше. Но почему военные писатели, а за ними и все, полагают, что этот фланговый марш есть весьма глубокомысленное изобретение какого нибудь одного лица, спасшее Россию и погубившее Наполеона, – весьма трудно понять. Во первых, трудно понять, в чем состоит глубокомыслие и гениальность этого движения; ибо для того, чтобы догадаться, что самое лучшее положение армии (когда ее не атакуют) находиться там, где больше продовольствия, – не нужно большого умственного напряжения. И каждый, даже глупый тринадцатилетний мальчик, без труда мог догадаться, что в 1812 году самое выгодное положение армии, после отступления от Москвы, было на Калужской дороге. Итак, нельзя понять, во первых, какими умозаключениями доходят историки до того, чтобы видеть что то глубокомысленное в этом маневре. Во вторых, еще труднее понять, в чем именно историки видят спасительность этого маневра для русских и пагубность его для французов; ибо фланговый марш этот, при других, предшествующих, сопутствовавших и последовавших обстоятельствах, мог быть пагубным для русского и спасительным для французского войска. Если с того времени, как совершилось это движение, положение русского войска стало улучшаться, то из этого никак не следует, чтобы это движение было тому причиною.

wiki-org.ru


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

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

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

Adblock
detector