Top.Mail.Ru

Пишем нейросеть на Python. Часть 2

На прошлом уроке мы начали писать нейросеть на Python. Напомню что уже разработали:

·        Класс нейрона.

·        Три передаточных функции («как есть», «сигмоида» и «пороговая функция»)

·        Простой тестовый пример.

А сейчас напишем целую нейросеть, которая будет обучаться через генетический алгоритм. Но сначала немного теории. Как можно обучить нейронную сеть? В случае одного нейрона используем коррекцию весов на величину Δw[1]:

Где η —  коэффициент обучения, выбираемый эмпирическим путем, t – желаемая реакция нейрона, y  — фактическая реакция нейрона, x – вход нейрона.

У реальной нейронной сети не один нейрон. Они могут быть организованы разными способами, мы же рассмотрим один из них – многослойный персептрон.  Один из способов его обучения – метод обратного распространения ошибки [2,3].

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

где — выходное значение нейросети при предъявлении k-ого образа, — 

желаемое выходное значение, соответствующее k-ому образу.

Далее, вычисляем весовые коэффициенты выходного (второго) слоя (коррекцию проводим от выхода ко входам):

где — новое значение весового коэффициента входа i (на  итерации k+1), — старое значение весового коэффициента, η — эмпирический коэффициент, представляющий скорость обучения, — 

выход первого (входного слоя).

Однако, данный метод имеет существенный недостаток: не гарантируется глобального экстремума[2,3]. Поэтому мы реализуем методом обучения нейронной сети через генетический алгоритм, описанный в [3]:

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

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

import numpy as np
import random as rnd
import math 
from abc import ABCMeta, abstractmethod


#Сравнение двух списков
#<param name="list1">Первый список (список float)</param>
#<param name="list2">Второй список (список float)</param>
#<returns>Результат сравнения (float)</returns>
def list_compare(list1, list2):
    if len(list1) != len(list2):
        s=str(len(list1))+" "+str(len(list2))
        raise Exception("Списки должны иметь одинаковый размер "+s)
    if len(list1) == 1:
        return abs(float(list1[0]) - float(list2[0]))
    res = 0.0
    i=0
    while i<len(list1):
        dif = float(list1[i]) - float(list2[i])
        res=res+(dif*dif)
        i=i+1
    return math.sqrt(res)

#Абстрактный класс передаточной функции
class TransFunc():
    __metaclass__=ABCMeta

    @abstractmethod
    def compute(self,income):
        """Рассчитать передаточную функцию"""
    
    @abstractmethod
    def get_id(self):
        """ИД передаточной функции"""

    #level - уровнь мутации
    @abstractmethod
    def mutation(level):
        """Мутация передаточной функции"""

    @abstractmethod
    def clone(self):
        """Копия передаточной функции"""             

#Передаточная функция "Как есть"
class AsIs(TransFunc):

    def compute(self,income):
        return income

    def get_id(self):
        return 1

    def clone(self):
        return AsIs()

    def mutation(level):
        return        
       
assert issubclass(AsIs, TransFunc)
assert isinstance(AsIs(), TransFunc)


#Передаточная функция "Сигмоида"
class SignFunc(TransFunc):
    def __init__(self):
        self.par=1
        
    def compute(self,income):
        return 1/(1+np.exp(-self.par*income))

    def get_id(self):
        return 2
    
    def clone(self):
        trans=SignFunc()
        trans.par=self.par
        return trans

    def mutation(level):
        self.par = self.par + rnd.random() * level - level / 2.0 

    
assert issubclass(SignFunc, TransFunc)
assert isinstance(SignFunc(), TransFunc)

#Передаточная функция "Пороговая"
class ThresholdFunc(TransFunc):
    def compute(self,income):
        if income>0:
            return 1
        else:
            return 0

    def get_id(self):
        return 3

    def clone(self):
        return ThresholdFunc()

    def mutation(level):  
        return
    
assert issubclass(ThresholdFunc, TransFunc)
assert isinstance(ThresholdFunc(), TransFunc)

