суббота, 26 февраля 2011 г.

Парсинг HTML: путь Ктулху!

Данная статья является переводом статьи Parsing HTML The Cthulhu Way Джефа Эфтвуда. 

Каждый программист в независимости от опыта понимает, что парсинг HTML при помощи регулярных выражений считается плохой идеей. Насколько плоха эта идея? Это сразу становится очевидным после рассмотрения одного случая, когда один из пользователей StackOverFlow.com дошел до грани безумия (текст по ссылке на английском языке): 

"Вы не можете парсить HTML используя регулярные выражения. Regex не является средством, которое позволяет корректно парсить HTML. Как я уже много раз отвечал на вопросы связанные с парсингом HTML, использование regex не позволяет вам полностью обработать HTML и учесть все нюансы этого языка разметки. 

Регулярные выражения - это не достаточно мощное средство для анализа конструкций присутствующих в HTML. HTML не является регулярным языком и следовательно не может быть обработан при помощи регулярных выражений. Regex не разработаны для того, что бы разбивать HTML на части, которые имеют смысл. Сколько раз я уже отвечаю, но чувствую, что это никогда не кончиться. Даже расширенные нерегулярные regex в Perl не предназначены для того, что бы парсить HTML. Вы никогда меня не поломаете. HTML - достаточно сложный язык для того, что бы его можно было парсить при помощи regex. 

Только Jon Skeet не может парсить HTML при помощи regex. Каждый раз когда вы пытаетесь парсить HTML при помощи регулярных выражений - ребенок плачет кровью девственниц и русские хакеры взламывают ваше веб-приложение. Парсинг HTML при помощи regex вызывает грешные души в царство живых. Regex и HTML вместе сочетаются также как и любовь, брак, а также ритуальное детоубийство. Тег <center> не будет сохранятся вечно - он уже мертв. Использование regex и HTML - это сила, которая при использовании на одном концептуальном уровне, просто вынесет ваш мозг вдребезги! Если вы парсите HTML при помощи regex вы приводите к гибели всех нас, которые бесчеловечно трудятся для одного языка, который не может быть выражен в рамках базовых многоязычных конструкций." 

И это правильно, если вы пытаетесь парсить HTML при помощи regex, то вы поддаетесь искушениям темного бога Ктулху... 


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

Я уже слышал этот аргумент. Обычно я слышу это как обоснование следующего кода:

# pull out data between tags
($table_data) = $html =~ /(.*?)<\/td>/gis; 

"Но это работает!" - они говорят. 
"Это легко!" 
"Это быстро!"
"Это хорошая работа."

Я ругаю их, что бы они были ленивыми. Вы должны быть ленивы как программист. Парсинг HTML - решенная проблема. Вы не должны решать ее. Вы всего лишь должны быть ленивы. Будьте ленивы, используйте CPAN и HTML::Sanitizer. Ваше программирование будет значительно более легким занятием. Ваш код будет расширяем. Вы не должны сидеть и вручную писать регулярные выражения. Ваш код будет более надежный. Вы не должны будете заниматься отладкой все время из-за ошибок, которые возникают, если HTML "поломает" ваши регулярные выражения. 

Новички программирования сильно удивляются, когда узнают, что парсинг HTML при помощи regex это - путь Ктулху, вместо использования необходимых библиотек, как поступил бы разумный человек или опытный программист. Это означает, что это обсуждение будет постоянно продолжаться на StackOverFlow. Выше написанный пост, которому уже 5 лет, может обсуждаться так, как будто он написан еще вчера, я думаю это позволительно, учитывая степень важности данной проблемы.

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

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


Кроме того, жесткие ограничение никак не должны быть связаны с HTML ограничениями. Они должны быть такие простые как "работа с этими наборами веб-страниц", "работа с данными с этих веб-страниц", "работать для 98% пользователей 98% времени" или даже "О, нет! Мы должны сделать эту работу за час, сделай все, что сможешь!". 

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

Таким образом, если я захочу парсить HTML при помощи regex, то я четко понимаю, что:
  • Это очень плохая идея. 
  • Если вы не дисциплинированы и строго себя не ограничиваете, то парсинг HTML при помощи regex приведет вас к безумию, а Ктулху это любит!
  • Я очень четко осознаю, что причина использования regex для парсинга HTML в данном сценарии (полу) оправдана. 
Я думаю, что полностью ограничить парсинг HTML при помощи регулярных выражений, это тоже самое, что решать простую тривиальную задачу парсинга HTML при помощи какого-нибудь огромного движка. Лучше понимать инструменты, их сильные и слабые стороны, чем просто подчинятся догмам. 

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

Какой бы метод вы не выбрали, никогда не открывайте тег <cthulhu> ради всего человечества. 

вторник, 22 февраля 2011 г.

Разоблачить коварные планы Цезаря!

Вы математик, молодой римский математик! В вашем доселе демократичном государстве назревают войны и великие потрясения. Вы решаетесь на подвиг - украсть тайные письма Цезаря! Самого Гая Юлия Цезаря! Вы еще понимаете, что вас ждет смертная казнь, но вы все же крадете его тайные письма.

Однако вы удивлены! Вы видите там просто набор букв! О, ваш бог, это же не что! Вы рисковали жизнью за это: "JQDHXVSRPSHLXVPDJQXVLVPBHQHPB".

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

Жду ответа!

пятница, 18 февраля 2011 г.

Ответы на вопросы по теории вероятностей и математической статистике.

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

Вот перечень вопросов, на которые я попытался ответить:

1. Дискретное пространство элементарных событий.  Операции над событиями. 
2. Классическое определение вероятности. Свойства вероятности. 
3. Произвольное пространство элементарных событий. Алгебра и ? - алгебра множеств. Борелевские множества. Вероятность. 
4. Геометрическая вероятность. 
5. Условные вероятности. Независимые события и их свойства. 
6. Формула полной вероятности. Формула Бейеса. 
7. Повторяющиеся испытания. Формула Бернулли. 
8. Случайные величины и функции распределения. Свойства функции распределения. 
9. Дискретные случайные величины. Биномиальное, геометрическое, гипергеометрическое распределения, распределения Пуассона. 
10. Абсолютно-непрерывные случайные величины. Равномерное распределение, нормальное распределение, показательное распределение. 
11. Математическое ожидание случайной величины и его свойства. 
12. Дисперсия случайной величины и ее свойства. 
13. Нормированные случайные величины. Коэффициент корреляции. 
14. Неравенства Чебышева. 
15. Закон больших чисел.
16. Локальная и интегральная теоремы Муавра-Лапласа.
17. Теорема Пуассона.
18. Характеристические функции и их свойства.
19. Сходимость случайных величин и функций распределения.
20. Центральная предельная теорема.
21. Основные задачи математической статистики. Выборка и вариационный ряд, полигон и гистограмма частот. 
22. Эмпирическая функция распределения. Эмпирические моменты. Метод условных вариант.
23. Точечные оценки параметров распределения. 
24. Метод моментов определения параметров распределения. 
25. Метод максимального правдоподобия нахождения параметров распределения.
26. Некоторые распределения связанные с нормальным распределением: Пирсона, Стьюдента.
27. Интервальные оценки параметров распределения. Нахождение доверительных интервалов для распределений Пуассона, биномиального, нормального.
28. Статистическая проверка статистических гипотез. Ошибки первого и второго рода.
29. Оптимальный критерий. Теорема Неймана-Пирсона.
30. Непараметрические критерии. Критерий Колмогорова.
31. Критерий Пирсона. Вычисление теоретических частот для различных видов распределений. 
32. Элементы теории корреляции. Понятие корреляционной зависимости. Точечные оценки для условных математических ожиданий и коэффициента корреляции. 
33. Цепи Маркова. Матрица перехода.
34. Классификация состояний цепи Маркова. Теорема солидарности. 
35. Теорема о предельных вероятностях.
36. Случайные процессы. Марковские процессы со счетным множеством состояний.
37. Локально-регулярные марковские процессы. Система уравнений Колмогорова. 
38. Применение теории марковских процессов к задачам теории массового обслуживания.
39. Процесс Пуассона. 

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

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

