Posts Tagged программы

LPT мигает светодиодом

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

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

Задача стоит весьма тривиальная: научиться управлять мерцанием светодиода, подключённого к ПК через LPT-порт. Почему именно LPT? Потому что он довольно прост и в меру интересен.
Поехали!

Подготовка

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

  • ПК
  • Компилятор какого-нибудь языка программирования (Assembler, С, С++, Pascal, etc…).
  • Некоторый программный инструментарий
  • Светодиод на 5В
  • LPT-шнур

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

image

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

image

Железо

Прежде чем приступить к практике, немного теории.

Как работаете LPT-порт? Об этом достаточно много написано, однако я всё-таки кратко расскажу как обстаят дела.

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

image

Подключать светодиод будем плюсом к 2 пину, а минусом – к пину 18. Смотрите, не перепутайте, в противном случае светодиод может сгореть. Лучше предварительно проверить где у него плюс, где минус на маломощной батарейке.

Если у вас шнур папа-мама, то есть при подключении к компьютеру, на вашем конце шнура остаются входы, дело простое – просто воткнуть в нужные пины диод. Следует быть осторожным, неправильно подключив, можно повредить LPT-порт.

image

Лично я, раскурочив шнур, определил, какой относится к D0, какой к земле, и у меня всё выглядит примерно так, как показано на следующем рисунке. Я сидел с пробником, и вычислял, какая линия относится к D0. Очень занимательно! Вы можете найти более удочное решение.

image

В мерах предосторожности можно последовательно к диоду припаять сопротивление. Рекомендуют 470 Ом. На счёт заземления: кто-то заземляет не на пин GRD, а на корпус коннектора, но я предпочитаю пин.

На этом этапе вы должны представлять себе, как можно подключить светодиод к LPT-порту.

Программная часть

Подключить светодиод к компьютеру мы готовы, но вот, дальше-то что? А дальше самое интересное: управление им с бортового пульта. С одним светодиодом много не сделаешь: только включать/выключать его можно. Но зато можно менять частоту мигания, можно подключить ещё 7 светодиодов к остальным пинам D1-D8, и сделать светомузыку. Или приобрести яркие белые светодиоды, и сделать настольную лампу, питающуюся от LPT-порта, которая включается, когда вы начинаете печатать, чтобы подсветить вам клавиатуру. Можно вывести зелёненький светодиод куда-нибудь на видное место и написать программу, мигающую им, когда вам на E-mail приходит письмо. Сколько всего можно сделать только лишь с использованием светодиода! А подковав себя в электронике, можно собирать полноценные внешние устройства, но это, к сожалению, выходит за рамки данной скромной статьи.

Управление LPT-портом зависит от ОС. В былые времена, операционные системы DOS и Windows 95/98 разрешали пользовательским программам напрямую иметь доступ к железу. С появлением Windows NT/2000/XP всё изменилось, теперь общение с портами напрямую пользовательским программам запрещено, а разрешено только коду, выполняющемуся в режиме ядра. Всё это сделано в целях защиты и в других нужных и полезных целях, однако всё это одновременно и усложняет нашу задачу по подмигиванию светодиодом. Нам пришлось бы писать специальный драйвер устройства, разбираться в HAL (Hardware Abstraction Layer) и прочих премудростях. Вы можете заняться этим, почитав материалы по Microsoft DDK (Device Driver Kit). Но существует несколько обходных путей, позволяющих напрямую общаться с нашим LPT. Вообще, так делать не рекомендуется, но всё же, мы сделаем именно так, ибо так проще и нагляднее. Другими словами, напрямую получить доступ к регистрам LPT-порта просто так не удастся. Вы можете работать с драйвером устройства LPT как с обычным файлом при помощи функций CreateFile, ReadFile, WriteFile, а так же получать некоторую информацию о состоянии устройства функцией DeviceIoContol. Но работать с регистром, например, D0, который включает наш светодиод, не получится.

В ОС Linux всё обстоит по-иному. Там можно делать всё просто, имея на то специальные права. Необходимо только узнать по какому адресу находится ваш LPT. Обычно он находится по адресу 0378h. Узнать адрес в вашей системе можно, просмотрев файл /proc/ioports. Хотя, проще работать с файлом устройства /dev/lp0.

