C# - Класс нейронной сети ClassLibraryNeuralNetworks.cs

Автор (dr.kernel - Nosov Alexander)
///////////////////////////////////
///////////////////////////////////
/// Created by dr.kernel ///
/// kernel-zone.ru  ///
/// Nosov Alexander  ///
///////////////////////////////////
///////////////////////////////////
 
using System;
using System.IO;
using System.Runtime.InteropServices;
 
 
namespace ClassLibraryNeuralNetworks
{
 
    // Структура дря разбиения переменных типа int и double на байты
    [StructLayout(LayoutKind.Explicit)]
    internal class DataToByte
    {
        [FieldOffset(0)]
        public double vDouble;
 
        [FieldOffset(0)]
        public int vInt;
 
        [FieldOffset(0)]
        public byte b1;
        [FieldOffset(1)]
        public byte b2;
        [FieldOffset(2)]
        public byte b3;
        [FieldOffset(3)]
        public byte b4;
        [FieldOffset(4)]
        public byte b5;
        [FieldOffset(5)]
        public byte b6;
        [FieldOffset(6)]
        public byte b7;
        [FieldOffset(7)]
        public byte b8;
    }
 
    // Класс - слой нейросети
    public class LayerNW
    {
        double[,] Weights;
        int cX, cY;
 
        // Заполняем веса случайными числами
        public void GenerateWeights()
        {
            Random rnd = new Random();
            for (int i = 0; i < cX; i++)
            {
                for (int j = 0; j < cY; j++)
                {
                    Weights[i, j] = rnd.NextDouble() - 0.5;
                }
            }
        }
 
        // Выделяет память под веса
        protected void GiveMemory()
        {
            Weights = new double[cX, cY];
        }
 
        // Конструктор с параметрами. передается количество входных и выходных нейронов
        public LayerNW(int countX, int countY)
        {
            cX = countX;
            cY = countY;
            GiveMemory();
        }
 
        public int countX
        {
            get { return cX; }
        }
 
        public int countY
        {
            get { return cY; }
        }
 
        public double this[int row, int col]
        {
            get { return Weights[row, col]; }
            set { Weights[row, col] = value; }
        }
 
    }
 
    // Класс - нейронная сеть
    public class NeuralNW
    {
        LayerNW[] Layers;
        int countLayers = 0, countX, countY;
        double[][] NETOUT;  // NETOUT[countLayers + 1][]
        double[][] DELTA;   // NETOUT[countLayers    ][]
 
        // Конструкторы
        /* Создает полносвязанную сеть из 1 слоя. 
           sizeX - размерность вектора входных параметров
           sizeY - размерность вектора выходных параметров */
        public NeuralNW(int sizeX, int sizeY)
        {
            countLayers = 1;
            Layers = new LayerNW[countLayers];
            Layers[0] = new LayerNW(sizeX, sizeY);
            Layers[0].GenerateWeights();
        }
 
        /* Создает полносвязанную сеть из n слоев. 
           sizeX - размерность вектора входных параметров
           layers - массив слоев. Значение элементов массива - количество нейронов в слое               
         */
        public NeuralNW(int sizeX, params int[] layers)
        {
            countLayers = layers.Length;
            countX = sizeX;
            countY = layers[layers.Length - 1];
            // Размерность выходов нейронов и Дельты
            NETOUT = new double[countLayers + 1][];
            NETOUT[0] = new double[sizeX];
            DELTA = new double[countLayers][];
 
            this.Layers = new LayerNW[countLayers];
 
            int countY1, countX1 = sizeX;
            // Устанавливаем размерность слоям и заполняем слоя случайнымичислами
            for (int i = 0; i < countLayers; i++)
            {
                countY1 = layers[i];
 
                NETOUT[i + 1] = new double[countY1];
                DELTA[i] = new double[countY1];
 
                this.Layers[i] = new LayerNW(countX1, countY1);
                this.Layers[i].GenerateWeights();
                countX1 = countY1;
            }
        }
 
        // Открывает НС
        public NeuralNW(String FileName)
        {
            OpenNW(FileName);
        }
 
