-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(classifiers): implement KNN algorithm
- Loading branch information
matthieu
committed
Aug 8, 2019
1 parent
b2bc8f1
commit 27e1875
Showing
3 changed files
with
121 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
"""K-Nearest Neighbors""" | ||
|
||
import numpy as np | ||
|
||
from collections import Counter | ||
|
||
from alchina.exceptions import NotFitted | ||
from alchina.metrics import accuracy_score | ||
|
||
|
||
class KNNClassifier(object): | ||
"""K-Nearest Neighbors algorithm""" | ||
|
||
def __init__(self, n_neighbors=3): | ||
self.n_neighbors = n_neighbors | ||
|
||
self.X_fit = None | ||
self.y_fit = None | ||
|
||
def euclidian(self, a, b): | ||
"""Compute the euclidian distance between two samples.""" | ||
return np.linalg.norm(a - b) | ||
|
||
def fit(self, X, y): | ||
"""Train the model.""" | ||
self.X_fit = X | ||
self.y_fit = y | ||
|
||
def predict(self, X): | ||
"""Predict a target given features.""" | ||
if self.X_fit is None or self.y_fit is None: | ||
raise NotFitted("the model must be fitted before usage") | ||
|
||
labels = [] | ||
for x in X: | ||
distances_labels = [ | ||
(self.euclidian(x, x_fit), y_fit) | ||
for x_fit, y_fit in zip(self.X_fit, self.y_fit) | ||
] | ||
neighbors = sorted(distances_labels, key=lambda d: d[0])[: self.n_neighbors] | ||
neighbors_labels = [neighbor[1][0] for neighbor in neighbors] | ||
labels.append( | ||
sorted( | ||
neighbors_labels, key=Counter(neighbors_labels).get, reverse=True | ||
)[0] | ||
) | ||
return np.array(labels).reshape(-1, 1) | ||
|
||
def score(self, X, y): | ||
"""Score of the model.""" | ||
if self.X_fit is None or self.y_fit is None: | ||
raise NotFitted("the model must be fitted before usage") | ||
return accuracy_score(self.predict(X), y) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
"""K-Nearest Neighbors tests.""" | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
from alchina.classifiers import KNNClassifier | ||
from alchina.exceptions import NotFitted | ||
|
||
|
||
# --- Linear classifier --- | ||
|
||
|
||
def test_knn_classifier(): | ||
"""Test of `KNNClassifier` class.""" | ||
knn = KNNClassifier(1) | ||
|
||
X = np.array([[0], [1]]) | ||
y = np.array([[0], [1]]) | ||
|
||
knn.fit(X, y) | ||
|
||
assert knn.score(X, y) == 1 | ||
|
||
|
||
def test_knn_classifier_predict(): | ||
"""Test of `KNNClassifier` class with a prediction.""" | ||
knn = KNNClassifier(1) | ||
|
||
X = np.array([[0], [1]]) | ||
y = np.array([[0], [1]]) | ||
|
||
knn.fit(X, y) | ||
|
||
assert np.equal(knn.predict(np.array([0])), np.array([0])) | ||
|
||
|
||
def test_knn_classifier_multiclass(): | ||
"""Test of `LinearClassifier` with multiclass.""" | ||
knn = KNNClassifier(1) | ||
|
||
X = np.array([[0], [1], [2]]) | ||
y = np.array([[0], [1], [2]]) | ||
|
||
knn.fit(X, y) | ||
|
||
assert knn.score(X, y) == 1 | ||
|
||
|
||
def test_knn_classifier_predict_not_fitted(): | ||
"""Test of `KNNClassifier` class with prediction without fit.""" | ||
knn = KNNClassifier(1) | ||
|
||
X = np.array([[0], [1]]) | ||
|
||
with pytest.raises(NotFitted): | ||
knn.predict(X) | ||
|
||
|
||
def test_knn_classifier_score_not_fitted(): | ||
"""Test of `KNNClassifier` class with score calculation without fit.""" | ||
knn = KNNClassifier(1) | ||
|
||
X = np.array([[0], [1]]) | ||
y = np.array([[0], [1]]) | ||
|
||
with pytest.raises(NotFitted): | ||
knn.score(X, y) == 1 |