Hoppa till innehåll

K-Närmaste Grannar klassificering i Python

Denna handledning visar hur du implementerar en algoritm för klassificering som kallas K-Närmaste Grannar i Python. Vi kommer att visualisera en datauppsättning, hitta de bästa parametrarna att använda, träna en modell och utvärdera resultaten. K-Närmaste Grannar är en enkel algoritm att förstå och den får vanligtvis mycket högt resultat när det gäller exakthet.

K-Närmaste Grannar för klassificering samlar in de K närmaste observationerna och returnerar den vanligaste klassen bland dessa. En modell med K-Närmaste Grannar behöver träningsdata för förutsägelser och detta innebär att modellen kommer att ha en stor storlek om det finns omfattande träningsdata. Denna algoritm är inte bra på att göra förutsägelser avseende indata som ligger utanför indata:n i träningsdata, du kan kontinuerligt lägga till mer träningsdata för att hålla modellen uppdaterad. En K-Närmaste Grannar-modell är komplex eftersom den passar träningsdata:n ganska bra, men den kan vara dålig på generalisering när det gäller ny och osedd data.

Datauppsättning och bibliotek

Jag kommer att använda datauppsättningen Iris-blomma (ladda ner) i den här handledningen, en liten datauppsättning som används för inlärning. Datauppsättningen för Iris-blomma består av 150 blommor, varje blomma har fyra indatavärden och ett utdatavärde. Jag använder också följande bibliotek: pandas, joblib, numpy, matplotlib och scikit-learn. Du måste installera dessa bibliotek med pip.

Python-modul

Jag har inkluderat all kod i en fil, ett projekt består normalt av många filer (moduler). Du kan skapa namnområden genom att placera filer i mappar och du importerar en fil med dess namnområden plus dess filnamn. En fil med namnet common.py i mappen annytab/learning importeras som import annytab.learn.common. Jag kommer att förklara mer om koden i avsnitten nedan.

# Import libraries
import pandas
import joblib
import numpy as np
import matplotlib.pyplot as plt
import sklearn.model_selection
import sklearn.neighbors
import sklearn.metrics
import sklearn.pipeline

# Visualize dataset
def visualize_dataset(ds):
    
    # Print first 5 rows in dataset
    print('--- First 5 rows ---\n')
    print(ds.head())

    # Print the shape
    print('\n--- Shape of dataset ---\n')
    print(ds.shape)

    # Print class distribution
    print('\n--- Class distribution ---\n')
    print(ds.groupby('species').size())

    # Box plots
    plt.figure(figsize = (12, 8))
    ds.boxplot()
    #plt.show()
    plt.savefig('plots\\iris-boxplots.png')
    plt.close()

    # Scatter plots (4 subplots in 1 figure)
    figure = plt.figure(figsize = (12, 8))
    grouped_dataset = ds.groupby('species')
    values = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
    for i, value in enumerate(values):
        plt.subplot(2, 2, i + 1) # 2 rows and 2 columns
        for name, group in grouped_dataset:
            plt.scatter(group.index, ds[value][group.index], label=name)
        plt.ylabel(value)
        plt.xlabel('index')
        plt.legend()
    #plt.show()
    plt.savefig('plots\\iris-scatterplots.png')
    
# Train and evaluate
def train_and_evaluate(X, Y):
    
    # Create a model
    model = sklearn.neighbors.KNeighborsClassifier(n_neighbors=10, weights='distance', algorithm='auto', leaf_size=10, p=2, metric='minkowski', metric_params=None, n_jobs=1)

    # Train the model on the whole dataset
    model.fit(X, Y)

    # Save the model (Make sure that the folder exists)
    joblib.dump(model, 'models\\knn.jbl')

    # Evaluate on training data
    print('\n-- Training data --\n')
    predictions = model.predict(X)
    accuracy = sklearn.metrics.accuracy_score(Y, predictions)
    print('Accuracy: {0:.2f}'.format(accuracy * 100.0))
    print('Classification Report:')
    print(sklearn.metrics.classification_report(Y, predictions))
    print('Confusion Matrix:')
    print(sklearn.metrics.confusion_matrix(Y, predictions))
    print('')
    # Evaluate with 10-fold CV
    print('\n-- 10-fold CV --\n')
    predictions = sklearn.model_selection.cross_val_predict(model, X, Y, cv=10)
    accuracy = sklearn.metrics.accuracy_score(Y, predictions)
    print('Accuracy: {0:.2f}'.format(accuracy * 100.0))
    print('Classification Report:')
    print(sklearn.metrics.classification_report(Y, predictions))
    print('Confusion Matrix:')
    print(sklearn.metrics.confusion_matrix(Y, predictions))