#Класс нейрона
class Neuron:
    def __init__(self, count, trans):
        self.inputs=[0]*count 
        self.weights=[rnd.random()-0.5 for i in range(count+1)]
        self.trans=trans
        self.output=0

    #Рассчитать нейрон
    def compute(self):
        res=0
        i=1
        count=len(self.weights)
        while i<count:
            res = res+(self.weights[i] * self.inputs[i-1])
            i=i+1
        res=res+self.weights[0]
        self.output = self.trans.compute(res)
        
    #Создать копию нейрона
    def clone(self):
        res = Neuron(len(self.weights)-1,self.trans.clone())
        i=0
        count=len(self.weights)
        while i<count:
            res.weights[i] = self.weights[i]
            i=i+1
        return res;

    def mutation(self,level):
        i=0
        count=len(self.weights)
        while i<count:
            self.weights[i] = self.weights[i] + rnd.random() * level - level / 2.0
            i=i+1
        
#Класс слоя
class Layer:
    #Конструктор
    #count - количество нейронов
    #inputs_count - количество входов в каждом нейроне
    #trans - передаточная функция
    def __init__(self, count, inputs_count, trans=""):
        self.inputs_count=inputs_count
        self.outputs=[]
        if count>0 and trans!="":
            self.neurons=[Neuron(inputs_count, trans.clone())]*count
        else:
            self.neurons=[]

    #Создать копию слоя
    def clone(self):
        i=0
        count=len(self.neurons)
        res = Layer(0,self.inputs_count)
        while i<count:
            res.neurons.append(self.neurons[i].clone())
            i=i+1
        res.count=count
        return res

    #Рассчитать слой
    def compute(self):
        res=0
        i=0
        count=len(self.neurons)
        self.outputs=[]
        while i<count:
            self.neurons[i].compute()
            self.outputs.append(self.neurons[i].output)
            i=i+1

    #Установить входные параметры
    def set_incomes(self, inputs):
        count=len(self.neurons)
        count_inputs=self.inputs_count
        i=0
        while i<count:
            j=0
            while j<count_inputs:
                self.neurons[i].inputs[j]=inputs[j]
                j=j+1
            i=i+1

    def mutation(self,level):
        i=0
        count=len(self.neurons)
        while i<count:
            self.neurons[i].mutation(level)
            i=i+1            

#Класс нейросети
class NeuralNet:
    #Конструктор
    #input_count - количество входов
    #output_count - количество выходов
    def __init__(self,input_count,output_count):
        self.target_function="Не расчитана"
        self.layers=[]
        if input_count>0:
            self.inputs=[0]*input_count
        else:
            self.inputs=[]
        if output_count>0:
            self.outputs=[0]*output_count
        else:
            self.outputs=[]

    #Установить входные параметры
    #<param name="a_incomes">Входные данные</param>
    def set_incomes(self, a_incomes):
        self.inputs=a_incomes

    #Создать копию нейросети
    def clone(self):
        i=0
        count=len(self.layers)
        res = NeuralNet(len(self.inputs), len(self.outputs))
        while i<count:
            res.layers.append(self.layers[i].clone())
            i=i+1
        return res

    #Рассчитать нейросеть
    def compute(self):
        res=0
        i=0
        count=len(self.layers)
        self.layers[0].set_incomes(self.inputs)
        while i<count:
            self.layers[i].compute()
            if i<count-1:
                self.layers[i+1].set_incomes(self.layers[i].outputs)
            i=i+1

        i=0
        count_outputs=len(self.layers[count-1].outputs)
        while i<count_outputs:
            self.outputs[i]=self.layers[count-1].outputs[i]
            i=i+1

    def mutation(self,level):
        i=0
        count=len(self.layers)
        while i<count:
            self.layers[i].mutation(level)
            i=i+1

    #Получить конфигурацию слоев
    def get_layers_conf(self):
        i=0
        res=[]
        count=len(self.layers)
        while i<count:
            res.append(len(self.layers[i].neurons))
            i=i+1
        return res

    #/// Создать слой по значению параметра сигмоидальной функции
    #/// <param name="count">количество нейронов</param>
    #/// <param name="inputs_count">Количество входов</param>
    #/// <param name="param">Значение параметра сигмоидальной функции</param>
    def create_layer(self,count, inputs_count, param):
        sg=SignFunc()
        sg.par=param
        layer = Layer(count, inputs_count, sg)
        self.layers.append(layer)

    #Печатать нейрость
    def print(self):
        print("------")
        count=len(self.layers)
        i=0
        while i<count:
            ncount=len(self.layers[i].neurons)
            print("Слой ",i," Нейронов: ",)
            j=0
            while j<ncount:
                neuron=self.layers[i].neurons[j]
                print("  Нейрон ",j," входов ",len(neuron.inputs)," весов ",len(neuron.weights)," trans ", neuron.trans.get_id())
                icount=len(neuron.inputs)
                k=0
                while k<icount:
                    print("    вход ",k,"=",neuron.inputs[k])
                    k=k+1
                j=j+1
            i=i+1    
        print("------")

