diff --git a/Web_Development/chatbot/README.md b/Web_Development/chatbot/README.md new file mode 100644 index 0000000000..f9bea28508 --- /dev/null +++ b/Web_Development/chatbot/README.md @@ -0,0 +1,59 @@ +## **PyTorch Based AI Chatbot** + + + + +### 🎯 **Goal** + +Python Project - This ChatBot allows programmers to attach it to any website where normal users can use it. + +Modules Used: + 1. Pytorch + 2. NLTK + 3. NumPy + 4. Random + 5. JSON + 6. Flask + + +# Chatbot Deployment with Flask and JavaScript +How to deploy: +- Deploy within Flask app with jinja2 template + +## Initial Setup: +``` +$ cd chatbot +$ py -m venv venv +$ . venv/scripts/activate +``` +Install dependencies +``` +$ (venv) pip install Flask torch torchvision nltk +``` +Install nltk package +``` +$ (venv) python +>>> import nltk +>>> nltk.download('punkt') +``` +Modify `intents.json` with different intents and responses for your Chatbot as per your need + +Run +``` +$ (venv) python train.py +``` +This will dump data.pth file. And then run +the following command to test it in the console. + +``` +Run the chatbot with frontend in cmd or editor +``` +``` +$python app.py +``` + +### ✒️ **Your Signature** + +`Ankan Mukhopadhyay` +[GitHub Profile](https://github.com/Peart-Guy) | [LinkedIn](https://www.linkedin.com/in/ankan-mukhopadhyay-06baa4315/) + diff --git a/Web_Development/chatbot/__pycache__/chat.cpython-312.pyc b/Web_Development/chatbot/__pycache__/chat.cpython-312.pyc new file mode 100644 index 0000000000..d84115feb9 Binary files /dev/null and b/Web_Development/chatbot/__pycache__/chat.cpython-312.pyc differ diff --git a/Web_Development/chatbot/__pycache__/model.cpython-312.pyc b/Web_Development/chatbot/__pycache__/model.cpython-312.pyc new file mode 100644 index 0000000000..8b39e2c49c Binary files /dev/null and b/Web_Development/chatbot/__pycache__/model.cpython-312.pyc differ diff --git a/Web_Development/chatbot/__pycache__/nltk_utils.cpython-312.pyc b/Web_Development/chatbot/__pycache__/nltk_utils.cpython-312.pyc new file mode 100644 index 0000000000..b7a7f02644 Binary files /dev/null and b/Web_Development/chatbot/__pycache__/nltk_utils.cpython-312.pyc differ diff --git a/Web_Development/chatbot/app.py b/Web_Development/chatbot/app.py new file mode 100644 index 0000000000..71ac5740b7 --- /dev/null +++ b/Web_Development/chatbot/app.py @@ -0,0 +1,21 @@ +from flask import Flask , render_template , request , jsonify + +from chat import get_response + +app = Flask(__name__) + +@app.get("/") +def index_get(): + return render_template("base.html") + +@app.post("/predict") +def predict(): + text = request.get_json().get("message") + #checking if message is valid + response = get_response(text) + message = {"answer": response} + + return jsonify(message) + +if __name__ == "__main__" : + app.run(debug=True) diff --git a/Web_Development/chatbot/chat.py b/Web_Development/chatbot/chat.py new file mode 100644 index 0000000000..e7cd1819b6 --- /dev/null +++ b/Web_Development/chatbot/chat.py @@ -0,0 +1,61 @@ +import random +import json + +import torch + +from model import NeuralNet +from nltk_utils import bag_of_words, tokenize + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + +with open('intents.json', 'r') as json_data: + intents = json.load(json_data) + +FILE = "data.pth" +data = torch.load(FILE) + +input_size = data["input_size"] +hidden_size = data["hidden_size"] +output_size = data["output_size"] +all_words = data['all_words'] +tags = data['tags'] +model_state = data["model_state"] + +model = NeuralNet(input_size, hidden_size, output_size).to(device) +model.load_state_dict(model_state) +model.eval() + +bot_name = "Ankan" + +def get_response(msg): + sentence = tokenize(msg) + X = bag_of_words(sentence, all_words) + X = X.reshape(1, X.shape[0]) + X = torch.from_numpy(X).to(device) + + output = model(X) + _, predicted = torch.max(output, dim=1) + + tag = tags[predicted.item()] + + probs = torch.softmax(output, dim=1) + prob = probs[0][predicted.item()] + if prob.item() > 0.75: + for intent in intents['intents']: + if tag == intent["tag"]: + return random.choice(intent['responses']) + + return "I do not understand..." + + +if __name__ == "__main__": + print("Let's chat! (type 'quit' to exit)") + while True: + # sentence = "do you use credit cards?" + sentence = input("You: ") + if sentence == "quit": + break + + resp = get_response(sentence) + print(resp) + diff --git a/Web_Development/chatbot/data.pth b/Web_Development/chatbot/data.pth new file mode 100644 index 0000000000..ffef5f78ce Binary files /dev/null and b/Web_Development/chatbot/data.pth differ diff --git a/Web_Development/chatbot/intents.json b/Web_Development/chatbot/intents.json new file mode 100644 index 0000000000..226c005329 --- /dev/null +++ b/Web_Development/chatbot/intents.json @@ -0,0 +1,72 @@ +{ + "intents": [ + { + "tag": "greeting", + "patterns": [ + "Hi", + "Hey", + "How are you", + "Is anyone there?", + "Hello", + "Good day" + ], + "responses": [ + "Hey :-)", + "Hello, thanks for visiting", + "Hi there, what can I do for you?", + "Hi there, how can I help?" + ] + }, + { + "tag": "goodbye", + "patterns": ["Bye", "See you later", "Goodbye"], + "responses": [ + "See you later, thanks for visiting", + "Have a nice day", + "Bye! Come back again soon." + ] + }, + { + "tag": "thanks", + "patterns": ["Thanks", "Thank you", "That's helpful", "Thank's a lot!"], + "responses": ["Happy to help!", "Any time!", "My pleasure"] + }, + { + "tag": "items", + "patterns": [ + "Which services do you provide?", + "What kinds of service are there?", + "What do you do?" + ], + "responses": [ + "We provide services." + + ] + }, + { + "tag": "payments", + "patterns": [ + "Do you take credit cards?", + "Do you accept Mastercard?", + "Can I pay with UPI?", + "Are you cash only?" + ], + "responses": [ + "We accept VISA, Mastercard and UPI", + "We accept most major credit cards, and UPI" + ] + }, + { + "tag": "Sorry", + "patterns": [ + "You were wrong", + "This is not right", + "I don't Understand" + ], + "responses": [ + "Sorry for Inconveninience. I am still in Learning phase", + "Please submit the feedback , I will surely learn from my mistakes." + ] + } + ] +} diff --git a/Web_Development/chatbot/model.py b/Web_Development/chatbot/model.py new file mode 100644 index 0000000000..df6fa8a512 --- /dev/null +++ b/Web_Development/chatbot/model.py @@ -0,0 +1,20 @@ +import torch +import torch.nn as nn + + +class NeuralNet(nn.Module): + def __init__(self, input_size, hidden_size, num_classes): + super(NeuralNet, self).__init__() + self.l1 = nn.Linear(input_size, hidden_size) + self.l2 = nn.Linear(hidden_size, hidden_size) + self.l3 = nn.Linear(hidden_size, num_classes) + self.relu = nn.ReLU() + + def forward(self, x): + out = self.l1(x) + out = self.relu(out) + out = self.l2(out) + out = self.relu(out) + out = self.l3(out) + # no activation and no softmax at the end + return out diff --git a/Web_Development/chatbot/nltk_utils.py b/Web_Development/chatbot/nltk_utils.py new file mode 100644 index 0000000000..b0da9f873f --- /dev/null +++ b/Web_Development/chatbot/nltk_utils.py @@ -0,0 +1,50 @@ +import numpy as np +import nltk +# nltk.download('punkt') +from nltk.stem.porter import PorterStemmer +stemmer = PorterStemmer() + + +# def tokenize(sentence): +# """ +# split sentence into array of words/tokens +# a token can be a word or punctuation character, or number +# """ +# return nltk.word_tokenize(sentence) + +def tokenize(sentence): + if sentence is None or sentence.strip() == "": + # If the input sentence is None or empty, return an empty list or handle accordingly + return [] + return nltk.word_tokenize(sentence) + + +def stem(word): + """ + stemming = find the root form of the word + examples: + words = ["organize", "organizes", "organizing"] + words = [stem(w) for w in words] + -> ["organ", "organ", "organ"] + """ + return stemmer.stem(word.lower()) + + +def bag_of_words(tokenized_sentence, words): + """ + return bag of words array: + 1 for each known word that exists in the sentence, 0 otherwise + example: + sentence = ["hello", "how", "are", "you"] + words = ["hi", "hello", "I", "you", "bye", "thank", "cool"] + bog = [ 0 , 1 , 0 , 1 , 0 , 0 , 0] + """ + # stem each word + sentence_words = [stem(word) for word in tokenized_sentence] + # initialize bag with 0 for each word + bag = np.zeros(len(words), dtype=np.float32) + for idx, w in enumerate(words): + if w in sentence_words: + bag[idx] = 1 + + return bag diff --git a/Web_Development/chatbot/static/app.js b/Web_Development/chatbot/static/app.js new file mode 100644 index 0000000000..2c6dc395bc --- /dev/null +++ b/Web_Development/chatbot/static/app.js @@ -0,0 +1,91 @@ +class Chatbox { + constructor() { + this.args = { + openButton: document.querySelector('.chatbox__button'), + chatBox: document.querySelector('.chatbox__support'), + sendButton: document.querySelector('.send__button') + } + + this.state = false; + this.messages = []; + } + + display() { + const {openButton, chatBox, sendButton} = this.args; + + openButton.addEventListener('click', () => this.toggleState(chatBox)) + + sendButton.addEventListener('click', () => this.onSendButton(chatBox)) + + const node = chatBox.querySelector('input'); + node.addEventListener("keyup", ({key}) => { + if (key === "Enter") { + this.onSendButton(chatBox) + } + }) + } + + toggleState(chatbox) { + this.state = !this.state; + + // show or hides the box + if(this.state) { + chatbox.classList.add('chatbox--active') + } else { + chatbox.classList.remove('chatbox--active') + } + } + + onSendButton(chatbox) { + var textField = chatbox.querySelector('input'); + let text1 = textField.value + if (text1 === "") { + return; + } + + let msg1 = { name: "User", message: text1 } + this.messages.push(msg1); + + fetch('http://127.0.0.1:5000/predict', { + method: 'POST', + body: JSON.stringify({ message: text1 }), + mode: 'cors', + headers: { + 'Content-Type': 'application/json' + }, + }) + .then(r => r.json()) + .then(r => { + let msg2 = { name: "Ankan", message: r.answer }; + this.messages.push(msg2); + this.updateChatText(chatbox) + textField.value = '' + + }).catch((error) => { + console.error('Error:', error); + this.updateChatText(chatbox) + textField.value = '' + }); + } + + updateChatText(chatbox) { + var html = ''; + this.messages.slice().reverse().forEach(function(item, index) { + if (item.name === "Ankan") + { + html += '
' + item.message + '
' + } + else + { + html += '
' + item.message + '
' + } + }); + + const chatmessage = chatbox.querySelector('.chatbox__messages'); + chatmessage.innerHTML = html; + } +} + + +const chatbox = new Chatbox(); +chatbox.display(); \ No newline at end of file diff --git a/Web_Development/chatbot/static/favicon.ico b/Web_Development/chatbot/static/favicon.ico new file mode 100644 index 0000000000..a4abdbf1e6 Binary files /dev/null and b/Web_Development/chatbot/static/favicon.ico differ diff --git a/Web_Development/chatbot/static/images/chatbox-icon.svg b/Web_Development/chatbot/static/images/chatbox-icon.svg new file mode 100644 index 0000000000..15617acc83 --- /dev/null +++ b/Web_Development/chatbot/static/images/chatbox-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Web_Development/chatbot/static/style.css b/Web_Development/chatbot/static/style.css new file mode 100644 index 0000000000..d44156c4b9 --- /dev/null +++ b/Web_Development/chatbot/static/style.css @@ -0,0 +1,200 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Nunito', sans-serif; + font-weight: 400; + font-size: 100%; + background: #F1F1F1; +} + +*, html { + --primaryGradient: linear-gradient(93.12deg, #5f5e61 0.52%, #3a373b 100%); + --secondaryGradient: linear-gradient(268.91deg, #5f5e61 -2.14%, #3a373b 99.69%); + --primaryBoxShadow: 0px 10px 15px rgba(0, 0, 0, 0.1); + --secondaryBoxShadow: 0px -10px 15px rgba(0, 0, 0, 0.1); + --primary: #5f5e61; +} + +/* CHATBOX +=============== */ +.chatbox { + position: absolute; + bottom: 30px; + right: 30px; +} + +/* CONTENT IS CLOSE */ +.chatbox__support { + display: flex; + flex-direction: column; + background: #eee; + width: 300px; + height: 350px; + z-index: -123456; + opacity: 0; + transition: all .5s ease-in-out; +} + +/* CONTENT ISOPEN */ +.chatbox--active { + transform: translateY(-40px); + z-index: 123456; + opacity: 1; + +} + +/* BUTTON */ +.chatbox__button { + text-align: right; +} + +.send__button { + padding: 6px; + background: transparent; + border: none; + outline: none; + cursor: pointer; +} + + +/* HEADER */ +.chatbox__header { + position: sticky; + top: 0; + background: orange; +} + +/* MESSAGES */ +.chatbox__messages { + margin-top: auto; + display: flex; + overflow-y: scroll; + flex-direction: column-reverse; +} + +.messages__item { + background: orange; + max-width: 60.6%; + width: fit-content; +} + +.messages__item--operator { + margin-left: auto; +} + +.messages__item--visitor { + margin-right: auto; +} + +/* FOOTER */ +.chatbox__footer { + position: sticky; + bottom: 0; +} + +.chatbox__support { + background: #f9f9f9; + height: 450px; + width: 350px; + box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1); + border-top-left-radius: 20px; + border-top-right-radius: 20px; +} + +/* HEADER */ +.chatbox__header { + background: var(--primaryGradient); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 15px 20px; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + box-shadow: var(--primaryBoxShadow); +} + +.chatbox__image--header { + margin-right: 10px; +} + +.chatbox__heading--header { + font-size: 1.2rem; + color: white; +} + +.chatbox__description--header { + font-size: .9rem; + color: white; +} + +/* Messages */ +.chatbox__messages { + padding: 0 20px; +} + +.messages__item { + margin-top: 10px; + background: #E0E0E0; + padding: 8px 12px; + max-width: 70%; +} + +.messages__item--visitor, +.messages__item--typing { + border-top-left-radius: 20px; + border-top-right-radius: 20px; + border-bottom-right-radius: 20px; +} + +.messages__item--operator { + border-top-left-radius: 20px; + border-top-right-radius: 20px; + border-bottom-left-radius: 20px; + background: var(--primary); + color: white; +} + +/* FOOTER */ +.chatbox__footer { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 20px 20px; + background: var(--secondaryGradient); + box-shadow: var(--secondaryBoxShadow); + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; + margin-top: 20px; +} + +.chatbox__footer input { + width: 80%; + border: none; + padding: 10px 10px; + border-radius: 30px; + text-align: left; +} + +.chatbox__send--footer { + color: white; +} + +.chatbox__button button, +.chatbox__button button:focus, +.chatbox__button button:visited { + padding: 10px; + background: white; + border: none; + outline: none; + border-top-left-radius: 50px; + border-top-right-radius: 50px; + border-bottom-left-radius: 50px; + box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1); + cursor: pointer; +} diff --git a/Web_Development/chatbot/templates/base.html b/Web_Development/chatbot/templates/base.html new file mode 100644 index 0000000000..73b9d06c3f --- /dev/null +++ b/Web_Development/chatbot/templates/base.html @@ -0,0 +1,43 @@ + + + + + + + + Chatbot + + +
+
+
+
+
+ image +
+
+

