You are on page 1of 70

TRƯỜNG ĐẠI HỌC CÔNG NGHIỆP HÀ NỘI

KHOA CÔNG NGHỆ THÔNG TIN


···🙞🙜🕮🙞🙜···

ĐỒ ÁN CHUYÊN NGÀNH
ĐỀ TÀI: PHÂN LOẠI TÌNH TRẠNG HOA QUẢ SỬ
DỤNG CNN KẾT HỢP KMEANS

GVHD: ThS. Ngô Thị Thanh Hòa


Nhóm - Nhóm 01 -
Lớp: 20224IT6070001
Thành Văn Minh Dương
viên: Ngô Thế Quyền
Phạm Hoàng Đức Tài

Hà Nội – 2023
MỤC LỤC
MỤC LỤC...........................................................................................................................................2
LỜI MỞ ĐẦU.....................................................................................................................................3
LỜI CẢM ƠN......................................................................................................................................4
CHƯƠNG 1: TỔNG QUAN...............................................................................................................5
1.1 Tổng quan về đề tài....................................................................................................................5
1.1.1 Lý do chọn đề tài.................................................................................................................5
1.1.2 Bài toán phân loại và phân cụm hoa quả.............................................................................5
1.1.3 Một số kỹ thuật hiện có để giải quyết bài toán....................................................................7
1.2 Cơ sở lý thuyết.........................................................................................................................10
1.2.1 Mô hình mạng CNN (Convolutional neural networks).....................................................10
1.2.2 K Means............................................................................................................................20
1.3 Các thư viện sử dụng trong bài toán.........................................................................................26
CHƯƠNG 2: THỰC NGHIỆM.........................................................................................................28
2.1 Dataset......................................................................................................................................28
2.2 Các mô hình sẽ sử dụng trong bài toán....................................................................................28
2.2.1 Mô hình tự tạo...................................................................................................................28
2.2.2 Mô hình có sẵn..................................................................................................................28
2.3 Thực nghiệm............................................................................................................................30
2.3.1 Huấn luyện mô hình (Sử dụng CNN)................................................................................30
2.3.2 Phân lớp và phân cụm hoa quả (Kmeans và best_model.h5)............................................54
CHƯƠNG 3: KIỂM THỬ CHƯƠNG TRÌNH.................................................................................57
3.1 Tổng quan về kiểm thử.............................................................................................................57
3.2 Phương pháp và kĩ thuật kiểm thử sử dụng..............................................................................57
3.2.1 Kiểm thử đơn vị................................................................................................................58
3.2.2 Ưu điểm, nhược điểm........................................................................................................58
3.2.3 Quy trình kiểm thử............................................................................................................59
3.3 Thực hiện kiểm thử..................................................................................................................60
3.3.1 Tổ chức module.................................................................................................................60
3.3.2 Xây dựng test case.............................................................................................................62
3.3.3 Test script..........................................................................................................................63
3.4 Đánh giá kiểm thử....................................................................................................................68
KẾT LUẬN.......................................................................................................................................70
TÀI LIỆU THAM KHẢO.................................................................................................................71

2
LỜI MỞ ĐẦU
Công nghệ thông tin đã trở thành một phần quan trọng không thể thiếu
trong cuộc sống hiện đại của con người. Từ những công trình nghiên cứu tiên
tiến trong công nghiệp hàng không vũ trụ, đến những ứng dụng và trang web
chúng ta sử dụng hang ngày. Sự phát triển vượt bậc của công nghệ thông tin
đã mang lại nhiều ứng dụng đáng kể, và trong số đó, trí tuệ nhân tạo
(Artificial Intelligence - AI) đóng một vai trò quan trọng.
Trí tuệ nhân tạo đã thúc đẩy sự phát triển và cải thiện nhiều khả năng
của máy tính, từ nhận dạng hình ảnh (nhận dạng con người, vân tay, …) , xử
lý ngôn ngữ tự nhiên đến dự đoán và phân tích dữ liệu phức tạp liên quan đến
các khối ngành kinh tế, thời tiết, đời sống... Một trong những lĩnh vực tiềm
năng của trí tuệ nhân tạo là trong việc phân cụm và phân loại dữ liệu, và đồ án
chuyên ngành này, nhóm em sẽ tập trung vào ứng dụng mô hình mạng CNN
(Convolutional Neural Network) và thuật toán K-means để thực hiện phân
cụm tình trạng hoa quả.
Đồ án chuyên ngành của chúng em gồm 4 phần chính:
- Chương 1: Tổng quan
- Chương 2: Thực nghiệm
- Chương 3: Kiểm thử
Trong quá trình hoàn thành đồ án, với trình độ kiến thức chuyên môn
chưa nhiều, kinh nghiệm thực tế còn ít và thời gian có hạn nên đồ án của
nhóm em không thể tránh được những thiếu sót. Do đó, nhóm em kính mong
được sự chỉ bảo thêm của các thầy, cô và đóng góp của các bạn để em được
hoàn thiện hơn.
Nhóm em xin chân thành cảm
ơn!

3
LỜI CẢM ƠN
Để có được một đồ án chuyên ngành chỉnh chu và đạt được kết quả tốt
đẹp, em đã nhận được sự giúp đỡ, hỗ trợ của các thầy cô bạn bè. Với tình cảm
sâu sắc, chân thành của mình, cho phép em được bày tỏ lòng biết ơn sâu sắc
đến tất cả các thầy cô và bạn bè đã nhiệt tình giúp đỡ, góp ý
Đặc biệt tôi xin gửi lời cảm ơn chân thành nhất tới cô giáo - Th.S Ngô
Thị Thanh Hoà đã quan tâm giúp đỡ, trực tiếp hướng dẫn tôi hoàn thành tốt
đồ án này trong thời gian qua.

4
CHƯƠNG 1: TỔNG QUAN
1.1 Tổng quan về đề tài
1.1.1 Lý do chọn đề tài
Lý do lựa chọn đề tài này bao gồm:
 Áp dụng công nghệ trí tuệ nhân tạo hiện đại vào lĩnh vực nông nghiệp
có ý nghĩa thiết thực.
 CNN và K-means là những kỹ thuật phù hợp với bài toán phân loại
hình ảnh và phân cụm dữ liệu.
 Bài toán phân loại chất lượng hoa quả là thực tế và có ý nghĩa kinh tế
lớn.
 Kết quả nghiên cứu có thể ứng dụng vào thực tế để nâng cao hiệu quả
sản xuất.
 Đề tài phù hợp với chuyên ngành công nghệ thông tin và trình độ kiến
thức của nhóm.
Như vậy, đề tài này có ý nghĩa khoa học và thực tiễn cao, phù hợp với xu
thế ứng dụng trí tuệ nhân tạo trong nông nghiệp hiện nay. Kết quả nghiên cứu
có thể góp phần nâng cao năng suất và chất lượng sản xuất hoa quả tại Việt
Nam.
1.1.2 Bài toán phân loại và phân cụm hoa quả
a, Bài toán
Bài toán mà nhóm đặt ra cần giải quyết là:
Đầu vào: Tập hợp các ảnh hoa quả có kích thước và góc chụp xác định
(hướng từ trên xuống).
Đầu ra:
 Xác định chính xác loại hoa quả trong ảnh.
 Phân cụm các ảnh hoa quả thành các nhóm dựa trên tình trạng chất
lượng.
Mục tiêu:

5
 Phân loại 15 loại hoa quả đã được xác định với độ chính xác cao
 Phân cụm hoa quả thành các cụm có mức độ giống nhau nhất
Giới hạn:
 Số lượng hoa quả: 15 loại
 Số lượng ảnh: khoảng 44000 ảnh
 Kích thước ảnh đầu vào: ngẫu nhiên pixel.
b, Ý nghĩa và ứng dụng thực tế
Việc áp dụng công nghệ trí tuệ nhân tạo để phân loại và phân cụm chất lượng
hoa quả có ý nghĩa rất lớn:
 Giúp đánh giá chất lượng nông sản một cách khách quan, khoa học
thay vì dựa trên cảm quan của con người.
 Tiết kiệm nhân công và thời gian cần thiết để phân loại sản phẩm thủ
công.
 Nâng cao năng suất và hiệu quả trong quá trình sơ chế, đóng gói và
xuất khẩu hoa quả.
 Giảm tổn thất do các sản phẩm bị loại bỏ không đáp ứng tiêu chuẩn
xuất khẩu.
 Cung cấp thông tin phản hồi để cải tiến quy trình canh tác, bảo quản
sau thu hoạch.
 Áp dụng vào hệ thống kiểm soát chất lượng tự động trong các nhà máy
chế biến.
Một số ứng dụng tiềm năng của bài toán bao gồm:
 Hệ thống phân loại chất lượng sản phẩm tự động trên dây chuyền đóng
gói.
 Phần mềm hỗ trợ ra quyết định phân loại cho các nhân viên kiểm định.
 Camera phân tích hoa quả tự động tại các trạm kiểm soát biên giới.
 Ứng dụng di động hỗ trợ đánh giá chất lượng hoa quả tại vườn cho
nông dân.

6
 Phần mềm phân tích dữ liệu về chất lượng đầu vào để cải tiến quy trình
sản xuất.
Như vậy, việc ứng dụng công nghệ phân loại và phân cụm AI vào lĩnh vực
nông nghiệp có thể mang lại hiệu quả lớn về mặt kinh tế và xã hội.
1.1.3 Một số kỹ thuật hiện có để giải quyết bài toán
a, Kỹ thuật phân loại truyền thống
Trước khi có sự bùng nổ của học sâu (deep learning), các bài toán phân
loại hình ảnh phổ biến được giải quyết bằng các kỹ thuật truyền thống như:
Máy vectơ hỗ trợ (SVM): sử dụng một số đặc trưng của ảnh như
histogram, SIFT, HOG...để huấn luyện các siêu phẳng phân chia các lớp.
SVM hiệu quả với bài toán phân loại ít lớp, nhưng không mạnh với các dữ
liệu phức tạp như ảnh.
Cây quyết định: các thuật toán như Random Forest, Gradient Boosted
Trees... dựa trên việc kết hợp nhiều cây quyết định để tăng độ chính xác.
Chúng linh hoạt với dữ liệu ảnh nhưng độ chính xác thấp hơn so với deep
learning.
K láng giềng gần nhất (KNN): so sánh khoảng cách tính năng của ảnh
input với các mẫu trong training set để quyết định nhãn cho ảnh input. Đơn
giản nhưng không hiệu quả với tập dữ liệu lớn.
Nhìn chung, các kỹ thuật truyền thống có những hạn chế về độ chính
xác và khả năng tổng quát hóa khi áp dụng cho các bài toán phân loại hình
ảnh phức tạp như hoa quả. Chúng cần sự thiết kế tính năng thủ công và hiệu
năng kém hơn so với học sâu trên các tập dữ liệu lớn và phức tạp.
b, Kỹ thuật phân loại hiện đại
Các kỹ thuật phân loại hình ảnh hiện đại chủ yếu dựa trên học sâu
(deep learning) với 2 hướng tiếp cận chính:
Mạng nơ-ron tích chập (CNN): sử dụng các lớp tích chập để tự động
trích xuất đặc trưng từ hình ảnh, sau đó kết hợp với các lớp kết nối đầy đủ

7
(FC) để dự đoán xác suất cho mỗi lớp. CNN rất hiệu quả trong việc học các
đặc trưng cấp thấp từ ảnh và phù hợp với bài toán phân loại hình ảnh.
Học chuyển giao (Transfer learning): sử dụng các mô hình CNN đã
được huấn luyện sẵn trên tập dữ liệu lớn (ImageNet, COCO...), sau đó tinh
chỉnh lại trên tập dữ liệu cụ thể. Giúp tận dụng tri thức đã học và rút ngắn quá
trình huấn luyện.
So với các phương pháp truyền thống, học sâu cho phép mô hình tự học
các đặc trưng phức tạp từ dữ liệu mà không cần thiết kế thủ công. CNN và
Transfer Learning đạt độ chính xác rất cao trong nhiều bài toán nhận dạng và
phân loại hình ảnh thực tế.
Một số mô hình CNN phổ biến:
 VGGNet: sử dụng nhiều lớp CNN 3x3 liên tiếp để tăng độ sâu.
 ResNet: sử dụng kết nối tắt (shortcut connection) để khắc phục hiện
tượng biến mất gradient.
 Inception: sử dụng các Inception block với nhiều kích thước lọc khác
nhau.
 Xception: dùng depthwise separable convolution thay cho standard
convolution.
 MobileNet: thiết kế nhẹ, hiệu quả cho thiết bị di động.
Một số lợi ích của CNN:
 Khả năng học tính năng tự động từ dữ liệu, không cần kỹ thuật thiết kế
thủ công.
 Xử lý tốt các dữ liệu hình ảnh, video nhờ các tầng tích chập.
 Tốc độ huấn luyện nhanh trên GPU. Có thể huấn luyện trên lượng dữ
