Skip to content

Commit fde311b

Browse files
authored
feat(knn): add k-Nearest Neighbors classifier (#105)
* feat(knn): add k-Nearest Neighbors classifier Implements a kNN classifier that leverages existing LSI infrastructure for similarity computations. Unlike Bayes which requires training, kNN uses instance-based learning—store examples and classify by finding the most similar ones. Key features: - Hash-style API consistent with Bayes and LSI (add, classify) - classify_with_neighbors() returns interpretable results with neighbor details, vote tallies, and confidence scores - Distance-weighted voting option for more nuanced classification - Full JSON/Marshal serialization and storage backend support - Handles edge cases like single-item classifiers gracefully This completes Issue #103 and provides a third classification algorithm suited for small datasets where interpretability matters. Closes #103 * docs: clarify kNN vs Bayes size guidance * refactor(knn): remove YARD tags, keep descriptions Remove @param/@return/@example tags that duplicate @RBS type info. Keep method descriptions that explain behavior. Remove deprecated add_item method (new class doesn't need legacy API). * refactor: address code review feedback - Fix LSI single-item bug instead of working around in KNN - Remove defensive max(0) check on similarity scores - Use early return for nil winner - Simplify guard clause with 'or next' - Condense class docstring - Avoid mutating input hash with .dup
1 parent 2bdfc9d commit fde311b

6 files changed

Lines changed: 895 additions & 4 deletions

File tree

README.md

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![CI](https://github.com/cardmagic/classifier/actions/workflows/ruby.yml/badge.svg)](https://github.com/cardmagic/classifier/actions/workflows/ruby.yml)
55
[![License: LGPL](https://img.shields.io/badge/License-LGPL_2.1-blue.svg)](https://opensource.org/licenses/LGPL-2.1)
66

7-
A Ruby library for text classification using Bayesian and Latent Semantic Indexing (LSI) algorithms.
7+
A Ruby library for text classification using Bayesian, LSI (Latent Semantic Indexing), and k-Nearest Neighbors (kNN) algorithms.
88

99
**[Documentation](https://rubyclassifier.com/docs)** · **[Tutorials](https://rubyclassifier.com/docs/tutorials)** · **[Guides](https://rubyclassifier.com/docs/guides)**
1010

@@ -13,6 +13,7 @@ A Ruby library for text classification using Bayesian and Latent Semantic Indexi
1313
- [Installation](#installation)
1414
- [Bayesian Classifier](#bayesian-classifier)
1515
- [LSI (Latent Semantic Indexing)](#lsi-latent-semantic-indexing)
16+
- [k-Nearest Neighbors (kNN)](#k-nearest-neighbors-knn)
1617
- [Persistence](#persistence)
1718
- [Performance](#performance)
1819
- [Development](#development)
@@ -170,9 +171,94 @@ gem 'pragmatic_segmenter'
170171
- [LSI Basics Guide](https://rubyclassifier.com/docs/guides/lsi/basics) - In-depth documentation
171172
- [Wikipedia: Latent Semantic Analysis](http://en.wikipedia.org/wiki/Latent_semantic_analysis)
172173

174+
## k-Nearest Neighbors (kNN)
175+
176+
Instance-based classification that stores examples and classifies by finding the most similar ones. No training phase required—just add examples and classify.
177+
178+
### Key Features
179+
180+
- **No Training Required**: Uses instance-based learning—store examples and classify by similarity
181+
- **Interpretable Results**: Returns neighbors that contributed to the decision
182+
- **Incremental Updates**: Easy to add or remove examples without retraining
183+
- **Distance-Weighted Voting**: Optional weighting by similarity score
184+
- **Built on LSI**: Leverages LSI's semantic similarity for better matching
185+
186+
### Quick Start
187+
188+
```ruby
189+
require 'classifier'
190+
191+
knn = Classifier::KNN.new(k: 3)
192+
193+
# Add labeled examples
194+
knn.add(spam: ["Buy now! Limited offer!", "You've won a million dollars!"])
195+
knn.add(ham: ["Meeting at 3pm tomorrow", "Please review the document"])
196+
197+
# Classify new text
198+
knn.classify "Congratulations! Claim your prize!"
199+
# => "spam"
200+
```
201+
202+
### Detailed Classification
203+
204+
Get neighbor information for interpretable results:
205+
206+
```ruby
207+
result = knn.classify_with_neighbors "Free money offer"
208+
209+
result[:category] # => "spam"
210+
result[:confidence] # => 0.85
211+
result[:neighbors] # => [{item: "Buy now!...", category: "spam", similarity: 0.92}, ...]
212+
result[:votes] # => {"spam" => 2.0, "ham" => 1.0}
213+
```
214+
215+
### Distance-Weighted Voting
216+
217+
Weight votes by similarity score for more accurate classification:
218+
219+
```ruby
220+
knn = Classifier::KNN.new(k: 5, weighted: true)
221+
222+
knn.add(
223+
positive: ["Great product!", "Loved it!", "Excellent service"],
224+
negative: ["Terrible experience", "Would not recommend"]
225+
)
226+
227+
# Closer neighbors have more influence on the result
228+
knn.classify "This was amazing!"
229+
# => "positive"
230+
```
231+
232+
### Updating the Classifier
233+
234+
```ruby
235+
# Add more examples anytime
236+
knn.add(neutral: "It was okay, nothing special")
237+
238+
# Remove examples
239+
knn.remove_item "Buy now! Limited offer!"
240+
241+
# Change k value
242+
knn.k = 7
243+
244+
# List all categories
245+
knn.categories
246+
# => ["spam", "ham", "neutral"]
247+
```
248+
249+
### When to Use kNN vs Bayes vs LSI
250+
251+
| Classifier | Best For |
252+
|------------|----------|
253+
| **Bayes** | Fast classification, any training size (stores only word counts) |
254+
| **LSI** | Semantic similarity, document clustering, search |
255+
| **kNN** | <1000 examples, interpretable results, incremental updates |
256+
257+
**Why the size difference?** Bayes stores aggregate statistics—adding 10,000 documents just increments counters. kNN stores every example and compares against all of them during classification, so performance degrades with size.
258+
173259
## Persistence
174260

175-
Save and load trained classifiers with pluggable storage backends. Works with both Bayes and LSI classifiers.
261+
Save and load classifiers with pluggable storage backends. Works with Bayes, LSI, and kNN classifiers.
176262

177263
### File Storage
178264

classifier.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Gem::Specification.new do |s|
22
s.name = 'classifier'
33
s.version = '2.1.0'
4-
s.summary = 'A general classifier module to allow Bayesian and other types of classifications.'
5-
s.description = 'A general classifier module to allow Bayesian and other types of classifications.'
4+
s.summary = 'Text classification with Bayesian, LSI, and k-Nearest Neighbors algorithms.'
5+
s.description = 'A Ruby library for text classification using Bayesian, LSI (Latent Semantic Indexing), and k-Nearest Neighbors (kNN) algorithms. Includes native C extension for fast LSI operations.'
66
s.author = 'Lucas Carlson'
77
s.email = 'lucas@rufy.com'
88
s.homepage = 'https://rubyclassifier.com'

lib/classifier.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@
3131
require 'classifier/extensions/vector'
3232
require 'classifier/bayes'
3333
require 'classifier/lsi'
34+
require 'classifier/knn'

0 commit comments

Comments
 (0)