Разберём, как справиться с ОС Windows, потому что это немного сложнее.

Перед тем, как перейти непосредственно к программированию своими силами, проверим, работает ли всё это дело, собранное в прошлой части статьи. Для этого я использую чудесную программу мониторинга параллельного порта, так и названную: Parallel Port Monitor by Neil Fraser.

Как делаю я: запускаю программу, выключаю в ней все пины с 2 до 9, затем подключаю светодиод к LPT-порту (в нашем случае во второй пин, в D0). После этого в Parallel Port Monitor подаю единицу на D0. Светодиод должен загореться. Если ничего не произошло, возможно, вы неправильно его подключили или же в программе выбрали не тот LPT-порт. Попробуйте LPT1, LPT2, LPT3, если у вас их несколько.

Светодиод мигает? Отлично. Теперь можно побаловаться с ним своим программным кодом. Как было сказано ранее, это делать мы будем обходным путём. Если вы используете Windows 95/98/ME, можете сразу перейти к написанию программы, обходные пути вам не нужны, эти версии ОС Windows позволяют напрямую обращаться к портам.

1) Использование драйвера UserPort

Программа UserPort (автор Tomas Franzon), это системный драйвер режима ядра для Windows NT/2000/XP, который позволяет обращаться к портам ввода-вывода напрямую. Как раз то, что нам необходимо. Найти её в любом поисковике не составит труда. Скачав, настроив, согласно документации и запустив, мы получаем возможность мигать светодиодом из наших программ.

Сперва определим адреса портов в Device Manager. Видим, LPT1 по адресу 0378-037F. Именно туда мы и будем писать биты управления светодиодом. Пишем 1 – на контакты светодиода подаётся напряжение +5В, он загорается, пишем бит 0 – светодиод гаснет.

image

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

#include <iostream>
#include <windows.h>

void doLight(bool on)
{
	__asm
	{
		mov DX,0378h
		mov AL,on
		out DX,AL
	}
}

int main()
{
	while(1)
	{
		doLight(true);
		std::cout<<"Light On!"<<std::endl;
		Sleep(1000);
		doLight(false);
		std::cout<<"Light Off!"<<std::endl;
		Sleep(1000);
	}
	return 0;
}

Компилируем, запускаем, проверяем. Если светодиод стал мигать, значит всё ОК, всё работает, и можно издеваться дальше. Если же появилось сообщение об ошибке, например «First-chance exception at 0×00411524 in program.exe: 0xC0000096: Privileged instruction», значит, вы неправильно запустили или настроили UserPort. Обратитесь к документации по нему, там есть пример.

2) Использование библиотеки inpout32.dll.

Домашняя страница разработчиков библиотеки: http://www.logix4u.net. Там же можно найти много полезной информации. Перед тем как писать код, поместим в каталог с нашим проектом файлы inpout32.dll и inpout32.lib. В этих библиотеках имеются функции, позволяющие читать и записывать в порты.

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

#include <iostream>
#include <windows.h>

#pragma comment(lib, "inpout32.lib")

// прототип функции
void _stdcall Out32(short PortAddress, short data);

void doLight(bool on)
{  Out32(0x378, on);
}

int main()
{   while(1)   {   doLight(true);   std::cout<<"Light On!"<<std::endl;   Sleep(1000);   doLight(false);   std::cout<<"Light Off!"<<std::endl;   Sleep(1000);   }  return 0;
}

Вот так вот:

image

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

Заключение

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

P.S.: Я где-то слышал, что люди собирают LPT- и USB-фонарики, и решил себе сделать нечто подобное из старого микрофона от наушников. Вот что вышло:

image

.NET, cc, e-mail, Hardware

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

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

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

Не так давно был анонсирован CTP библиотеки, находящийся на верхушке .NET Framework – Parallel FX Library. Наверняка, с её помощью можно будет быстрее писать многопоточные программы, которые к тому же будут менее подвержены ошибкам. Суть в том, что наш код автоматически распараллеливается на множестве имеющихся процессоров. Это чем-то похоже на распараллеливание запросов, которое делает СУБД, но здесь мы имеем это в нашем коде и с нашими объектами.

