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

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

пример на java и полезные ссылки
наглядная реализация с использованием ООП

Поскольку теории очень много по этой теме хотелось бы приступить к реализации.

Реализация

using UnityEngine;
using System.Collections;
using System.Xml.Serialization;

public class Neuron {

	[XmlAttribute("weight")]
	public string data;

	[XmlIgnore]
	public int[,] weight; // веса нейронов
	
	[XmlIgnore]
	public int minimum = 50; // порог
	
	[XmlIgnore]
	public int row = 64,column = 64;

	/**
     * Конструктор нейрона, создает веси и устанавливает случайные значения
     */
	public Neuron()
	{
		weight = new int[row,column];
		randomizeWeights();
	}

	/**
     * ответы нейронов, жесткая пороговая
     * @param input - входной вектор
     * @return ответ 0 или 1
     */
	public int transferHard(int[,] input)
	{
		int power = 0;
		for(int r = 0; r < row;r++)
		{
			for(int c = 0; c < column;c++)
			{
				power += weight[r,c]*input[r,c];
			}
		}
		//Debug.Log("Power: " + power);
		return power >= minimum ? 1 : 0;
	}

	/**
     * ответы нейронов с вероятностями
     * @param input - входной вектор
     * @return n вероятность
     */
	public int transfer(int[,] input)
	{
		int power = 0;
		for(int r = 0; r < row;r++)
			for(int c = 0; c < column;c++)
				power += weight[r,c]*input[r,c];

		//Debug.Log("Power: " + power);
		return power;
	}

	/**
     * устанавливает начальные произвольные значения весам 
     */
	void randomizeWeights()
	{
		for(int r = 0; r < row;r++)
			for(int c = 0; c < column;c++)
				weight[r,c] = Random.Range(0,10);
	}

	/**
     * изменяет веса нейронов
     * @param input - входной вектор
     * @param d - разница между выходом нейрона и нужным выходом
     */
	public void changeWeights(int[,] input,int d)
	{
		for(int r = 0; r < row;r++)
			for(int c = 0; c < column;c++)
				weight[r,c] += d*input[r,c];
	}

	public void prepareForSerialization()
	{
		data = "";
		for(int r = 0; r < row;r++)
		{
			for(int c = 0; c < column;c++)
			{
				data += weight[r,c] + " ";
			}
			data += "\n";
		}
	}

	public void onDeserialize()
	{
		weight = new int[row,column];

		string[] rows = data.Split(new char[]{'\n'});
		for(int r = 0; r < row;r++)
		{
			string[] columns = rows[r].Split(new char[]{' '});
			for(int c = 0; c < column;c++)
			{
				weight[r,c] = int.Parse(columns[c]);
			}
		}
	}
}



Класс нейронов который содержит weight — двоичный массив весов, minimum — порог функции. Функция transferHard возвращает ответ на входной вектор. Поскольку ответ функции жесткий, я использую его для обучения. На мой взгляд это более эффективно обучает нейроны. Я буду очень благодарен если будут отзывы по этому поводу. Функция transfer возвращает ответ на входной вектор но с вероятностью, сумма может быть ближе к нулю или отрицательной если нейрон обучен для другого символа.

using UnityEngine;
using System.Collections;
using System.Xml.Serialization;
using System.Xml;
using System.IO;

public class NeuralNetwork {

	[XmlArray("Neurons")]
	public Neuron[] neurons;

	/**
     * Конструктор сети создает нейроны
     */
	public NeuralNetwork()
	{
		neurons = new Neuron[10];

		for(int i = 0;i<neurons.Length;i++)
			neurons[i] = new Neuron();
	}

	/**
     * функция распознавания символа, используется для обучения
     * @param input - входной вектор
     * @return массив из нулей и единиц, ответы нейронов
     */
	int[] handleHard(int[,] input)
	{
		int[] output = new int[neurons.Length];
		for(int i = 0;i<output.Length;i++)
			output[i] = neurons[i].transferHard(input);

		return output;
	}

	/**
     * функция распознавания символа, используется для конечного ответа
     * @param input -  входной вектор
     * @return массив из вероятностей, ответы нейронов
     */
	int[] handle(int[,] input)
	{
		int[] output = new int[neurons.Length];
		for(int i = 0;i<output.Length;i++)
			output[i] = neurons[i].transfer(input);
		
		return output;
	}

	/**
     * ответ сети
     * @param input - входной вектор
     * @return индекс нейронов предназначенный для конкретного символа
     */
	public int getAnswer(int[,] input)
	{
		int[] output = handle(input);
		int maxIndex = 0;
		for(int i = 1; i < output.Length;i++)
			if(output[i] > output[maxIndex])
				maxIndex = i;

		return maxIndex;
	}

	/**
     * функция обучения
     * @param input - входной вектор
     * @param correctAnswer - правильный ответ
     */
	public void study(int[,] input,int correctAnswer)
	{
		int[] correctOutput = new int[neurons.Length];
		correctOutput[correctAnswer] = 1;

		int[] output = handleHard(input);
		while(!compareArrays(correctOutput,output))
		{
			for(int i = 0; i < neurons.Length;i++)
			{
				int dif = correctOutput[i]-output[i];
				neurons[i].changeWeights(input,dif);
			}
			output = handleHard(input);
		}
	}