# Perform a grid search to find the best parameters
def grid_search(X, Y):

    # Create a pipeline
    clf_pipeline = sklearn.pipeline.Pipeline([
        ('m', sklearn.neighbors.KNeighborsClassifier(metric='minkowski', metric_params=None, n_jobs=1))
        ])

    # Set parameters (name in pipeline + name of parameter)
    parameters = { 
        'm__n_neighbors': (1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 
        'm__weights': ('uniform', 'distance'), 
        'm__algorithm': ('auto', 'ball_tree', 'kd_tree', 'brute'), 
        'm__leaf_size': (10, 20, 30),
        'm__p': (1, 2)
        }

    # Create a grid search classifier
    gs_classifier = sklearn.model_selection.GridSearchCV(clf_pipeline, parameters, cv=5, iid=False, n_jobs=2, scoring='accuracy', verbose=1)
    
    # Start a search (Warning: can take a long time if the whole dataset is used)
    gs_classifier = gs_classifier.fit(X, Y)

    # Print results
    print('---- Results ----')
    print('Best score: ' + str(gs_classifier.best_score_))
    for name in sorted(parameters.keys()):
        print('{0}: {1}'.format(name, gs_classifier.best_params_[name]))

# Predict and evaluate on unseen data
def predict_and_evaluate(X, Y):

    # Load the model
    model = joblib.load('models\\knn.jbl')

    # Make predictions
    predictions = model.predict(X)

    # Print results
    print('\n---- Results ----')
    for i in range(len(predictions)):
        print('Input: {0}, Predicted: {1}, Actual: {2}'.format(X[i], predictions[i], Y[i]))
    accuracy = sklearn.metrics.accuracy_score(Y, predictions)
    print('\nAccuracy: {0:.2f}'.format(accuracy * 100.0))
    print('\nClassification Report:')
    print(sklearn.metrics.classification_report(Y, predictions))
    print('Confusion Matrix:')
    print(sklearn.metrics.confusion_matrix(Y, predictions))

# The main entry point for this module
def main():

    # Load dataset (includes header values)
    dataset = pandas.read_csv('files\\iris.csv')

    # Visualize dataset
    visualize_dataset(dataset)

    # Slice dataset in values and targets (2D-array)
    X = dataset.values[:,0:4]
    Y = dataset.values[:,4]

    # Split dataset in train and test (use random state to get the same split every time, and stratify to keep balance)
    X_train, X_test, Y_train, Y_test = sklearn.model_selection.train_test_split(X, Y, test_size=0.2, random_state=1, stratify=Y)

    # Make sure that data still is balanced
    print('\n--- Class balance ---\n')
    print(np.unique(Y_train, return_counts=True))
    print(np.unique(Y_test, return_counts=True))

    # Perform a grid search
    #grid_search(X_train, Y_train)

    # Train and evaluate
    #train_and_evaluate(X_train, Y_train)

    # Predict on testset
    predict_and_evaluate(X_test, Y_test)

# Tell python to run main method
if __name__ == "__main__": main()

Läs in och visualisera datauppsättningen

Datauppsättningen laddas med pandas genom att använda en relativ sökväg, använd en absolut sökväg om dina filer lagras utanför projektet. Vi vill visualisera datauppsättningen för att se till att den är balanserad och vi vill lära oss mer om datauppsättningen. Det är viktigt att ha en balanserad datauppsättning vid klassificering, varje klass tränas lika frekvent med en balanserad träningsuppsättning. Vi kan plotta en datauppsättning för att hitta mönster, ta bort extremvärden och för att besluta om de lämpligaste algoritmerna att använda.

# Load dataset (includes header values)
dataset = pandas.read_csv('files\\iris.csv')

# Visualize dataset
visualize_dataset(dataset)
--- First 5 rows ---

   sepal_length  sepal_width  petal_length  petal_width      species
0           5.1          3.5           1.4          0.2  Iris-setosa
1           4.9          3.0           1.4          0.2  Iris-setosa
2           4.7          3.2           1.3          0.2  Iris-setosa
3           4.6          3.1           1.5          0.2  Iris-setosa
4           5.0          3.6           1.4          0.2  Iris-setosa

--- Shape of dataset ---

(150, 5)

--- Class distribution ---

species
Iris-setosa        50
Iris-versicolor    50
Iris-virginica     50
dtype: int64
Iris spridningsdiagram

Dela upp datauppsättningen

Jag måste först dela upp värden i datauppsättningen för att få indata (X) och utdata (Y), de första 4 kolumnerna är indata och den sista kolumnen utgör målvärdet. Jag delar upp datauppsättningen i en träningsuppsättning och en testuppsättning, 80 % är för träning och 20 % för test. Jag vill se till att datauppsättningarna fortfarande är balanserade efter denna delning och jag använder därför en stratify-parameter.

# Slice dataset in values and targets (2D-array)
X = dataset.values[:,0:4]
Y = dataset.values[:,4]

# Split dataset in train and test (use random state to get the same split every time, and stratify to keep balance)
X_train, X_test, Y_train, Y_test = sklearn.model_selection.train_test_split(X, Y, test_size=0.2, random_state=1, stratify=Y)

# Make sure that data still is balanced
print('\n--- Class balance ---\n')
print(np.unique(Y_train, return_counts=True))
print(np.unique(Y_test, return_counts=True))

Baslinjeprestanda

Vår datauppsättning har 150 blommor och 50 blommor i varje klass, vår träningsuppsättning har samma balans. En slumpvis förutsägelse kommer att vara korrekt i 33% (50/150) av alla fall och vår modell måste ha en noggrannhet som är bättre än 33 % för att vara användbar.

Rutnätssökning

Jag gör en rutnätsökning för att hitta de bästa parametrarna för träning. En rutnätsökning kan ta lång tid att utföra på stora datauppsättningar, men det är antagligen snabbare jämfört med en manuell process. Resultatet från denna process visas nedan och jag kommer att använda dessa parametrar när jag tränar modellen.

# Perform a grid search
grid_search(X, Y)

Fitting 5 folds for each of 480 candidates, totalling 2400 fits
[Parallel(n_jobs=2)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=2)]: Done 1030 tasks      | elapsed:    2.6s
[Parallel(n_jobs=2)]: Done 2400 out of 2400 | elapsed:    4.4s finished
---- Results ----
Best score: 0.9866666666666667
m__algorithm: auto
m__leaf_size: 10
m__n_neighbors: 10
m__p: 2
m__weights: distance