Chat support

+

Hi. My name is Ankan. How can I help you?

+
+
+
+
+
+ +
+
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/Web_Development/chatbot/train.py b/Web_Development/chatbot/train.py new file mode 100644 index 0000000000..3555f040ab --- /dev/null +++ b/Web_Development/chatbot/train.py @@ -0,0 +1,129 @@ +import numpy as np +import random +import json + +import torch +import torch.nn as nn +from torch.utils.data import Dataset, DataLoader + +from nltk_utils import bag_of_words, tokenize, stem +from model import NeuralNet + +with open('intents.json', 'r') as f: + intents = json.load(f) + +all_words = [] +tags = [] +xy = [] +# loop through each sentence in our intents patterns +for intent in intents['intents']: + tag = intent['tag'] + # add to tag list + tags.append(tag) + for pattern in intent['patterns']: + # tokenize each word in the sentence + w = tokenize(pattern) + # add to our words list + all_words.extend(w) + # add to xy pair + xy.append((w, tag)) + +# stem and lower each word +ignore_words = ['?', '.', '!'] +all_words = [stem(w) for w in all_words if w not in ignore_words] +# remove duplicates and sort +all_words = sorted(set(all_words)) +tags = sorted(set(tags)) + +print(len(xy), "patterns") +print(len(tags), "tags:", tags) +print(len(all_words), "unique stemmed words:", all_words) + +# create training data +X_train = [] +y_train = [] +for (pattern_sentence, tag) in xy: + # X: bag of words for each pattern_sentence + bag = bag_of_words(pattern_sentence, all_words) + X_train.append(bag) + # y: PyTorch CrossEntropyLoss needs only class labels, not one-hot + label = tags.index(tag) + y_train.append(label) + +X_train = np.array(X_train) +y_train = np.array(y_train) + +# Hyper-parameters +num_epochs = 1000 +batch_size = 8 +learning_rate = 0.001 +input_size = len(X_train[0]) +hidden_size = 8 +output_size = len(tags) +print(input_size, output_size) + +class ChatDataset(Dataset): + + def __init__(self): + self.n_samples = len(X_train) + self.x_data = X_train + self.y_data = y_train + + # support indexing such that dataset[i] can be used to get i-th sample + def __getitem__(self, index): + return self.x_data[index], self.y_data[index] + + # we can call len(dataset) to return the size + def __len__(self): + return self.n_samples + +dataset = ChatDataset() +train_loader = DataLoader(dataset=dataset, + batch_size=batch_size, + shuffle=True, + num_workers=0) + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + +model = NeuralNet(input_size, hidden_size, output_size).to(device) + +# Loss and optimizer +criterion = nn.CrossEntropyLoss() +optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) + +# Train the model +for epoch in range(num_epochs): + for (words, labels) in train_loader: + words = words.to(device) + labels = labels.to(dtype=torch.long).to(device) + + # Forward pass + outputs = model(words) + # if y would be one-hot, we must apply + # labels = torch.max(labels, 1)[1] + loss = criterion(outputs, labels) + + # Backward and optimize + optimizer.zero_grad() + loss.backward() + optimizer.step() + + if (epoch+1) % 100 == 0: + print (f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') + + +print(f'final loss: {loss.item():.4f}') + +data = { +"model_state": model.state_dict(), +"input_size": input_size, +"hidden_size": hidden_size, +"output_size": output_size, +"all_words": all_words, +"tags": tags +} + +FILE = "data.pth" +torch.save(data, FILE) + +print(f'training complete. file saved to {FILE}')