Xây dựng bộ phân loại chó, mèo hay gấu trúc với k-NN

Share on:

k-NN - K-nearest neighbors là thuật toán khá đơn giản khi bắt đầu làm quen với Machine Learning. Ở bài viết này tôi sẽ cùng các bạn xây dựng bộ phân loại chó, mèo hay gấu trúc với k-NN nhé.

Trước tiên, các bạn cần tìm hiểu một chút lý thuyết của k-NN, xem k-NN là gì, hoạt động như thế nào. Đừng lo, thuật toán này khá dễ hiểu. Có thể hiểu đơn giản, k-NN sẽ lưu lại toàn bộ dữ liệu sử dụng khi huấn luyện thuận toán. Ở đây, khi chúng ta xây dựng bộ nhận dạng chó, mèo hay gấu trúc, k-NN sẽ lưu ảnh ảnh của cả 3 loài, kèm label (tên của chúng). Khi cần xét một ảnh mới, xem là loài nào trong số 3 loài trên, thuật toán thực hiện so sánh ảnh này với tất cả các ảnh trong bộ dữ liệu, chọn ra k ảnh giống nhất với nó, rồi dựa vào kết quả đó để xác định xem ảnh đang xét thuộc loài nào. Ví dụ trong k ảnh vừa được chọn ra có phần lớn là ảnh mèo, chúng ta có thể kết luận ảnh đang xét là ảnh của một con mèo. Về thuật toán k-NN nói chung, tôi nghĩ các bạn nên đọc thêm tại blog Machine learning cơ bản. Tác giả blog đã mô tả khá chi tiết và dễ hiểu về thuật toán này.

Minh hoạ so sánh ảnh chó, mèo hay gấu trúc

Minh hoạ so sánh ảnh chó, mèo hay gấu trúc

Thông thường, để có kết quả tốt hơn khi phân loại ảnh bằng KNN, người ta dùng các phương pháp trích xuất đặc trưng ảnh, có thể là một phương pháp trích xuất đặc trưng truyền thống (màu, HOG, HAAR, SIFT...) hoặc một phương pháp học máy (CNN), sau đó mới đưa các đặc trưng này vào lưu trữ và áp dụng KNN lên đó. Tuy nhiên, ở bài viết này, để cho đơn giản, tôi sẽ hướng dẫn các bạn thực hiện so sánh ảnh dựa trên các điểm ảnh, không sử dụng thêm các thuật toán để trích xuất đặc trưng từ ảnh. Mục tiêu chính của chúng ta sẽ là làm quen với các thao tác trên bộ dữ liệu và thử áp dụng thử thuật toán k-NN cơ bản vào phân loại hình ảnh.

Cấu trúc project và dữ liệu cần có

Ở đây tôi sẽ hướng dẫn các bạn tạo một project nhỏ với Python, với cấu trúc đơn giản như sau:

1|--- dataset_loader.py
2|--- knn.py
3|--- animals_dataset
4    ...

Trước tiên bạn hãy tạo một project chứa các thư mục và file tương ứng.

Tiếp đó hãy tải bộ dữ liệu hình ảnh chó, mèo và gấu trúc từ địa chỉ sau và giải nén vào thư mục animals_dataset: https://www.kaggle.com/ashishsaxena2209/animal-image-datasetdog-cat-and-panda.

Sau khi giải nén, bạn sẽ thu được cấu trúc thư mục như sau. Các bạn lưu ý là trong thư mục animals_dataset/animals/ chỉ chứa 3 thư mục con là cats, dogspanda nhé. Trong mỗi thư mục này sẽ chứa hình ảnh của động vật tương ứng. Nếu sau khi giải nén các bạn thấy thừa các thư mục không cần thiết khác thì hãy xoá đi nhé.

1|--- dataset_loader.py
2|--- knn.py
3|--- animals_dataset
4    |--- animals
5        | --- cats
6        | --- dogs
7        | --- panda
8    |--- images
9        ...

Xây dựng bộ tiền xử lý và nạp dữ liệu

Mở file dataset_loader.py và chèn đoạn code sau:

 1# Import necessary packages
 2import numpy as np
 3import cv2 as cv
 4import os
 5
 6class DatasetLoader
 7    def load(self, image_paths, verbose=-1):
 8
 9        # Initialize the list of images and labels
10        data = []
11        labels = []
12
13        # Loop over input paths to read the data
14        for (i, path) in enumerate(image_paths):
15            # Load images
16            # Assuming path in following format            # /path/to/dataset/{class}/{image-name}.jpg
17            image = cv.imread(path)
18            label = path.split(os.path.sep)[-2]
19
20            # Resize image
21            image = cv.resize(image, (32, 32))
22
23            # Push into data list
24            data.append(image)
25            labels.append(label)
26
27            # Show update
28            if verbose > 0 and i > 0 and (i + 1) % verbose == 0:
29                print("[INFO] processed {}/{}".format(i + 1, len(image_paths)))
30
31        # Return a tuple of data and labels
32        return (np.array(data), np.array(labels))

Ở đây, class DatasetLoader sẽ giúp đọc các hình ảnh từ bộ nhớ lên, chuyển chúng về cùng kích thước 32x32 và lưu lại label tương ứng với mỗi ảnh.

Thông thường các thuật toán machine learning như k-NN, SVM, CNNs sẽ yêu cầu chúng ta đưa đầu vào là một feature vector (hiểu nôm na là một chuỗi số đặc trưng cho dữ liệu) có chiều dài cố định. Thông thường các ảnh đầu vào sẽ được resize về một kích thước cố định. Ở đây, các ảnh sẽ được đưa về kích thước là 32x32 (dòng 23), và sau đó sẽ được coi như một chuỗi số có kích thước 32x32x3 = 3072 để so sánh với nhau (3 ở đây là 3 màu RGB).

Xây dựng bộ phân loại k-NN để nhận biết chó, mèo và gấu trúc

Bộ phân loại mà chúng ta xây dựng sẽ nhận vào một hình ảnh và cho biết đó là chó, mèo hay gấu trúc. Mở file knn.py và chép vào đoạn code sau:

 1from sklearn.neighbors import KNeighborsClassifier
 2from sklearn.preprocessing import LabelEncoder
 3from sklearn.model_selection import train_test_split
 4from sklearn.metrics import classification_report
 5from dataset_loader import DatasetLoader
 6from imutils import paths
 7import argparse
 8
 9# Parse arguments
10ap = argparse.ArgumentParser()
11ap.add_argument("-d", "--dataset", required=True, 
12    help="path to input dataset")
13ap.add_argument("-k", "--neighbors", type=int, default=1,
14    help="# of nearest neighbors for classification")
15ap.add_argument("-j", "--jobs", type=int, default=-1,
16    help="# of CPU cores used for classification")
17args = vars(ap.parse_args())
18
19# Load images
20print("[INFO] Loading images")
21image_paths = list(paths.list_images(args["dataset"]))
22sdl = DatasetLoader()
23(data, labels) = sdl.load(image_paths, verbose=500)
24data = data.reshape((data.shape[0], 32*32*3))
25
26# Show memory consumption
27print("[INFO] features matrix: {:.1f}MB".format(
28    data.nbytes / (1024 * 1024.0)
29))
30
31# Encode labels as intergers
32le = LabelEncoder()
33labels = le.fit_transform(labels)
34
35# Partition the data.
36# training: 75%, testing: 25%
37(trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42)
38
39# Evaluate k-NN classifier
40print("[INFO] evaluating k-NN classifier")
41model = KNeighborsClassifier(n_neighbors=args["neighbors"], 
42    n_jobs=args["jobs"])
43model.fit(trainX, trainY)
44print(classification_report(testY, model.predict(testX), target_names=le.classes_))
  • Dòng 1: Ở đây chúng ta sẽ sử dụng class KNeighborsClassifier có trong package sklearn cho thuật toán k-NN.
  • Dòng 2: LabelEncoder sẽ giúp chuyển label của hình ảnh từ dạng chữ (dog, cat, panda) sang sạng số (0, 1, 2). Đây là việc thường thấy khi sử dụng các thuật toán machine learning. Việc này sẽ giúp công việc lập trình và xử lý đơn giản hơn.
  • Dòng 3: train_test_split giúp chúng ta chia dữ liệu train và test dễ dàng.
  • Dòng 4: classification_report giúp chúng ta đánh giá hiệu quả của model vừa huấn luyện.

Việc huấn luyện (train) và đánh giá (evaluate) model k-NN thực hiện theo 4 bước:

  • Bước 1 (dòng 19-33): Nạp dữ liệu: Dataset của chúng ta có 3000 ảnh, 1000 ảnh mỗi loài. Sau khi load ảnh lên, chúng ta đưa ảnh về kích thước 32x32. Vì mỗi ảnh có 3 kênh màu R,G,B nên mỗi ảnh sẽ được biểu diễn bằng 32x32x3 = 3072 số nguyên. Ở dòng 24, dữ liệu sẽ được làm phẳng (reshape, flatten) để mỗi ảnh trở thành một dãy số có 3072 phần tử.
  • Bước 2 (dòng 35-37): Chia dữ liệu: Dataset được chia làm 2 phần, 1 phần cho training (huấn luyện), một phần cho testing (đánh giá model). Ở đây 75% dữ liệu sẽ được dùng cho training và 25% còn lại sẽ được sử dụng cho testing.
  • Bước 3 (dòng 41-43): Huấn luyện: Thực tế việc huấn luyện k-NN chỉ là tạo bộ phân loại bằng KNeighborsClassifier và nạp các dữ liệu training vào.
  • Bước 4 (dòng 44): Đánh giá model: Đánh giá hiệu quả của model k-NN đã thu được.

Kết quả

Để chạy đánh giá bộ phân loại knn, trước tiên chúng ta cần cài đủ thư viện cần dùng: sklearn, numpy, imutils, opencv-python. Tôi khuyến khích bạn sử dụng môi trường ảo cho Python. Ở đây phiên bản Python cần dùng là 3.x. Để setup một môi trường ảo và cài các gói cần thiết:

1python3 -m venv venv
2source venv/bin/activate
3pip install sklearn numpy imutils opencv-python

Để chạy thử và đánh giá bộ phân loại chúng ta vừa tạo ra:

python knn.py --dataset ./animals_dataset/animals/

Bạn sẽ nhận được kết quả như sau:

 1$ python knn.py --dataset ./animals_dataset/animals/
 2[INFO] Loading images
 3[INFO] processed 500/3000
 4[INFO] processed 1000/3000
 5[INFO] processed 1500/3000
 6[INFO] processed 2000/3000
 7[INFO] processed 2500/3000
 8[INFO] processed 3000/3000
 9[INFO] features matrix: 8.8MB
10[INFO] evaluating k-NN classifier
11              precision    recall  f1-score   support
12
13        cats       0.41      0.60      0.48       262
14        dogs       0.34      0.42      0.38       239
15       panda       0.85      0.25      0.39       249
16
17    accuracy                           0.43       750
18   macro avg       0.53      0.42      0.42       750
19weighted avg       0.53      0.43      0.42       750
20

Nhìn vào kết quả, chúng ta có thể kết luận độ chính xác (precision) của bộ phân loại là 53% (0.53). Đó chắc chắn không phải là kết quả tốt, vì suy cho cùng chúng ta chỉ thực hiện so sánh các điểm ảnh của các bức ảnh với nhau, không sử dụng thêm thuật toán nào để trích xuất các đặc trưng từ ảnh. Tuy nhiên đối với một thuật toán chỉ dựa vào việc so sánh các ảnh, độ chính xác 52% cũng là khá cao so với việc đoán ngẫu nhiên (ở đây xác suất đoán ngẫu nhiên là 1/3 ~ 33%).

Gấu trúc được phân loại đúng đến 87% vì có thể ảnh gấu trúc có các vùng trắng và đen rất rõ ràng tạo thành các vùng điểm trắng đen nằm liên tiếp nhau, do vậy dễ dàng có thể nhận ra bằng cách so sánh các điểm ảnh. Tuy thế, việc nhận ra chó và mèo là khá tệ. Điều này dễ hiểu vì trong các bức ảnh có quá nhiều chi tiết khác nhau, từ màu sắc lông chó, mèo đến màu sắc nền của bức ảnh. Do vậy, chỉ dựa vào việc so sánh các giá trị điểm ảnh thì không thể đòi hỏi độ chính xác cao hơn được.

Nhận xét về thuật toán k-NN

Cuối cùng thì cũng ta cũng cùng nhau xây dựng được một bộ phân loại chó, mèo và gấu trúc bằng thuật toán k-NN. Trên thực tế, k-NN cũng ít được sử dụng theo cách chúng ta đang làm. Hãy cùng xem xét một số ưu, nhược điểm của k-NN:

  • Ưu điểm:

    • Ưu điểm lớn nhất của k-NN là rất dễ hiểu và implement.

    • Không tốn thời gian huấn luyện. Tất cả những gì chúng ta cần làm là lưu lại toàn bộ dataset.

  • Nhược điểm:

    • Tuy không tốn thời gian huấn luyện nhưng k-NN phải chạy trên toàn bộ dataset khi phân loại. Chính vì thế thuật toán này tốn nhiều thời gian chạy, nhất là trong các bài toán có dataset lớn. Trên thực tế có thể sử dụng các biến thể khác như KdTree hoặc Ball tree để tăng tốc thực hiện.

    • Thuật toán không phù hợp với bộ dữ liệu với số chiều lớn và nhiều nhiễu (như hình ảnh).

Cuối cùng, bạn có thể tải mã nguồn của bài viết này tại đây. Nếu có thắc mắc hay bình luận nào, vui lòng để lại bên dưới bài viết. Xin cảm ơn!

Related Posts