#Класс обучающей выборки
class StudyMatrixItem:

    #Конструктор
    #a_incomes - входы
    #a_outcomes - выходы
    def __init__(self,a_incomes, a_outcomes):
        self.incomes=a_incomes #входы
        self.outcomes=a_outcomes #выходы
            
#Класс генетического алгоритма
class GeneticAlgorith:

    #Конструктор
    def __init__(self):
    
        #Минимальное количество особей в популяции (если окажется меньше этого числа то селекцию не делаем)
        self.min_count = 5

        #Максимальное количество особей в популяции (если окажется больше этого числа то обрезаем до этого числа)
        self.max_count = 30

        #Начальное количество мест в популяции
        self.count=10

        #Разрешить мутацию замены передаточной функции
        self.allow_change_trans_function=True

        self.p_mutation=0.1 #Вероятность мутации
        self.population=[] #популяция
        self.selection=[] #Обучающая выборка

        self.level=0.5 #Уровень мутации

    #Инициализировать из нейросети
    #net - нейросеть
    #count_source_mutation - Количество мутаций исходной нейросети в начальной выборкe
    def init_population_from_net(self, net, count_source_mutation):        
        count_mut = count_source_mutation;
        i=1
        while i<=self.count:
            lnet = None
            if count_mut>0:
                if count_mut == count_source_mutation:
                    lnet = net
                else:
                    lnet = net.clone()
                    lnet.mutation(self.level)
                count_mut=count_mut-1
            else:
                lnet = NeuralNet(len(net.inputs), len(net.outputs))
                size = len(net.inputs)
                conf = net.get_layers_conf()
                for item in conf:
                    lnet.create_layer(item, size, 1)
                    size = item
            self.population.append(lnet);
            i=i+1
        self.sorting()


    #/// Размножение
    def reproduction(self):
        self.count=len(self.population)
        i=0
        while i<self.count:
            j=i+1
            while j<self.count:
                p=self.cross_probability(i,j,self.count)
                if rnd.random()<p:        
                    self.population.append(self.cross(self.population[i],self.population[j]))
                j=j+1
            i=i+1

    #/// Вычислить вероятность скрещивания
    #/// <param name="net1">Первая нейросеть (номер)</param>
    #/// <param name="net2">Вторая нейросеть (номер)</param>
    #/// <param name="count">Количество "особей" в популяции</param>
    #/// <returns>Вероятность скрещивания</returns>
    def cross_probability(self,net1, net2, count):
        return (2.0*float(count)-float(net1)-float(net2)) / (2.0*float(count)-1.0)
            
    #/// Скрещивание нейронных сетей
    #/// <param name="net1">Первая нейросеть</param>
    #/// <param name="net2">Вторая нейросеть</param>
    #/// <returns>Результат скрещивания</returns>
    def cross(self,net1, net2):
        res = NeuralNet(len(net1.inputs), len(net1.outputs))
        i=0
        while i<len(net1.layers):
            layer1=net1.layers[i]
            layer2=net2.layers[i]
            l_layer = Layer(count=0,inputs_count=layer1.inputs_count)
            j=0
            count = rnd.randint(0,len(layer1.neurons)-1)  
            while j<=count:
                l_layer.neurons.append(layer1.neurons[j].clone())
                j=j+1
            j = count + 1
            while j<len(layer1.neurons):
                l_layer.neurons.append(layer2.neurons[j].clone())
                j=j+1
            res.layers.append(l_layer);
            i=i+1
        if rnd.random()<self.p_mutation:
            res.mutation(self.level)
        return res;
      

    #Осуществить селекцию
    def selecticting(self):
        if len(self.population)<self.min_count:
            return
        items_for_removed=[]

        #Начинаем со второго элемента (индекс 1), так как первый (индекс 0) должен выжить в любом случае)
        while len(self.population)>self.max_count:
            i = 1
            _count=float(len(self.population))
            while i < len(self.population):
                if rnd.random()<float(i)/_count:
                    items_for_removed.append(self.population[i])
                i=i+1

            #удаляем выбранные для удаления
            for item in items_for_removed:
                self.population.remove(item);

            #очистим список, чтобы не удалять второй раз
            items_for_removed.clear()

    #Сортировка по передаточной функции
    @staticmethod
    def sort_by_target_function(net):
        if net.target_function=="Не расcчитана":
            return 99999999999999
        else:
            return net.target_function

    #Установить входные данные
    #<param name="net">Нейросеть (GANeuralNet)</param>
    #<param name="item">Входные данные (StudyMatrixItem)</param>
    def set_incomes(self, net, item):
        net.set_incomes(item.incomes)

    #Вычислить целевую функцию   
    def calk_target_function(self,net):
        Esumm = 0.0
        i = 0
        count=float(len(self.selection))
        while i < len(self.selection):
            item = self.selection[i]
            res = item.outcomes
            self.set_incomes(net,item)
            net.compute()
            Esumm = Esumm+list_compare(res, net.outputs)
            i=i+1
        net.target_function = Esumm/count;
              
    #Сортировка популяции
    def sorting(self):
                    
        #сначала надо вычислить целевую функцию
        i = 1
        while i < len(self.population):
            self.calk_target_function(self.population[i])
            i=i+1

        #сортируем
        self.population.sort(key=self.sort_by_target_function)

        self.best_net = self.population[0];                  

    #Следующая эпоха
    def next_age(self):
        self.prev_spec = self.population[0]
        self.reproduction()
        self.sorting()
        self.selecticting()

