Phân loại K-Nearest Neighbors trong Python

Hướng dẫn này sẽ chỉ cho bạn cách triển khai thuật toán K-Nearest Neighbors để phân loại trong Python. Chúng ta sẽ hình dung một tập dữ liệu, tìm các tham số tốt nhất để sử dụng, đào tạo một mô hình và đánh giá kết quả. K-Nearest Neighbors là một thuật toán đơn giản để hiểu và nó thường nhận được điểm chính xác rất cao.

K-Nearest Neighbors để phân loại tập hợp K quan sát gần nhất và trả về lớp phổ biến nhất trong số này. Mô hình K-Nearest Neighbors cần dữ liệu huấn luyện cho các dự đoán và điều này có nghĩa là mô hình sẽ có kích thước lớn nếu tập dữ liệu huấn luyện lớn. Thuật toán này không tốt trong việc đưa ra dự đoán về các giá trị đầu vào nằm ngoài tập dữ liệu huấn luyện, bạn có thể liên tục thêm dữ liệu huấn luyện khác để giữ cho mô hình được cập nhật. Mô hình K-Nearest Neighbors rất phức tạp vì nó khá phù hợp với dữ liệu đào tạo nhưng có thể không tốt khi tổng quát hóa trên dữ liệu mới và chưa nhìn thấy.

Tập dữ liệu và thư viện

Tôi sẽ sử dụng bộ dữ liệu hoa Iris ( tải xuống ) trong hướng dẫn này, một bộ dữ liệu đồ chơi nhỏ được sử dụng để học tập. Tập dữ liệu về hoa Iris bao gồm 150 bông hoa, mỗi bông hoa có bốn giá trị đầu vào và một giá trị đầu ra. Tôi cũng đang sử dụng các thư viện sau: pandas, joblib, numpy, matplotlib và scikit-learning. Bạn cần cài đặt các thư viện này bằng pip.

Mô-đun Python

Tôi đã gộp tất cả mã vào một tệp, một dự án thường bao gồm nhiều tệp (mô-đun). Bạn có thể tạo không gian tên bằng cách đặt tệp vào thư mục và nhập tệp bằng không gian tên cộng với tên tệp của nó. Một file có tên common.py trong một thư mục sharesieutoc/learn được nhập khẩu theo import sharesieutoc.learn.common. Tôi sẽ giải thích thêm về mã trong các phần bên dưới.

# 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()

Tải và trực quan hóa tập dữ liệu

Tập dữ liệu được tải bằng gấu trúc bằng cách sử dụng đường dẫn tương đối đến thư mục gốc của dự án, sử dụng đường dẫn tuyệt đối nếu tệp của bạn được lưu trữ bên ngoài dự án. Chúng tôi muốn trực quan hóa tập dữ liệu để đảm bảo rằng nó được cân bằng và chúng tôi muốn tìm hiểu thêm về dữ liệu. Điều quan trọng là phải có một tập dữ liệu cân bằng khi thực hiện phân loại, mọi lớp sẽ được huấn luyện nhiều lần như nhau với một tập huấn luyện cân bằng. Chúng tôi có thể vẽ một tập dữ liệu để tìm các mẫu, loại bỏ các ngoại lệ và quyết định các thuật toán phù hợp nhất để sử dụng.

# 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
Image_Alt_Here

Tách tập dữ liệu

Đầu tiên tôi cần cắt các giá trị trong tập dữ liệu để lấy giá trị đầu vào (X) và giá trị đầu ra (Y), 4 cột đầu tiên là giá trị đầu vào và cột cuối cùng là giá trị đích. Tôi chia tập dữ liệu thành tập huấn luyện và tập kiểm tra, 80% là tập huấn luyện và 20% là tập kiểm tra. Tôi muốn đảm bảo rằng các tập dữ liệu vẫn được cân bằng sau lần phân tách này và do đó tôi sử dụng tham số phân tầng.

# 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))

Hiệu suất cơ bản

Tập dữ liệu của chúng tôi có 150 bông hoa và 50 bông hoa trong mỗi lớp, tập hợp đào tạo của chúng tôi có cùng số dư. Một dự đoán ngẫu nhiên sẽ đúng trong 33% (50/150) trong tất cả các trường hợp và mô hình của chúng tôi phải có độ chính xác tốt hơn 33% thì mới hữu ích.

GridSearch

Tôi đang thực hiện tìm kiếm lưới để tìm các thông số tốt nhất để sử dụng cho việc đào tạo. Tìm kiếm lưới có thể mất nhiều thời gian để thực hiện trên các tập dữ liệu lớn nhưng nó có thể nhanh hơn quy trình thủ công. Kết quả từ quá trình này được hiển thị bên dưới và tôi sẽ sử dụng các tham số này khi đào tạo mô hình.

# 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

Đào tạo và đánh giá

Tôi đang đào tạo mô hình bằng cách sử dụng các tham số từ tìm kiếm lưới và lưu mô hình vào một tệp có joblib . Đánh giá được thực hiện trên tập huấn luyện và có xác nhận chéo. Đánh giá xác nhận chéo sẽ đưa ra gợi ý về hiệu suất tổng quát hóa của mô hình. Tôi có độ chính xác 100% về dữ liệu đào tạo và độ chính xác 96,67% với xác nhận chéo 10 lần.

# 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]]

Dự đoán và đánh giá

Bước cuối cùng trong quá trình này là đưa ra dự đoán và đánh giá hiệu suất trên tập dữ liệu thử nghiệm. Tôi tải mô hình, đưa ra dự đoán và in kết quả. Biến X là một mảng 2D, nếu bạn muốn thực hiện một dự đoán trên một bông hoa bạn cần phải thiết lập các đầu vào như thế này: 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]]