	/**
     * сравнение двух вектор
     * @param true - если массивы одинаковые, false - если нет
     */
	bool compareArrays(int[] a,int[] b)
	{
		if(a.Length != b.Length)
			return false;

		for(int i = 0;i<a.Length;i++)
			if(a[i] != b[i])
				return false;

		return true;
	}
	
	void prepareForSerialization()
	{
		foreach(Neuron n in neurons)
			n.prepareForSerialization();
	}

	void onDeserialize()
	{
		foreach(Neuron n in neurons)
			n.onDeserialize();
	}

	public void saveLocal()
	{
		prepareForSerialization();

		XmlSerializer serializer = new XmlSerializer(this.GetType());
		FileStream stream = new FileStream(Application.dataPath + "/NeuralNetwork.txt", FileMode.Create);
		XmlWriter writer = new XmlTextWriter(stream, new System.Text.ASCIIEncoding());
		using(writer)
		{
			serializer.Serialize(writer, this);
		}
	}

	public static NeuralNetwork fromXml()
	{
		string xml = "";
		FileStream fStream = new FileStream(Application.dataPath + "/NeuralNetwork.txt",
		                                    FileMode.OpenOrCreate);
		if(fStream.Length > 0)
		{
			byte[] tempData = new byte[fStream.Length];
			fStream.Read(tempData, 0, tempData.Length);
			
			xml = System.Text.Encoding.ASCII.GetString(tempData);
		}
		fStream.Close();

		if(string.IsNullOrEmpty(xml))
			return new NeuralNetwork();


		NeuralNetwork data;

		XmlSerializer serializer = new XmlSerializer(typeof(NeuralNetwork));
		using(TextReader reader = new StringReader(xml))
		{
			data = serializer.Deserialize(reader) as NeuralNetwork;
		}

		data.onDeserialize();
		
		return data;
	}
}



Класс NeuralNetwork содержит массив нейронов, каждый из них предназначен для конкретного символа. В конструкторе создается массив из десяти элементов, потому что пример сделан для распознавания цифр(0-9). Если вы хотите использовать сеть для распознавания букв то поменяйте размер массива соответствующим образом. Функция handleHard вызывается для обучение нейронов, возвращает массив из нулей и единиц. Функция getAnswer ответ сети для входного вектора, использует функцию handle для получения массива ответов, каждый элемент массива содержит ответ нейрона с вероятностью. Функция getAnswer выбирает индекс элемента который содержит наибольшую вероятность и возвращает эго как ответ.

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

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

Заключение

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

Хотелось увидеть различные отзывы на тему и на пост.
Поделиться с друзьями
-->

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


  1. ZOXEXIVO
    24.08.2016 19:08

    Вы определитесь, все-таки: Pascal или Camel case вы используете


    1. Ununtrium
      26.08.2016 14:01

      В C# для методов используется только PascalCase — https://msdn.microsoft.com/en-us/library/ms229043(v=vs.110).aspx.


    1. hakandr
      26.08.2016 14:24

      Исправил кое какие переменные. Просто до этого писал на Java поэтому использовал Camel case.


  1. Stawros
    24.08.2016 19:32
    +2

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

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


    1. hakandr
      24.08.2016 22:54

      Уже понятно, Спасибо.


  1. QtRoS
    24.08.2016 21:38
    +2

    Это такая тонкая шутка? -_-

    наглядная реализацыя с исползованием ООП

    Похоже, что нет, но ведь spellcheck сейчас даже в браузере есть…


    1. hakandr
      24.08.2016 22:53

      спасибо за замечание, исправил.


  1. Valle
    25.08.2016 04:27
    +1

    И какой error rate на MNIST получается?


  1. ctac2010
    25.08.2016 16:43

    Ссылка пример на java и полезные ссылки говорит, что доступ закрыт.


  1. Splo1ter
    25.08.2016 16:43

    Почему в C# Java?


  1. Sinatr
    25.08.2016 16:44

    Перед тем, как показывать код кому-то, следовало бы немножко подготовиться (в частности прочитать вот это). Если вы пишете о чем-то специфическом, то может быть не следует отвлекать читателя, к примеру, деталями реализации чего-то отвлеченного (в частности XML сериализации). Стиль комментариев тоже «необычный», к тому же присутствует полное отсутствие комментариев в коде.

    Извините за придирки, но вы сами попросили отзывов, а меня лично такой стиль исходников немного раздражает (возможно потому, что в моей команде из 4 программистов два пришли из С и продолжают забивать болт на C# стиль и коментарии, уже накипело).


  1. Idot
    25.08.2016 17:40

    Хотелось бы увидеть пример распознавание символов с помощью Радиально Базисной функции

    Мне бы хотелось пример простенького AI для простенькой игры на нейросетях. Например, хотя бы игру в крестики-нолики.


  1. Drag13
    25.08.2016 18:03

    А можно рабочий пример? А то сериализация есть, а зачем и что непонятно.


    1. hakandr
      26.08.2016 14:29

      Просто используйте картинки с цифрами на белом фоне как входной параметр для функции study и getAnswer.