А вот тестовый пример:

import neuro_library as nl
import matplotlib.pyplot as plt

#Создадим генетический алгоритм с обучающей выборкой
ga=nl.GeneticAlgorith()
ga.selection.append(nl.StudyMatrixItem([0,0],[0]))
ga.selection.append(nl.StudyMatrixItem([0,1],[1]))
ga.selection.append(nl.StudyMatrixItem([1,0],[1]))
ga.selection.append(nl.StudyMatrixItem([1,1],[0]))


#Создаем нейросеть
net=nl.NeuralNet(2,1)
net.create_layer(2,2,100)
net.create_layer(1,2,100)


#Выведем результат теста
for item in ga.selection:
    net.set_incomes(item.incomes)
    net.compute()
    print("Вход: ",item.incomes,"; выход желаемый: ",item.outcomes," выход реальный: ", net.outputs)

#Инициализиурем генетический алгоритм
ga.init_population_from_net(net,3)

print("-------------")

#задаем начальные массивы
x=[]
y=[]

#Проводим обучение
i=1
k=1
x.append(0)
y.append(ga.population[0].target_function) 
while i<=150:
    ga.next_age()
    x.append(i)
    y.append(ga.population[0].target_function)
    i=i+1
    k=k+1

#Выведем результат теста
print("После оптимизации")
net=ga.population[0]
for item in ga.selection:
    net.set_incomes(item.incomes)
    net.compute()
    print("Вход: ",item.incomes,"; выход желаемый: ",item.outcomes," выход реальный: ", net.outputs)
        

#Строим график
fig = plt.figure()
plt.plot(x, y)
     
#Отображаем заголовки и подписи осей
plt.title('График функции')
plt.ylabel('Ось Y')
plt.xlabel('Ось X')
plt.grid(True)
plt.show()

    

Результат работы данной программы будет примерно следующее.

Вот такой текстовый вывод:

И график изменения целевой функции по итерациям:

 

Как видим, данная нейросеть, благодаря многословности, справилась с функцией XOR (однослойная нейросеть с такой задачей не справляется).

 

Литература

  1. Статья в Интернет. «Теория нейронных сетей. Урок 4. Обучение нейросети. Линейный персептрон». URL: http://easyprog.ru/index.php?option=com_content&task=view&id=1491&Itemid=75/
  2. Коробейников А. В. Программирование нейронных сетей. Учебно-методическое пособие по дисциплинам «Методы оптимизации. Нейронные сети», «Нейрокомпьютерные системы» и «Нечеткая логика и генетические алгоритмы». – Ижевск: Изд-во ИжГТУ — 2013 – 44 с.
  3. Шуравин А. П. Исследование обучаемости нейронной сети на примере предсказания числовых рядов // Научные достижения и открытия 2017: сборник статей III Международного научно-практического конкурса. Пенза:Наука и Просвещение, 2017. С. 31-36.

 

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar