import matplotlib matplotlib.use('Agg') import datetime from keras import backend from keras.models import Sequential from keras.layers.core import Dense, Activation from keras.optimizers import Adadelta from keras.models import load_model from keras import utils import copy import numpy as np import collections from operator import add try: from src.Functions import Functions from src.PltData import PltData from src.FeatureReduction import FeatureReduction from src.LossHistory import LossHistory except ImportError: import Functions from PltData import PltData from FeatureReduction import FeatureReduction from LossHistory import LossHistory class NeuralNetwork: def __init__(self, numberOfNeurons, activationFunctions, featureReduction, numberLoops, loss='mean_squared_error', printVectors=False, path="../images/", fitByHillClimber=False, standardDeviation = 0.01, numberOtRandomShots=20, checkNewWeightsIsReallyBetter=False): ''' :param numberOfNeurons: Array mit Integers. Gibt die Anzahl der Neuronen der einzelnen Schichten an :param activationFunctions: Array mit Strings für die Aktivierungsfunktionen der einzelnen Schichten :param featureReduction: String der die Funktion zur Feature Reduzierung angibt :param numberLoops: die Anzahl der Schleifendruchläufe :param loss: Fehlerfunktion :param printVectors: Boolean gibt an ob die Gewichte und der dazugehörige die die feature reduction funktion transformierte Vektor ausgeben werden soll :param path: der Pfad zu den Ergebnissen ''' backend.clear_session() self.model = Sequential() self.optimzier = Adadelta() self.epochs = 1 self.fitByHillClimber = fitByHillClimber self.checkNewWeightsIsReallyBetter=checkNewWeightsIsReallyBetter self.numberOtRandomShots = numberOtRandomShots self.standardDeviation = standardDeviation self.numberOfNeurons = numberOfNeurons self.activationFunctions = activationFunctions self.featureReduction = featureReduction self.featureReductionFunction = FeatureReduction(self.featureReduction) self.numberLoops = numberLoops self.loss = loss self.addedLayers = "No_Layers_added" self.result = [] self.fileForVectors = None self.printVectors = printVectors self.path = path self.beginGrowing = 0 self.stopGrowing = 0 self.LM = 0 self.dataHistory = [] self.minFailure = 100 self.minFailureLoop = None def addLayers(self): i = 2 self.addedLayers = "inputDim_" + str(self.numberOfNeurons[0]) + "_" + str(self.activationFunctions[0]) + \ "_" + str(self.numberOfNeurons[1]) self.model.add(Dense(self.numberOfNeurons[1], kernel_initializer="uniform", input_dim=self.numberOfNeurons[0], activation=self.activationFunctions[0])) while i < len(self.numberOfNeurons): self.addLayer(self.activationFunctions[i-1], self.numberOfNeurons[i]) i+= 1 def addLayer(self, activationFunction, numberOfNeurons): self.model.add(Dense(numberOfNeurons, kernel_initializer="uniform")) self.model.add((Activation(activationFunction))) self.addedLayers += "_" + activationFunction+"_"+ str(numberOfNeurons) def fitByStochasticHillClimberV3(self, inputD, outputD, callbacks=None): ''' Diese Version des stochastischen Hill Climbers, überorüft den Fehler nur Anhand der neuen Gewichte. :param inputD: :param outputD: :param callbacks: :return: ''' weights = self.model.get_weights() aktWeights = self.model.get_weights() memDict = {} i = 0 if callbacks != None: for f in callbacks: f.on_train_begin() while i <= self.numberOtRandomShots: i+= 1 loss = Functions.calcMeanSquaredError(self.model.predict(inputD, batch_size=1), outputD) if i == 1: if callbacks != None: for f in callbacks: f.addLoss(loss) memDict[loss] = weights weights = self.joinWeights(self.getRandomWeights(),weights) self.model.set_weights(weights) inputD = np.array([self.featureReductionFunction.calc(self.model.get_weights(), self.numberOfNeurons[0])]) outputD = inputD od = collections.OrderedDict(sorted(memDict.items())) od = list(od.items()) self.model.set_weights(od[0][1]) return def fitByStochasticHillClimber(self, inputD, outputD, callbacks=None): ''' Die ersten beiden Versionen des Hill Climber. V1 wird ausgeführt wenn self.checkNewWeightsIsReallyBetter nicht True ist. In diesem Fall wird nur gegen die alten Gewichte und dessen Repräsentation geprüft. V2 wird ausgeführt wenn self.checkNewWeightsIsReallyBetter True ist. In diesem Fall wird ein zweiter Check auf die neuen Gewichte ausgeführt. Nur wenn beide ein besseres Ergebnis liefern, werden die neuen Gewichte übernommen. Die bessere Variante ist fitByStochasticHillClimberV3 - in der nur gegen die neuen Gewichte geprüft wird. :param inputD: :param outputD: :param callbacks: :return: ''' weights = self.model.get_weights() aktWeights = self.model.get_weights() memDict = {} i = 0 if callbacks != None: for f in callbacks: f.on_train_begin() while i <= self.numberOtRandomShots: i+= 1 loss = Functions.calcMeanSquaredError(self.model.predict(inputD), outputD) if i == 1: if callbacks != None: for f in callbacks: f.addLoss(loss) memDict[loss] = weights weights = self.joinWeights(self.getRandomWeights(),weights) self.model.set_weights(weights) od = collections.OrderedDict(sorted(memDict.items())) od = list(od.items()) if self.checkNewWeightsIsReallyBetter: self.model.set_weights(od[0][1]) iData = np.array([self.featureReductionFunction.calc(self.model.get_weights(), self.numberOfNeurons[0])]) errorWithNewWeights = Functions.calcMeanSquaredError(self.model.predict(iData, batch_size=1), iData) self.model.set_weights(aktWeights) errorWithOldWeights = Functions.calcMeanSquaredError(self.model.predict(iData, batch_size=1), iData) if errorWithNewWeights 2500: break if searchForThreshold: if self.checkGrowing(self.result, 100): return self.result[0], True if i > 1000: return self.result[0], False if checkLM: if len(self.result) > 1000: dd = np.sum(np.array(self.result[-1000:])) if dd == 0.: # Wenn die Summe der letzten 1000 Fehler echt null ist - muss ein Fixpunkt gefunden worden sein self.beginGrowing = 0 break if self.checkGrowing(self.result, 10) and self.beginGrowing == 0: # Fehler steigt wieder self.beginGrowing = i if stepWise: self.numberLoops = i self.printEvaluation() if self.beginGrowing > 0: ''' if len(self.result) > 1000: if (i > 10000 and self.checkGrowing(self.result, 100)): if stepWise: self.numberLoops = i self.printEvaluation() #print("BREAK 1", round(dd,6), dd) break ''' if not self.checkGrowing(self.result, 10, checkSame=False) and i-self.beginGrowing>500 and not iamHere: # In einigen Fällen ist der Wachstum sehr langsam, deswegen checkSame = False, # nach beginGrowing kommt es manchmal vor, dass der Wachstum kurz aussetzt, deswegen # müssen sollten zwischen beginGrowing und endGrowing 500 Schritte liegen self.stopGrowing = i self.LM =self.result[len(self.result) - 1] if stepWise: self.numberLoops = i self.printEvaluation() iamHere = True else: break pl = PltData(np.array(self.result)) pl.linePlot(self.getFileName(i), width=1600, text=self.getDescription()) def printEvaluation(self): start = -100000 stop = 100000 step = 1 data = np.arange(start, stop, step) self.evaluate(data, str(start) + "_" + str(stop) + "_" + str(step), text=self.getDescription() + "\nStart: " + str(start) + "\nStop " + str(stop) + "\nStep " + str(step)) def checkGrowing(self, mArray, range, checkSame=True): if len(mArray) < range*2: return False values = np.array(mArray[-1*range*2:]) values = values.reshape(-1, int(len(values)/2)) if np.sum(values[0]) == np.sum(values[1]) and checkSame: return False if np.sum(values[0]) > np.sum(values[1]): return False else: return True def evaluate(self, inputData, filename, text=""): pD = self.model.predict(inputData, batch_size=1) pD = np.reshape(pD,(1,len(pD)))[0] pl = PltData(pD) pl.linePlot(self.path + self.getFileName() + "_" + filename, width=1024, text=text, xLegend="X", yLegend="Y", x=inputData, yTextPos=-0.0015, xTextPos=-10000) def loadModel(self): file = "../nnModels/" + self.getFileName() + ".h5" if not Functions.checkFileExists(file): print("no model found in " + str(file)) return self.model = load_model(file) print("Model loaded from " + str(file)) def saveModel(self): self.model.save("../nnModels/" + str(self.getFileName()) + ".h5") def printVec(self, loop, weight, input): if self.fileForVectors == None: self.fileForVectors = open(self.path + self.getFileName() + ".txt", "w") self.fileForVectors.write("numberOfLoop \t weights \t " +self.featureReduction + " result\n") self.fileForVectors.write(str(loop) + " \t " + repr(weight) +" \t " + repr(input) + " \n") def getFileName(self, numberLoops=None): if numberLoops is None: numberLoops = self.numberLoops fileName ="nOL_" + str(len(self.activationFunctions)) + \ self.addedLayers + "_nLoops_" + str(numberLoops) + "_fR_" +self.featureReduction if self.fitByHillClimber: fileName += "_standardDeviation_" +str(self.standardDeviation) +\ "_numberOtRandomShots_"+str(self.numberOtRandomShots) if self.checkNewWeightsIsReallyBetter: fileName += "_checkNewWeightsIsReallyBetter" return fileName def getDescription(self): text = "Loops: " + str(self.numberLoops) + \ "\nLayers:" + self.getLayerText() +\ "\nOptimizer: Adadelta" + \ "\nFeature Reduction: " + self.featureReduction if self.fitByHillClimber: text += "\nStandard Deviation: " +str(self.standardDeviation) +\ "\nNumber Of Random Shots: " + str(self.numberOtRandomShots) +\ "\nMinimaler Fehler: " +str(self.minFailure) +" bei Loop: " + str(self.minFailureLoop)+\ "\ncheckNewWeightsIsReallyBetter:" + str(self.checkNewWeightsIsReallyBetter) return text def getLayerText(self): text = "" i = 0 for l in self.activationFunctions: text += "\n" + str(i+1) + " Layer " + l + " " + str(self.numberOfNeurons[i+1]) + " Neuronen" i += 1 return text