class KNN:
def __init__(self, n_neighbors):
self.n_neighbors = n_neighbors
self.model_trained = False
'''
Ucitavanje trening podataka
* X - matrica ulaznih podataka
* y - kolona oznaka klasa
'''
def fit(self, X, y):
if isinstance(X, pd.DataFrame):
self.X = data.values
else:
self.X = X
if isinstance(y, pd.DataFrame):
self.y = data.values
else:
self.y = y
if self.X.shape[0] != self.y.shape[0]:
raise Exception('X and y have different shapes')
self.X = None
self.y = None
self.model_trained = True
'''
Priprema matrica podataka
* data - matrica ulaznih podataka (numpy matrica/pandas dataframe/2D niz/...)
'''
def _prepare_data_matrix(self, data):
data_matrix = None
# Provera formata ulaznih podataka
if isinstance(data, pd.DataFrame):
data_matrix = data.values
else:
data_matrix = np.array(data)
if len(data_matrix.shape) < 2:
raise Exception('Invalid data shape')
# Konacni oblik pripremljenih podataka za obradu je numpy matrica
return data_matrix
'''
Izracunavanje udaljenosti izmedju dva vektora
* u - vektor
* v - vektor
* metric - odabrana metrika (podrazumevano je euklidsko rastojanje)
'''
def distance(self, u, v, metric='euclidean'):
if metric == 'euclidean':
return np.linalg.norm(u-v)
else:
raise Exception('Unknown metric')
'''
Izracunavanje matrice udaljenosti izmedju ulaznih i trening podataka
* data_matrix - matrica ulaznih podataka
'''
def _compute_distance_matrix(self, data_matrix):
X = self.X
return np.array(
[
[self.distance(train_vector, prediction_vector) for train_vector in X]
for prediction_vector in data_matrix
])
'''
Izracunavanje matrice konfuzije nad pripremljenim podacima
* true_labels - stvarne oznake klasa
* predicted_labels - predvidjene oznake klasa
'''
def _compute_confusion_matrix(self, true_labels, predicted_labels):
num_classes = np.max(true_labels) + 1
num_labels = true_labels.shape[0]
confusion_matrix = np.zeros((num_classes, num_classes))
for i in range(num_labels):
true_label = true_labels[i]
predicted_label = predicted_labels[i]
confusion_matrix[true_label, predicted_label] += 1
return confusion_matrix
'''
Priprema podataka i izracunavanje matrice konfuzije
* true_labels - stvarne oznake klasa
* predicted_labels - predvidjene oznake klasa
'''
def get_confusion_matrix(self, true_labels, predicted_labels):
if not self.model_trained:
raise Exception('No training data provided')
true_labels_matrix = None
predicted_labels_matrix = None
if isinstance(true_labels, pd.DataFrame):
true_labels_matrix = true_labels.values.ravel()
else:
true_labels_matrix = np.array(true_labels).ravel()
if isinstance(predicted_labels, pd.DataFrame):
predicted_labels_matrix = predicted_labels.values.ravel()
else:
predicted_labels_matrix = np.array(predicted_labels).ravel()
return self._compute_confusion_matrix(true_labels_matrix, predicted_labels_matrix)
'''
Izracunavanje tacnosti klasifikacije (accuracy)
nad pripremljenim podacima
* true_labels - stvarne oznake klasa
* predicted_labels - predvidjene oznake klasa
'''
def _compute_accuracy(self, true_labels, predicted_labels):
num_correct_labels = np.sum(
[1 for i in range(true_labels.shape[0]) if true_labels[i] == predicted_labels[i]]
)
return num_correct_labels / true_labels.shape[0]
'''
Generisanje izvestaja o kvalitetu klasifikacije
* true_labels - stvarne oznake klasa
* predicted_labels - predvidjene oznake klasa
'''
def generate_report(self, true_labels, predicted_labels):
if not self.model_trained:
raise Exception('No training data provided')
true_labels_matrix = None
predicted_labels_matrix = None
if isinstance(true_labels, pd.DataFrame):
true_labels_matrix = true_labels.values.ravel()
else:
true_labels_matrix = np.array(true_labels).ravel()
if isinstance(predicted_labels, pd.DataFrame):
predicted_labels_matrix = predicted_labels.values.ravel()
else:
predicted_labels_matrix = np.array(predicted_labels).ravel()
accuracy = self._compute_accuracy(true_labels_matrix, predicted_labels_matrix)
return {
'accuracy': accuracy
}
'''
Predvidjanje oznaka klaza nepoznatih poznataka
* data - matrica vrednosti (numpy matrica/pandas dataframe/2D niz/...)
* metric - metrika za izracunavanje udaljenosti
* y_true - stvarne oznake klasa ulaznih podataka
* include_confusion_matrix - izracunavanje matrice konfuzije (samo ukoliko je y_true prosledjeno)
'''
def predict(self, data, metric='euclidean', y_true=None, include_confusion_matrix=False):
# Provera da li su ucitani trening podaci
if not self.model_trained:
raise Exception('No training data provided')
X = self.X
y = self.y
data_matrix = self._prepare_data_matrix(data)
'''
Racunanje matrice udaljenosti svakog ulaznog podatka u odnosu na svaki
podatak iz trening skupa
Matrica distance_matrix je oblika:
- red (i): ulazni podatak / prediction_vector
- kolona (j): trening podatak / train_vector,
- polje(i, j) rastojanje
izmedju ulaznog podatka (i)
i trening podatka (j)
'''
distance_matrix = self._compute_distance_matrix(data_matrix)
'''
Sortiranje po kolonama matrice (axis = 1)
- Sortiranje se vrsi u rastucem poretku
- Za svaki red (ulazni podatak) sortiraju se
vrednosti rastojanja izmedju ulaznog podatka i trening podataka
- metod argsort ne vrsi konacno sortiranje vec vraca redosled KLJUCEVA (indeksa)
kolona polazne matrice u redosledu u kome bi se nasli u sortiranoj matrici
Primer:
distance_matrix = [# 0 1 2 -- indeksi kolona
[10, 4, 1],
[ 3, 2, 14]
]
sorted_keys = argsort(distance_matrix) -> [
[2, 1, 0],
[1, 0, 2]
]
'''
sorted_keys = distance_matrix.argsort(axis = 1)
'''
"Primenom" matrice kljuceva na matricu istog oblika dobijaju se vrednosti
matrice u redosledu sortiranih kljuceva.
Primer:
distance_matrix[sorted_keys] -> [
[1, 4, 10],
[2, 3, 14]
]
'''
sorted_classes = y[sorted_keys[:, :self.n_neighbors]]
class_labels = np.array([mode(row).mode for row in sorted_classes]).ravel()
return class_labels
'''
Pronalazenje k najblizih suseda zadatih podataka
* data - matrica vrednosti ulaznih podataka
* metric - metrika za racunanje udaljenosti
* return_indices - True: povratna vrednost ce biti indeksi najblizih suseda,
False: povratna vrednost ce biti vrednosti najblizih suseda
'''
def k_neighbors(self, data, metric='euclidean', return_indices=False):
data_matrix = self._prepare_data_matrix(data)
distance_matrix = self._compute_distance_matrix(data_matrix)
sorted_keys = distance_matrix.argsort(axis = 1)
if return_indices:
return sorted_keys[:,:self.n_neighbors]
else:
return self.X[sorted_keys][:,:self.n_neighbors,:]