This document explains the approach taken to design and implement a keyword extraction system as part of the AI-powered text analysis project. The system extracts the most important keywords or phrases from input text or .txt files via a web interface, using a React front-end and Flask backend, deployed with Docker. Below, I outline the problem breakdown, choice of algorithms and models, text preprocessing techniques (inspired by Aysel Aydin's article), implementation details, potential improvements, and evaluation strategies.
The task was to build a simple AI-powered text analysis system, selecting one of three options: keyword extraction, text summarization, or topic classification. I chose keyword extraction because:
- I think it is a useful content analysis tool for search engine which I am interested in.
- It leverages modern transformer-based models like BERT, which excel at capturing contextual semantics, and I had hands-on experience in using BERT related models in my Machine learning course porject.
- It aligns with the requirement for a simple yet impactful system that can be extended with a front-end interface.
I first break it down into sub-problems:
- Core Functionality: Develop an AI model to extract keywords/phrases from text.
- Text Preprocessing: Clean and prepare raw text for analysis, following best practices in NLP.
- Interface: Provide a user-friendly API and front-end for interaction.
- Deployment: Ensure easy setup and scalability using containerization.
After doing some research on models and text preprocessing,
I selected KeyBERT, a lightweight, pre-trained keyword extraction framework built on BERT embeddings, for the following reasons:
- BERT Embeddings: KeyBERT uses BERT-based models (e.g.,
all-MiniLM-L6-v2) to generate contextual embeddings for words and phrases. Unlike traditional methods like TF-IDF, BERT captures semantic relationships, making it ideal for identifying contextually relevant keywords. - Ease of Use: KeyBERT combines BERT with cosine similarity and Maximal Marginal Relevance (MMR) for diversity, reducing implementation complexity while maintaining effectiveness.
- Performance: The
all-MiniLM-L6-v2model (22M parameters, 384-dimensional embeddings) is optimized for speed, suitable for a simple system and being lightweight. - Preprocessing Integration: KeyBERT supports custom candidate phrases (e.g., noun phrases), enabling seamless integration with spaCy for enhanced preprocessing.
I used spaCy with the en_core_web_md model for text preprocessing:
- Noun Phrase Extraction: spaCy identifies noun phrases (e.g., “artificial intelligence”) as candidate keywords, improving input quality for KeyBERT.
- Efficiency: The medium-sized model (31 MB, 300-dimensional vectors) balances accuracy and speed.
- Preprocessing Support: spaCy’s pipeline handles tokenization and syntactic analysis, critical for cleaning text.
I employed NLTK for stopword removal:
- Simplicity: NLTK’s stopword list is lightweight and effective for filtering common words (e.g., “the”, “is”).
- Customization: I extended the stopword list with domain-specific terms (e.g., “via”, “skip”) to improve keyword relevance.
- TF-IDF: Lacks contextual understanding, producing less relevant keywords.
- TextRank: Graph-based, less effective for short texts and lacks BERT’s semantic depth.
- LDA: Suited for topic modeling, not precise keyword extraction.
- Larger BERT Models: Models like
bert-base-uncased(110M parameters) are computationally expensive for a simple system. - T5/BART: Better for summarization or generation, not keyword extraction.
KeyBERT, spaCy, and NLTK provided a balance of accuracy, simplicity, and deployability.
Inspired by Aysel Aydin’s article on text preprocessing for NLP, I implemented the following techniques to clean and prepare text for keyword extraction. These steps ensure the text is structured and noise-free, enhancing KeyBERT’s performance.
- Purpose: Converts all text to lowercase to ensure case-insensitive processing (e.g., “AI” and “ai” are treated as the same).
- Implementation: In
backend/app.py, theTextPreprocessor.clean_textmethod uses Python’stext.lower():text = text.lower()
- Why: Prevents KeyBERT from treating identical words with different cases as distinct, improving consistency.
- Purpose: Removes punctuation (e.g., commas, exclamation marks) and special characters to focus on meaningful words.
- Implementation: Uses a regular expression in
clean_text:text = re.sub(r'[^\w\s-]', '', text)
- Why: Reduces noise, ensuring KeyBERT processes only alphanumeric content and hyphens (for compound terms like “machine-learning”).
- Purpose: Eliminates common words (e.g., “the”, “is”) that don’t contribute to meaning.
- Implementation: Uses NLTK’s stopword list in
TextPreprocessor:stop_words = set(stopwords.words('english')) stop_words.update(['via', 'using', 'eg', 'ie', 'skip']) word_tokens = text.split() text = ' '.join([word for word in word_tokens if len(word) >= 1])
- Why: Focuses KeyBERT on content words, with custom stopwords (e.g., “skip”) to filter irrelevant terms in snippets like “Skip to content”.
- Purpose: Removes URLs (e.g.,
https://example.com) that are irrelevant to keyword extraction. - Implementation: Uses a regex in
clean_text:text = re.sub(r'https?://\S+|www\.\S+', '', text)
- Why: Eliminates web-specific noise, common in user inputs like social media snippets.
- Purpose: Strips HTML tags (e.g.,
<p>) from web-scraped or formatted text. - Implementation: Uses a regex in
clean_text:text = re.sub(r'<.*?>', '', text)
- Why: Ensures clean text for KeyBERT, especially for inputs copied from web pages.
- Whitespace Normalization: Collapses multiple spaces into a single space:
text = re.sub(r'\s+', ' ', text.strip())
- Noun Phrase Extraction: Uses spaCy’s dependency parser in
extract_noun_phrasesto identify multi-word phrases (e.g., “machine learning”):doc = self.nlp(text) phrases = [chunk.text for chunk in doc.noun_chunks if len(chunk.text) >= 1]
- Why: Noun phrases are more meaningful candidates for KeyBERT than single words, improving keyword quality.
The preprocessing steps are applied in the following order:
- Lowercasing
- URL removal
- HTML tag removal
- Punctuation removal
- Whitespace normalization
- Stopword removal (implicitly via spaCy and NLTK)
- Noun phrase extraction
This order ensures case-insensitive processing before removing noise, as recommended by Aydin’s article (e.g., lowercasing before stopword removal to catch “This” and “this”).
- Framework: Flask, chosen for its lightweight RESTful API capabilities.
- Preprocessing:
TextPreprocessorclass implements the above techniques, with caching (lru_cache) for noun phrase extraction.- Combines spaCy for syntactic analysis and NLTK for stopwords.
- Keyword Extraction:
KeywordExtractorwraps KeyBERT, using MMR (diversity=0.3) and a score threshold (>0.05).- Fallbacks: Uses top-2 keywords or re-runs KeyBERT without candidates if no high-scoring keywords.
- API:
/extract_keywordsaccepts JSON ({"text": "..."}) or.txtfiles, returning keywords and scores. - CORS: Allows requests from
http://frontend:8080(Docker) andhttp://localhost:8080(local). - Deployment: Gunicorn with 2 workers and 120-second timeout in Docker.
- Framework: React for component-based UI.
- Styling: Tailwind CSS for responsive design.
- Build: Babel compiles JSX to static JavaScript, served by Nginx.
- Features: Text/file input, keyword display with toggleable scores, error handling, and loading states.
- API Calls: Fetches
/extract_keywordsfromhttp://backend:5001.
- Docker: Containerizes frontend (Node.js + Nginx) and backend (Python + Gunicorn).
- Docker Compose: Defines services with a shared
keyword-networkfor communication. - Ports: Frontend at
8080, backend at5001.
- Modularity: Separated preprocessing and extraction into classes.
- Error Handling: API returns 400/500 status codes; frontend displays user-friendly errors.
- Logging: Backend logs requests and errors.
- Preprocessing:
- Add stemming/lemmatization (e.g., spaCy’s lemmatizer) to normalize words (e.g., “running” → “run”).
- Use dependency parsing for more precise multi-word phrases.
- Model:
- Fine-tune
all-MiniLM-L6-v2on domain-specific data (e.g., news) for better relevance. - Experiment with larger models (e.g.,
all-distilroberta-v1) if resources allow.
- Fine-tune
- Deployment:
- Deploy to AWS ECS or Kubernetes for production.
- Add CI/CD with GitHub Actions.
- Multilingual Support:
- Use
paraphrase-multilingual-MiniLM-L12-v2and spaCy’s multilingual models.
- Use
- Manual Evaluation:
- Curate texts (e.g., news, Wikipedia) with human-annotated keywords.
- Compute precision, recall, and F1-score against ground truth.
- Automated Metrics:
- Use ROUGE to compare keywords to reference summaries.
- Measure cosine similarity between keyword and document embeddings.
The system combines KeyBERT’s semantic power, spaCy/NLTK preprocessing, and a modular Flask/React architecture. Docker ensures portability. Future enhancements could boost accuracy, scalability, and user experience.