        // Открывает НС
        public void OpenNW(String FileName)
        {
            byte[] binNW = File.ReadAllBytes(FileName);
 
            int k = 0;
            // Извлекаем количество слоев из массива
            countLayers = ReadFromArrayInt(binNW, ref k);
            Layers = new LayerNW[countLayers];
 
            // Извлекаем размерность слоев
            int CountY1=0, CountX1 = ReadFromArrayInt(binNW, ref k);
            // Размерность входа
            countX = CountX1;
            // Выделяемпамять под выходы нейронов и дельта
            NETOUT = new double[countLayers + 1][];
            NETOUT[0] = new double[CountX1];
            DELTA = new double[countLayers][];
 
            for (int i = 0; i < countLayers; i++)
            {
                CountY1 = ReadFromArrayInt(binNW, ref k);
                Layers[i] = new LayerNW(CountX1, CountY1);
                CountX1 = CountY1;
 
                // Выделяем память
                NETOUT[i + 1] = new double[CountY1];
                DELTA[i] = new double[CountY1];
            }
            // Размерность выхода
            countY = CountY1;
            // Извлекаем и записываем сами веса
            for (int r = 0; r < countLayers; r++)
                for (int p = 0; p < Layers[r].countX; p++)
                    for (int q = 0; q < Layers[r].countY; q++)
                    {
                        Layers[r][p, q] = ReadFromArrayDouble(binNW, ref k);
                    }
        }
 
        // Сохраняет НС
        public void SaveNW(String FileName)
        {
            // размер сети в байтах
            int sizeNW = GetSizeNW();
            byte[] binNW = new byte[sizeNW];
 
            int k = 0;
            // Записываем размерности слоев в массив байтов
            WriteInArray(binNW, ref k, countLayers);
            if (countLayers <= 0)
                return;
 
            WriteInArray(binNW, ref k, Layers[0].countX);
            for (int i = 0; i < countLayers; i++)
                WriteInArray(binNW, ref k, Layers[i].countY);
 
            // Зпаисвыаем сами веса
            for (int r = 0; r < countLayers; r++)
                for (int p = 0; p < Layers[r].countX; p++)
                    for (int q = 0; q < Layers[r].countY; q++)
                    {
                        WriteInArray(binNW, ref k, Layers[r][p, q]);
                    }
 
 
            File.WriteAllBytes(FileName, binNW);
        }
 
        // Возвращает значение j-го слоя НС
        public void NetOUT(double[] inX, out double[] outY, int jLayer)
        {
            GetOUT(inX, jLayer);
            int N = NETOUT[jLayer].Length;
 
            outY = new double[N];
 
            for (int i = 0; i < N; i++)
            {
                outY[i] = NETOUT[jLayer][i];
            }
 
        }
 
        // Возвращает значение НС
        public void NetOUT(double[] inX, out double[] outY)
        {
            int j = countLayers;
            NetOUT(inX, out outY, j);
        }
 
        // Возвращает ошибку (метод наименьших квадратов)
        public double CalcError(double[] X, double[] Y)
        {
            double kErr = 0;
            for (int i = 0; i < Y.Length; i++)
            {
                kErr += Math.Pow(Y[i] - NETOUT[countLayers][i], 2);
            }
 
            return 0.5 * kErr;
        }
 
        /* Обучает сеть, изменяя ее весовые коэффициэнты. 
           X, Y - обучающая пара. kLern - скорость обучаемости
           В качестве результата метод возвращает ошибку 0.5(Y-outY)^2 */
        public double LernNW(double[] X, double[] Y, double kLern)
        {
            double O;  // Вход нейрона
            double s;
 
            // Вычисляем выход сети
            GetOUT(X);
 
            // Заполняем дельта последнего слоя
            for (int j = 0; j < Layers[countLayers - 1].countY; j++)
            {
                O = NETOUT[countLayers][j];
                DELTA[countLayers - 1][j] = (Y[j] - O) * O * (1 - O);
            }
 
            // Перебираем все слои начиная споследнего 
            // изменяя веса и вычисляя дельта для скрытого слоя
            for (int k = countLayers - 1; k >= 0; k--)
            {
                // Изменяем веса выходного слоя
                for (int j = 0; j < Layers[k].countY; j++)
                {
                    for (int i = 0; i < Layers[k].countX; i++)
                    {
                        Layers[k][i, j] += kLern * DELTA[k][j] * NETOUT[k][i];
                    }
                }
                if (k > 0)
                {
 
                    // Вычисляем дельта слоя к-1
                    for (int j = 0; j < Layers[k - 1].countY; j++)
                    {
 
                        s = 0;
                        for (int i = 0; i < Layers[k].countY; i++)
                        {
                            s += Layers[k][j, i] * DELTA[k][i];
                        }
 
                        DELTA[k - 1][j] = NETOUT[k][j] * (1 - NETOUT[k][j]) * s;
                    }
                }
            }
 
            return CalcError(X, Y);
        }
 
        // Свойства. Возвращает число входов и выходов сети
        public int GetX
        {
            get { return countX; }
        }
 
        public int GetY
        {
            get { return countY; }
        }
 
        public int CountLayers
        {
            get { return countLayers; }
        }
        /* Вспомогательные закрытые функции */
 
        // Возвращает все значения нейронов до lastLayer слоя
        void GetOUT(double[] inX, int lastLayer)
        {
            double s;
 
            for (int j = 0; j < Layers[0].countX; j++)
                NETOUT[0][j] = inX[j];
 
            for (int i = 0; i < lastLayer; i++)
            {
                // размерность столбца проходящего через i-й слой
                for (int j = 0; j < Layers[i].countY; j++)
                {
                    s = 0;
                    for (int k = 0; k < Layers[i].countX; k++)
                    {
                        s += Layers[i][k, j] * NETOUT[i][k];
                    }
 
                    // Вычисляем значение активационной функции
                    s = 1.0 / (1 + Math.Exp(-s));
                    NETOUT[i + 1][j] = 0.998 * s + 0.001;
 
                }
            }
 
        }
 
        // Возвращает все значения нейронов всех слоев
        void GetOUT(double[] inX)
        {
            GetOUT(inX, countLayers);
        }
 
        // Возвращает размер НС в байтах
        int GetSizeNW()
        {
            int sizeNW = sizeof(int) * (countLayers + 2);
            for (int i = 0; i < countLayers; i++)
            {
                sizeNW += sizeof(double) * Layers[i].countX * Layers[i].countY;
            }
            return sizeNW;
        }
 
        // Возвращает num-й слой Нейронной сети
        public LayerNW Layer(int num)
        {
            return Layers[num]; 
        }
 
        // Разбивает переменную типа int на байты и записывает в массив
        void WriteInArray(byte[] mas, ref int pos, int value)
        {
            DataToByte DTB = new DataToByte();
            DTB.vInt = value;
            mas[pos++] = DTB.b1;
            mas[pos++] = DTB.b2;
            mas[pos++] = DTB.b3;
            mas[pos++] = DTB.b4;
        }
 
        // Разбивает переменную типа int на байты и записывает в массив
        void WriteInArray(byte[] mas, ref int pos, double value)
        {
            DataToByte DTB = new DataToByte();
            DTB.vDouble = value;
            mas[pos++] = DTB.b1;
            mas[pos++] = DTB.b2;
            mas[pos++] = DTB.b3;
            mas[pos++] = DTB.b4;
            mas[pos++] = DTB.b5;
            mas[pos++] = DTB.b6;
            mas[pos++] = DTB.b7;
            mas[pos++] = DTB.b8;
        }
 
        // Извлекает переменную типа int из 4-х байтов массива
        int ReadFromArrayInt(byte[] mas, ref int pos)
        {
            DataToByte DTB = new DataToByte();
            DTB.b1 = mas[pos++];
            DTB.b2 = mas[pos++];
            DTB.b3 = mas[pos++];
            DTB.b4 = mas[pos++];
 
            return DTB.vInt;
        }
 
        // Извлекает переменную типа double из 8-ми байтов массива
        double ReadFromArrayDouble(byte[] mas, ref int pos)
        {
            DataToByte DTB = new DataToByte();
            DTB.b1 = mas[pos++];
            DTB.b2 = mas[pos++];
            DTB.b3 = mas[pos++];
            DTB.b4 = mas[pos++];
            DTB.b5 = mas[pos++];
            DTB.b6 = mas[pos++];
            DTB.b7 = mas[pos++];
            DTB.b8 = mas[pos++];
 
            return DTB.vDouble;
        }
 
    }
}
 

Комментарии

Популярные сообщения