Xây dựng bộ phân loại chó, mèo hay gấu trúc với k-NN
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.
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:
|--- 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.
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
, dogs
và panda
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é.
|--- dataset_loader.py
|--- knn.py
|--- animals_dataset
|--- animals
| --- cats
| --- dogs
| --- panda
|--- images
...
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:
# 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:
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 dataset_loader 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:
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:
python knn.py --dataset ./animals_dataset/animals/
Bạn sẽ nhận được kết quả như sau:
$ python knn.py --dataset ./animals_dataset/animals/
[INFO] Loading images
[INFO] processed 500/3000
[INFO] processed 1000/3000
[INFO] processed 1500/3000
[INFO] processed 2000/3000
[INFO] processed 2500/3000
[INFO] processed 3000/3000
[INFO] features matrix: 8.8MB
[INFO] evaluating k-NN classifier
precision recall f1-score support
cats 0.41 0.60 0.48 262
dogs 0.34 0.42 0.38 239
panda 0.85 0.25 0.39 249
accuracy 0.43 750
macro avg 0.53 0.42 0.42 750
weighted avg 0.53 0.43 0.42 750
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!