ParallelFX состоит из двух частей: PLINQ и TPL. PLINQ – это движок параллельного выполнения запросов для LINQ (Более подробно про LINQ в вы можете почитать, например, в моей статье), благодаря которому мы можем с лёгкостью распараллелить LINQ-запросы. TPL же вводит такие конструкции, как параллельные циклы; задачи (Task, маленькие части кода, которые могут быть выполнены независимо, они чем-то похожи на Thread, но легче синхронизируемые), и «будущие времена» (Future, специальная задача, которая возвращает результат). Стоит отметить, что информации по библиотеке совсем мало, а та, что есть, уже немного устарела, поскольку всё это ещё находится в разработке и меняется…

Что к чему

Для того, чтобы испытать PFX в действии вам потребуется .NET Framework 3.5, а так же PFX December 2007 CTP, который вы можете закачать отсюда. Если вы не поклонник сугубо текстовых редакторов и сборки из мейкфайлов, вам так же потребуется Visual Studio 2008. Так же было бы неплохо иметь доступ к многопроцессорному компьютеру, чтобы проверить всё собственноручно.

Добавьте ссылку на сборку System.Threading.dll, так, все необходимые нам классы находятся в пространстве имён System.Threading.

PLINQ

Допустим, у нас имеются какие-то сложные и большие LINQ-запросы. А машина, на которой исполняется наша программа – многоядерная. Так, мы хотим использовать каждое ядро по максимуму с минимумом затрат времени и сил. PLINQ – то, что нам поможет. Мы просто вызываем метод расширения AsParallel() для наших данных, и они «обвёртываются» чем-то, что знает, как всё распараллелить.

IEnumerable<T> data = ...;
var q = from a in data.AsParallel() where w(a) orderby o(a) select f(a);

Так же нам доступен «параллельный» аналог класса Enumerable – ParallelEnumerable с аналогичными статическими методами.

Таким образом, PLINQ помогает LINQ-запросам работать быстрее и обрабатывать большее количество данных, используя доступные процессоры.

TPL и Параллельные циклы

Взгляните на последовательный простой цикл:

for (int i = 0; i < N; i++) {   a[i] = Math.Sqrt(a[i]);
}

Мы можем попросить нашу библиотеку распараллелить его. Для этого мы пользуемся параллельной версией цикла for – статической функцией из класса Parallel:

Parallel.For(0, N, i => {   a[i] = Math.Sqrt(a[i]);
});

Так, если у нас двуядерная машина, то половина итераций будет выполняться на одном ядре, тогда как другая половина – на другом. Библиотека адаптируется к конкретному компьютеру, и распараллеливает наш код на доступное число процессоров. На однопроцессорной машине настоящего распараллеливания не получится, и данный цикл выльется в простой последовательный.

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

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

Считаем Pi

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

Pi/4 = 1/1 - 1/3 + 1/5 - 1/7 + 1/9 - ...

Последовательное вычисление можно организовать так:

public double CalculatePi()
{   double sum = 0;   int sign = 1;   for (int i = 1; i < ITER_COUNT; i += 2, sign = -sign;)   sum += (double)sign / i;   return 4 * sum ;
}

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

В следующем коде присутствуют некоторые причудливые синтаксические конструкции из новой версии С#, так что для понимания, что же здесь происходит рекомендую сперва ознакомиться с C# 3.0. Здесь так же используется перегрузка параллельного цикла For с шагом и состоянием потока.

const int ITER_COUNT = 100000000;
const int INNER_ITER_COUNT = 1000;     

public double CalculatePi_ParallelFor()
{   double sum = 0;   Parallel.For<double>(1, ITER_COUNT, INNER_ITER_COUNT, () => 0,   (i, state) =>   {   int sign = (i / 2) % 2 == 0 ? 1 : -1;   for (int j = i; j < i + INNER_ITER_COUNT; j += 2, sign=-sign)   state.ThreadLocalState += (double)sign / j;   }, partialSum => { lock (this) {sum += partialSum;} }   );   return 4 * sum;
}

Здесь в каждую параллельную итерацию внешнего цикла, помимо счётчика передаётся переменная состояния state, в её свойстве ThreadLocalState мы сохраняем «частичную сумму». Впоследствии все частичные суммы добавляются к общей сумме sum, формируя окончательный результат. Я запустил оба варианта на компьютере с четырёхядерным процессором, и вот что из этого вышло:

D:\>ParallelPi.exe
Calculating Pi in total 1000000000 iterations
And 1000 inner iterations in each process
OS: Microsoft Windows NT 5.2.3790 Service Pack 2
Processors count: 4     

Working Non-Parallel For...
> 3,14159265
00:00:07.3516085     

Working Parallel For...
> 3,14159265
00:00:03.9321267     

Time rating:
1. 00:00:03.9321267     Parallel For
2. 00:00:07.3516085     Non-Parallel For

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

image

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

image

Запустив этот же расчёт на своей однопроцессорной машине, я получил следующий результат:

Calculating Pi in total 1000000000 iterations
And 1000 inner iterations in each process
OS: Microsoft Windows NT 5.1.2600 Service Pack 2
Processors count: 1    

Working Non-Parallel For...
> 3,14159265
00:00:04.4050481    

Working Parallel For...
> 3,14159265
00:00:09.2341356    

Time rating:
1. 00:00:04.4050481     Non-Parallel For
2. 00:00:09.2341356     Parallel For

Последовательная версия оказалась в два раза быстрее «параллельной». Наверняка так получилось из-за того, что затраты на организацию параллелизма (а как его получить на однопроцессорной машине?) не окупились из-за отсутствия реального распараллеливания.

image

Заключение

На мой взгляд, технология ParallelFX весьма интересна. В виду нынешнего расцвета многоядерных процессоров идея параллельности становится как нельзя актуальнее. С появлением технологии LINQ, и функциональных расширений для C# и других языков, что-то подобное обязательно должно было появиться. Ну что ж, посмотрим, что будет дальше…

Мне было бы очень интересно услышать о ваших экспериментах с PFX, если вы решите испытать её :).

.NET, CSharp, framework, LINQ

Распознаём образы: Нейронная сеть Хопфилда

Допустим, у нас имеется некоторое количество эталонных образов – изображений, либо ещё чего-нибудь. Нам дают некий искажённый образ, и наша задача состоит в том, чтобы «распознать» в нём один из эталонных. Каким образом человек это сделает – вопрос сложный. А вот каким образом с данной задачей справится искусственная нейронная сеть – мы вполне можем себе представить. Тем более, если это нейронная сеть Хопфилда.

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

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

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

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

image
Однако эту сеть нельзя научить практически ничему. Нам нужно намного больше нейронов. Сеть, содержащая N нейронов может запомнить не более ~0.15*N образов. Так что реальная сеть должна содержать достаточно внушительное количество нейронов. Это одно из существенных недостатков сети Хопфилда – небольшая ёмкость. Плюс ко всему образы не должны быть очень похожи друг на друга, иначе в некоторых случаях возможно зацикливание при распознавании.

Как работает сеть

Образ, который сеть запоминает или распознаёт (любой входной образ) может быть представлен в виде вектора X размерностью n, где n – число нейронов в сети. Выходной образ представляется вектором Y с такой же размерностью. Каждый элемент вектора может принимать значения: +1 либо -1 (Можно свести к 0 и 1, однако +1 и -1 удобнее для расчётов).

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

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

Как было сказано, обучение сети строится на вычислении весовых коэффициентов. Для этого мы будем поддерживать матрицу W размером n x n. При обучении сети некому образу X коэффициенты устанавливаются так:

for i in range(0,n):
    for j in range(0,n):
        if (i == j):
            self.W[i][j] = 0
        else:
            self.W[i][j] += self.X[i] * self.X[j]