liệu rất lớn.
 Khả năng tổng quát hóa tốt khi kết hợp regularization và data
augmentation.
c, Thuật toán phân cụm K-means

8
Trong bài toán phân cụm chất lượng hoa quả, một kỹ thuật phù hợp là
thuật toán K-means. Đây là một thuật toán phân cụm không giám sát, không
yêu cầu nhãn của dữ liệu.
Thuật toán K-means hoạt động như sau:
 Khởi tạo ngẫu nhiên K điểm làm centroid ban đầu của các cụm
 Lặp lại cho đến khi hội tụ:
 Gán mỗi điểm dữ liệu vào cụm có khoảng cách nhỏ nhất với
centroid
 Cập nhật lại vị trí centroid bằng cách lấy trung bình cộng của các
điểm trong cụm
 Trả về các nhãn cụm cho mỗi điểm dữ liệu
Ưu điểm của K-means:
 Thuật toán đơn giản, dễ hiểu và dễ cài đặt
 Tốc độ huấn luyện nhanh cho tập dữ liệu lớn
 Có thể áp dụng trên nhiều loại dữ liệu khác nhau
 Không yêu cầu dữ liệu được gán nhãn
Tuy nhiên thuật toán cũng có một số nhược điểm cần lưu ý:
 Kết quả phụ thuộc vào lựa chọn ban đầu của centroid
 Dễ bị mắc kẹt ở điểm cực tiểu cục bộ
 Không hiệu quả với các cụm có hình dạng phức tạp
 Cần chỉ định số lượng cụm K trước
Tóm lại, K-means là một lựa chọn phù hợp cho bài toán phân cụm chất lượng
hoa quả do tính đơn giản, hiệu quả và khả năng mở rộng tốt.
d, Thách thức và hướng phát triển
Mặc dù vậy, việc xây dựng hệ thống phân loại và phân cụm hoa quả thực tế
vẫn còn một số thách thức:
 Xây dựng được tập dữ liệu ảnh hoa quả đủ lớn, đa dạng các loại và chất
lượng.

9
 Mô hình CNN cần kiến trúc và siêu tham số phù hợp để đạt độ chính
xác cao.
 Cải thiện khả năng tổng quát hóa của mô hình trên các điều kiện mới.
 Điều chỉnh thuật toán K-means để xử lý tốt các cụm dữ liệu phức tạp.
 Xử lý các yếu tố gây nhiễu trong thực tế như ánh sáng, góc chụp, nền
phức tạp...
 Tối ưu hệ thống cho thiết bị nhúng và xử lý real-time.
Một số hướng phát triển tiềm năng:
 Kết hợp nhiều mô hình CNN khác nhau thông qua bỏ phiếu mềm hoặc
tập hợp.
 Sử dụng các kỹ thuật tăng cường dữ liệu ảnh để nâng cao độ chính xác.
 Nghiên cứu các thuật toán phân cụm mới như DBSCAN, Mean-shift để
xử lý tốt hơn các cụm dữ liệu không đều.
 Triển khai giải pháp trên nền tảng di động và web để dễ dàng sử dụng.
 Tích hợp với các cảm biến và thiết bị IoT để thu thập dữ liệu đầu vào.
Như vậy, với sự phát triển mạnh mẽ của học máy và khả năng thu thập dữ
liệu lớn, bài toán phân loại và phân cụm chất lượng hoa quả có thể đạt độ
chính xác ngày càng cao hơn trong thực tế. Kết quả nghiên cứu sẽ góp phần
đưa công nghệ trí tuệ nhân tạo vào ứng dụng nông nghiệp, nâng cao năng suất
và giá trị hoa quả.
1.2 Cơ sở lý thuyết
1.2.1 Mô hình mạng CNN (Convolutional neural networks)
a, Tổng quan và khái niệm cơ bản
Kiến trúc truyền thống của một mạng CNN ― Mạng neural tích chập
(Convolutional neural networks), còn được biết đến với tên CNNs, là một
dạng mạng neural được cấu thành bởi các tầng sau:

10
Tầng tích chập và tầng pooling có thể được hiệu chỉnh theo các siêu
tham số (hyperparameters) được mô tả.
b, Các kiểu tầng
Tầng tích chập (CONV) ― Tầng tích chập (CONV) sử dụng các bộ
lọc để thực hiện phép tích chập khi đưa chúng đi qua đầu vào I theo các chiều
của nó. Các siêu tham số của các bộ lọc này bao gồm kích thước bộ lọc F và
độ trượt (stride) S. Kết quả đầu ra O được gọi là feature map hay activation

map.

Lưu ý: Bước tích chập cũng có thể được khái quát hóa cả với trường
hợp một chiều (1D) và ba chiều (3D).
Pooling (POOL) ― Tầng pooling (POOL) là một phép downsampling,
thường được sử dụng sau tầng tích chập, giúp tăng tính bất biến không gian.
Cụ thể, max pooling và average pooling là những dạng pooling đặc biệt, mà
tương ứng là trong đó giá trị lớn nhất và giá trị trung bình được lấy ra.

Kiểu Max pooling Average pooling

11
Từng phép pooling chọn giá Từng phép pooling tính trung
Chức năng trị lớn nhất trong khu vực mà bình các giá trị trong khu vực mà
nó đang được áp dụng nó đang được áp dụng

Minh họa

Nhận xét Bảo toàn các đặc trưng đã Giảm kích thước feature map
phát hiện và được sử dụng Được sử dụng trong mạng
thường xuyên LeNet

Fully Connected (FC) ― Tầng kết nối đầy đủ (FC) nhận đầu vào là
các dữ liệu đã được làm phẳng, mà mỗi đầu vào đó được kết nối đến tất cả
neuron. Trong mô hình mạng CNNs, các tầng kết nối đầy đủ thường được tìm
thấy ở cuối mạng và được dùng để tối ưu hóa mục tiêu của mạng ví dụ như độ
chính xác của lớp.

12
c, Các siêu tham số của bộ lọc
Tầng tích chập chứa các bộ lọc mà rất quan trọng cho ta khi biết ý
nghĩa đằng sau các siêu tham số của chúng.
Các chiều của một bộ lọc ― Một bộ lọc kích thước F × F áp dụng lên
đầu vào chứa C kênh (channels) thì có kích thước tổng kể là F × F × C thực
hiện phép tích chập trên đầu vào kích thước I × I × C và cho ra một feature
map (hay còn gọi là activation map) có kích thước O × O × 1.

Lưu ý: Việc áp dụng K bộ lọc có kích thước F × F K. cho ra một


feature map có kích thước O × O × K

Stride ― Đối với phép tích chập hoặc phép pooling, độ trượt S ký hiệu
số pixel mà cửa sổ sẽ di chuyển sau mỗi lần thực hiện phép tính.

13
Zero-padding ― Zero-padding là tên gọi của quá trình thêm P số
không vào các biên của đầu vào. Giá trị này có thể được lựa chọn thủ công
hoặc một cách tự động bằng một trong ba những phương pháp mô tả bên
dưới:

Phương Valid Same Full


pháp
Giá trị Pstart = Pend= Pstart [ 0, F-1]
P=0
(S SI )−I + F−S P end = F-1

Minh họa

Mục đích + Không sử dụng + Sử dụng padding + Padding tối đa sao


padding để làm cho cho các phép tích
+ Bỏ phép tích feature map có chập có thể được sử
chập cuối nếu I dụng tại các rìa của
kích thước ( S )
số chiều không đầu vào
khớp
+ Kích thước
+ Bộ lọc 'thấy' được
đầu ra thuận lợi
đầu vào
về mặt toán học
từ đầu đến cuối

14
Còn được gọi là
'half' padding

d, Điều chỉnh siêu tham số


Tính tương thích của tham số trong tầng tích chập ― Bằng cách ký
hiệu I là độ dài kích thước đầu vào, F là độ dài của bộ lọc, P là số lượng zero
padding, S là độ trượt, ta có thể tính được độ dài O của feature map theo một
chiều bằng công thức:

I −F + P start + Pend
O= S
+1

Lưu ý: Trong một số trường hợp, Pstart = Pend ≜ P, ta có thể thay thế
Pstart + Pend bằng $2P trong công thức trên.
Hiểu về độ phức tạp của mô hình ― Để đánh giá độ phức tạp của
một mô hình, cách hữu hiệu là xác định số tham số mà mô hình đó sẽ có.
Trong một tầng của mạng neural tích chập, nó sẽ được tính toán như sau:
CONV POOL FC

15
Minh họa

Kích I×I×C I×I×C Nin


thước
đầu vào
Kích O×O×K O×O×C Nout
thước
đầu ra
Số lượng (F × F × C + 1). K 0 (Nin + 1) × Nout
tham số
Lưu ý + Một tham số bias + Phép pooling được + Đầu vào được làm
với mỗi bộ lọc áp dụng lên từng phẳng
+ Trong đa số trường kênh (channel-wise) + Mỗi neuron có
hợp, S < F + Trong đa số trường một tham số bias
+ Một lựa chọn phổ hợp, S = F + Số neuron trong
biến cho K là 2C một tầng FC phụ
thuộc vào ràng buộc
kết cấu

Trường thụ cảm ― Trường thụ cảm (receptive field) tại tầng k là vùng
được ký hiệu Rk × Rk của đầu vào mà những pixel của activation map thứ k có
thể "nhìn thấy". Bằng cách gọi Fj là kích thước bộ lọc của tầng j và Si là giá trị

16
độ trượt của tầng i và để thuận tiện, ta mặc định S0 = 1, trường thụ cảm của
tầng k được tính toán bằng công thức:
k j−1
R k =1+ ∑ ❑ ( Fj−1 ) ∏ ❑ Si
j=0 i=0

Trong ví dụ bên dưới, ta có F 1 = F2 = 3 và S1 = S2 = 1, nên cho ra


được R2 = 1 + 2 ⋅ 1 + 2 ⋅ 1 = 5.

e, Các hàm kích hoạt thường gặp


Rectified Linear Unit ― Tầng rectified linear unit (ReLU) là một hàm
kích hoạt g được sử dụng trên tất cả các thành phần. Mục đích của nó là tăng
tính phi tuyến tính cho mạng. Những biến thể khác của ReLU được tổng hợp
ở bảng dưới:
ReLU Leaky ReLU ELU
g(𝑥) = max (0, 𝑥) g(𝑥) = max (ϵ𝑥, 𝑥) g(𝑥) = max (α (e𝑥
1),
với ϵ ≪ 1 𝑥)
với α ≪ 1
+ Độ phức tạp phi tuyến + Gán vấn đề ReLU chết + Khả vi tại mọi nơi
tính có thể thông dịch cho những giá trị âm
được về mặt sinh học

17
Softmax ― Bước softmax có thể được coi là một hàm logistic tổng
quát lấy đầu vào là một vector chứa các giá trị x ∈ Rn và cho ra là một vector
gồm các xác suất p ∈ Rn thông qua một hàm softmax ở cuối kiến trúc. Nó
được định nghĩa như sau:
xi
e
pi= n
P= p 1 ⋮ pn với
∑ ❑ e xi
j=1

f, Ứng dụng của CNN


CNN được biết là hoạt động tốt trên chuỗi văn bản, âm thanh và video,
đôi khi kết hợp với các mạng khác quả cầu kiến trúc hoặc bằng cách chuyển
đổi các chuỗi thành hình ảnh có thể được xử lý của CNN. Một số vấn đề dữ
liệu cụ thể có thể được giải quyết bằng cách sử dụng CNN với chuỗi dữ liệu
là các bản dịch văn bản bằng máy, xử lý ngôn ngữ tự nhiên và gắn thẻ khung
video, trong số nhiều người khác
Classification: Đây là nhiệm vụ được biết đến nhiều nhất trong
computer vision. Nhiệm vụ chính là phân loại nội dung chung của hình ảnh
thành một tập hợp các danh mục, được gọi là nhãn. Ví dụ: phân loại có thể
xác định xem một hình ảnh có phải là của một con chó, một con mèo hay bất

18
kỳ động vật khác. Việc phân loại này được thực hiện bằng cách xuất ra xác
suất của hình ảnh thuộc từng lớp:

Localization: Mục đích chính của localization là tạo ra một hộp giới
hạn mô tả vị trí của đối tượng trong hình ảnh (được gọi là biên). Đầu ra bao

gồm một nhãn lớp và một hộp giới hạn. Tác vụ này có thể được sử dụng trong
cảm biến để xác định xem một đối tượng ở bên trái hay bên phải của màn
hình:

Detection: Nhiệm vụ này bao gồm thực hiện localization trên tất cả các
đối tượng trong ảnh. Các đầu ra bao gồm nhiều hộp giới hạn, cũng như nhiều
nhãn lớp (một cho mỗi hộp). Nhiệm vụ này được ứng dụng trong việc chế tạo
ô tô tự lái, với mục tiêu là có thể xác định vị trí các biển báo giao thông,

19
đường, ô tô khác, người đi bộ và bất kỳ đối tượng nào khác có thể phù hợp để
đảm bảo trải nghiệm lái xe an toàn…

Segmentation: Nhiệm vụ ở đây là xuất ra cả nhãn lớp và đường viền


của mỗi đối tượng hiện diện trong hình ảnh. Điều này chủ yếu được sử dụng
để đánh dấu các đối tượng quan trọng của hình ảnh cho phân tích sâu hơn. Ví
dụ: tác vụ này có thể được sử dụng để phân định rõ ràng khu vực tương ứng
với khối u trong hình ảnh phổi của bệnh nhân. Hình sau mô tả cách vật thể
quan tâm được phác thảo và gán nhãn:

1.2.2 K Means
a, Tổng quan

Trong thuật toán K-means clustering, chúng ta không biết nhãn (label)
của từng điểm dữ liệu. Mục đích là làm thể nào để phân dữ liệu thành các
cụm (cluster) khác nhau sao cho dữ liệu trong cùng một cụm có tính chất
giống nhau.

20
Ý tưởng đơn giản nhất về cluster (cụm) là tập hợp các điểm ở gần
nhau trong một không gian nào đó (không gian này có thể có rất nhiều chiều
trong trường hợp thông tin về một điểm dữ liệu là rất lớn). Hình bên dưới là
một ví dụ về 3 cụm dữ liệu (cluster).

Bài toán với 3 clusters.

Giả sử mỗi cluster có một điểm đại diện (center) màu vàng. Và
những điểm xung quanh mỗi center thuộc vào cùng nhóm với center đó.
Một cách đơn giản nhất, xét một điểm bất kỳ, ta xét xem điểm đó gần với
center nào nhất thì nó thuộc về cùng nhóm với center đó.

Để hiểu hơn về việc phân nhóm ta lấy ví dụ 1 bài toán: Trên một
vùng biển hình vuông lớn có ba đảo hình vuông, tam giác, và tròn màu vàng
như hình trên. Một điểm trên biển được gọi là thuộc lãnh hải của một đảo
nếu nó nằm gần đảo này hơn so với hai đảo kia. Hãy xác định ranh giới lãnh
hải của các đảo.
Hình dưới đây là một hình minh họa cho việc phân chia lãnh hải nếu
có 5 đảo khác nhau được biểu diễn bằng các hình tròn màu đen:

21
Phân vùng lãnh hải của mỗi đảo. Các vùng khác nhau có màu sắc khác nhau.
Chúng ta thấy rằng đường phân định giữa các lãnh hải là các đường
thẳng (chính xác hơn thì chúng là các đường trung trực của các cặp điểm gần
nhau). Vì vậy, lãnh hải của một đảo sẽ là một hình đa giác.
Cách phân chia này trong toán học được gọi là Voronoi Diagram.
Trong không gian ba chiều, lấy ví dụ là các hành tinh, ta có thể gọi lãnh
không của mỗi hành tinh sẽ là một đa diện. Trong không gian nhiều chiều hơn
chúng ta sẽ có những siêu đa diện (hyperpolygon).
b, Ý nghĩa toán học thuật toán Kmeans
Trước khi tóm tắt thuật toán ta hãy cùng phân tích toán học cho thuật
toán. Mục đích cuối cùng của thuật toán phân nhóm này là: từ dữ liệu đầu vào
và số lượng nhóm chúng ta muốn tìm, hãy chỉ ra center của mỗi nhóm và
phân các điểm dữ liệu vào các nhóm tương ứng. Giả sử thêm rằng mỗi điểm
dữ liệu chỉ thuộc vào đúng một nhóm.
Một số ký hiệu toán học

22
Giả sử có N điểm dữ liệu là X = [x1, x2, …, xN ] ∈ Rd×N và K < N là

số cluster chúng ta muốn phân chia. Chúng ta cần tìm các center m1, m2, …,
1
mK ∈ Rd× và label của mỗi điểm dữ liệu.

Một số lưu ý: các số vô hướng được biểu diễn dưới dạng không in
đậm và có thể được viết hoa, ví dụ x 1, x2 Các vector được biểu diễn bằng các
chữ cái viết thường in đậm ví dụ a, b. Các ma trận được biểu diễn bởi các
chữ viết hoa in đậm ví dụ X, M, Y.

Với mỗi điểm dữ liệu xi đặt yi = [yi1, yi2, … , yiK] và label vector của

nó, trong đó nếu xi được phân vào cluster k thì y ik = 1 và yij = 0, ∀j k.

Điều này có nghĩa là có đúng một phần tử của vector y i là bằng 1 tương ứng

với cluster của xi, các phần tử còn lại bằng 0. Ví dụ nếu một điểm dữ liệu có

label vector là [1, 0, 0, ..., 0] nó thuộc cluster 1, là [0, 1, 0, ..., 0] thì nó thuộc

vào cluster 2, ... cách mã hóa label như vậy được gọi là biểu diễn one-hot.

Ràng buộc của yi có thể viết dưới dạng toán học như sau:
K

yik ∈ {0, 1}, ∑ ❑ yik = 1 (1)


k =1

Hàm mất mát và bài toán tối ưu


Nếu ta coi center mk là các center hay representative (đại diện) của
mỗi cluster và ước lượng tất cả các điểm được phân vào cluster này bởi mk, thì
một điểm dữ liệu xi được phân vào cluster k sẽ bị sai số là (xi – mk). Chúng ta
mong muốn sai số này có trị tuyệt đối nhỏ nhất nên ta sẽ tìm cách đưa đại
lượng sau đây về giá trị nhỏ nhất:
2
‖ xi−mk ‖
2
Hơn nữa, vì xi được phân vào cluster k nên yik = 1, yij = 0, ∀ j ≠k. Khi
đó, biểu thức bên trên sẽ được viết lại là:
23
K
2 2
2 ∑
yik‖ xi−mk ‖ = ❑ yij ‖ xi−mj ‖
j=1 2

sai số cho toàn bộ dữ liệu sẽ là:

N K
2
L(Y,M) = ∑ ❑ ∑ ❑ yij ‖ xi−mk ‖
i=1 j=1 2

Trong đó Y = [y1; y2;...; yN], M = [m1, m2,...mK] lần lượt là các ma trận
được tạo bởi label vector của mỗi điểm dữ liệu và center của mỗi cluster.
Hàm số mất mát trong bài toán K-means clustering của chúng ta là hàm
L(Y,M) với ràng buộc như được nêu trong phương trình (1).
Tóm lại, chúng ta cần tối ưu bài toán sau:
N K
2
Y,M = arg ∑ ❑ ∑ ❑ yij ‖ xi−mk ‖ (2)
i=1 j=1 2
K

Thỏa mãn: yij ∈ {0, 1} ∀ i,j; ∑ ❑ yij = 1 ∀ i


j=1

Trong đó arg min là giá trị của biến số để hàm số đó đạt giá trị nhỏ
nhất. Ví dụ nếu f(x) = x2 – 2x + 1 = (x - 1)2 thì giá trị nhỏ nhất của hàm số này
bằng 0, đạt được khi x = 1. Trong ví dụ này minx f(x) = 0 và arg minx f(x) =
1. Mặt khác nếu cho x 1 = 0, x2 = 4, x3 = 7 thì ta nói arg minx x i = 1 vì 1 là chỉ
số để xi đạt giá trị nhỏ nhất ( = 0 ). Biến số viết bên dưới min là biến số
chúng ta cần tối ứu. Trong các bài toán tối ưu, ta thường quan tâm tới arg min
hơn là min.
Thuật toán tối ưu hàm mất mát
Bài toán (2) là một bài toán khó tìm điểm tối ưu vì nó có thêm các
điều kiện ràng buộc. Bài toán này thuộc loại mix-integer programming (điều
kiện biến là số nguyên) - là loại rất khó tìm nghiệm tối ưu toàn cục (global
optimal point, tức nghiệm làm cho hàm mất mát đạt giá trị nhỏ nhất có thể).

24
Tuy nhiên, trong một số trường hợp chúng ta vẫn có thể tìm được phương
pháp để tìm được nghiệm gần đúng hoặc điểm cực tiểu. (Nếu chúng ta vẫn
nhớ chương trình toán ôn thi đại học thì điểm cực tiểu chưa chắc đã phải là
điểm làm cho hàm số đạt giá trị nhỏ nhất).
Một cách đơn giản để giải bài toán (2) là giải xen kẽ Y và M khi biến
còn lại được cố định. Đây là một thuật toán lặp, cũng là kỹ thuật phổ biến khi
giải bài toán tối ưu. Chúng ta sẽ lần lượt giải quyết hai bài toán sau đây:
Cố định M, tìm Y
Giả sử đã tìm được các centers, hãy tìm các label vector để hàm mất
mát đạt giá trị nhỏ nhất. Điều này tương đương với việc tìm cluster cho mỗi
điểm dữ liệu.
Khi các centers là cố định, bài toán tìm label vector cho toàn bộ dữ
liệu có thể được chia nhỏ thành bài toán tìm label vector cho từng điểm dữ
liệu xi như sau:
K
2
yi = arg ∑ ❑ yij ‖ xi−mj ‖ (3)
j=1 2
K

thỏa mãn: yij ∈ {0, 1} ∀ j; ∑ ❑ yij = 1


j=1

Vì chỉ có một phần tử của label vector yi = 1 nên bài toán (3) có thể
tiếp tục được viết dưới dạng đơn giản hơn:
2
j = arg ‖ xi−mj ‖ 2
2
Vì ‖ xi−mj ‖ 2 chính là bình phương khoảng cách tính từ điểm xi tới mj,

a có thể kết luận rằng mỗi điểm xi thuộc vào cluster có center gần nó nhất!
Từ đó ta có thể dễ dàng suy ra label vector của từng điểm dữ liệu.
Cố định Y, tìm M
Giả sử đã tìm được cluster cho từng điểm, hãy tìm center mới cho mỗi
cluster để hàm mất mát đạt giá trị nhỏ nhất.

25
Một khi chúng ta đã xác định được label vector cho từng điểm dữ liệu,
bài toán tìm center cho mỗi cluster được rút gọn thành:
N
2
mj = arg ∑ ❑ yij ‖ xi−mj ‖
i=1 2

Tới đây, ta có thể tìm nghiệm bằng phương pháp giải đạo hàm bằng 0,
vì hàm cần tối ưu là một hàm liên tục và có đạo hàm xác định tại mọi điểm.
Và quan trọng hơn, hàm này là hàm convex (lồi) theo mj nên chúng ta sẽ tìm
được giá trị nhỏ nhất và điểm tối ưu tương ứng.
Đặt l(mj) là hàm bên trong dấu arg min, ta có đạo hàm:
N
∂ l(mj)
=2 ∑ ❑ yij(mj−xi)
∂ mj i=1

Giải phương trình đạo hàm bằng 0 ta có:

N N
mj ∑ ❑ yij=∑ ❑ yijxi
i=1 i=1

∑ ❑ yijxi
→ mj= i=1N
∑ ❑ yij
i=1

Nếu để ý một chút, chúng ta sẽ thấy rằng mẫu số chính là phép đếm số
lượng các điểm dữ liệu trong cluster j. Còn tử số chính là tổng các điểm dữ
liệu trong cluster j.
Hay nói một cách đơn giản hơn nhiều: mj là trung bình cộng của các
điểm trong cluster j.
c, Tóm tắt thuật toán
Đầu vào: Dữ liệu X và số lượng cluster cần tìm K.
Đầu ra: Các center M và label vector cho từng điểm dữ liệu Y.
1. Chọn K điểm bất kỳ làm các center ban đầu.
2. Phân mỗi điểm dữ liệu vào cluster có center gần nó nhất.
3. Nếu việc gán dữ liệu vào từng cluster ở bước 2 không thay đổi
so với vòng lặp trước nó thì ta dừng thuật toán.
26
4. Cập nhật center cho từng cluster bằng cách lấy trung bình cộng
của tất các các điểm dữ liệu đã được gán vào cluster đó sau
bước 2.
5. Quay lại bước 2.
Chúng ta có thể đảm bảo rằng thuật toán sẽ dừng lại sau một số hữu
hạn vòng lặp. Thật vậy, vì hàm mất mát là một số dương và sau mỗi bước 2
hoặc 3, giá trị của hàm mất mát bị giảm đi. Theo kiến thức về dãy số trong
chương trình cấp 3: nếu một dãy số giảm và bị chặn dưới thì nó hội tụ! Hơn
nữa, số lượng cách phân nhóm cho toàn bộ dữ liệu là hữu hạn nên đến một lúc
nào đó, hàm mất mát sẽ không thể thay đổi, và chúng ta có thể dừng thuật
toán tại đây.
1.3 Các thư viện sử dụng trong bài toán
Trong quá trình giải quyết bài toán, chúng em sử dụng một số thư viện
Python phổ biến sau:
1. Numpy
 Numpy là thư viện cung cấp cấu trúc dữ liệu mảng đa chiều
