computer vision,

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

Việt Anh Việt Anh Bài viết Sep 01, 2019 · 12 phút đọc
Xây dựng bộ phân loại chó, mèo hay gấu trúc với k-NN
Chia sẻ

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 đây.

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

Ở 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 luôn, 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
2
3
4
|--- dataset_loader.py
|--- knn.py
|--- animals_dataset
    ...

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.

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Import necessary packages
import numpy as np
import cv2 as cv
import os

class DatasetLoader:

    def load(self, image_paths, verbose=-1):

        # Initialize the list of images and labels
        data = []
        labels = []

        # Loop over input paths to read the data
        for (i, path) in enumerate(image_paths):
            # Load images
            # Assuming path in following format:
            # /path/to/dataset/{class}/{image-name}.jpg
            image = cv.imread(path)
            label = path.split(os.path.sep)[-2]

            # Resize image
            image = cv.resize(image, (32, 32))

            # Push into data list
            data.append(image)
            labels.append(label)

            # Show update
            if verbose > 0 and i > 0 and (i + 1) % verbose == 0:
                print("[INFO] processed {}/{}".format(i + 1, len(image_paths)))

        # Return a tuple of data and labels
        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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from datasetloader import DatasetLoader
from imutils import paths
import argparse

# Parse arguments
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True, 
    help="path to input dataset")
ap.add_argument("-k", "--neighbors", type=int, default=1,
    help="# of nearest neighbors for classification")
ap.add_argument("-j", "--jobs", type=int, default=-1,
    help="# of CPU cores used for classification")
args = vars(ap.parse_args())

# Load images
print("[INFO] Loading images")
image_paths = list(paths.list_images(args["dataset"]))
sdl = DatasetLoader()
(data, labels) = sdl.load(image_paths, verbose=500)
data = data.reshape((data.shape[0], 32*32*3))

# Show memory consumption
print("[INFO] features matrix: {:.1f}MB".format(
    data.nbytes / (1024 * 1024.0)
))

# Encode labels as intergers
le = LabelEncoder()
labels = le.fit_transform(labels)

# Partition the data.
# training: 75%, testing: 25%
(trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42)

# Evaluate k-NN classifier
print("[INFO] evaluating k-NN classifier")
model = KNeighborsClassifier(n_neighbors=args["neighbors"], 
    n_jobs=args["jobs"])
model.fit(trainX, trainY)
print(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:

1
2
3
python3 -m venv venv
source venv/bin/activate
pip 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:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ python knn.py --dataset ./animals_dataset/
[INFO] Loading images
[INFO] processed 500/3003
[INFO] processed 1000/3003
[INFO] processed 1500/3003
[INFO] processed 2000/3003
[INFO] processed 2500/3003
[INFO] processed 3000/3003
[INFO] features matrix: 8.8MB
[INFO] evaluating k-NN classifier
              precision    recall  f1-score   support

        cats       0.38      0.58      0.46       262
        dogs       0.29      0.37      0.33       230
       panda       0.87      0.17      0.29       258

    accuracy                           0.38       751
   macro avg       0.38      0.28      0.27       751
weighted avg       0.52      0.38      0.36       751

Nhìn vào kết quả, chúng ta có thể kết luận độ chính xác của bộ phân loại là 52% (0.52). Đó 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!

Đăng ký nhận bài viết mới
Đăng ký nhận bài viết mới qua email