From b38f6ce3645a644538253ae163b1836b2cd883ad Mon Sep 17 00:00:00 2001 From: Verve <87475981+VerveIsGod@users.noreply.github.com> Date: Thu, 29 Sep 2022 18:15:36 +0200 Subject: [PATCH 1/2] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e7e798f..0e6f4a9 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ long_description_content_type="text/markdown", long_description=long_description, packages=find_packages(), - install_requires=['numpy', 'nltk', 'tensorflow'], + install_requires=['numpy', 'nltk', 'tensorflow', 'gtts', 'pyttsx3'], keywords=['python', 'neural', 'machine learning', 'chatbots', 'chat', 'artificial intelligence', 'virtual assistant'], classifiers=[ "Development Status :: 1 - Planning", @@ -30,4 +30,4 @@ "Operating System :: Unix", "Operating System :: Microsoft :: Windows", ] -) \ No newline at end of file +) From 1e41ad7e51baf38af1ed49fe88c0f5677356bc87 Mon Sep 17 00:00:00 2001 From: Verve <87475981+VerveIsGod@users.noreply.github.com> Date: Thu, 29 Sep 2022 19:17:26 +0300 Subject: [PATCH 2/2] Add files via upload --- neuralintents/main.py | 416 +++++++++++++++++-------------- neuralintents/voice_assistant.py | 15 ++ 2 files changed, 244 insertions(+), 187 deletions(-) create mode 100644 neuralintents/voice_assistant.py diff --git a/neuralintents/main.py b/neuralintents/main.py index 9d24bc2..81dbc1e 100644 --- a/neuralintents/main.py +++ b/neuralintents/main.py @@ -1,187 +1,229 @@ -from abc import ABCMeta, abstractmethod - -import random -import json -import pickle -import numpy as np -import os - -os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' - -import nltk -from nltk.stem import WordNetLemmatizer - -from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Dense, Dropout -from tensorflow.keras.optimizers import SGD -from tensorflow.keras.models import load_model - -nltk.download('punkt', quiet=True) -nltk.download('wordnet', quiet=True) - -class IAssistant(metaclass=ABCMeta): - - @abstractmethod - def train_model(self): - """ Implemented in child class """ - - @abstractmethod - def request_tag(self, message): - """ Implemented in child class """ - - @abstractmethod - def get_tag_by_id(self, id): - """ Implemented in child class """ - - @abstractmethod - def request_method(self, message): - """ Implemented in child class """ - - @abstractmethod - def request(self, message): - """ Implemented in child class """ - - -class GenericAssistant(IAssistant): - - def __init__(self, intents, intent_methods={}, model_name="assistant_model"): - self.intents = intents - self.intent_methods = intent_methods - self.model_name = model_name - - if intents.endswith(".json"): - self.load_json_intents(intents) - - self.lemmatizer = WordNetLemmatizer() - - def load_json_intents(self, intents): - self.intents = json.loads(open(intents).read()) - - def train_model(self): - - self.words = [] - self.classes = [] - documents = [] - ignore_letters = ['!', '?', ',', '.'] - - for intent in self.intents['intents']: - for pattern in intent['patterns']: - word = nltk.word_tokenize(pattern) - self.words.extend(word) - documents.append((word, intent['tag'])) - if intent['tag'] not in self.classes: - self.classes.append(intent['tag']) - - self.words = [self.lemmatizer.lemmatize(w.lower()) for w in self.words if w not in ignore_letters] - self.words = sorted(list(set(self.words))) - - self.classes = sorted(list(set(self.classes))) - - - - training = [] - output_empty = [0] * len(self.classes) - - for doc in documents: - bag = [] - word_patterns = doc[0] - word_patterns = [self.lemmatizer.lemmatize(word.lower()) for word in word_patterns] - for word in self.words: - bag.append(1) if word in word_patterns else bag.append(0) - - output_row = list(output_empty) - output_row[self.classes.index(doc[1])] = 1 - training.append([bag, output_row]) - - random.shuffle(training) - training = np.array(training) - - train_x = list(training[:, 0]) - train_y = list(training[:, 1]) - - self.model = Sequential() - self.model.add(Dense(128, input_shape=(len(train_x[0]),), activation='relu')) - self.model.add(Dropout(0.5)) - self.model.add(Dense(64, activation='relu')) - self.model.add(Dropout(0.5)) - self.model.add(Dense(len(train_y[0]), activation='softmax')) - - sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) - self.model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy']) - - self.hist = self.model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1) - - def save_model(self, model_name=None): - if model_name is None: - self.model.save(f"{self.model_name}.h5", self.hist) - pickle.dump(self.words, open(f'{self.model_name}_words.pkl', 'wb')) - pickle.dump(self.classes, open(f'{self.model_name}_classes.pkl', 'wb')) - else: - self.model.save(f"{model_name}.h5", self.hist) - pickle.dump(self.words, open(f'{model_name}_words.pkl', 'wb')) - pickle.dump(self.classes, open(f'{model_name}_classes.pkl', 'wb')) - - def load_model(self, model_name=None): - if model_name is None: - self.words = pickle.load(open(f'{self.model_name}_words.pkl', 'rb')) - self.classes = pickle.load(open(f'{self.model_name}_classes.pkl', 'rb')) - self.model = load_model(f'{self.model_name}.h5') - else: - self.words = pickle.load(open(f'{model_name}_words.pkl', 'rb')) - self.classes = pickle.load(open(f'{model_name}_classes.pkl', 'rb')) - self.model = load_model(f'{model_name}.h5') - - def _clean_up_sentence(self, sentence): - sentence_words = nltk.word_tokenize(sentence) - sentence_words = [self.lemmatizer.lemmatize(word.lower()) for word in sentence_words] - return sentence_words - - def _bag_of_words(self, sentence, words): - sentence_words = self._clean_up_sentence(sentence) - bag = [0] * len(words) - for s in sentence_words: - for i, word in enumerate(words): - if word == s: - bag[i] = 1 - return np.array(bag) - - def _predict_class(self, sentence): - p = self._bag_of_words(sentence, self.words) - res = self.model.predict(np.array([p]))[0] - ERROR_THRESHOLD = 0.1 - results = [[i, r] for i, r in enumerate(res) if r > ERROR_THRESHOLD] - - results.sort(key=lambda x: x[1], reverse=True) - return_list = [] - for r in results: - return_list.append({'intent': self.classes[r[0]], 'probability': str(r[1])}) - return return_list - - def _get_response(self, ints, intents_json): - try: - tag = ints[0]['intent'] - list_of_intents = intents_json['intents'] - for i in list_of_intents: - if i['tag'] == tag: - result = random.choice(i['responses']) - break - except IndexError: - result = "I don't understand!" - return result - - def request_tag(self, message): - pass - - def get_tag_by_id(self, id): - pass - - def request_method(self, message): - pass - - def request(self, message): - ints = self._predict_class(message) - - if ints[0]['intent'] in self.intent_methods.keys(): - self.intent_methods[ints[0]['intent']]() - else: - return self._get_response(ints, self.intents) \ No newline at end of file +import os +import nltk +import voice +import random +import pickle +import numpy as np +from json import load +from gtts import gTTS, langs +from abc import ABCMeta, abstractmethod +from nltk.stem import WordNetLemmatizer +from .voice_assistant import VoiceAssistant +from tensorflow.keras.optimizers import SGD +from tensorflow.keras.layers import Dense, Dropout +from typing import Union, List, Dict, Text, Optional, Any +from tensorflow.keras.models import Sequential, load_model + +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + +nltk.download('punkt', quiet=True) +nltk.download('wordnet', quiet=True) + +class IAssistant(metaclass=ABCMeta): + + @abstractmethod + def train_model(self) -> Optional[Any]: + """ Implemented in child class """ + + @abstractmethod + def request_tag(self, message: Text) -> Optional[Any]: + """ Implemented in child class """ + + @abstractmethod + def get_tag_by_id(self, id: Text) -> Optional[Any]: + """ Implemented in child class """ + + @abstractmethod + def request_method(self, message: Text) -> Optional[Any]: + """ Implemented in child class """ + + @abstractmethod + def request(self, message: Text) -> Optional[Any]: + """ Implemented in child class """ + + +class GenericAssistant(IAssistant): + def __init__(self, + intents: Text, + intent_methods: Dict = {}, + model_name: Text = "assistant_model", + voice_assistant: bool = False, + voice_saving: bool = False, + language: Text = "english", + encoding: Text = "utf-8") -> None: + """ + + :param intents: str, the intents file name + :param intent_methods: Dict, the methods of intents file + :param model_name: str, the name for your model + :param voice_assistant: bool, True if you want to play the answer + :param voice_saving: bool, True if you want to save the answer voice to file + :param language: str, the language for your NLTP + :param encoding: str, the model file encoding + """ + self.intents = intents + self.intent_methods = intent_methods + self.model_name = model_name + self.language = language + self.encoding = encoding + self.voice_assistant = voice_assistant + self.voice_saving = voice_saving + + if intents.endswith(".json"): + self.load_json_intents(intents) + + self.lemmatizer = WordNetLemmatizer() + + def load_json_intents(self, intents: Text) -> None: + with open(intents, encoding=self.encoding) as intents_file: + self.intents = load(intents_file) + + def train_model(self) -> None: + """ + + Trains the model for NLTP + :return: None + """ + self.words = [] + self.classes = [] + documents = [] + ignore_letters = ['!', '?', ',', '.'] + + for intent in self.intents['intents']: + for pattern in intent['patterns']: + word = nltk.word_tokenize(pattern, language=self.language) + self.words.extend(word) + documents.append((word, intent['tag'])) + if intent['tag'] not in self.classes: + self.classes.append(intent['tag']) + + self.words = [self.lemmatizer.lemmatize(w.lower()) for w in self.words if w not in ignore_letters] + self.words = sorted(list(set(self.words))) + self.classes = sorted(list(set(self.classes))) + + training = [] + output_empty = [0] * len(self.classes) + + for doc in documents: + bag = [] + word_patterns = doc[0] + word_patterns = [self.lemmatizer.lemmatize(word.lower()) for word in word_patterns] + for word in self.words: + bag.append(1) if word in word_patterns else bag.append(0) + + output_row = list(output_empty) + output_row[self.classes.index(doc[1])] = 1 + training.append([bag, output_row]) + + random.shuffle(training) + training = np.array(training) + + train_x = list(training[:, 0]) + train_y = list(training[:, 1]) + + self.model = Sequential() + self.model.add(Dense(128, input_shape=(len(train_x[0]),), activation='relu')) + self.model.add(Dropout(0.5)) + self.model.add(Dense(64, activation='relu')) + self.model.add(Dropout(0.5)) + self.model.add(Dense(len(train_y[0]), activation='softmax')) + + sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) + self.model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy']) + + self.hist = self.model.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1) + + def save_model(self, model_name: Text = None) -> None: + """ + + Saves trained model + :param model_name: Optional (str), the new model name for saving + :return: None + """ + self.model.save(f"{model_name if model_name is not None else self.model_name}.h5", self.hist) + with open(f'{model_name if model_name is not None else self.model_name}_words.pkl', 'wb') as file: + pickle.dump(self.words, file) + with open(f'{model_name if model_name is not None else self.model_name}_classes.pkl', 'wb') as file: + pickle.dump(self.classes, file) + + def load_model(self, model_name: Text = None) -> None: + """ + + Loads the existing model + :param model_name: Optional (str), the name of your model + :return: None + """ + with open(f'{model_name if model_name is not None else self.model_name}_words.pkl', 'rb') as file: + self.words = pickle.load(file) + with open(f'{model_name if model_name is not None else self.model_name}_classes.pkl', 'rb') as file: + self.classes = pickle.load(file) + self.model = load_model(f'{self.model_name}.h5') + + def _clean_up_sentence(self, sentence: Text) -> List[Text]: + sentence_words = nltk.word_tokenize(sentence) + sentence_words = [self.lemmatizer.lemmatize(word.lower()) for word in sentence_words] + return sentence_words + + def _bag_of_words(self, sentence: Text, words: Text): + sentence_words = self._clean_up_sentence(sentence) + bag = [0] * len(words) + for s in sentence_words: + for i, word in enumerate(words): + if word == s: + bag[i] = 1 + return np.array(bag) + + def _predict_class(self, sentence: Text) -> List[Dict[Text, Union[Text, List[Text]]]]: + p = self._bag_of_words(sentence, self.words) + res = self.model.predict(np.array([p]))[0] + ERROR_THRESHOLD = 0.1 + results = [[i, r] for i, r in enumerate(res) if r > ERROR_THRESHOLD] + + results.sort(key=lambda x: x[1], reverse=True) + return_list = [] + for r in results: + return_list.append({'intent': self.classes[r[0]], 'probability': Text(r[1])}) + return return_list + + def _get_response(self, ints: list, intents_json: Dict) -> Text: + try: + tag = ints[0]['intent'] + list_of_intents = intents_json['intents'] + for i in list_of_intents: + if i['tag'] == tag: + result = random.choice(i['responses']) + break + except IndexError: + result = "I don't understand!" + return result + + def request_tag(self, message: Text) -> None: + pass + + def get_tag_by_id(self, id: Text) -> None: + pass + + def request_method(self, message: Text) -> None: + pass + + def request(self, message: Text) -> Text: + """ + + Request for the result of your message + :param message: str, the message to the NLTP + :return: str, answer of NLTP + """ + ints = self._predict_class(message) + + if ints[0]['intent'] in self.intent_methods.keys(): + self.intent_methods[ints[0]['intent']]() + else: + response = self._get_response(ints, self.intents) + if self.voice_saving: + tts = gTTS(text=response, + lang=list(langs._langs.keys())[list(langs._langs.values()).index(self.language.capitalize())]) + tts.save(f"{self.model_name}_answer.mp3") + if self.voice_assistant: + VoiceAssistant(response).say() + else: + return response diff --git a/neuralintents/voice_assistant.py b/neuralintents/voice_assistant.py new file mode 100644 index 0000000..6dcf9c0 --- /dev/null +++ b/neuralintents/voice_assistant.py @@ -0,0 +1,15 @@ +from typing import Text +from pyttsx3 import init + +engine = init() +voices = engine.getProperty('voices') +engine.setProperty('voices',voices[0].id) + +class VoiceAssistant: + def __init__(self, audiostring: Text): + self.audiostring = audiostring + + def say(self): + print(self.audiostring) + engine.say(self.audiostring) + engine.runAndWait()