Chatbot (NLP).
Intent-based bot trained from JSON corpus.
Lightweight Q/A without heavy infra.
Intent classification is the cornerstone of conversational AI systems. When a user sends a message to a chatbot, the system must understand what the user wants to accomplish, their intent, before it can respond appropriately. This project tackles the fundamental challenge of building a lightweight, accurate intent classification system without relying on heavy infrastructure or cloud-based NLP services. Traditional rule-based chatbots use keyword matching, which breaks down quickly with natural language variation. Modern approaches use machine learning to recognize patterns in language, enabling the bot to understand semantically similar phrases. This project demonstrates how to build an intent-based conversational agent using classical NLP techniques and neural networks, trained on a custom JSON corpus that maps example phrases to specific intents. The goal was to create a fast, deployable chatbot prototype suitable for FAQs, customer service automation, and utility functions, all while maintaining low latency and minimal resource requirements.
- Tokenization, stemming, bag-of-words; intent matching.
- Modular intents for rapid updates.
Architecture
The system follows a pipeline architecture from raw text input to predicted intent: **1. User Input → Preprocessing** Raw user text undergoes normalization: lowercasing, tokenization (splitting into words), and stemming (reducing words to root forms using Porter Stemmer or similar). This reduces vocabulary size and improves generalization. **2. Preprocessing → Feature Extraction** Processed tokens are converted into numerical representations using bag-of-words (BoW) encoding. Each word in the training vocabulary becomes a binary feature (1 if present, 0 if absent), creating a fixed-length vector that represents the input sentence. **3. Feature Vector → Neural Network** The bag-of-words vector feeds into a feedforward neural network trained to classify intents. The network learns to map word patterns to intent categories through supervised learning on the labeled training corpus. **4. Intent Prediction → Response Generation** The model outputs a probability distribution over all possible intents. The highest-probability intent is selected, and the system retrieves a corresponding response from the JSON corpus. Responses can be randomized from a pool to add variety. **JSON Corpus Structure:** The training data is organized as intents, each containing example patterns (user inputs) and responses. This modular structure allows rapid updates, adding new intents simply requires extending the JSON file and retraining.
Text Preprocessor
Bag-of-Words Encoder
Intent Classifier (Neural Network)
Response Generator
Training Pipeline
Data flow
- Intent corpus loaded from JSON JSON file contains intent tags, example patterns, and response templates. Parser extracts training data and builds vocabulary.
- Training data generation Each pattern is tokenized, stemmed, and converted to bag-of-words representation. Labels are one-hot encoded for intent categories.
- Model training Neural network is trained using gradient descent (Adam optimizer) with categorical cross-entropy loss. Validation set monitors overfitting.
- User input processing At inference time, user text follows the same preprocessing → bag-of-words → model prediction pipeline.
- Intent confidence thresholding If highest probability falls below confidence threshold (e.g., 0.7), the bot returns a fallback response indicating uncertainty.
- Response delivery Selected intent triggers response lookup from JSON corpus. System randomly selects from available responses for natural conversation flow.
Key decisions and trade-offs
Classical NLP over Transformers
Chose bag-of-words + neural network instead of BERT/GPT models to minimize computational overhead. This approach provides sub-50ms inference on CPU, making it suitable for lightweight deployment scenarios where heavy transformer models would be overkill.
Trade-off: Sacrifices semantic understanding of complex queries for speed and simplicity. Works well for domain-specific intents with clear patterns but struggles with highly ambiguous or context-dependent inputs.
Stemming vs. Lemmatization
Used Porter Stemmer rather than lemmatization for preprocessing. Stemming is faster and doesn't require POS tagging, reducing pipeline complexity while still achieving vocabulary reduction.
Trade-off: Stemming can be overly aggressive (e.g., 'university' → 'univers'), potentially merging semantically different words. Acceptable trade-off for intent classification where exact word forms matter less than pattern recognition.
Modular JSON corpus architecture
Designed intent definitions as standalone JSON objects rather than hardcoding logic. This enables non-technical users to update bot behavior by editing JSON without touching code.
Trade-off: Requires retraining when intents change, adding a deployment step. However, training is fast enough (<1 minute on small datasets) that this isn't a major bottleneck.
Confidence threshold with fallback
Implemented confidence scoring to detect when the model is uncertain. Low-confidence predictions trigger generic fallback responses rather than potentially incorrect answers.
Trade-off: May miss some valid queries that fall outside training distribution. Better to admit uncertainty than provide wrong information in customer-facing applications.
Code highlights
import nltk
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
def preprocess(sentence):
"""
Tokenize and stem input sentence.
Returns list of stemmed word tokens.
"""
# Tokenize into words
tokens = nltk.word_tokenize(sentence.lower())
# Remove punctuation and stem each word
stemmed_words = [
stemmer.stem(word)
for word in tokens
if word.isalnum()
]
return stemmed_words
# Example
# Input: "What's the weather forecast?"
# Output: ['what', 'weather', 'forecast']
def bag_of_words(tokenized_input, vocabulary):
"""
Convert tokenized input to binary bag-of-words vector.
vocabulary: list of all unique words from training data
"""
bag = [0] * len(vocabulary)
for token in tokenized_input:
if token in vocabulary:
idx = vocabulary.index(token)
bag[idx] = 1
return bag
# Example
# vocabulary = ['hello', 'weather', 'forecast', 'goodbye']
# Input tokens: ['weather', 'forecast']
# Output: [0, 1, 1, 0]
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Dropout
def build_model(input_size, num_intents):
"""
Build feedforward neural network for intent classification.
"""
model = Sequential([
Dense(128, input_shape=(input_size,), activation='relu'),
Dropout(0.5),
Dense(64, activation='relu'),
Dropout(0.5),
Dense(num_intents, activation='softmax')
])
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
return model
# Architecture:
# Input Layer: Bag-of-words vector (e.g., 200 features)
# Hidden Layer 1: 128 neurons, ReLU activation
# Dropout: 50% to prevent overfitting
# Hidden Layer 2: 64 neurons, ReLU activation
# Dropout: 50%
# Output Layer: Softmax over intent classes
import numpy as np
def predict_intent(user_input, model, vocabulary, intents, threshold=0.7):
"""
Predict intent from user input with confidence thresholding.
"""
# Preprocess input
tokens = preprocess(user_input)
bow = bag_of_words(tokens, vocabulary)
bow_array = np.array([bow])
# Get prediction probabilities
predictions = model.predict(bow_array)[0]
max_prob = np.max(predictions)
# Check confidence threshold
if max_prob < threshold:
return {
'intent': 'unknown',
'confidence': max_prob,
'response': "I'm not sure I understand. Could you rephrase?"
}
# Get highest probability intent
intent_idx = np.argmax(predictions)
predicted_intent = intents[intent_idx]
return {
'intent': predicted_intent['tag'],
'confidence': float(max_prob),
'response': np.random.choice(predicted_intent['responses'])
}
{
"intents": [
{
"tag": "greeting",
"patterns": [
"Hi",
"Hello",
"Hey there",
"Good morning",
"What's up"
],
"responses": [
"Hello! How can I help you today?",
"Hi there! What can I do for you?",
"Hey! What brings you here?"
]
},
{
"tag": "weather",
"patterns": [
"What's the weather like?",
"Tell me the forecast",
"Is it going to rain?",
"Weather forecast please"
],
"responses": [
"I can help with weather info! What city?",
"Sure, which location do you need weather for?"
]
},
{
"tag": "goodbye",
"patterns": [
"Bye",
"See you later",
"Goodbye",
"Thanks, that's all"
],
"responses": [
"Goodbye! Have a great day!",
"See you later!",
"Thanks for chatting!"
]
}
]
}
- Snappy prototype for FAQs & utilities.
Intent classification quality depends heavily on training data diversity
The model performs well only when training patterns cover the variety of ways users express the same intent. Initially struggled with sparse training data, adding 3-5 more pattern variations per intent improved accuracy by ~20%. Data augmentation through paraphrasing and synonym replacement helped expand coverage without manual labeling.
Preprocessing consistency is critical between training and inference
Early bug: training data was stemmed but inference input wasn't, causing poor predictions. Learned to abstract preprocessing into reusable functions and validate that exact same pipeline runs during both training and deployment. Saved preprocessing configuration alongside model weights.
Bag-of-words loses word order information
BoW treats 'not good' and 'good' identically if both words appear, missing negation. For this project scope it was acceptable, but production systems need n-grams or sequential models (RNN/LSTM) to capture context. Identified this as a limitation when testing edge cases.
Confidence thresholding significantly improves user experience
Without confidence checks, the bot would confidently return wrong answers for out-of-domain queries. Implementing a 0.7 threshold and fallback responses reduced user frustration, better to say 'I don't know' than mislead. Tested threshold values empirically using validation set.
Neural networks vs. classical ML for intent classification
Experimented with both neural networks and traditional classifiers (SVM, Naive Bayes). Neural networks handled sparse BoW vectors better and generalized to unseen patterns more effectively, despite being more complex. The non-linear activation functions helped learn subtle pattern combinations that linear models missed.
Modular architecture enables rapid iteration
Separating intent definitions (JSON), preprocessing logic (Python module), and model architecture (separate file) made experimentation faster. Could swap out models or add intents without refactoring. This design pattern proved valuable when testing different neural network architectures and preprocessing strategies.