class Client(): def __init__(self): self.Helpers = Helpers() self.Helpers = Helpers() self.confs = self.Helpers.loadConfigs() self.LogFile = self.Helpers.setLogFile(self.confs["aiCore"]["Logs"] + "Client/") self.apiUrl = "http://" + self.confs["aiCore"]["IP"] + ":" + str( self.confs["aiCore"]["Port"]) + "/infer" self.headers = {"content-type": 'application/json'} self.Helpers.logMessage(self.LogFile, "CLIENT", "INFO", "Client Ready")
class Data(): def __init__(self): ############################################################### # # Sets up all default requirements and placeholders # needed for the NLU engine to run. # # - Helpers: Useful global functions # - Logging: Logging class # - LancasterStemmer: Word stemmer # ############################################################### self.ignore = [',', '.', '!', '?'] self.Helpers = Helpers() self.confs = self.Helpers.loadConfigs() self.LogFile = self.Helpers.setLogFile(self.confs["aiCore"]["Logs"] + "JumpWay/") self.LancasterStemmer = LancasterStemmer() def loadTrainingData(self): ############################################################### # # Loads the NLU and NER training data from Model/Data/training.json # ############################################################### with open("Model/Data/training.json") as jsonData: trainingData = json.load(jsonData) self.Helpers.logMessage(self.LogFile, "Data", "INFO", "Training Data Ready") return trainingData def loadTrainedData(self): ############################################################### # # Loads the saved training configuratuon # ############################################################### with open("Model/model.json") as jsonData: modelData = json.load(jsonData) self.Helpers.logMessage(self.LogFile, "Data", "INFO", "Model Data Ready") return modelData def sortList(self, listToSort): ############################################################### # # Sorts a list by sorting the list, and removing duplicates # # https://www.programiz.com/python-programming/methods/built-in/sorted # https://www.programiz.com/python-programming/list # https://www.programiz.com/python-programming/set # ############################################################### return sorted(list(set(listToSort))) def extract(self, data=None, splitIt=False): ############################################################### # # Extracts words from sentences, stripping out characters in # the ignore list above # # https://www.nltk.org/_modules/nltk/stem/lancaster.html # http://insightsbot.com/blog/R8fu5/bag-of-words-algorithm-in-python-introduction # ############################################################### return [ self.LancasterStemmer.stem(word) for word in (data.split() if splitIt == True else data) if word not in self.ignore ] def makeBagOfWords(self, sInput, words): ############################################################### # # Makes a bag of words used by the inference and training # features. If makeBagOfWords is called during training, sInput # will be a list. # # http://insightsbot.com/blog/R8fu5/bag-of-words-algorithm-in-python-introduction # ############################################################### if type(sInput) == list: bagOfWords = [] for word in words: if word in sInput: bagOfWords.append(1) else: bagOfWords.append(0) return bagOfWords else: bagOfWords = np.zeros(len(words)) for cword in self.extract(sInput, True): for i, word in enumerate(words): if word == cword: bagOfWords[i] += 1 return np.array(bagOfWords) def prepareClasses(self, intent, classes): ############################################################### # # Adds an intent key to classes if it does not already exist # ############################################################### if intent not in classes: classes.append(intent) return classes def prepareData(self, trainingData=[], wordsHldr=[], dataCorpusHldr=[], classesHldr=[]): ############################################################### # # Prepares the NLU and NER training data, loops through the # intents from our dataset, converts any entities / synoynms # ############################################################### counter = 0 intentMap = {} for intent in trainingData['intents']: theIntent = intent['intent'] for text in intent['text']: if 'entities' in intent and len(intent['entities']): i = 0 for entity in intent['entities']: tokens = text.replace( trainingData['intents'][counter]["text"][i], "<" + entity["entity"] + ">").lower().split() wordsHldr.extend(tokens) dataCorpusHldr.append((tokens, theIntent)) i = i + 1 else: tokens = text.lower().split() wordsHldr.extend(tokens) dataCorpusHldr.append((tokens, theIntent)) intentMap[theIntent] = counter classesHldr = self.prepareClasses(theIntent, classesHldr) counter = counter + 1 return self.sortList(self.extract( wordsHldr, False)), self.sortList(classesHldr), dataCorpusHldr, intentMap def finaliseData(self, classes, dataCorpus, words): ############################################################### # # Finalises the NLU training data # ############################################################### trainData = [] out = np.zeros(len(classes)) for document in dataCorpus: output = list(out) output[classes.index(document[1])] = 1 trainData.append([ self.makeBagOfWords(self.extract(document[0], False), words), output ]) random.shuffle(trainData) trainData = np.array(trainData) self.Helpers.logMessage(self.LogFile, "Data", "INFO", "Finalised Training Data Ready") return list(trainData[:, 0]), list(trainData[:, 1])
class Trainer(): ############################################################### # # Sets up all default requirements and placeholders # needed for the NLU engine to run. # # - Helpers: Useful global functions # - JumpWay/jumpWayClient: iotJumpWay class and connection # - Logging: Logging class # ############################################################### def __init__(self): self.Helpers = Helpers() self._confs = self.Helpers.loadConfigs() self.LogFile = self.Helpers.setLogFile(self._confs["aiCore"]["Logs"] + "Train/") self.intentMap = {} self.words = [] self.classes = [] self.dataCorpus = [] self.Model = Model() self.Data = Data() def setupData(self): self.trainingData = self.Data.loadTrainingData() self.words, self.classes, self.dataCorpus, self.intentMap = self.Data.prepareData( self.trainingData) self.x, self.y = self.Data.finaliseData(self.classes, self.dataCorpus, self.words) self.Helpers.logMessage(self.LogFile, "TRAIN", "INFO", "NLU Training Data Ready") def setupEntities(self): if self._confs["NLU"]["Entities"] == "Mitie": self.entityController = Entities() self.entityController.trainEntities( self._confs["NLU"]["Mitie"]["ModelLocation"], self.trainingData) self.Helpers.logMessage(self.LogFile, "TRAIN", "OK", "NLU Trainer Entities Ready") def trainModel(self): while True: self.Helpers.logMessage(self.LogFile, "TRAIN", "ACTION", "Ready To Begin Training ? (Yes/No)") userInput = input(">") if userInput == 'Yes': break if userInput == 'No': exit() self.setupData() self.setupEntities() humanStart, trainingStart = self.Helpers.timerStart() self.Model.trainDNN(self.x, self.y, self.words, self.classes, self.intentMap) trainingEnd, trainingTime, humanEnd = self.Helpers.timerEnd( trainingStart) self.Helpers.logMessage( self.LogFile, "TRAIN", "OK", "NLU Model Trained At " + humanEnd + " In " + str(trainingEnd) + " Seconds")
class NLU(): def __init__(self): ############################################################### # # Sets up all default requirements and placeholders # needed for the NLU engine to run. # # - Helpers: Useful global functions # - Logging: Logging class # ############################################################### self.isTraining = False self.ner = None self.Helpers = Helpers() self.confs = self.Helpers.loadConfigs() self.user = {} self.LogFile = self.Helpers.setLogFile(self.confs["aiCore"]["Logs"]+"NLU/") self.ChatLogFile = self.Helpers.setLogFile(self.confs["aiCore"]["Logs"]+"Chat/") def initiateSession(self): ############################################################### # # Initiates empty guest user session, GeniSys will ask the user # verify their GeniSys user by speaking or typing if it does # not know who it is speaking to. # ############################################################### self.userID = 0 if not self.userID in self.user: self.user[self.userID] = {} self.user[self.userID]["history"] = {} def initNLU(self): ############################################################### # # Initiates the NLU setting up the data, NLU / entities models # and required modules such as context and extensions. # ############################################################### self.Data = Data() self.trainingData = self.Data.loadTrainingData() self.trainedData = self.Data.loadTrainedData() self.Model = Model() self.Context = Context() self.Extensions = Extensions() self.restoreData() self.restoreNER() self.restoreNLU() self.initiateSession() self.setThresholds() def commandsCallback(self,topic,payload): ############################################################### # # The callback function that is triggerend in the event of a # command communication from the iotJumpWay. # ############################################################### self.Helpers.logMessage( self.LogFile, "iotJumpWay", "INFO", "Recieved iotJumpWay Command Data : " + str(payload)) commandData = json.loads(payload.decode("utf-8")) def restoreData(self): ############################################################### # # Sets the local trained data using data retrieved above # ############################################################### self.trainedWords = self.trainedData["words"] self.trainedClasses = self.trainedData["classes"] self.x = self.trainedData["x"] self.y = self.trainedData["y"] self.intentMap = self.trainedData["intentMap"][0] def loadEntityController(self): ############################################################### # # Initiates the entity extractor class from tools # ############################################################### self.entityController = Entities() def restoreNER(self): ############################################################### # # Loads entity controller and restores the NER model # ############################################################### self.loadEntityController() self.ner = self.entityController.restoreNER() def restoreNLU(self): ############################################################### # # Restores the NLU model # ############################################################### self.tmodel = self.Model.buildDNN(self.x, self.y) def setThresholds(self): ############################################################### # # Sets the threshold for the NLU engine, this can be changed # using arguments to commandline programs or paramters for # API calls. # ############################################################### self.threshold = self.confs["NLU"]["Threshold"] self.entityThrshld = self.confs["NLU"]["Mitie"]["Threshold"] def communicate(self, sentence): ############################################################### # # First checks to ensure that the program is not training, # then parses any entities that may be in the intent, then # checks context and extensions before providing a response. # ############################################################### if self.isTraining == False: parsed, fallback, entityHolder, parsedSentence = self.entityController.parseEntities( sentence, self.ner, self.trainingData ) classification = self.Model.predict(self.tmodel, parsedSentence, self.trainedWords, self.trainedClasses) if len(classification) > 0: clearEntities = False theIntent = self.trainingData["intents"][self.intentMap[classification[0][0]]] if len(entityHolder) and not len(theIntent["entities"]): clearEntities = True if(self.Context.checkSessionContext(self.user[self.userID], theIntent)): if self.Context.checkClearContext(theIntent, 0): self.user[self.userID]["context"] = "" contextIn, contextOut, contextCurrent = self.Context.setContexts(theIntent,self.user[self.userID]) if not len(entityHolder) and len(theIntent["entities"]): response, entities = self.entityController.replaceResponseEntities(random.choice(theIntent["fallbacks"]), entityHolder) extension, extensionResponses, exEntities = self.Extensions.setExtension(theIntent) elif clearEntities: entityHolder = [] response = random.choice(theIntent["responses"]) extension, extensionResponses, exEntities = self.Extensions.setExtension(theIntent) else: response, entities = self.entityController.replaceResponseEntities(random.choice(theIntent["responses"]), entityHolder) extension, extensionResponses, exEntities = self.Extensions.setExtension(theIntent) if extension != None: classParts = extension.split(".") classFolder = classParts[0] className = classParts[1] theEntities = None if exEntities != False: theEntities = entities module = __import__(classParts[0]+"."+classParts[1], globals(), locals(), [className]) extensionClass = getattr(module, className)() response = getattr(extensionClass, classParts[2])(extensionResponses, theEntities) return { "Response": "OK", "ResponseData": [{ "Received": sentence, "Intent": classification[0][0], "Confidence": str(classification[0][1]), "Response": response, "Context": [{ "In": contextIn, "Out": contextOut, "Current": contextCurrent }], "Extension": extension, "Entities": entityHolder }] } else: self.user[self.userID]["context"] = "" contextIn, contextOut, contextCurrent = self.Context.setContexts(theIntent, self.user[self.userID]) if fallback and fallback in theIntent and len(theIntent["fallbacks"]): response, entities = self.entityController.replaceResponseEntities(random.choice(theIntent["fallbacks"]), entityHolder) extension, extensionResponses = None, [] else: response, entities = self.entityController.replaceResponseEntities(random.choice(theIntent["responses"]), entityHolder) extension, extensionResponses, exEntities = self.Extensions.setExtension(theIntent) if extension != None: classParts = extension.split(".") classFolder = classParts[0] className = classParts[1] theEntities = None if exEntities != False: theEntities = entities module = __import__(classParts[0]+"."+classParts[1], globals(), locals(), [className]) extensionClass = getattr(module, className)() response = getattr(extensionClass, classParts[2])(extensionResponses, theEntities) else: response = self.entityController.replaceResponseEntities(random.choice(theIntent["responses"]), entityHolder) return { "Response": "OK", "ResponseData": [{ "Received": sentence, "Intent": classification[0][0], "Confidence": str(classification[0][1]), "Response": response, "Context": [{ "In": contextIn, "Out": contextOut, "Current": contextCurrent }], "Extension": extension, "Entities": entityHolder }] } else: contextCurrent = self.Context.getCurrentContext(self.user[self.userID]) return { "Response": "FAILED", "ResponseData": [{ "Received": sentence, "Intent": "UNKNOWN", "Confidence": "NA", "Responses": [], "Response": random.choice(self.confs["NLU"]["defaultResponses"]), "Context": [{ "In": "NA", "Out": "NA", "Current": contextCurrent }], "Extension":"NA", "Entities": entityHolder }] } else: return { "Response": "FAILED", "ResponseData": [{ "Status": "Training", "Message": "NLU Engine is currently training" }] }
class Model(): def __init__(self): ############################################################### # # Sets up all default requirements # # - Helpers: Useful global functions # - Data: Data functions # ############################################################### self.Helpers = Helpers() self.confs = self.Helpers.loadConfigs() self.Data = Data() def createDNNLayers(self, x, y): ############################################################### # # Sets up the DNN layers, configuration in required/confs.json # ############################################################### net = tflearn.input_data(shape=[None, len(x[0])]) for i in range(self.confs["NLU"]['FcLayers']): net = tflearn.fully_connected(net, self.confs["NLU"]['FcUnits']) net = tflearn.fully_connected(net, len(y[0]), activation=str( self.confs["NLU"]['Activation'])) if self.confs["NLU"]['Regression']: net = tflearn.regression(net) return net def trainDNN(self, x, y, words, classes, intentMap): ############################################################### # # Trains the DNN, configuration in required/confs.json # ############################################################### tf.reset_default_graph() tmodel = tflearn.DNN( self.createDNNLayers(x, y), tensorboard_dir=self.confs["NLU"]['TFLearn']['Logs'], tensorboard_verbose=self.confs["NLU"]['TFLearn']['LogsLevel']) tmodel.fit(x, y, n_epoch=self.confs["NLU"]['Epochs'], batch_size=self.confs["NLU"]['BatchSize'], show_metric=self.confs["NLU"]['ShowMetric']) self.saveModelData( self.confs["NLU"]['TFLearn']['Data'], { 'words': words, 'classes': classes, 'x': x, 'y': y, 'intentMap': [intentMap] }, tmodel) def saveModelData(self, path, data, tmodel): ############################################################### # # Saves the model data for TFLearn and the NLU engine, # configuration in required/confs.json # ############################################################### tmodel.save(self.confs["NLU"]['TFLearn']['Path']) with open(path, "w") as outfile: json.dump(data, outfile) def buildDNN(self, x, y): ############################################################### # # Loads the DNN model, configuration in required/confs.json # ############################################################### tf.reset_default_graph() tmodel = tflearn.DNN(self.createDNNLayers(x, y)) tmodel.load(self.confs["NLU"]['TFLearn']['Path']) return tmodel def predict(self, tmodel, parsedSentence, trainedWords, trainedClasses): ############################################################### # # Makes a prediction against the trained model, checking the # confidence and then logging the results. # ############################################################### predictions = [[index, confidence] for index, confidence in enumerate( tmodel.predict( [self.Data.makeBagOfWords(parsedSentence, trainedWords)])[0])] predictions.sort(key=lambda x: x[1], reverse=True) classification = [] for prediction in predictions: classification.append( (trainedClasses[prediction[0]], prediction[1])) return classification
class Entities(): def __init__(self): ############################################################### # # Sets up all default requirements # # - Helpers: Useful global functions # - LancasterStemmer: Word stemmer # ############################################################### self.Helpers = Helpers() self._confs = self.Helpers.loadConfigs() self.stemmer = LancasterStemmer() def restoreNER(self): ############################################################### # # Restores the NER model, in this case MITIE # ############################################################### if os.path.exists(self._confs["NLU"]["EntitiesDat"]): return named_entity_extractor(self._confs["NLU"]["EntitiesDat"]) def parseEntities(self, sentence, ner, trainingData): ############################################################### # # Parses entities in intents/sentences # ############################################################### entityHolder = [] fallback = False parsedSentence = sentence parsed = "" if os.path.exists(self._confs["NLU"]["EntitiesDat"]): tokens = sentence.lower().split() entities = ner.extract_entities(tokens) for e in entities: range = e[0] tag = e[1] score = e[2] scoreText = "{:0.3f}".format(score) if score > self._confs["NLU"]["Mitie"]["Threshold"]: parsed, fallback = self.replaceEntity( " ".join(tokens[i] for i in range), tag, trainingData) entityHolder.append({ "Entity":tag, "ParsedEntity":parsed, "Confidence":str(scoreText)}) parsedSentence = sentence.replace( " ".join(sentence.split()[i] for i in range), "<"+tag+">") else: parsed, fallback = self.replaceEntity( " ".join(tokens[i] for i in range), tag, trainingData) entityHolder.append({ "Entity":tag, "ParsedEntity":parsed, "Confidence":str(scoreText)}) parsed = parsedSentence return parsed, fallback, entityHolder, parsedSentence def replaceResponseEntities(self, response, entityHolder): ############################################################### # # Replaces entities in responses # ############################################################### entities = [] for entity in entityHolder: response = response.replace("<"+entity["Entity"]+">", entity["ParsedEntity"].title()) entities.append( entity["ParsedEntity"].title()) return response, entities def replaceEntity(self, value, entity, trainingData): ############################################################### # # Replaces entities/synonyms # ############################################################### lowEntity = value.lower() match = True if "entitieSynonyms" in trainingData: for entities in trainingData["entitieSynonyms"]: for synonyms in entities[entity]: for synonym in synonyms["synonyms"]: if lowEntity == synonym.lower(): lowEntity = synonyms["value"] match = False break return lowEntity, match def trainEntities(self, mitiemLocation, trainingData): ############################################################### # # Trains the NER model # ############################################################### trainer = ner_trainer(mitiemLocation) counter = 0 hasEnts = 0 for intents in trainingData['intents']: i = 0 for entity in intents['entities']: hasEnts = 1 tokens = trainingData['intents'][counter]["text"][i].lower().split() data = ner_training_instance(tokens) data.add_entity( xrange( entity["rangeFrom"], entity["rangeTo"]), entity["entity"]) trainer.add(data) i = i + 1 counter = counter + 1 if hasEnts: trainer.num_threads = 4 ner = trainer.train() ner.save_to_disk(self._confs["NLU"]["EntitiesDat"])