Skip to content

Commit 0aa800c

Browse files
authored
feat: Add Logistic Regression classifier (#108)
* feat: add Logistic Regression classifier Add Classifier::LogisticRegression with SGD training and L2 regularization. Provides better calibrated probabilities than Naive Bayes and feature weights for interpretability. Closes #102 * style: fix rubocop offenses in logistic regression tests * docs: add 'Why This Library?' section highlighting unique features * docs: improve README with comparison table and modernized examples * feat: add storage API to TF-IDF and complete LogisticRegression storage - Add .storage, .save, .load, .reload, .reload!, .dirty? to TF-IDF - Add .reload and .reload! to LogisticRegression - Add StorageAPIConsistencyTest to enforce all classifiers implement storage API - Add comprehensive storage tests for both TF-IDF and LogisticRegression
1 parent 04324e6 commit 0aa800c

6 files changed

Lines changed: 1637 additions & 31 deletions

File tree

README.md

Lines changed: 106 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,48 @@
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, LSI (Latent Semantic Indexing), k-Nearest Neighbors (kNN), and TF-IDF algorithms.
7+
A Ruby library for text classification using Bayesian, Logistic Regression, LSI (Latent Semantic Indexing), k-Nearest Neighbors (kNN), and TF-IDF algorithms.
88

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

11+
> **Note:** This is the original `classifier` gem, maintained for 20 years since 2005. After a quieter period, active development resumed in 2025 with major new features. If you're choosing between this and a fork, this is the canonical, most actively-developed version.
12+
13+
## Why This Library?
14+
15+
This gem has features no fork provides:
16+
17+
| | This Gem | Forks |
18+
|:--|:--|:--|
19+
| **Algorithms** | 5 classifiers | 2 only |
20+
| **LSI Performance** | Native C (5-50x faster) | Pure Ruby, or requires GSL/Numo + system libs |
21+
| **Persistence** | Pluggable (file, Redis, S3, SQL) | Marshal only |
22+
| **Thread Safety** |||
23+
| **Type Annotations** | RBS throughout ||
24+
| **Laplace Smoothing** | Numerically stable | ❌ Unstable |
25+
| **Probability Calibration** |||
26+
| **Feature Weights** | ✅ Interpretable ||
27+
28+
### Recent Developments (Late 2025)
29+
30+
- Added Logistic Regression classifier with SGD and L2 regularization
31+
- Added k-Nearest Neighbors classifier with distance-weighted voting
32+
- Added TF-IDF vectorizer with n-gram support
33+
- Built zero-dependency native C extension (replaces GSL or Numo requirement)
34+
- Added pluggable storage backends for persistence
35+
- Made all classifiers thread-safe
36+
- Fixed Laplace smoothing for numerical stability
37+
- Added RBS type signatures throughout
38+
- Modernized for new Ruby coding standards
39+
1140
## Table of Contents
1241

1342
- [Installation](#installation)
14-
- [Bayesian Classifier](#bayesian-classifier)
15-
- [LSI (Latent Semantic Indexing)](#lsi-latent-semantic-indexing)
16-
- [k-Nearest Neighbors (kNN)](#k-nearest-neighbors-knn)
17-
- [TF-IDF Vectorizer](#tf-idf-vectorizer)
43+
- [Algorithms](#bayesian-classifier)
44+
- [Bayesian Classifier](#bayesian-classifier)
45+
- [Logistic Regression](#logistic-regression)
46+
- [LSI (Latent Semantic Indexing)](#lsi-latent-semantic-indexing)
47+
- [k-Nearest Neighbors (kNN)](#k-nearest-neighbors-knn)
48+
- [TF-IDF Vectorizer](#tf-idf-vectorizer)
1849
- [Persistence](#persistence)
1950
- [Performance](#performance)
2051
- [Development](#development)
@@ -43,7 +74,7 @@ gem install classifier
4374

4475
### Native C Extension
4576

46-
The gem includes a native C extension for fast LSI operations. It compiles automatically during gem installation. No external dependencies are required.
77+
The gem includes a zero-dependency native C extension for fast LSI operations (5-50x faster than pure Ruby). It compiles automatically during installation.
4778

4879
To verify the native extension is active:
4980

@@ -58,22 +89,6 @@ To force pure Ruby mode (for debugging):
5889
NATIVE_VECTOR=true ruby your_script.rb
5990
```
6091

61-
To suppress the warning when native extension isn't available:
62-
63-
```bash
64-
SUPPRESS_LSI_WARNING=true ruby your_script.rb
65-
```
66-
67-
### Compatibility
68-
69-
| Ruby Version | Status |
70-
|--------------|--------|
71-
| 4.0 | Supported |
72-
| 3.4 | Supported |
73-
| 3.3 | Supported |
74-
| 3.2 | Supported |
75-
| 3.1 | EOL (unsupported) |
76-
7792
## Bayesian Classifier
7893

7994
Fast, accurate classification with modest memory requirements. Ideal for spam filtering, sentiment analysis, and content categorization.
@@ -86,7 +101,7 @@ require 'classifier'
86101
classifier = Classifier::Bayes.new(:spam, :ham)
87102

88103
# Train with keyword arguments
89-
classifier.train(spam: "Buy cheap viagra now! Limited offer!")
104+
classifier.train(spam: "Buy cheap v1agra now! Limited offer!")
90105
classifier.train(ham: "Meeting scheduled for tomorrow at 10am")
91106

92107
# Train multiple items at once
@@ -106,6 +121,66 @@ classifier.classify "Congratulations! You've won a prize!"
106121
- [Build a Spam Filter Tutorial](https://rubyclassifier.com/docs/tutorials/spam-filter) - Step-by-step guide
107122
- [Paul Graham: A Plan for Spam](http://www.paulgraham.com/spam.html)
108123

124+
## Logistic Regression
125+
126+
Linear classifier using Stochastic Gradient Descent (SGD). Often more accurate than Naive Bayes while remaining fast and interpretable. Provides well-calibrated probability estimates and feature weights for understanding which words drive predictions.
127+
128+
### Key Features
129+
130+
- **More Accurate**: No independence assumption means better accuracy on many text problems
131+
- **Calibrated Probabilities**: Unlike Bayes, probabilities reflect true confidence
132+
- **Interpretable**: Feature weights show which words matter for each class
133+
- **Fast**: Linear time prediction, efficient SGD training
134+
- **L2 Regularization**: Prevents overfitting on small datasets
135+
136+
### Quick Start
137+
138+
```ruby
139+
require 'classifier'
140+
141+
classifier = Classifier::LogisticRegression.new(:spam, :ham)
142+
143+
# Train with keyword arguments (same API as Bayes)
144+
classifier.train(spam: ["Buy now! Free money!", "Click here for prizes!"])
145+
classifier.train(ham: ["Meeting tomorrow", "Please review the document"])
146+
147+
# Classify new text
148+
classifier.classify "Claim your free prize!"
149+
# => "Spam"
150+
151+
# Get calibrated probabilities
152+
classifier.probabilities "Claim your free prize!"
153+
# => {"Spam" => 0.92, "Ham" => 0.08}
154+
155+
# See which words matter most
156+
classifier.weights(:spam, limit: 5)
157+
# => {:free => 2.3, :prize => 1.9, :claim => 1.5, ...}
158+
```
159+
160+
### Hyperparameters
161+
162+
```ruby
163+
classifier = Classifier::LogisticRegression.new(
164+
:positive, :negative,
165+
learning_rate: 0.1, # SGD step size (default: 0.1)
166+
regularization: 0.01, # L2 penalty strength (default: 0.01)
167+
max_iterations: 100, # Training iterations (default: 100)
168+
tolerance: 1e-4 # Convergence threshold (default: 1e-4)
169+
)
170+
```
171+
172+
### When to Use Logistic Regression vs Bayes
173+
174+
| Aspect | Logistic Regression | Naive Bayes |
175+
|--------|---------------------|-------------|
176+
| **Accuracy** | Often higher | Good baseline |
177+
| **Probabilities** | Well-calibrated | Tend to be extreme |
178+
| **Training** | Needs to fit model | Instant (just counting) |
179+
| **Interpretability** | Feature weights | Word frequencies |
180+
| **Small datasets** | Use higher regularization | Works well |
181+
182+
Use Logistic Regression when you need accurate probabilities or want to understand feature importance. Use Bayes when you need instant updates or have very small training sets.
183+
109184
## LSI (Latent Semantic Indexing)
110185

111186
Semantic analysis using Singular Value Decomposition (SVD). More flexible than Bayesian classifiers, providing search, clustering, and classification based on meaning rather than just keywords.
@@ -117,18 +192,18 @@ require 'classifier'
117192

118193
lsi = Classifier::LSI.new
119194

120-
# Add documents with hash-style syntax (category => item(s))
121-
lsi.add("Pets" => "Dogs are loyal pets that love to play fetch")
122-
lsi.add("Pets" => "Cats are independent and love to nap")
123-
lsi.add("Programming" => "Ruby is a dynamic programming language")
195+
# Add documents (category: item(s))
196+
lsi.add(pets: "Dogs are loyal pets that love to play fetch")
197+
lsi.add(pets: "Cats are independent and love to nap")
198+
lsi.add(programming: "Ruby is a dynamic programming language")
124199

125200
# Add multiple items with the same category
126-
lsi.add("Programming" => ["Python is great for data science", "JavaScript runs in browsers"])
201+
lsi.add(programming: ["Python is great for data science", "JavaScript runs in browsers"])
127202

128203
# Batch operations with multiple categories
129204
lsi.add(
130-
"Pets" => ["Hamsters are small furry pets", "Birds can be great companions"],
131-
"Programming" => "Go is fast and concurrent"
205+
pets: ["Hamsters are small furry pets", "Birds can be great companions"],
206+
programming: "Go is fast and concurrent"
132207
)
133208

134209
# Classify new text
@@ -330,7 +405,7 @@ loaded = Marshal.load(data)
330405

331406
## Persistence
332407

333-
Save and load classifiers with pluggable storage backends. Works with Bayes, LSI, and kNN classifiers.
408+
Save and load classifiers with pluggable storage backends. Works with Bayes, LogisticRegression, LSI, and kNN classifiers.
334409

335410
### File Storage
336411

lib/classifier.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@
3333
require 'classifier/lsi'
3434
require 'classifier/knn'
3535
require 'classifier/tfidf'
36+
require 'classifier/logistic_regression'

0 commit comments

Comments
 (0)