четверг, 20 января 2011 г.

C#/.NET маленькие чудеса: ограничение обобщений при помощи условия where

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

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

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

Проблема - C# допускает наименьший общий знаменатель

В C++, вы можете создать шаблон и делать практически все с параметром шаблона, конечно, если это допускается синтаксически, и C++ не будет проверять корректен ли вызванный метод/поле/операция до тех пор, пока вы не объявите реализацию типа. Давайте-ка я вам это продемонстрирую:

// компилируется нормально, C++ не делает 
// никаких предположений о типе T
template <typename T>
class ReverseComparer
{
public:
     int Compare(const T& lhs, const T& rhs)
     {
         return rhs.CompareTo(lhs);
     }
};

Заметьте, что мы спокойно вызываем метод CompareTo() для шаблонного типа T. Потому, как мы на данный момент не знаем, что из себя представляет тип T, и C++ не делает ни каких предположений, следовательно и не возникает ошибок.

C++ стремится не проверять используемый тип шаблона до тех пор, пока метод действительно не будет вызван для конкретного типа, что очень отличается от поведения C#:

// это НЕ скомпилируется! 
// C# допускает наименьший общий знаменатель
public class ReverseComparer<T>
{
     public int Compare(T lhs, T rhs)
     {
         return lhs.CompareTo(rhs);
     }
}

Так почему же C# выдает ошибку компиляции когда, мы еще не знаем, что за тип имеет T? Это происходит потому, что C# идет по-другому пути создания обобщений, в отличии от C++. Пока вы не укажите обратное, T трактуется как нечто похожее на object (заметьте я не сказал как object).

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

Сейчас, чем шире объект является, тем более абстрактным (более общим) он должен быть. Так как же мы позволим нашему обобщенному типу заменителю, делать нечто большее, чем может делать object?

Решение: ограничить тип используя условие where

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

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

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

Одно из удобных возможностей where ограничений - указывать какой интерфейс обобщенный тип должен реализовать или какой класс обобщенный тип должен наследовать. Например, вы не могли вызвать метод CompareTo() в нашем первом C# обобщении, но если ограничить тип T интерфейсом IComparable<T>, то вы сможете:

public class ReverseComparer<T> 
    where T : IComparable<T>
{
    public int Compare(T lhs, T rhs)
    {
        return lhs.CompareTo(rhs);
    }
}

Теперь, когда мы ограничили T реализацией IComparable<T>, это означает, что наши переменные обобщенного типа могут вызывать любые члены IComparable<T>. Теперь можно легально вызвать CompareTo().

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

Ограничение обобщенных типов  только ссылочными типами

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

Хорошо, мы можете это исправить ограничением class в where условии. Объявляя то, что обобщенный тип, должен быть class, мы говорим, что он является ссылочным типом и может принимать значение null для экземпляров этого типа:

public static class ObjectExtensions
{
    public static TOut Maybe<TIn, TOut>(
        this TIn value, Func<TIn, TOut> accessor)
        where TOut : class
        where TIn : class
    {
        return (value != null) 
                   ? accessor(value)  
                   : null;
    }
}
>

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

Ограничение обобщенных типов только типами значений

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

Рассмотрим следующий метод, который будет конвертировать, что угодно реализующее IConvertible (int, double, string и т. п.) в тип значение, который вы укажете, или null, если экземпляр является null:

public static T? ConvertToNullable<T>(
    IConvertible value)
    where T : struct
{
    T? result = null;
  
    if (value != null)
    {
        result = (T)Convert.ChangeType(
                     value, typeof(T));
    }
  
    return result;
}

Так как T был ограничен типом значений, мы можем использовать T? (System.Nullable<T>), где мы не могли этого делать, если T был ссылочным типом.

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

Вы также можете ограничить тип требованием к наличию конструктора по умолчанию. Так как C# по умолчанию не знает какой конструктор имеет или не имеет обобщенный тип заместитель, то он не может позволить конструктор. Это говорит о том, что если обобщенный тип будет ограничен new(), то это будет обозначать, что тип реализующий обобщенный тип должен иметь конструктор по умолчанию (без параметров).

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

// Полученный набор Action 
// отображений будет отображен из TFrom в TTo
public class Adapter<TFrom, TTo> : 
    IEnumerable<Action<TFrom, TTo>>
    where TTo : class, new()
{

    public List<Action<TFrom, TTo>> Translations 
    { 
        get; 
        private set; 
    }
   
    public Adapter()
    {
        Translations = new List<
                           Action<TFrom, TTo>>();
    }
  
    public void Add(
        Action<TFrom, TTo> translation)
    {
        Translations.Add(translation);
    }


    void Add(
        Predicate<TFrom> conditional, 
        Action<TFrom, TTo> translation)
    {
        Translations.Add((from, to) =>
            {
                if (conditional(from))
                {
                    translation(from, to);
                }
            });
    }
  
    public TTo Adapt(TFrom sourceObject)
    {
        var resultObject = new TTo();
  
        Translations.ForEach(
            t => t(sourceObject, resultObject));
        return resultObject;
    }

    public IEnumerator<
        Action<TFrom, TTo>> GetEnumerator()
    {
        return Translations.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

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

Выводы

Условие where - это превосходная вещь, которая дает .NET обобщениям больше мощи для выполнения заданий, которые требуют большего поведения, чем базовое (речь идет об object).

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

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

В дополнение, вы не можете перегружать определение шаблона, разными ограничениями. Например, вы можете определить Adapter where T : struct и Adapter where T : class. 

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

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

воскресенье, 21 ноября 2010 г.

Doctrine 2 - Getting Started XML-Edition на русском

Наконец-то окончил перевод Getting Started XML-Edition на русский язык.. Это руководство, которое позволяет быстро ознакомиться с Doctrine 2 и ее возможностями и сразу после прочтения руководства начать использование.

Перевод вы можете скачать более удобном для вас виде:

      

А также в онлайн виде на Google Docs - это версия всегда будет самой свежей, а остальные я буду периодически обновлять.

Огромная просьба, если вы заметите ошибки в переводе или знаете как его можно улучшить сообщите мне об этом в комментариях здесь или на email - krasun.net@gmail.com. Также если вы узнаете об обновлении руководства раньше, чем я сообщите мне об этом.

Только вместе мы сможем сделать перевод руководства лучше.

пятница, 19 ноября 2010 г.

Модульное тестирование при использовании SWI-Prolog

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

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

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

Теперь о SWI-Prolog, в ПроЛоге мы с вами используем предикаты для реализации логики. И значит, тестировать мы будем предикаты. Идея такая: мы создаем предикат test_p, который тестирует наш предикат p. Как будет проходить тест?

Все очень просто. Для тестирования, мы будем задавать заранее известные входные параметры (термы) и сверять результат исчисления предиката с заранее известным правильным ответом. Если ответы совпали, все в порядке идем дальше, если нет выдаем информативное сообщение.

Для наглядности покажу простой пример, используя SWI-Prolog, заранее оговорюсь, эти же идеи применимы для любой другой версии компилятора ПроЛога.

Напишем предикат max(X, Y, Max), вычисляющий максимальное значение из 2-ух элементов, для начала опишем тест, затем реализацию:

% сперва напишем тест
test_max1 :- 
    max(3, 2, X), 
    X = 3 , !.
test_max1 :- 
    write('max(3, 2, X) вычислен не правильно!'),  
    nl.

test_max2 :- 
    max(2, 3, X), 
    X = 3, !.
test_max2 :- 
    write('max(2, 3, X) вычислен не правильно!'), 
    nl.

test_max3 :- 
    max(2, 2, X), 
    X = 2, !.
test_max3 :- 
    write('max(2, 2, X) вычислен не правильно!'), 
    nl.

run_tests :- 
    test_max1, 
    test_max2, 
    test_max3.
    
% теперь опишем предикат, max 
max(A, B, B) :- 
    A < B, 
    !.
max(A, _, A).

Запустим программу и вот, что увидим:



Изменим, чуть код, не важному почему, допустим случайно, или так надо было:

% ...    

% теперь опишем предикат, max 
max(A, B, B) :- 
    A > B, 
    !.
max(A, _, A).

И вот, что мы увидим в результате.



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

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

понедельник, 15 ноября 2010 г.

Определяем уравнение прямой при помощи искусственных нейронных сетей

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

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



Нам будет дан набор точек и на основе него, необходимо найти коэффициенты с и m - в уравнение прямой y = mx + c. Для обучения сети будем использовать дельта-правило - (правило Видроу-Хоффа). Если вкратце, то суть правила такова, мы на вход сети (в нашем случае перцептрон)  подадим значение координаты X, сеть вычислит значение координаты Y, мы сравним вычисленное сетью значение с ожидаемым и получим отклонение, то есть ошибку E. После чего мы полученную ошибку умножим на норму обучения n (она задается вручную) и на сигнал приходящий к Y, в результате мы и получаем отклонение весов dw1, dw2 и так далее. Остается только скорректировать весы w1 = w1 + dw1, w2 = w2 + dw2, ... 

Архитектура сети, также взято из Калан. Р., не удивляйтесь на то, что я привожу материалы из источников, моя задача запрограммировать сеть с использованием C#: 



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


// задаем начальное значение весов случайным образом 
// в интервале от [-0.3, 0.3] 
double[] w = new double[] 
{ 
    ((rD = r.Next(-100, 100) * 0.003) == 0) ? 
        0.3 : rD,
    ((rD = r.Next(-100, 100) * 0.003) == 0) ? 
        0.3 : rD
};

double[] w1 = new double[2]; 
w.CopyTo(w1, 0);

// входные сигналы
double[] x = new double[]
{
    1.0, 
    0.0
};

// норма обучения или скорость обучения 
double n = 0.1; 

double[,] pairs = new double[,] 
{
    {0.2, 1.3},
    {0.25, 1.3},
    {0.4, 1.4},
    {0.48, 1.52},
    {0.6, 1.6},
    {0.8, 1.6},
    {0.9, 2},
    {1, 2.1}
};
            
for (int i = 0; i < 10000; i++)
{
    for (int j = 0; j < pairs.GetLength(0); j++)
    {
        // для x на входе
        x[1] = pairs[j, 0];     
        // ожидаемый y на выходе
        double d = pairs[j, 1];
        // сумматор
        double y = x[0] * w[0] + x[1] * w[1];
        // вычисляем ошибку
        double e = d - y;
        // отклонение весов 
        double dW0 = e * n * x[0];
        double dW1 = e * n * x[1];
        // кооректируем весы 
        w[0] = w[0] + dW0;
        w[1] = w[1] + dW1;
    }
}

Console.WriteLine("w0 = {0}, w1 = {1}", 
    w1[0], w1[1]);
Console.WriteLine("m = {0}, c = {1}, |m - c| = {2}", 
    w[1], w[0], Math.Abs(w[1] - w[0]));
// m = 0.993801..., c = 1.03101...

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

Проект для Visual Studio 2010 Premium вы можете скачать отсюда. Спасибо, за внимание! Если у вас возникли вопросы, то задавайте. Либо если вы заметили ошибку, то сообщите мне об этом. Я мог здесь ошибиться, ибо я новичок в искусственных нейронных сетях.