(ndarray) và các hàm đi kèm để thao tác với chúng.
 Hỗ trợ tính toán ma trận nhanh chóng dựa trên C và Fortran.
 Trong bài toán này, Numpy được dùng để lưu trữ và xử lý các mảng
pixel của ảnh, cũng như vector đặc trưng.
2. Pandas
 Pandas cung cấp cấu trúc dữ liệu DataFrame để lưu trữ và thao tác
dữ liệu dạng bảng.
 Hỗ trợ các phương thức xử lý như groupby, pivot, join, merge.
 Trong bài toán, Pandas được dùng để lưu các siêu dữ liệu về ảnh
cũng như kết quả dự đoán.
3. Matplotlib
 Thư viện vẽ các loại đồ thị như đường, cột, mặt phẳng, hình 3D.

27
 Cung cấp giao diện lập trình ở cấp cao cho các chức năng của thư
viện vẽ matplotlib của Python.
 Sử dụng để trực quan hóa kết quả phân tích, đồ thị so sánh các mô
hình.
4. Seaborn
 Dựa trên matplotlib, cung cấp các lớp vẽ đồ thị thống kê cao cấp
hơn.
 Hỗ trợ vẽ các biểu đồ phân phối, tương quan, phân tích hồi quy.
 Giúp phân tích và trực quan hóa phân phối của dữ liệu.
5. Scikit-learn
 Thư viện học máy phổ biến nhất trong Python.
 Bao gồm các thuật toán phân loại, hồi quy, chụm cụm.
 Trong bài toán dùng KMeans, SVM cho phân cụm và phân loại.
6. Keras
 Keras là thư viện deep learning để xây dựng và huấn luyện mô hình
học sâu.
 Cung cấp các lớp mạng nơ-ron, tích chập, tối ưu hóa, huấn luyện mô
hình.
 Sử dụng để xây dựng và huấn luyện mô hình CNN.
7. OpenCV
 Thư viện xử lý ảnh và thị giác máy tính mã nguồn mở.
 Hỗ trợ xử lý ảnh cơ bản: cắt, xoay, thay đổi kích thước.
 Dùng để tiền xử lý ảnh trước khi đưa vào mô hình.

28
CHƯƠNG 2: THỰC NGHIỆM
2.1 Dataset
- Tên dataset: Fruit Recognition
- Kích thước: 9 GB
- Số lượng ảnh: 44406 ảnh được gán nhãn
- Kích cỡ từng ảnh: 320×258 pixel
- Số loại hoa quả: 15 loại
- Link dataset: Fruit Recognition (kaggle.com)
2.2 Các mô hình sẽ sử dụng trong bài toán
2.2.1 Mô hình tự tạo
Trong phần thực nghiệm, chúng em sẽ xây dựng một mô hình CNN tự
tạo để phân loại các loại hoa quả. Kiến trúc mô hình bao gồm:
- Các lớp Convolutional 2D với filter size 3x3 và số lượng filter tăng
dần từ 32 đến 64
- Các lớp MaxPool2D để giảm chiều dữ liệu
- Lớp Flatten để chuyển các feature maps thành vector đặc trưng
- Lớp Dense với 128 neuron và hàm aktivasi ReLU
- Lớp Dense đầu ra với 15 neuron tương ứng 15 nhãn hoa quả và hàm
aktivasi Softmax
Mô hình sẽ được huấn luyện trên tập dataset gồm 44406 ảnh hoa quả
được gán nhãn với 15 loại hoa quả khác nhau.
2.2.2 Mô hình có sẵn
Ngoài việc xây dựng mô hình CNN tự tạo, chúng em cũng sẽ thử nghiệm
huấn luyện một số kiến trúc mô hình CNN có sẵn như sau:
 DenseNet: Sử dụng các khối dense block với kết nối đặc trưng giữa các
lớp để truyền tải thông tin hiệu quả. Trong mỗi dense block, mỗi lớp
được kết nối trực tiếp với tất cả các lớp phía trước thông qua việc ghép
nối các bản đồ đặc trưng (feature maps). Phiên bản DenseNet121,

29
DenseNet169 và DenseNet201 có độ sâu tăng dần từ 121 lớp đến 201
lớp.
 EfficientNet: Dòng mô hình được thiết kế để cân bằng tối ưu giữa
chiều rộng (width), chiều sâu (depth) và độ phân giải (resolution) của
ảnh đầu vào. Các phiên bản EfficientNetB0 đến EfficientNetB7 có số
lượng tham số và độ sâu mô hình tăng dần, cho phép cân bằng giữa độ
chính xác và chi phí tính toán. Các phiên bản càng cao thì càng đạt độ
chính xác cao hơn nhưng cũng tốn nhiều tài nguyên tính toán hơn.
 InceptionResNetV2: Kết hợp kiến trúc Inception module với ResNet
architecture để tăng hiệu suất, sử dụng các kết nối tắt (shortcut
connection) giúp lưu truyền gradient hiệu quả hơn, giảm tình trạng biến
mất gradient trong quá trình lan truyền ngược.
 InceptionV3: Sử dụng các Inception module song song với nhau để
học được nhiều chiều biểu diễn khác nhau từ cùng một đầu vào. Mỗi
Inception module bao gồm nhiều lớp tích chập và pooling cỡ khác nhau
được áp dụng song song.
 MobileNet: Thiết kế nhẹ, hiệu quả cho mobile và embedded vision. Sử
dụng depthwise separable convolutions thay cho standard convolutions
giúp giảm đáng kể số lượng tham số cần huấn luyện.
 NASNetMobile: Kiến trúc được tối ưu bằng thuật toán Neural
Architecture Search (NAS), tự động tìm ra kiến trúc tốt nhất thông qua
quá trình thử nghiệm nhiều kiến trúc khác nhau.
 ResNet: Sử dụng kết nối tắt (residual connection) để khắc phục hiện
tượng biến mất gradient trong quá trình lan truyền ngược. Phiên bản
ResNet 101 và ResNet 152 có độ sâu lên tới hơn 100 lớp nhờ vào các
kết nối tắt này.
 VGG16, VGG19: Mô hình sử dụng nhiều lớp tích chập 3x3 liên tiếp để
tăng độ sâu mô hình. VGG19 có thêm một số lớp so với VGG16 nên độ
sâu và độ chính xác cao hơn.
30
 Xception: Dựa trên kiến trúc Inception, sử dụng depthwise separable
convolution thay vì standard convolution để giảm số lượng tham số cần
huấn luyện.
Việc sử dụng các mô hình đã được pre-trained trên tập dữ liệu lớn sẽ giúp
nâng cao khả năng phân loại và giảm thời gian huấn luyện. Chúng em sẽ huấn
luyện lại các lớp cuối cùng của mô hình để phân loại các lớp hoa quả. Kết quả
của các thử nghiệm sẽ được trình bày chi tiết ở phần thực nghiệm chương 3.
Như vậy, chúng em hy vọng việc kết hợp huấn luyện cả mô hình tự tạo và
mô hình có sẵn sẽ giúp tìm ra phương án tốt nhất cho bài toán phân loại hoa
quả.
2.3 Thực nghiệm
2.3.1 Huấn luyện mô hình (Sử dụng CNN)
Do cấu hình của máy tính bị hạn chế, nên việc thực hiện huấn luyện mô
hình sẽ được thực hiện trên Kaggle.
In [1]:
# Import các thư viện cần thiết
import pandas as pd
import numpy as np
import seaborn as sns
import os
import cv2
import matplotlib.pyplot as plt
import random
import time
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
from sklearn.model_selection import train_test_split
import keras
from keras import Sequential
from keras.layers import Activation, Dropout, Flatten, Dense, Conv2D, MaxPooling2D
from keras.utils import to_categorical
from keras.callbacks import EarlyStopping, ModelCheckpoint
import gc
from IPython.display import Markdown, display

# Hàm in định dạng markdown


def printmd(string):
display(Markdown(string))
# Đặt seed cho việc tạo số ngẫu nhiên để có kết quả nhất quán
np.random.seed(0)

# Hàm load hình ảnh từ một thư mục cụ thể


def load_images_from_folder(folder, only_path=False, label=""):

31
if only_path == False:
images = []
# Duyệt qua tất cả các file trong thư mục
for filename in os.listdir(folder):
# Đọc hình ảnh bằng thư viện matplotlib
img = plt.imread(os.path.join(folder, filename))
# Nếu hình ảnh không rỗng, thêm vào danh sách hình ảnh
if img is not None:
images.append(img)
return images
else:
path = []
# Duyệt qua tất cả các file trong thư mục
for filename in os.listdir(folder):
img_path = os.path.join(folder, filename)
# Nếu đường dẫn không rỗng, thêm vào danh sách đường dẫn với nhãn
if img_path is not None:
path.append([label, img_path])
return path
In [2]:
# Khởi tạo danh sách rỗng để lưu trữ thông tin hình ảnh
images = []
# Đường dẫn đến thư mục chứa dữ liệu về trái cây
dirp = "/kaggle/input/fruit-recognition-khmt02/"
# Duyệt qua tất cả các tệp và thư mục trong thư mục chứa dữ liệu
for f in os.listdir(dirp):
# Kiểm tra xem tệp có chứa hình ảnh PNG không
if "png" in os.listdir(dirp+f)[0]:
# Nếu có, thêm đường dẫn của hình ảnh với nhãn tương ứng vào danh sách
images += load_images_from_folder(dirp+f, True, label=f)
else:
# Nếu không phải là tệp PNG, duyệt qua tất cả các thư mục con
for d in os.listdir(dirp+f):
# Thêm đường dẫn của hình ảnh trong thư mục con với nhãn tương ứng vào danh sách
images += load_images_from_folder(dirp+f+"/"+d, True, label=f)

# Tạo DataFrame từ danh sách hình ảnh và đặt tên cột là "fruit" và "path"
df = pd.DataFrame(images, columns=["fruit", "path"])
# Trộn dữ liệu để đảm bảo tính ngẫu nhiên, sử dụng random_state để có kết quả nhất quán
from sklearn.utils import shuffle
df = shuffle(df, random_state=0)

# Đặt lại chỉ số của DataFrame


df = df.reset_index(drop=True)

# Sắp xếp tên trái cây theo thứ tự chữ cái


fruit_names = sorted(df.fruit.unique())

# Tạo một ánh xạ từ tên trái cây sang số nguyên để làm nhãn cho mô hình
mapper_fruit_names = dict(zip(fruit_names, [t for t in range(len(fruit_names))]))
df["label"] = df["fruit"].map(mapper_fruit_names)

# In ra ánh xạ giữa tên trái cây và số nguyên


print(mapper_fruit_names)

# Hiển thị 5 dòng đầu tiên của DataFrame

32
df.head()
Out [2]:

In [3]:
# Tính số lượng hình ảnh cho mỗi loại trái cây
vc = df["fruit"].value_counts()

# Vẽ biểu đồ cột sử dụng thư viện Seaborn


plt.figure(figsize=(10, 5))
sns.barplot(x=vc.index, y=vc, palette="rocket")

# Đặt tiêu đề cho biểu đồ


plt.title("Number of pictures of each category", fontsize=15)

# Xoay nhãn trên trục x để dễ đọc


plt.xticks(rotation=90)

# Hiển thị biểu đồ


plt.show()

Out [3]:

33
In [4]:
# Tạo một lưới 4x5 subplot với kích thước tổng cộng là 15x15
fig, axes = plt.subplots(nrows=4, ncols=5, figsize=(15, 15),
subplot_kw={'xticks': [], 'yticks': []})

# Duyệt qua mỗi subplot và hiển thị hình ảnh tương ứng
for i, ax in enumerate(axes.flat):
# Hiển thị hình ảnh từ đường dẫn được lấy từ DataFrame
ax.imshow(plt.imread(df.path[i]))

# Đặt tiêu đề của subplot là loại trái cây tương ứng


ax.set_title(df.fruit[i], fontsize=12)

# Tinh chỉnh layout để giảm khoảng cách giữa các subplot


plt.tight_layout(pad=0.0)

# Hiển thị toàn bộ biểu đồ


plt.show()
Out [4]:

In [5]:
34
# Đọc hình ảnh từ đường dẫn trong DataFrame
img = plt.imread(df.path[0])

# Hiển thị hình ảnh gốc


plt.imshow(img)
plt.title("Original image")
plt.show()

# Thay đổi kích thước hình ảnh sử dụng OpenCV


resized_img = cv2.resize(img, (150, 150))

# Hiển thị hình ảnh sau khi đã thay đổi kích thước
plt.imshow(resized_img)
plt.title("After resizing")
plt.show()

Out [5]:

35
In [6]:
def cut_df(df, number_of_parts, part):
"""
Cắt DataFrame thành các phần và trả về phần được chỉ định.

Parameters:
- df: DataFrame
DataFrame cần cắt.
- number_of_parts: int
Số lượng phần cần cắt DataFrame.
- part: int
Phần cần lấy từ DataFrame.

Returns:
- DataFrame
DataFrame là một phần của DataFrame được cắt.
"""
if part < 1:
print("Error, the part should be at least 1")
elif part > number_of_parts:
print("Error, the part cannot be higher than the number_of_parts")
number_imgs_each_part = int(df.shape[0] / number_of_parts)
idx1 = (part - 1) * number_imgs_each_part
idx2 = part * number_imgs_each_part

36
return df.iloc[idx1:idx2]

def load_img(df):
"""
Tải hình ảnh từ DataFrame.
Parameters:
- df: DataFrame
DataFrame chứa thông tin về hình ảnh.

Returns:
- tuple
Tuple chứa hai mảng NumPy: X (hình ảnh) và y (nhãn).
"""
img_paths = df["path"].values
img_labels = df["label"].values
X = []
y = []
for i, path in enumerate(img_paths):
img = plt.imread(path)
img = cv2.resize(img, (150, 150))
label = img_labels[i]
X.append(img)
y.append(label)
return np.array(X), np.array(y)
In [7]:
def create_model():
# Kích thước của hình ảnh đầu vào
shape_img = (150, 150, 3)

# Khởi tạo mô hình Sequential


model = Sequential()

# Thêm lớp Conv2D với 32 bộ lọc, kích thước kernel (3,3), hàm kích hoạt là 'relu', và padding 'same'
model.add(Conv2D(filters=32, kernel_size=(3,3), input_shape=shape_img, activation='relu',
padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# Thêm lớp Conv2D với 64 bộ lọc, kích thước kernel (3,3), hàm kích hoạt là 'relu', và padding 'same'
model.add(Conv2D(filters=64, kernel_size=(3,3), input_shape=shape_img, activation='relu',
padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# Lặp lại quá trình thêm lớp Conv2D và MaxPooling2D nhiều lần
# để giảm kích thước của đầu ra và tăng độ sâu của mô hình
for _ in range(4):
model.add(Conv2D(filters=64, kernel_size=(3,3), input_shape=shape_img, activation='relu',
padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# Biến đổi tensor 2D thành vector


model.add(Flatten())

# Thêm lớp Dense với 256 đơn vị và hàm kích hoạt 'relu'
model.add(Dense(256))
model.add(Activation('relu'))

37
# Áp dụng Dropout để giảm overfitting
model.add(Dropout(0.5))

# Thêm lớp Dense với số lượng đơn vị bằng số lượng loại trái cây và hàm kích hoạt 'softmax'
model.add(Dense(len(mapper_fruit_names)))
model.add(Activation('softmax'))

# Compile mô hình với hàm mất mát là categorical_crossentropy, tối ưu hóa bằng 'adam', và đánh
giá theo độ chính xác
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

return model

In [8]:
def from_categorical(lst):
"""
Chuyển đổi từ dạng one-hot encoding sang nhãn dạng số nguyên.

Parameters:
- lst: list
Danh sách chứa các vector one-hot encoding.

Returns:
- list
Danh sách chứa các nhãn dạng số nguyên.
"""
lst = lst.tolist()
lst2 = []
for x in lst:
lst2.append(x.index(max(x)))
return lst2

def display_stats(y_test, pred):


"""
Hiển thị kết quả của các dự đoán, bao gồm báo cáo phân loại, ma trận nhầm, và độ chính xác.

Parameters:
- y_test: array-like
Nhãn thực tế của dữ liệu kiểm tra.
- pred: array-like
Nhãn dự đoán từ mô hình.

Returns:
- None
"""
print(f"### Result of the predictions using {len(y_test)} test data ###\n")
y_test_class = from_categorical(y_test)
print("Classification Report:\n")
print(classification_report(y_test_class, pred))
print("\nConfusion Matrix:\n\n")
print(confusion_matrix(y_test_class, pred))
print("\n")
printmd(f"# Accuracy: {round(accuracy_score(y_test_class, pred), 5)}")

def plot_training(model):

38
"""
Vẽ đồ thị hiển thị kết quả của quá trình đào tạo mô hình.

Parameters:
- model: keras.models.Sequential
Mô hình đã được đào tạo.

Returns:
- None
"""
history = pd.DataFrame(model.history.history)
history[["accuracy", "val_accuracy"]].plot()
plt.title("Training results")
plt.xlabel("# epoch")
plt.show()
In [9]:
# Tạo mô hình
model = create_model()

# Danh sách để lưu trữ các lịch sử đào tạo từ các fold khác nhau
hists = []

# Chia dữ liệu thành các phần và lấy phần đầu tiên


divisor = 5
start_time = time.time()
X_train, y_train = load_img(cut_df(df, divisor, 1))

# Chuyển đổi nhãn thành dạng one-hot encoding


y_train = to_categorical(y_train)

# Callbacks để theo dõi và ngừng đào tạo khi cần thiết


callbacks = [EarlyStopping(monitor='val_loss', patience=20),
ModelCheckpoint(filepath='best_model.h5', monitor='val_loss', save_best_only=True)]

# Đào tạo mô hình


model.fit(X_train, y_train, batch_size=128, epochs=100, callbacks=callbacks, validation_split=0.1,
verbose=1)

# Lưu lịch sử đào tạo vào danh sách


hists.append(model.history.history)
Out [9]:

In [10]:
# Thu gom rác sau khi train model để giải phóng bộ nhớ
39
gc.collect()
Out [10]:

In [11]:
# Tính thời gian đào tạo mô hình bằng cách lấy thời điểm hiện tại và trừ đi thời điểm bắt đầu
time_model = time.time() - start_time

# In ra thời gian đào tạo mô hình


print(f"Time to train the model: {int(time_model)} seconds")
Out [11]:

In [12]:
# Tạo danh sách trống để lưu trữ accuracy và val_accuracy từ mỗi lịch sử đào tạo
acc = []
val_acc = []

# Duyệt qua từng lịch sử đào tạo và lấy accuracy và val_accuracy


for i in range(len(hists)):
acc += hists[i]["accuracy"]
val_acc += hists[i]["val_accuracy"]

# Tạo DataFrame từ dữ liệu đã lấy được


hist_df = pd.DataFrame({"# Epoch": [e for e in range(1, len(acc) + 1)], "Accuracy": acc,
"Val_accuracy": val_acc})

# Vẽ đồ thị hiển thị Accuracy và Validation Accuracy theo số epoch


hist_df.plot(x="# Epoch", y=["Accuracy", "Val_accuracy"])
plt.title("Accuracy vs Validation Accuracy")
plt.show()

Out [12]:

40
In [13]:
# Tắt các cảnh báo để giảm nhiễu đầu ra
import warnings
warnings.filterwarnings("ignore")

# Tải dữ liệu kiểm tra từ phần 20 của DataFrame


X, y = load_img(cut_df(df, 20, 20))

# Dự đoán xác suất của từng lớp cho dữ liệu kiểm tra
pred_probabilities = model.predict(X)

# Lấy nhãn dự đoán dựa trên xác suất


pred = np.argmax(pred_probabilities, axis=1)

# Chuyển đổi nhãn thực tế thành dạng one-hot encoding


y_test = to_categorical(y)

# Hiển thị các thông số đánh giá (báo cáo phân loại, ma trận nhầm, và độ chính xác)
display_stats(y_test, pred)

Out [13]:

41
In [14]:
42
# Tạo subplot 4x4 để hiển thị 16 hình ảnh
fig, axes = plt.subplots(nrows=4, ncols=4, figsize=(10, 10),
subplot_kw={'xticks': [], 'yticks': []})

# Duyệt qua từng ô subplot và hiển thị hình ảnh kèm theo nhãn thực tế và dự đoán
for i, ax in enumerate(axes.flat):
# Hiển thị hình ảnh từ dữ liệu kiểm tra
ax.imshow(X[-i])

# Đặt tiêu đề cho mỗi ô với nhãn thực tế và nhãn dự đoán


ax.set_title(f"True label: {fruit_names[y[-i]]}\nPredicted label: {fruit_names[pred[-i]]}")

# Sắp xếp và hiển thị đồ thị subplot


plt.tight_layout()
plt.show()
Out [14]:

In [15]:
# Chia dữ liệu thành tập huấn luyện và tập kiểm thử
# - df[['path','fruit']]: Chọn chỉ cột 'path' và 'fruit' từ DataFrame df.
# - sample(frac=0.05, random_state=0): Lấy mẫu 5% ngẫu nhiên từ DataFrame với seed là 0.

43
# - test_size=0.2: Chia dữ liệu thành 80% tập huấn luyện và 20% tập kiểm thử.
# - random_state=0: Seed để đảm bảo việc chia dữ liệu là nhất quán mỗi lần chạy mã.
train_df, test_df = train_test_split(df[['path', 'fruit']].sample(frac=0.05, random_state=0), test_size=0.2,
random_state=0)
In [16]:
import tensorflow as tf
from time import perf_counter
# Tạo hàm để tạo generator cho dữ liệu huấn luyện và kiểm thử
def create_gen():
train_generator = tf.keras.preprocessing.image.ImageDataGenerator(
preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input,
validation_split=0.1
)
test_generator = tf.keras.preprocessing.image.ImageDataGenerator(
preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input
)

# Tạo generator cho dữ liệu huấn luyện từ DataFrame


train_images = train_generator.flow_from_dataframe(
dataframe=train_df,
x_col='path',
y_col='fruit',
target_size=(224, 224),
color_mode='rgb',
class_mode='categorical',
batch_size=32,
shuffle=True,
seed=0,
subset='training',
)

# Tạo generator cho dữ liệu kiểm thử từ DataFrame


val_images = train_generator.flow_from_dataframe(
dataframe=train_df,
x_col='path',
y_col='fruit',
target_size=(224, 224),
color_mode='rgb',
class_mode='categorical',
batch_size=32,
shuffle=True,
seed=0,
subset='validation',
)

# Tạo generator cho dữ liệu kiểm thử từ tập kiểm thử


test_images = test_generator.flow_from_dataframe(
dataframe=test_df,
x_col='path',
y_col='fruit',
target_size=(224, 224),
color_mode='rgb',
class_mode='categorical',
batch_size=32,
shuffle=False
)

44
return train_generator, test_generator, train_images, val_images, test_images

# Hàm để tạo mô hình dựa trên mô hình được chọn


def get_model(model):
kwargs = {'input_shape':(224, 224, 3),
'include_top':False,
'weights':'imagenet',
'pooling':'avg'}
pretrained_model = model(**kwargs)
pretrained_model.trainable = False
inputs = pretrained_model.input
x = tf.keras.layers.Dense(128, activation='relu')(pretrained_model.output)
x = tf.keras.layers.Dense(128, activation='relu')(x)
outputs = tf.keras.layers.Dense(15, activation='softmax')(x)
model = tf.keras.Model(inputs=inputs, outputs=outputs)
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
return model

# Danh sách các mô hình được chọn để đánh giá


models = {
"DenseNet121": {"model":tf.keras.applications.DenseNet121, "perf":0},
"MobileNetV2": {"model":tf.keras.applications.MobileNetV2, "perf":0},
"DenseNet169": {"model":tf.keras.applications.DenseNet169, "perf":0},
"DenseNet201": {"model":tf.keras.applications.DenseNet201, "perf":0},
"EfficientNetB0": {"model":tf.keras.applications.EfficientNetB0, "perf":0},
"EfficientNetB1": {"model":tf.keras.applications.EfficientNetB1, "perf":0},
"EfficientNetB2": {"model":tf.keras.applications.EfficientNetB2, "perf":0},
"EfficientNetB3": {"model":tf.keras.applications.EfficientNetB3, "perf":0},
"EfficientNetB4": {"model":tf.keras.applications.EfficientNetB4, "perf":0},
"EfficientNetB5": {"model":tf.keras.applications.EfficientNetB4, "perf":0},
"EfficientNetB6": {"model":tf.keras.applications.EfficientNetB4, "perf":0},
"EfficientNetB7": {"model":tf.keras.applications.EfficientNetB4, "perf":0},
"InceptionResNetV2": {"model":tf.keras.applications.InceptionResNetV2, "perf":0},
"InceptionV3": {"model":tf.keras.applications.InceptionV3, "perf":0},
"MobileNet": {"model":tf.keras.applications.MobileNet, "perf":0},
"MobileNetV2": {"model":tf.keras.applications.MobileNetV2, "perf":0},
"MobileNetV3Large": {"model":tf.keras.applications.MobileNetV3Large, "perf":0},
"MobileNetV3Small": {"model":tf.keras.applications.MobileNetV3Small, "perf":0},
"NASNetMobile": {"model":tf.keras.applications.NASNetMobile, "perf":0},
"ResNet101": {"model":tf.keras.applications.ResNet101, "perf":0},
"ResNet101V2": {"model":tf.keras.applications.ResNet101V2, "perf":0},
"ResNet152": {"model":tf.keras.applications.ResNet152, "perf":0},
"ResNet152V2": {"model":tf.keras.applications.ResNet152V2, "perf":0},
"ResNet50": {"model":tf.keras.applications.ResNet50, "perf":0},
"ResNet50V2": {"model":tf.keras.applications.ResNet50V2, "perf":0},
"VGG16": {"model":tf.keras.applications.VGG16, "perf":0},
"VGG19": {"model":tf.keras.applications.VGG19, "perf":0},
"Xception": {"model":tf.keras.applications.Xception, "perf":0}
}
# Tạo generator và đào tạo mô hình cho từng mô hình trong danh sách
train_generator, test_generator, train_images, val_images, test_images = create_gen()
print('\n')

45
for name, model in models.items():
m = get_model(model['model'])
models[name]['model'] = m
start = perf_counter()

# Đào tạo mô hình với một epoch và đánh giá thời gian đào tạo
history = m.fit(train_images, validation_data=val_images, epochs=1, verbose=0)
duration = perf_counter() - start
duration = round(duration, 2)

# Lưu thời gian đào tạo vào biến 'perf'


models[name]['perf'] = duration
print(f"{name:20} trained in {duration} sec")

# Lưu độ chính xác trên tập kiểm thử sau mỗi epoch vào biến 'val_acc'
val_acc = history.history['val_accuracy']
models[name]['val_acc'] = [round(v, 4) for v in val_acc]
Out [16]:

In [17]:
# Duyệt qua từng mô hình trong danh sách và thực hiện dự đoán trên tập kiểm thử
for name, model in models.items():

# Dự đoán nhãn cho dữ liệu kiểm thử


pred = models[name]['model'].predict(test_images)
pred = np.argmax(pred, axis=1)

# Lấy tên các nhãn từ generator


labels = (train_images.class_indices)
labels = dict((v, k) for k, v in labels.items())

# Chuyển đổi nhãn dự đoán từ dạng số sang dạng chuỗi


pred = [labels[k] for k in pred]

# Lấy nhãn thực tế từ DataFrame kiểm thử


y_test = list(test_df.fruit)

# Tính độ chính xác và lưu vào biến 'acc'


acc = accuracy_score(y_test, pred)
models[name]['acc'] = round(acc, 4)

Out [17]:

In [18]:

46
# Tạo một danh sách để lưu kết quả của mỗi mô hình
models_result = []

# Duyệt qua từng mô hình và lấy thông tin về độ chính xác, độ chính xác trên tập kiểm thử, và thời gian
đào tạo
for name, v in models.items():
models_result.append([name, models[name]['val_acc'][-1], models[name]['acc'], models[name]
['perf']])

# Tạo DataFrame từ danh sách kết quả


df_results = pd.DataFrame(models_result, columns=['model', 'val_accuracy', 'accuracy', 'Training time
(sec)'])

# Sắp xếp DataFrame theo độ chính xác giảm dần


df_results.sort_values(by='accuracy', ascending=False, inplace=True)

# Đặt lại chỉ số của DataFrame


df_results.reset_index(inplace=True, drop=True)

# Hiển thị DataFrame kết quả


df_results
Out [18]:

47
In [19]:
# Vẽ đồ thị cột hiển thị độ chính xác của mỗi mô hình trên tập kiểm thử
plt.figure(figsize=(15, 5))
sns.barplot(x='model', y='accuracy', data=df_results)

# Đặt tiêu đề cho đồ thị


plt.title('Accuracy on the test set (after 1 epoch)', fontsize=15)

# Giới hạn trục y trong khoảng từ 0 đến 1 để dễ quan sát


plt.ylim(0, 1)

# Xoay tên các mô hình để dễ đọc


plt.xticks(rotation=90)

# Hiển thị đồ thị


plt.show()
Out [19]:

In [20]:
48
# Sử dụng train_test_split để chia dữ liệu thành tập huấn luyện và tập kiểm thử
train_df, test_df = train_test_split(df, test_size=0.1, random_state=0)

# Sử dụng hàm create_gen để tạo generator cho dữ liệu huấn luyện và kiểm thử
train_generator, test_generator, train_images, val_images, test_images = create_gen()
Out [20]:

In [21]:
# Sử dụng hàm get_model để tạo mô hình DenseNet201
model = get_model(tf.keras.applications.DenseNet201)

# Huấn luyện mô hình trên dữ liệu huấn luyện và kiểm thử


history = model.fit(
train_images, # Dữ liệu huấn luyện
validation_data=val_images, # Dữ liệu kiểm thử
epochs=5, # Số lượng epoch (vòng lặp qua toàn bộ dữ liệu)
callbacks=[ # Danh sách các callbacks (truyền vào EarlyStopping để kiểm soát quá trình đào tạo)
tf.keras.callbacks.EarlyStopping(
monitor='val_loss', # Theo dõi sự thay đổi của hàm loss trên tập kiểm thử
patience=1, # Số epoch mà không có sự cải thiện nào được chấp nhận trước khi dừng đào tạo
restore_best_weights=True # Khôi phục trọng số của mô hình tại epoch có val_loss tốt nhất
)
]
)
Out [21]:

In [22]:
# Tạo DataFrame từ lịch sử huấn luyện
history_df = pd.DataFrame(history.history)

# Chọn chỉ mục 'accuracy' và 'val_accuracy' từ DataFrame và vẽ đồ thị


history_df[['accuracy', 'val_accuracy']].plot()

# Đặt tiêu đề cho đồ thị


plt.title("Accuracy")

# Hiển thị đồ thị


plt.show()
Out [22]:

49
In [23]:
# Tạo DataFrame từ lịch sử huấn luyện
history_df = pd.DataFrame(history.history)

# Chọn chỉ mục 'loss' và 'val_loss' từ DataFrame và vẽ đồ thị


history_df[['loss', 'val_loss']].plot()

# Đặt tiêu đề cho đồ thị


plt.title("Loss")

# Hiển thị đồ thị


plt.show()

Out [23]:

50
In [24]:
# Thực hiện dự đoán trên tập kiểm thử
pred = model.predict(test_images)

# Chọn lớp có xác suất cao nhất cho mỗi dự đoán


pred = np.argmax(pred, axis=1)

# Chuyển đổi chỉ số của lớp thành tên lớp sử dụng class_indices
labels = (train_images.class_indices)
labels = dict((v, k) for k, v in labels.items())
pred = [labels[k] for k in pred]

# Lấy nhãn thực tế từ DataFrame test_df


y_test = list(test_df.fruit)

# Tính toán độ chính xác trên tập kiểm thử


acc = accuracy_score(y_test, pred)

# Hiển thị độ chính xác trên tập kiểm thử


printmd(f'# Accuracy on the test set: {acc * 100:.2f}%')

Out [24]:

51
In [25]:
# Tạo lưới hình ảnh với 4 hàng và 6 cột, tổng cộng 24 ô
fig, axes = plt.subplots(nrows=4, ncols=6, figsize=(20, 12),
subplot_kw={'xticks': [], 'yticks': []})

# Lặp qua từng ô trong lưới


for i, ax in enumerate(axes.flat):
# Hiển thị hình ảnh từ đường dẫn trong DataFrame test_df
ax.imshow(plt.imread(test_df.path.iloc[i]))

# Đặt tiêu đề cho mỗi ô, hiển thị nhãn thực tế và dự đoán


ax.set_title(f"True: {test_df.fruit.iloc[i].split('_')[0]}\nPredicted: {pred[i].split('_')[0]}", fontsize=15)

# Tăng độ rộng của các đồng chất để tránh chồng chéo


plt.tight_layout()

# Hiển thị lưới hình ảnh


plt.show()
Out [25]:

Sau 2 lần training, ta thu được model (best_model.h5) với độ chính xác
99.8%
2.3.2 Phân lớp và phân cụm hoa quả (Kmeans và best_model.h5)
from keras.models import load_model
from keras.preprocessing import image
import numpy as np

52
import os
import shutil
from keras.models import Model
from PIL import Image
from sklearn.cluster import KMeans

# Nhãn của các loại hoa quả


fruit_labels = {
'Apple': 0, 'Banana': 1, 'Carambola': 2, 'Guava': 3, 'Kiwi': 4,
'Mango': 5, 'Orange': 6, 'Peach': 7, 'Pear': 8, 'Persimmon': 9,
'Pitaya': 10, 'Plum': 11, 'Pomegranate': 12, 'Tomatoes': 13,
'muskmelon': 14
}

# Load model
model = load_model('best_model.h5')

# Thư mục chứa các ảnh hoa quả cần phân loại
input_images_directory = 'Data_test/test_1'

# Thư mục chứa ảnh đã được dự đoán


folder_predicted = 'data_predicted'

# Đường dẫn đến thư mục chứa các hình ảnh cần phân loại
folder_clustered = 'clustered'

# Kích thước ảnh đã sử dụng khi huấn luyện model


image_width, image_height = 150, 150

# Kiểm tra và tạo mới hoặc làm sạch thư mục 'data_predicted'
if os.path.exists(folder_predicted):
shutil.rmtree(folder_predicted)
os.makedirs(folder_predicted, exist_ok=True)

# Kiểm tra và tạo mới hoặc làm sạch thư mục 'clustered'
if os.path.exists(folder_clustered):
shutil.rmtree(folder_clustered)
os.makedirs(folder_clustered, exist_ok=True)

# Đọc và tiền xử lý ảnh đầu vào


def load_and_preprocess_image(image_path):
img = image.load_img(image_path, target_size=(image_width,
image_height))
img_array = image.img_to_array(img)
img_array = np.expand_dims(img_array, axis=0) # Thêm chiều batch
img_array = img_array / 255.0 # Chuẩn hóa giá trị pixel về khoảng
[0, 1]
return img_array

# Dự đoán nhãn và di chuyển ảnh vào thư mục tương ứng


for image_file in os.listdir(input_images_directory):
image_path = os.path.join(input_images_directory, image_file)

# Dự đoán nhãn của ảnh


predictions = model.predict(load_and_preprocess_image(image_path))
predicted_label = np.argmax(predictions, axis=1)

# Lấy tên nhãn dự đoán từ dictionary fruit_labels


predicted_fruit = [key for key, value in fruit_labels.items() if

53
value == predicted_label[0]][0]

# Tạo thư mục cho nhãn dự đoán nếu chưa tồn tại
output_class_directory = os.path.join(folder_predicted,
predicted_fruit)
os.makedirs(output_class_directory, exist_ok=True)

# Di chuyển ảnh vào thư mục tương ứng


shutil.copy(image_path, output_class_directory)

print("Phân loại hoàn thành. Các ảnh đã được di chuyển vào các thư mục
tương ứng với nhãn dự đoán.")

# Tạo một mô hình trích xuất đặc trưng từ mô hình gốc


feature_extraction_model = Model(inputs=model.input,
outputs=model.layers[-2].output)

# Số lượng cụm cho mỗi loại hoa quả


num_clusters = 3

# Duyệt qua các thư mục của các loại hoa quả
for fruit_folder in os.listdir(folder_predicted):
fruit_folder_path = os.path.join(folder_predicted, fruit_folder)

# Lấy danh sách các tệp tin trong thư mục và loại bỏ các thư mục
không chứa hình ảnh
image_files = [os.path.join(fruit_folder_path, f) for f in
os.listdir(fruit_folder_path) if
f.endswith(('.png', '.jpg', '.jpeg'))]

# Khởi tạo một danh sách để lưu trữ các đặc trưng
features_list = []

# Trích xuất đặc trưng từ từng hình ảnh


for img_path in image_files:
img = Image.open(img_path).convert('RGB')
img = img.resize((image_width, image_height)) # Resize hình ảnh
về kích thước mong muốn
img_array = np.array(img) / 255.0

# Trích xuất đặc trưng từ hình ảnh


features =
feature_extraction_model.predict(np.expand_dims(img_array, axis=0))
features_list.append(features)

# Chuyển đổi danh sách đặc trưng thành một mảng numpy
features_array = np.vstack(features_list)

# Áp dụng thuật toán K-Means để phân cụm ảnh


kmeans = KMeans(n_clusters=num_clusters)
cluster_labels = kmeans.fit_predict(features_array)

# Lưu trữ ảnh vào các thư mục tương ứng


for i, cluster_label in enumerate(cluster_labels):
img_path = image_files[i]
cluster_folder_path = os.path.join(folder_clustered,
fruit_folder, f'Cluster_{cluster_label}')
os.makedirs(cluster_folder_path, exist_ok=True)
shutil.copy(img_path, cluster_folder_path)

print("Phân loại và lưu trữ hoàn tất.")

54
Kết quả sau khi chạy chương trình, ta được thư mục chứa ảnh cấu trúc như
sau:
Mỗi loại quả có 3 cụm (K = 3)

Cây thư mục gồm các loại quả được phân cụm:

CHƯƠNG 3: KIỂM THỬ CHƯƠNG TRÌNH


3.1 Tổng quan về kiểm thử
Kiểm thử phần mềm là quá trình đánh giá và kiểm tra các tính năng của
phần mềm xem chúng có hoạt động đúng như yêu cầu và thiết kế hay không.
Mục đích của kiểm thử là tìm ra các lỗi và sai sót trong phần mềm để có thể
55
sửa chữa chúng trước khi phát hành sản phẩm. Kiểm thử giúp đảm bảo chất
lượng và độ tin cậy của phần mềm.
Có nhiều cách phân loại kiểm thử phần mềm khác nhau dựa trên các
tiêu chí khác nhau:
1. Dựa trên mức độ kiểm thử:
- Kiểm thử unit: kiểm thử từng đơn vị code nhỏ như hàm, lớp.
- Kiểm thử tích hợp: kiểm tra sự tương tác giữa các
module/component.
- Kiểm thử hệ thống: kiểm tra toàn bộ hệ thống.
- Kiểm thử chấp nhận: bởi người dùng cuối để chấp nhận phần mềm.
2. Dựa trên kiến thức cần có:
- Kiểm thử hộp trắng: dựa trên kiến thức về code và cấu trúc bên trong.
- Kiểm thử hộp đen: không cần hiểu mã nguồn, chỉ dựa vào các chức
năng.
3. Dựa trên mục đích:
- Kiểm thử chức năng: kiểm tra các chức năng có đúng như yêu cầu.
- Kiểm thử hiệu năng: kiểm tra về hiệu năng hệ thống.
- Kiểm thử giao diện: kiểm tra UI/UX.
- Kiểm thử bảo mật: kiểm tra lỗ hổng bảo mật.
Như vậy có nhiều cách phân loại kiểm thử khác nhau, tùy thuộc vào
mục đích và tiêu chí phân loại. Cần kết hợp nhiều kỹ thuật để đảm bảo chất
lượng phần mềm tốt nhất.
3.2 Phương pháp và kĩ thuật kiểm thử sử dụng.
Trong đồ án lần này, chúng em sử dụng phương pháp kiểm thử đơn vị
để thực hiện kiểm thử chương trình.
3.2.1 Kiểm thử đơn vị
Kiểm thử đơn vị, thường được gọi là Unit Testing, là một loại kiểm thử
phần mềm tập trung vào việc kiểm tra từng phần riêng lẻ của mã nguồn phần
mềm (đơn vị) để đảm bảo rằng chúng hoạt động đúng cách. Mục tiêu của
56
kiểm thử đơn vị là xác minh tính chính xác của từng thành phần (hàm, lớp,
phương thức) và đảm bảo rằng chúng thực hiện những chức năng cụ thể mà
chúng được thiết kế để thực hiện.
3.2.2 Ưu điểm, nhược điểm
Ưu điểm của kiểm thử đơn vị:
- Sớm phát hiện lỗi: Kiểm thử đơn vị giúp phát hiện lỗi sớm trong quá
trình phát triển, khi chúng còn dễ dàng sửa chữa và chi phí sửa lỗi thấp hơn so
với khi lỗi được phát hiện sau này.
- Tăng tính nhất quán: Kiểm thử đơn vị đảm bảo rằng mỗi đơn vị mã
nguồn hoạt động đúng cách, làm tăng tính nhất quán của toàn bộ hệ thống.
- Dự án phát triển nhanh hơn: Kiểm thử đơn vị giúp tăng tốc quy trình
phát triển bằng việc giảm thời gian cần để tìm và sửa lỗi sau khi tích hợp.
- Tự động hóa: Các kịch bản kiểm thử đơn vị có thể được tự động hóa,
giúp tiết kiệm thời gian và đảm bảo tính nhất quán trong kiểm thử.
- Thúc đẩy thiết kế tốt hơn: Kiểm thử đơn vị khuyến khích việc viết mã
nguồn dễ bảo trì, tái sử dụng, và kiểm tra, giúp cải thiện thiết kế mã nguồn.
Nhược điểm:
- Chỉ kiểm tra từng đơn vị riêng lẻ: Kiểm thử đơn vị không đảm bảo
tích hợp của các đơn vị thành một hệ thống hoàn chỉnh, và có thể bỏ sót các
vấn đề liên quan đến tích hợp.
- Không thể kiểm tra tất cả trường hợp: Do hạn chế về thời gian và tài
nguyên, kiểm thử đơn vị thường không thể kiểm tra tất cả các trường hợp có
thể xảy ra.
- Khó khảo sát phi chức năng và tương tác người dùng: Kiểm thử đơn
vị tập trung vào kiểm tra chức năng, nên không thích hợp để kiểm tra các khía
cạnh phi chức năng (như hiệu năng) và tương tác người dùng.
- Cần thời gian và nguồn lực: Tạo và duy trì kịch bản kiểm thử đơn vị
có thể tốn thời gian và nguồn lực đáng kể, đặc biệt đối với các dự án lớn và
phức tạp.
57
- Có thể bỏ sót các lỗi cao cấp: Một số lỗi cơ bản như lỗi thiết kế hoặc
lỗi tích hợp không thể phát hiện bằng kiểm thử đơn vị.
3.2.3 Quy trình kiểm thử
Quy trình kiểm thử đơn vị thường được thực hiện trong các giai đoạn sau:
1. Tạo kịch bản kiểm thử (Creating Test Cases):
- Xác định mục tiêu: Trước hết, xác định mục tiêu cụ thể cho đơn vị
kiểm thử. Điều này bao gồm việc hiểu rõ chức năng hoặc phần mã cần kiểm
tra và đặt ra các kịch bản kiểm thử cụ thể.
- Thiết kế kịch bản kiểm thử: Tạo các kịch bản kiểm thử dựa trên yêu
cầu chức năng của đơn vị hoặc mã nguồn cần kiểm tra. Mỗi kịch bản kiểm
thử nên bao gồm một tập hợp các trạng thái đầu vào, các thao tác thực hiện,
và dự đoán về kết quả mong đợi.
- Viết kịch bản kiểm thử: Viết kịch bản kiểm thử dưới dạng mã hoặc tài
liệu mô tả chi tiết về cách thực hiện kiểm thử. Mã kiểm thử thường được viết
bằng ngôn ngữ lập trình phù hợp với ngôn ngữ chương trình cần kiểm tra.
2. Xem xét kịch bản kiểm thử (Reviewing Test Cases):
- Kiểm tra tính đầy đủ: Đảm bảo rằng tất cả các tình huống quan trọng
và kịch bản kiểm thử đã được bao gồm trong danh sách kiểm thử.
- Xem xét mã kiểm thử: Nếu mã kiểm thử đã được viết, kiểm tra mã để
đảm bảo rằng nó đáp ứng các tiêu chuẩn và quy tắc lập trình.
- Xem xét kết quả mong đợi: Xác minh rằng kết quả mong đợi đã được
định nghĩa một cách chính xác cho từng kịch bản kiểm thử.

3. Xây dựng cơ sở kiểm thử (Baselining Test Cases):


- Thực hiện kiểm tra ban đầu (Baseline Testing): Chạy các kịch bản
kiểm thử lần đầu để thiết lập cơ sở kiểm thử, tức là xác định các kết quả kiểm
thử khi phần mã đơn vị chưa được thay đổi hoặc sau khi đã sửa lỗi.
4. Thực hiện kịch bản kiểm thử (Executing Test Cases):

58
- Thực hiện kiểm thử đơn vị: Chạy các kịch bản kiểm thử đã được xác
định trong giai đoạn 1 trên đơn vị mã nguồn cụ thể. Đảm bảo rằng tất cả các
trạng thái và thao tác kiểm thử đã được thực hiện theo đúng yêu cầu.
- So sánh kết quả kiểm thử: So sánh kết quả kiểm thử thực tế với kết
quả mong đợi đã xác định trong giai đoạn 1. Nếu kết quả không khớp, báo
cáo lỗi và các vấn đề tương tự.
3.3 Thực hiện kiểm thử
3.3.1 Tổ chức module
Module CNN : thực hiện phân tách thành cách hàm nhỏ để thực hiện test
- Hàm đọc ảnh và xử lí ảnh với đầu là đường dẫn ảnh
def load_and_preprocess_image(image_path, image_width=150,
image_height=150):
img = image.load_img(image_path,target_size=(image_width,
image_height))
img_array = image.img_to_array(img)
img_array = np.expand_dims(img_array, axis=0)
img_array = img_array / 255.0
return img_array

- Hàm dự đoán và di chuyển ảnh đến folder


def predict_and_move_image(model, image_path,
output_directory, fruit_labels):
predictions =
model.predict(load_and_preprocess_image(image_path))
predicted_label = np.argmax(predictions, axis=1)
predicted_fruit = [key for key, value in
fruit_labels.items() if value == predicted_label[0]][0]

output_class_directory = os.path.join(output_directory,
predicted_fruit)
os.makedirs(output_class_directory, exist_ok=True)
shutil.copy(image_path, output_class_directory)

- Hàm phân loại ảnh (thực hiện phân loại ảnh quả là quả nào)
def classify_images(input_images_directory,
output_directory, model, fruit_labels):
os.makedirs(output_directory, exist_ok=True)

for image_file in os.listdir(input_images_directory):


image_path = os.path.join(input_images_directory,

59
image_file)
predict_and_move_image(model, image_path,
output_directory, fruit_labels)
- Hàm main:
def main():
model_path = "..\\Modal\\best_model.h5"
model = load_model(model_path)

fruit_labels = {
'Apple': 0, 'Banana': 1, 'Carambola': 2, 'Guava': 3,
'Kiwi': 4,
'Mango': 5, 'Orange': 6, 'Peach': 7, 'Pear': 8,
'Persimmon': 9,
'Pitaya': 10, 'Plum': 11, 'Pomegranate': 12,
'Tomatoes': 13, 'muskmelon': 14
}

input_images_directory = 'D:\\Dataset\\Data test\\test_1'


output_directory = 'D:\\Python - craw data\\DACN\\Main\\
Fruits'

classify_images(input_images_directory, output_directory,
model, fruit_labels)
print("Phân loại hoàn thành. Các ảnh đã được di chuyển
vào các thư mục tương ứng với nhãn dự đoán.")
Module Kmeans:
- Hàm load model
def load_pretrained_model(model_path):
return load_model(model_path)

- Hàm trích chọn đặc trưng


def extract_features(model, img_array):
feature_extraction_model = Model(inputs=model.input,
outputs=model.layers[-2].output)
return
feature_extraction_model.predict(np.expand_dims(img_array,
axis=0))

- Hàm điều chỉnh kích thước


def resize_image(img, target_size):
img = img.resize(target_size)
return np.array(img) / 255.0
- Hàm phân cụm ảnh

60
def cluster_images(features_array, num_clusters):
kmeans = KMeans(n_clusters=num_clusters)
return kmeans.fit_predict(features_array)
- Hàm main
def main(input_folder_path, output_folder_path, model,
num_clusters):
for fruit_folder in os.listdir(input_folder_path):
fruit_folder_path = os.path.join(input_folder_path,
fruit_folder)
image_files = [os.path.join(fruit_folder_path, f) for
f in os.listdir(fruit_folder_path) if f.endswith('.png')]
features_list = []

for img_path in image_files:


img = Image.open(img_path).convert('RGB')
img_array = resize_image(img, (150, 150))
features = extract_features(model, img_array)
features_list.append(features)

features_array = np.vstack(features_list)
cluster_labels = cluster_images(features_array,
num_clusters)

for i, cluster_label in enumerate(cluster_labels):


img_path = image_files[i]
cluster_folder_path =
os.path.join(output_folder_path, fruit_folder,
f'Cluster_{cluster_label}')
os.makedirs(cluster_folder_path, exist_ok=True)
shutil.copy(img_path, cluster_folder_path)
3.3.2 Xây dựng test case
Module CNN
Tên hàm Mục đích Test Đầu vào Expected
case
test_load_and_preprocess_image Thực 1 Đường dẫn Mảng ảnh có kích thước (1,
hiện hàm file ảnh hợp 150, 150, 3)
kiểm tra lệ
test_load_and_preprocess_image_faile đọc ảnh 2 Đường dẫn Không load được ảnh
d file không tồn
tại
test_predict_and_move_image Thực 3 Đầu vào hợp File ảnh được di chuyển vào
hiện hàm lệ thư mục output tương ứng
dự đoán 4 Đường dẫn Không thực hiện di chuyển
test_predict_and_move_image_failed và di file ảnh ảnh
chuyển không tồn tại
ảnh
test_classify_images Thực 5 Đầu vào hợp Các file ảnh trong thư mục
hiện lệ input được di chuyển vào các
phân loại thư mục con tương ứng trong
ảnh thư mục output

61
test_classify_images_failed 6 Đường dẫn Không thực hiện được dự
thư mục input đoán ảnh
không tồn tại

Module Kmeans
Tên hàm Mục đích Test Đầu vào Expected
cas
e
test_load_pretrained_model_success Thực 1 Đường dẫn model Đường dẫn tới file
hiện load hợp lệ model .h5
model
test_load_pretrained_model_failure 2 Đường dẫn model Đường dẫn không tồn tại,
train không hợp lệ và không load đc model
test_extract_features Thực 3 Đầu vào hợp lệ Đối tượng Model đã
hiện kiểm được load, mảng ảnh có
tra hàm kích thước (150, 150, 3)
test_extract_features_invalid_input trích chọn 4 Đầu vào không hợp Không thể trích chọn đặc
đặc trưng lệ trưng
test_resize_image_success Thực 5 Đầu vào hợp lệ Đối tượng Image, kích
hiện kiểm thước mục tiêu (150,
tra thay 150)
đổi kích
thước
ảnh
test_cluster_images_success Thực 6 Kích thước mục tiêu Ảnh được phân cụm
hiện kiểm không hợp lệ
tra phân
test_cluster_images_invalid_clusters 7 Số cụm không hợp Không thể phân cụm
cụm ảnh lệ

3.3.3 Test script


Module CNN
import unittest
import os
import pytest
from keras.models import load_model
from Main.CNN_module import load_and_preprocess_image,
predict_and_move_image, classify_images

class TestFruitImageClassification(unittest.TestCase):

def setUp(self):
self.model_path = "../Modal/best_model.h5"
self.model = load_model(self.model_path)
self.fruit_labels = {
'Apple': 0, 'Banana': 1, 'Carambola': 2, 'Guava':
3, 'Kiwi': 4,
'Mango': 5, 'Orange': 6, 'Peach': 7, 'Pear': 8,
'Persimmon': 9,
'Pitaya': 10, 'Plum': 11, 'Pomegranate': 12,

62
'Tomatoes': 13, 'muskmelon': 14
}

self.input_images_directory = "D:\\Dataset\\Data
test\\test_2"
self.output_directory = "D:\\Python - craw data\\
DACN\\Test\\FruitsTestFolder"

# Test case 1: thực hiện load ảnh thành công với đường
dẫn đứng
def test_load_and_preprocess_image(self):
image_path = "D:\\Dataset\\Data test\\test_2\\Apple
343.png"
img_array = load_and_preprocess_image(image_path)

self.assertEqual(img_array.shape, (1, 150, 150, 3))


print("Test passed!")

# Test case 2: thực hiện load ảnh với đường dẫn sai
def test_load_and_preprocess_image_failed(self):
image_path = "wrong_path"
with self.assertRaises(FileNotFoundError):
img_array = load_and_preprocess_image(image_path)
print("Test failed!")

# Test case 3: thực hiện dự đoán ảnh và di chuyển ảnh


với đường dẫn hợp lệ
def test_predict_and_move_image(self):
image_path = "D:\\Dataset\\Data test\\test_2\\Apple
343.png"
output_path = self.output_directory
predict_and_move_image(self.model, image_path,
output_path, self.fruit_labels)
self.assertTrue(os.path.exists(output_path))
print("Test passed!")

# Test case 4: thực hiện dự đoán ảnh và di chuyển ảnh


với đường dẫn sai
def test_predict_and_move_image_failed(self):
image_path = "wrong_path"
output_path = "wrong_path"
with self.assertRaises(Exception):
predict_and_move_image(self.model, image_path,
output_path, self.fruit_labels)
print("Test failed!")

# Test case 5: thực hiện dự đoán ảnh với đường dẫn đúng
def test_classify_images(self):
classify_images(self.input_images_directory,
self.output_directory, self.model, self.fruit_labels)

63
for fruit_label in self.fruit_labels:
fruit_directory =
os.path.join(self.output_directory, fruit_label)
self.assertTrue(os.path.exists(fruit_directory))

print("Test passed!")

# Test case 6: thực hiện dự đoán ảnh với đường dẫn


không hợp lệ
def test_classify_images_failed(self):
# Thiết lập sai đầu vào
wrong_input_dir = "wrong_dir"

with self.assertRaises(Exception):
classify_images(wrong_input_dir,
self.output_directory, self.model, self.fruit_labels)

print("Test failed!")

if __name__ == "__main__":
pytest.main()

Module Kmeans:
import unittest
import os
import numpy as np
from keras.models import Model
from PIL import Image
from Main.Kmean_module import load_pretrained_model,
extract_features, resize_image, cluster_images

class TestClusteringFunctions(unittest.TestCase):

def setUp(self):
# Thiết lập biến sử dụng chung cho các bài kiểm tra
self.model_path = '../Modal/best_model.h5'
self.model = load_pretrained_model(self.model_path)
self.image_path = '../Main/Fruits/Apple/Apple
E05100.png'
self.sample_image = np.random.rand(150, 150,
3).astype(np.float32)

# Test case 1: thực hiện load model với đường dẫn đúng
def test_load_pretrained_model_success(self):
model_path = self.model_path
loaded_model = load_pretrained_model(model_path)
self.assertIsNotNone(loaded_model)
print("Model loaded successfully!")

64
# Test case 2: thực hiện load model với đường dẫn sai
def test_load_pretrained_model_failure(self):
model_path = "invalid_path"
with self.assertRaises(OSError):
loaded_model = load_pretrained_model(model_path)
print("Failed to load model!")

# Test case 3: thực hiện trích chọn đặc trưng với ảnh
hợp lệ
def test_extract_features(self):
features = extract_features(self.model,
self.sample_image)

self.assertIsNotNone(features)
self.assertEqual(features.ndim, 2)

print("Test passed!")

# Test case 4: thực hiện trích chọn đặc trưng với ảnh
không hợp lệ
def test_extract_features_invalid_input(self):
invalid_image = None

with self.assertRaises(ValueError):
features = extract_features(self.model,
invalid_image)

print("Test failed due to invalid input!")

# Test case 5: thực hiện thay đổi kích thước ảnh với
ảnh hợp lệ
def test_resize_image_success(self):
img = Image.fromarray((self.sample_image *
255).astype(np.uint8))
resized_img = resize_image(img, (50, 50))

self.assertEqual(resized_img.shape, (50, 50, 3))

print(" Resize image successfully!")

# Test case 6: thực hiện phân cụm ảnh với số cụm hợp lệ
def test_cluster_images_success(self):
features_array = np.random.rand(10, 1280)
num_clusters = 5

cluster_labels = cluster_images(features_array,
num_clusters)

65
self.assertEqual(cluster_labels.shape, (10,))

print("Cluster images successfully!")

# Test case 7: thực hiện phân cụm ảnh với số cụm không
hợp lệ
def test_cluster_images_invalid_clusters(self):
features_array = np.random.rand(10, 1280)
num_clusters = 0

with self.assertRaises(ValueError):
cluster_labels = cluster_images(features_array,
num_clusters)

print("Failed to cluster due to invalid number of


clusters!")

if __name__ == '__main__':
unittest.main()

4.3.4 Kết quả Test


Module CNN

6 test case đều pass với thời gian 116.54s

Module Kmeans:

66
7 test đều pass trong vòng 4.89s.
3.4 Đánh giá kiểm thử
Với bộ test case xây dựng và sau khi thực hiện xong kiểm thử, chương
trình của chúng em đã thực hiện chạy ổn định với các dữ liệu đầu vào được
kiểm thử.
Tên hàm Test Đầu vào Expected Kết quả
case test
test_load_and_preprocess_image 1 Đường Mảng ảnh có kích Passed
dẫn file thước (1, 150,
ảnh hợp 150, 3)
lệ
test_load_and_preprocess_image_faile 2 Đường Không load được Passed
d dẫn file ảnh
không tồn
tại
test_predict_and_move_image 3 Đầu vào File ảnh được di Passed
hợp lệ chuyển vào thư
mục output tương
ứng
test_predict_and_move_image_failed 4 Đường Không thực hiện Passed
dẫn file di chuyển ảnh
ảnh
không tồn
tại
test_classify_images 5 Đầu vào Các file ảnh trong Passed
hợp lệ thư mục input
được di chuyển
vào các thư mục
con tương ứng
trong thư mục
output
test_classify_images_failed 6 Đường Không thực hiện Passed

67
dẫn thư được dự đoán
mục input ảnh
không tồn
tại

Tên hàm Test Đầu vào Expected Kết quả


case
test_load_pretrained_model_success 1 Đường Đường dẫn tới Passed
dẫn file model .h5
model
hợp lệ
test_load_pretrained_model_failure 2 Đường Đường dẫn Passed
dẫn không tồn tại, và
model không load đc
không model
hợp lệ
test_extract_features 3 Đầu vào Đối tượng Passed
hợp lệ Model đã được
load, mảng ảnh
có kích thước
(150, 150, 3)
test_extract_features_invalid_input 4 Đầu vào Không thể trích Passed
không chọn đặc trưng
hợp lệ
test_resize_image_success 5 Đầu vào Đối tượng Passed
hợp lệ Image, kích
thước mục tiêu
(150, 150)
test_cluster_images_success 6 Kích Ảnh được phân
thước cụm Passed
mục tiêu
không
hợp lệ
test_cluster_images_invalid_clusters 7 Số cụm Không thể phân Passed
không cụm
hợp lệ

68
KẾT LUẬN
Trong đồ án này, chúng em đã thực hiện và nghiên cứu mô hình mạng
CNN kết hợp với thuật toán phân cụm Kmeans để thực hiện giải quyết bài
toán: Phân loại tình trạng hoa quả.
Sau đây là một số tổng kết chúng em rút ra được:
- Mô hình CNN được train với độ chính xác cao (>90%) thực
hiện thành công nhận dạng hoa quả (15 loại hoa quả) với càng nhiều
lớp thì càng nhiều đặc trưng được nhặt ra và kết hợp lại thành kết quả
đầu ra.
- Thuật toán Kmeans thực hiện phân cụm dựa trên trích chọn đặc
trưng từ các ảnh đầu vào (các đặc trưng được thực hiện trích chọn
thông qua mô hình CNN) cho độ chính xác cao (bộ dữ liệu test gồm 5
bộ dữ liệu - mỗi bộ dữ liệu trung bình tầm 2000 ảnh)
- Khả năng tổ chức công việc và lên kế hoạch cho hoạt động làm
việc nhóm giúp chúng em làm quen với việc kết nối, trao đổi và giúp
đỡ không chỉ trong môi trường học đường mà có thể hữu ích với cả môi
trường doanh nghiệp.
Và mong muốn của chúng em là đạt được những tầm cao mới như:
- Đạt được độ chính xác gần sát với tuyệt đối (97-99)%.
- Xây dựng thêm giao diện người dùng giúp dễ dàng thao tác với
người dùng cuối.
- Mở rộng số lượng hoa quả giúp đưa vào giúp ích cho việc sản
xuất nông nghiệp.
Nhóm em hy vọng những nghiên cứu và kết quả trong đồ án này sẽ góp phần
giải quyết hiệu quả bài toán phân loại và phân cụm hoa quả, từ đó ứng dụng
vào thực tiễn sản xuất và chế biến. Đây cũng là hướng nghiên cứu tiềm năng
để áp dụng trí tuệ nhân tạo trong lĩnh vực nông nghiệp.

69
TÀI LIỆU THAM KHẢO
1. Tác phẩm sách:
[1] Hồ Viết Hoàng. (2018). Xử lý ảnh và nhận dạng đối tượng.
[2] Trần Đình Cường. (2017). Python cơ bản và nâng cao
[3] Lê Đình Duy. (2017). Deep Learning - Nhập môn máy học sâu
[4] Nguyễn Văn Lợi. (2017). Xử lý ảnh số và thị giác máy tính với
Python và OpenCV
[5] M. Emre Celebi và Bogdan Smolka. (2016). Convolutional Neural
Networks in Visual Computing: A Concise Guide.
[6] Adrian Rosebrock. (2016). Practical Python and OpenCV.
[7] Kevin P. Murphy (2012). Machine Learning: A Probabilistic
Perspective.
2. Website:
[1] https://stanford.edu/~shervine/l/vi/teaching/cs-230/cheatsheet-
convolutional-neural-networks
[2] Stanford University CS231n: Deep Learning for Computer Vision
[3] sklearn.cluster.KMeans — scikit-learn 1.3.2 documentation
3. Tài liệu học thuật trực tuyến (ebook, học liệu trực tuyến):
[1] Nguyễn Thanh Tuấn. (2020). Deep Learning Cơ Bản.
https://nttuan8.com/sach-deep-learning-co-ban
[2] Vũ Hữu Tiệp. (2020). Machine Learning cơ bản.
https://machinelearningcoban.com/ebook/

70

You might also like