Если нам нужно обучить сеть следующему образу, мы просто меняем вектор X и заново повторяем эту процедуру. Вы видите, в элементах матрице сохраняется сумма значений для всех образов, которым мы обучили сеть. Установка значения элемента в 0 при i==j это отражения устройства сети, когда выход некого нейрона не попадает на его же вход.

Распознавание образа

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

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

Другой вариант развития событий – если в памяти сети есть похожие образы, и входящий искажённый похож на их обоих, то сеть может впасть в бесконечный цикл. Так, в нашей реализации если при некотором заданном количестве итераций распознавания образ не распознан, цикл прекращается, распознавание признаётся неудачным и сеть выводит вольную «импровизацию» своей работы.

Сеть выполняет следующую работу пока результат не совпадёт с одним из эталонным образов, либо не привесится порог итераций. Случайным образом выбирается нейрон r для обновления. Для него рассчитывается новое состояние s, используя нашу матрицу коэффициентов следующим образом:

net = 0
for i in range(0, n):
    net += Y[i] * W[i][r]
s = signum(net)

А затем мы обновляем его состояние:

Y[r] = s

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

Пример

Посмотрим как наша реализация сети сможет распознать образы букв. Мы будем представлять их в виде «битовых полей» размера 7х7. Так, имея три эталонных образа и один образ для распознавания, эмуляция нейронной сети даёт следующий результат:

Known Shapes:
- @ @ @ @ @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -

@ @ @ @ @ @ @
- - - @ - - -
- - - @ - - -
- - - @ - - -
- - - @ - - -
- - - @ - - -
- - - @ - - -

@ - - - - - @
@ - - - @ @ -
@ - - @ - - -
@ @ @ - - - -
@ - - @ - - -
@ - - - @ @ -
@ - - - - - @

Teaching...

Modified shape:

@ @ - - - - -
@ - - - @ @ -
@ - - @ - - -
- - @ - - - -
@ - - @ - - -
@ - - - @ @ -
@ @ - - - - @

Shape recognizing...

Neuron   6 : -1  ->   1
Neuron  43 :  1  ->  -1
Neuron  21 : -1  ->   1
Neuron  22 : -1  ->   1
Neuron   1 :  1  ->  -1

Success. Shape recognized in 94 iterations:

@ - - - - - @
@ - - - @ @ -
@ - - @ - - -
@ @ @ - - - -
@ - - @ - - -
@ - - - @ @ -
@ - - - - - @

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

А теперь посмотрим, что будем если подать на вход образ, похожий и на П и на Т:

Teaching...

[ вырезано ]

Modified shape:
@ @ @ @ @ @ @
- @ - @ - @ -
- @ - @ - @ -
- @ - @ - @ -
- @ - @ - @ -
- @ - @ - @ -
- @ - @ - @ -

Shape recognizing…
Neuron  12 :  1  ->  -1
Neuron   6 :  1  ->  -1
Neuron  40 :  1  ->  -1
Neuron   0 :  1  ->  -1
Neuron  22 :  1  ->  -1
Neuron  17 :  1  ->  -1
Neuron  31 :  1  ->  -1

Fail. Shape not recognized in 273 iterations…
- @ @ @ @ @ -
- @ - @ - - -
- @ - - - @ -
- - - @ - @ -
- @ - - - @ -
- @ - @ - - -
- @ - @ - @ -

Однако запустив программу несколько раз мы сможем наблюдать и такой вариант, когда сеть всё же смогла распознать образ П (а не Т, поскольку входящий образ больше похож именно на П):

Shape recognizing...
Neuron   0 :  1  ->  -1
Neuron  22 :  1  ->   0
Neuron  17 :  1  ->  -1
Neuron   6 :  1  ->  -1
Neuron  31 :  1  ->  -1
Neuron  10 :  1  ->  -1
Neuron  38 :  1  ->  -1
Neuron  22 :  0  ->   1
Neuron  45 :  1  ->  -1
Neuron  24 :  1  ->  -1

Success. Shape recognized in 116 iterations:
- @ @ @ @ @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -
- @ - - - @ -

Заключение

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

Исходные коды программы доступны здесь.

Neural network, Programming, python, wikipedia