Träning och utvärdering

Jag tränar modellen genom att använda parametrarna från rutnätsökningen och sparar modellen till en fil med joblib. Utvärderingen görs på träningsuppsättningen och med korsvalidering. Korsvalideringsutvärderingen ger en antydan om modellens generaliseringsprestanda. Jag hade 100% exakthet på träningsdata och 96,67% exakthet med tiofaldig korsvalidering.

# Train and evaluate
train_and_evaluate(X_train, Y_train)

-- Training data --

Accuracy: 100.00
Classification Report:
                 precision    recall  f1-score   support

    Iris-setosa       1.00      1.00      1.00        40
Iris-versicolor       1.00      1.00      1.00        40
 Iris-virginica       1.00      1.00      1.00        40

       accuracy                           1.00       120
      macro avg       1.00      1.00      1.00       120
   weighted avg       1.00      1.00      1.00       120

Confusion Matrix:
[[40  0  0]
 [ 0 40  0]
 [ 0  0 40]]


-- 10-fold CV --

Accuracy: 96.67
Classification Report:
                 precision    recall  f1-score   support

    Iris-setosa       1.00      1.00      1.00        40
Iris-versicolor       0.97      0.93      0.95        40
 Iris-virginica       0.93      0.97      0.95        40

       accuracy                           0.97       120
      macro avg       0.97      0.97      0.97       120
   weighted avg       0.97      0.97      0.97       120

Confusion Matrix:
[[40  0  0]
 [ 0 37  3]
 [ 0  1 39]]

Förutsägelse och utvärdering

Det sista steget i denna process är att göra förutsägelser och utvärdera prestationer avseende testdata. Jag läser in modellen, gör förutsägelser och skriver ut resultaten. X-variabeln är en 2D-array, om du vill göra en förutsägelse för en blomma måste du ange indata så här: X = np.array ([[7.3, 2.9, 6.3, 1.8]]).

# Predict on test set
predict_and_evaluate(X_test, Y_test)

---- Results ----
Input: [7.3 2.9 6.3 1.8], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [4.9 3.1 1.5 0.1], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [5.1 2.5 3.0 1.1], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [4.8 3.4 1.6 0.2], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [5.0 3.5 1.6 0.6], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [5.1 3.5 1.4 0.2], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [6.2 3.4 5.4 2.3], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [6.4 2.7 5.3 1.9], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [5.6 2.8 4.9 2.0], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [6.8 2.8 4.8 1.4], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [5.4 3.9 1.3 0.4], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [5.5 2.3 4.0 1.3], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [6.8 3.0 5.5 2.1], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [6.0 2.2 4.0 1.0], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [5.7 2.5 5.0 2.0], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [5.7 4.4 1.5 0.4], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [7.1 3.0 5.9 2.1], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [6.1 2.8 4.0 1.3], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [4.9 2.4 3.3 1.0], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [6.1 3.0 4.9 1.8], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [6.4 2.9 4.3 1.3], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [5.6 3.0 4.5 1.5], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [4.9 3.1 1.5 0.1], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [4.4 2.9 1.4 0.2], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [6.5 3.0 5.2 2.0], Predicted: Iris-virginica, Actual: Iris-virginica
Input: [4.9 2.5 4.5 1.7], Predicted: Iris-versicolor, Actual: Iris-virginica
Input: [5.4 3.9 1.7 0.4], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [4.8 3.0 1.4 0.1], Predicted: Iris-setosa, Actual: Iris-setosa
Input: [6.3 3.3 4.7 1.6], Predicted: Iris-versicolor, Actual: Iris-versicolor
Input: [6.5 2.8 4.6 1.5], Predicted: Iris-versicolor, Actual: Iris-versicolor

Accuracy: 96.67

Classification Report:
                 precision    recall  f1-score   support

    Iris-setosa       1.00      1.00      1.00        10
Iris-versicolor       0.91      1.00      0.95        10
 Iris-virginica       1.00      0.90      0.95        10

       accuracy                           0.97        30
      macro avg       0.97      0.97      0.97        30
   weighted avg       0.97      0.97      0.97        30

Confusion Matrix:
[[10  0  0]
 [ 0 10  0]
 [ 0  1  9]]
Etiketter:

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *