Пишем нейросеть на 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 (однослойная нейросеть с такой задачей не справляется).
Литература
- Статья в Интернет. «Теория нейронных сетей. Урок 4. Обучение нейросети. Линейный персептрон». URL: http://easyprog.ru/index.php?option=com_content&task=view&id=1491&Itemid=75/
- Коробейников А. В. Программирование нейронных сетей. Учебно-методическое пособие по дисциплинам «Методы оптимизации. Нейронные сети», «Нейрокомпьютерные системы» и «Нечеткая логика и генетические алгоритмы». – Ижевск: Изд-во ИжГТУ — 2013 – 44 с.
- Шуравин А. П. Исследование обучаемости нейронной сети на примере предсказания числовых рядов // Научные достижения и открытия 2017: сборник статей III Международного научно-практического конкурса. Пенза:Наука и Просвещение, 2017. С. 31-36.
Comments
So empty here ... leave a comment!