1. Trước khi bắt đầu
Việc sử dụng mô hình TensorFlow.js đã tăng theo cấp số nhân trong vài năm qua và hiện tại, nhiều nhà phát triển JavaScript đang tìm cách lấy các mô hình hiện đại hiện có và huấn luyện lại để chúng hoạt động với dữ liệu tuỳ chỉnh dành riêng cho ngành của họ. Hành động lấy một mô hình hiện có (thường được gọi là mô hình cơ sở) và sử dụng mô hình đó trên một miền tương tự nhưng khác biệt được gọi là học chuyển giao.
Học chuyển giao có nhiều ưu điểm hơn so với việc bắt đầu từ một mô hình hoàn toàn trống. Bạn có thể sử dụng lại kiến thức đã học được từ một mô hình được huấn luyện trước đó và bạn cần ít ví dụ hơn về mục mới mà bạn muốn phân loại. Ngoài ra, quá trình huấn luyện thường diễn ra nhanh hơn đáng kể do bạn chỉ phải huấn luyện lại một vài lớp cuối cùng của cấu trúc mô hình thay vì toàn bộ mạng. Vì lý do này, học chuyển giao rất phù hợp với môi trường trình duyệt web, nơi tài nguyên có thể thay đổi tuỳ theo thiết bị thực thi, nhưng cũng có quyền truy cập trực tiếp vào các cảm biến để dễ dàng thu thập dữ liệu.
Lớp học lập trình này hướng dẫn bạn cách tạo một ứng dụng web từ đầu, tái tạo trang web "Dạy cho máy" phổ biến của Google. Trang web này cho phép bạn tạo một ứng dụng web có chức năng mà mọi người dùng đều có thể sử dụng để nhận dạng một đối tượng tuỳ chỉnh chỉ bằng một vài hình ảnh mẫu từ webcam của họ. Trang web này được thiết kế tối giản để bạn có thể tập trung vào các khía cạnh Học máy của lớp học lập trình này. Tuy nhiên, giống như trang web Dạy cho máy ban đầu, bạn có thể áp dụng nhiều kinh nghiệm hiện có của nhà phát triển web để cải thiện trải nghiệm người dùng.
Điều kiện tiên quyết
Lớp học lập trình này dành cho những nhà phát triển web đã quen thuộc với các mô hình được tạo sẵn của TensorFlow.js và cách sử dụng API cơ bản, đồng thời muốn bắt đầu với học chuyển giao trong TensorFlow.js.
- Bạn cần có kiến thức cơ bản về TensorFlow.js, HTML5, CSS và JavaScript trong lớp học này.
Nếu bạn mới làm quen với Tensorflow.js, hãy cân nhắc tham gia khoá học miễn phí này từ người mới bắt đầu đến chuyên gia. Khoá học này không yêu cầu bạn phải có kiến thức nền tảng về Học máy hoặc TensorFlow.js, đồng thời dạy cho bạn mọi thứ bạn cần biết theo từng bước nhỏ.
Kiến thức bạn sẽ học được
- TensorFlow.js là gì và lý do bạn nên sử dụng TensorFlow.js trong ứng dụng web tiếp theo của mình.
- Cách tạo một trang web HTML/CSS /JS đơn giản mô phỏng trải nghiệm người dùng của Dạy cho máy.
- Cách sử dụng TensorFlow.js để tải một mô hình cơ sở được huấn luyện trước, cụ thể là MobileNet, nhằm tạo các đặc điểm hình ảnh có thể dùng trong học chuyển giao.
- Cách thu thập dữ liệu từ webcam của người dùng cho nhiều lớp dữ liệu mà bạn muốn nhận dạng.
- Cách tạo và xác định một mạng nơ-ron nhiều lớp lấy các đặc điểm của hình ảnh và học cách phân loại các đối tượng mới bằng cách sử dụng các đặc điểm đó.
Hãy bắt đầu tấn công...
Bạn cần có
- Bạn nên có tài khoản Glitch.com để theo dõi, hoặc bạn có thể sử dụng môi trường phân phát web mà bạn có thể thoải mái chỉnh sửa và chạy.
2. TensorFlow.js là gì?

TensorFlow.js là một thư viện học máy nguồn mở có thể chạy ở bất cứ nơi nào có JavaScript. TensorFlow.js dựa trên thư viện TensorFlow ban đầu được viết bằng Python và nhằm mục đích tạo lại trải nghiệm của nhà phát triển cũng như bộ API này cho hệ sinh thái JavaScript.
Có thể sử dụng chế độ này ở đâu?
Nhờ tính di động của JavaScript, giờ đây, bạn có thể viết bằng 1 ngôn ngữ và thực hiện học máy trên tất cả các nền tảng sau đây một cách dễ dàng:
- Phía máy khách trong trình duyệt web bằng JavaScript thuần
- Phía máy chủ và thậm chí cả các thiết bị IoT như Raspberry Pi bằng Node.js
- Ứng dụng dành cho máy tính sử dụng Electron
- Ứng dụng di động gốc sử dụng React Native
TensorFlow.js cũng hỗ trợ nhiều chương trình phụ trợ trong mỗi môi trường này (môi trường dựa trên phần cứng thực tế mà chương trình có thể thực thi, chẳng hạn như CPU hoặc WebGL. "Phần phụ trợ" trong ngữ cảnh này không có nghĩa là môi trường phía máy chủ – phần phụ trợ để thực thi có thể là phía máy khách trong WebGL chẳng hạn) để đảm bảo khả năng tương thích và cũng giúp mọi thứ chạy nhanh. TensorFlow.js hiện hỗ trợ:
- Thực thi WebGL trên thẻ đồ hoạ (GPU) của thiết bị – đây là cách nhanh nhất để thực thi các mô hình lớn hơn (có kích thước trên 3 MB) bằng tính năng tăng tốc GPU.
- Thực thi Web Assembly (WASM) trên CPU – để cải thiện hiệu suất CPU trên các thiết bị, chẳng hạn như điện thoại di động thế hệ cũ. Điều này phù hợp hơn với các mô hình nhỏ hơn (kích thước dưới 3 MB) có thể thực sự thực thi nhanh hơn trên CPU bằng WASM so với WebGL do chi phí tải nội dung lên bộ xử lý đồ hoạ.
- Thực thi CPU – dự phòng nếu không có môi trường nào khác. Đây là cách chậm nhất trong 3 cách nhưng luôn sẵn sàng hỗ trợ bạn.
Lưu ý: Bạn có thể chọn buộc một trong các chương trình phụ trợ này nếu biết thiết bị mà bạn sẽ thực thi hoặc bạn có thể chỉ cần để TensorFlow.js quyết định cho bạn nếu bạn không chỉ định chương trình phụ trợ.
Sức mạnh siêu phàm của phía máy khách
Việc chạy TensorFlow.js trong trình duyệt web trên máy khách có thể mang lại một số lợi ích đáng cân nhắc.
Quyền riêng tư
Bạn có thể huấn luyện và phân loại dữ liệu trên máy khách mà không cần gửi dữ liệu đến máy chủ web của bên thứ ba. Đôi khi, đây có thể là một yêu cầu để tuân thủ luật pháp địa phương, chẳng hạn như GDPR, hoặc khi xử lý bất kỳ dữ liệu nào mà người dùng có thể muốn lưu giữ trên máy của họ và không gửi cho bên thứ ba.
Speed
Vì bạn không phải gửi dữ liệu đến một máy chủ từ xa, nên quá trình suy luận (hành động phân loại dữ liệu) có thể diễn ra nhanh hơn. Thậm chí, bạn có thể truy cập trực tiếp vào các cảm biến của thiết bị như camera, micrô, GPS, gia tốc kế và nhiều cảm biến khác nếu người dùng cấp cho bạn quyền truy cập.
Phạm vi tiếp cận và quy mô
Chỉ bằng một cú nhấp chuột, bất kỳ ai trên thế giới đều có thể nhấp vào đường liên kết bạn gửi cho họ, mở trang web trong trình duyệt và sử dụng những gì bạn đã tạo. Bạn không cần thiết lập Linux phức tạp phía máy chủ bằng trình điều khiển CUDA và nhiều thứ khác chỉ để sử dụng hệ thống học máy.
Chi phí
Không có máy chủ nghĩa là bạn chỉ cần trả tiền cho một CDN để lưu trữ các tệp HTML, CSS, JS và mô hình. Chi phí của CDN rẻ hơn nhiều so với việc duy trì một máy chủ (có thể có gắn thẻ đồ hoạ) hoạt động 24/7.
Các tính năng phía máy chủ
Việc tận dụng việc triển khai Node.js của TensorFlow.js sẽ cho phép các tính năng sau.
Hỗ trợ đầy đủ CUDA
Về phía máy chủ, để tăng tốc bằng card đồ hoạ, bạn phải cài đặt trình điều khiển NVIDIA CUDA để cho phép TensorFlow hoạt động với card đồ hoạ (không giống như trong trình duyệt sử dụng WebGL – không cần cài đặt). Tuy nhiên, với khả năng hỗ trợ đầy đủ CUDA, bạn có thể tận dụng tối đa các khả năng ở cấp thấp của card đồ hoạ, giúp giảm thời gian huấn luyện và suy luận. Hiệu suất tương đương với việc triển khai TensorFlow bằng Python vì cả hai đều dùng chung một phần phụ trợ C++.
Kích thước mô hình
Đối với các mô hình tiên tiến từ nghiên cứu, bạn có thể làm việc với các mô hình rất lớn, có thể có kích thước lên đến hàng gigabyte. Hiện tại, bạn không thể chạy các mô hình này trong trình duyệt web do giới hạn về mức sử dụng bộ nhớ cho mỗi thẻ trình duyệt. Để chạy các mô hình lớn hơn này, bạn có thể sử dụng Node.js trên máy chủ của riêng mình với các thông số kỹ thuật phần cứng mà bạn cần để chạy mô hình đó một cách hiệu quả.
IOT
Node.js được hỗ trợ trên các máy tính bảng đơn phổ biến như Raspberry Pi. Điều này có nghĩa là bạn cũng có thể thực thi các mô hình TensorFlow.js trên những thiết bị như vậy.
Speed
Node.js được viết bằng JavaScript, tức là Node.js hưởng lợi từ quá trình biên dịch tức thì. Điều này có nghĩa là bạn có thể thường thấy hiệu suất tăng lên khi sử dụng Node.js vì nó sẽ được tối ưu hoá trong thời gian chạy, đặc biệt là đối với mọi hoạt động tiền xử lý mà bạn có thể đang thực hiện. Bạn có thể xem một ví dụ điển hình về điều này trong nghiên cứu điển hình này. Nghiên cứu này cho thấy cách Hugging Face sử dụng Node.js để tăng hiệu suất gấp 2 lần cho mô hình xử lý ngôn ngữ tự nhiên của họ.
Giờ đây, bạn đã hiểu những kiến thức cơ bản về TensorFlow.js, nơi có thể chạy và một số lợi ích, hãy bắt đầu làm những việc hữu ích với TensorFlow.js!
3. Học chuyển giao
Chính xác thì học chuyển giao là gì?
Học chuyển giao là quá trình tiếp thu kiến thức đã học để giúp học một kiến thức khác nhưng tương tự.
Con người chúng ta luôn làm điều này. Bạn có cả một đời trải nghiệm được lưu trữ trong não bộ mà bạn có thể dùng để nhận biết những điều mới lạ mà bạn chưa từng thấy trước đây. Hãy xem xét ví dụ về cây liễu này:

Tuỳ thuộc vào nơi bạn sinh sống, có thể bạn chưa từng thấy loại cây này.
Tuy nhiên, nếu tôi yêu cầu bạn cho biết có cây liễu nào trong hình ảnh mới bên dưới hay không, thì có lẽ bạn có thể phát hiện ra chúng khá nhanh, mặc dù chúng ở một góc khác và hơi khác so với cây liễu ban đầu mà tôi cho bạn xem.

Bạn đã có một nhóm nơ-ron trong não bộ biết cách xác định các vật thể giống như cây và những nơ-ron khác giỏi tìm đường thẳng dài. Bạn có thể sử dụng kiến thức đó để nhanh chóng phân loại cây liễu, là một đối tượng giống như cây có nhiều cành thẳng đứng dài.
Tương tự, nếu có một mô hình học máy đã được huấn luyện trên một miền (chẳng hạn như nhận dạng hình ảnh), bạn có thể sử dụng lại mô hình đó để thực hiện một nhiệm vụ khác nhưng có liên quan.
Bạn có thể làm tương tự với một mô hình nâng cao như MobileNet. Đây là một mô hình nghiên cứu rất phổ biến có thể thực hiện tính năng nhận dạng hình ảnh trên 1.000 loại đối tượng khác nhau. Từ chó đến ô tô, mô hình này được huấn luyện dựa trên một tập dữ liệu khổng lồ có tên là ImageNet, bao gồm hàng triệu hình ảnh được gắn nhãn.
Trong ảnh động này, bạn có thể thấy số lượng lớn các lớp mà mô hình MobileNet V1 có:

Trong quá trình huấn luyện, mô hình này đã học được cách trích xuất các đặc điểm chung quan trọng cho tất cả 1.000 đối tượng đó, đồng thời nhiều đặc điểm cấp thấp mà mô hình này sử dụng để xác định các đối tượng như vậy cũng có thể hữu ích trong việc phát hiện các đối tượng mới mà mô hình này chưa từng thấy trước đây. Sau tất cả, mọi thứ cuối cùng chỉ là sự kết hợp của các đường kẻ, kết cấu và hình dạng.
Hãy xem xét một cấu trúc Mạng nơ-ron tích chập (CNN) truyền thống (tương tự như MobileNet) và xem cách học chuyển giao có thể tận dụng mạng được huấn luyện này để học một điều gì đó mới. Hình ảnh dưới đây cho thấy cấu trúc mô hình điển hình của một CNN. Trong trường hợp này, CNN được huấn luyện để nhận dạng các chữ số viết tay từ 0 đến 9:

Nếu có thể tách các lớp cấp thấp được huấn luyện trước của một mô hình đã huấn luyện hiện có (như mô hình ở bên trái) khỏi các lớp phân loại gần cuối mô hình (như mô hình ở bên phải) (đôi khi được gọi là phần phân loại của mô hình), thì bạn có thể sử dụng các lớp cấp thấp để tạo ra các đặc điểm đầu ra cho bất kỳ hình ảnh nào dựa trên dữ liệu gốc mà mô hình được huấn luyện. Sau đây là mạng lưới tương tự nhưng đã loại bỏ phần đầu phân loại:

Giả sử đối tượng mới mà bạn đang cố gắng nhận dạng cũng có thể sử dụng các tính năng đầu ra như vậy mà mô hình trước đó đã học được, thì có nhiều khả năng các tính năng đó có thể được dùng lại cho một mục đích mới.
Trong sơ đồ trên, mô hình giả định này được huấn luyện trên các chữ số, vì vậy, có thể những gì đã học được về chữ số cũng có thể áp dụng cho các chữ cái như a, b và c.
Giờ đây, bạn có thể thêm một tiêu đề phân loại mới để thử dự đoán a, b hoặc c, như minh hoạ:

Ở đây, các lớp cấp thấp bị đóng băng và không được huấn luyện, chỉ có tiêu đề phân loại mới tự cập nhật để học hỏi từ các tính năng do mô hình được cắt nhỏ đã được huấn luyện trước ở bên trái cung cấp.
Hành động này được gọi là học chuyển giao và là những gì Dạy cho máy thực hiện ở chế độ nền.
Bạn cũng có thể thấy rằng chỉ cần huấn luyện perceptron đa lớp ở cuối mạng, mạng này sẽ huấn luyện nhanh hơn nhiều so với khi bạn phải huấn luyện toàn bộ mạng từ đầu.
Nhưng làm cách nào để bạn có thể lấy được các bộ phận phụ của một mô hình? Hãy chuyển đến phần tiếp theo để tìm hiểu.
4. TensorFlow Hub – mô hình cơ sở
Tìm một mô hình cơ sở phù hợp để sử dụng
Đối với các mô hình nghiên cứu phổ biến và nâng cao hơn như MobileNet, bạn có thể truy cập vào TensorFlow Hub, sau đó lọc các mô hình phù hợp với TensorFlow.js sử dụng cấu trúc MobileNet phiên bản 3 để tìm các kết quả như những kết quả được hiển thị ở đây:

Xin lưu ý rằng một số kết quả thuộc loại "phân loại hình ảnh" (được nêu chi tiết ở phía trên cùng bên trái của mỗi kết quả thẻ mô hình) và một số kết quả khác thuộc loại "vectơ đặc trưng của hình ảnh".
Về cơ bản, những kết quả này là các phiên bản được cắt sẵn của MobileNet mà bạn có thể dùng để lấy vectơ đặc trưng của hình ảnh thay vì phân loại cuối cùng.
Các mô hình như thế này thường được gọi là "mô hình cơ sở". Sau đó, bạn có thể sử dụng các mô hình này để thực hiện việc học chuyển giao theo cách tương tự như trong phần trước bằng cách thêm một tiêu đề phân loại mới và huấn luyện tiêu đề đó bằng dữ liệu của riêng bạn.
Điều tiếp theo cần kiểm tra là đối với một mô hình cơ sở nhất định mà bạn quan tâm, mô hình đó được phát hành dưới định dạng TensorFlow.js nào. Nếu mở trang cho một trong các mô hình MobileNet v3 vectơ đặc trưng này, bạn có thể thấy trong tài liệu JS rằng mô hình này có dạng mô hình đồ thị dựa trên đoạn mã ví dụ trong tài liệu sử dụng tf.loadGraphModel().

Bạn cũng nên lưu ý rằng nếu tìm thấy một mô hình ở định dạng lớp thay vì định dạng biểu đồ, bạn có thể chọn lớp cần cố định và lớp cần huỷ cố định để huấn luyện. Điều này có thể rất hữu ích khi tạo một mô hình cho một tác vụ mới, thường được gọi là "mô hình chuyển". Tuy nhiên, hiện tại, bạn sẽ sử dụng loại mô hình đồ thị mặc định cho hướng dẫn này. Hầu hết các mô hình TF Hub đều được triển khai dưới dạng này. Để tìm hiểu thêm về cách làm việc với các mô hình Layers, hãy tham khảo khoá học TensorFlow.js từ con số 0 đến chuyên gia.
Ưu điểm của học chuyển giao
Việc sử dụng phương pháp học chuyển giao thay vì huấn luyện toàn bộ cấu trúc mô hình từ đầu có những ưu điểm gì?
Trước tiên, thời gian huấn luyện là một lợi thế chính khi sử dụng phương pháp học chuyển giao vì bạn đã có một mô hình cơ sở được huấn luyện để xây dựng.
Thứ hai, bạn có thể chỉ cần cho thấy ít ví dụ hơn nhiều về thứ mới mà bạn đang cố gắng phân loại do quá trình huấn luyện đã diễn ra.
Điều này thực sự hữu ích nếu bạn có ít thời gian và nguồn lực để thu thập dữ liệu mẫu về đối tượng bạn muốn phân loại, đồng thời cần nhanh chóng tạo một nguyên mẫu trước khi thu thập thêm dữ liệu huấn luyện để làm cho nguyên mẫu đó mạnh mẽ hơn.
Do nhu cầu về ít dữ liệu hơn và tốc độ huấn luyện một mạng lưới nhỏ hơn, nên việc học chuyển giao ít tốn tài nguyên hơn. Điều này khiến WebGPU rất phù hợp với môi trường trình duyệt, chỉ mất vài chục giây trên một máy hiện đại thay vì hàng giờ, hàng ngày hoặc hàng tuần để huấn luyện mô hình đầy đủ.
Đã xong! Giờ đây, bạn đã biết bản chất của học chuyển giao, đã đến lúc tạo phiên bản Dạy cho máy của riêng bạn. Hãy bắt đầu!
5. Thiết lập để viết mã
Bạn cần có
- Một trình duyệt web hiện đại.
- Kiến thức cơ bản về HTML, CSS, JavaScript và Chrome DevTools (xem đầu ra của bảng điều khiển).
Hãy bắt đầu lập trình
Các mẫu mã nguồn đã được tạo cho Glitch.com hoặc Codepen.io. Bạn chỉ cần sao chép một trong hai mẫu làm trạng thái cơ sở cho lớp học lập trình này chỉ bằng một cú nhấp chuột.
Trên Glitch, hãy nhấp vào nút "remix this" (trộn lại) để phân nhánh và tạo một bộ tệp mới mà bạn có thể chỉnh sửa.
Hoặc trên Codepen, hãy nhấp vào"fork" ở dưới cùng bên phải màn hình.
Khung rất đơn giản này cung cấp cho bạn các tệp sau:
- Trang HTML (index.html)
- Biểu định kiểu (style.css)
- Tệp để ghi mã JavaScript (script.js)
Để thuận tiện cho bạn, có một câu lệnh nhập được thêm vào tệp HTML cho thư viện TensorFlow.js. Thông báo sẽ có dạng như sau:
index.html
<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>
Cách khác: Sử dụng trình chỉnh sửa web mà bạn muốn hoặc làm việc trên máy
Nếu bạn muốn tải mã xuống và làm việc cục bộ hoặc trên một trình chỉnh sửa trực tuyến khác, chỉ cần tạo 3 tệp có tên như trên trong cùng một thư mục, sau đó sao chép và dán mã từ mẫu Glitch của chúng tôi vào từng tệp.
6. Nguyên mẫu HTML của ứng dụng
Tôi nên bắt đầu từ đâu?
Tất cả các nguyên mẫu đều yêu cầu một số giàn giáo HTML cơ bản mà bạn có thể hiển thị các phát hiện của mình. Thiết lập ngay bây giờ. Bạn sắp thêm:
- Tiêu đề của trang.
- Một số văn bản mô tả.
- Một đoạn văn về trạng thái.
- Video để giữ nguồn cấp dữ liệu webcam sau khi sẵn sàng.
- Một số nút để khởi động camera, thu thập dữ liệu hoặc đặt lại trải nghiệm.
- Nhập cho TensorFlow.js và các tệp JS mà bạn sẽ lập trình sau.
Mở index.html rồi dán nội dung sau lên mã hiện có để thiết lập các tính năng nêu trên:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Transfer Learning - TensorFlow.js</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import the webpage's stylesheet -->
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>Make your own "Teachable Machine" using Transfer Learning with MobileNet v3 in TensorFlow.js using saved graph model from TFHub.</h1>
<p id="status">Awaiting TF.js load</p>
<video id="webcam" autoplay muted></video>
<button id="enableCam">Enable Webcam</button>
<button class="dataCollector" data-1hot="0" data-name="Class 1">Gather Class 1 Data</button>
<button class="dataCollector" data-1hot="1" data-name="Class 2">Gather Class 2 Data</button>
<button id="train">Train & Predict!</button>
<button id="reset">Reset</button>
<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js" type="text/javascript"></script>
<!-- Import the page's JavaScript to do some stuff -->
<script type="module" src="/script.js"></script>
</body>
</html>
Chia nhỏ vấn đề
Hãy phân tích một số mã HTML ở trên để làm nổi bật một số điểm chính mà bạn đã thêm.
- Bạn đã thêm thẻ
<h1>cho tiêu đề trang cùng với thẻ<p>có mã nhận dạng là "status". Đây là nơi bạn sẽ in thông tin, khi bạn sử dụng các phần khác nhau của hệ thống để xem dữ liệu đầu ra. - Bạn đã thêm một phần tử
<video>có mã nhận dạng là "webcam". Sau này, bạn sẽ kết xuất luồng webcam vào phần tử này. - Bạn đã thêm 5 phần tử
<button>. Nút đầu tiên có mã nhận dạng là "enableCam" sẽ bật camera. Hai nút tiếp theo có một lớp là "dataCollector", cho phép bạn thu thập hình ảnh mẫu cho các đối tượng mà bạn muốn nhận dạng. Mã bạn viết sau này sẽ được thiết kế để bạn có thể thêm bất kỳ số lượng nút nào trong số này và chúng sẽ tự động hoạt động như dự kiến.
Xin lưu ý rằng các nút này cũng có một thuộc tính đặc biệt do người dùng xác định có tên là data-1hot, với giá trị số nguyên bắt đầu từ 0 cho lớp đầu tiên. Đây là chỉ mục dạng số mà bạn sẽ dùng để biểu thị dữ liệu của một lớp nhất định. Chỉ mục này sẽ được dùng để mã hoá chính xác các lớp đầu ra bằng một biểu diễn dạng số thay vì một chuỗi, vì các mô hình học máy chỉ có thể hoạt động với các con số.
Ngoài ra, còn có một thuộc tính data-name chứa tên mà người dùng có thể đọc được mà bạn muốn sử dụng cho lớp này. Thuộc tính này cho phép bạn cung cấp một tên có ý nghĩa hơn cho người dùng thay vì giá trị chỉ mục dạng số từ quá trình mã hoá 1 lần.
Cuối cùng, bạn có một nút huấn luyện và đặt lại để bắt đầu quy trình huấn luyện sau khi thu thập dữ liệu hoặc đặt lại ứng dụng tương ứng.
- Bạn cũng đã thêm 2 lệnh nhập
<script>. Một cho TensorFlow.js và một cho script.js mà bạn sẽ xác định trong thời gian ngắn.
7. Thêm kiểu
Giá trị mặc định của phần tử
Thêm kiểu cho các phần tử HTML mà bạn vừa thêm để đảm bảo các phần tử đó hiển thị đúng cách. Sau đây là một số kiểu được thêm vào để định vị và điều chỉnh kích thước các phần tử một cách chính xác. Không có gì đặc biệt. Chắc chắn bạn có thể bổ sung vào phần này sau để tạo ra trải nghiệm người dùng tốt hơn nữa, như bạn đã thấy trong video về teachable machine.
style.css
body {
font-family: helvetica, arial, sans-serif;
margin: 2em;
}
h1 {
font-style: italic;
color: #FF6F00;
}
video {
clear: both;
display: block;
margin: 10px;
background: #000000;
width: 640px;
height: 480px;
}
button {
padding: 10px;
float: left;
margin: 5px 3px 5px 10px;
}
.removed {
display: none;
}
#status {
font-size:150%;
}
Tuyệt vời! Đó là tất cả những gì bạn cần. Nếu bạn xem trước kết quả ngay bây giờ, kết quả sẽ có dạng như sau:

8. JavaScript: Hằng số và trình nghe khoá
Xác định các hằng số chính
Trước tiên, hãy thêm một số hằng số khoá mà bạn sẽ sử dụng trong suốt ứng dụng. Bắt đầu bằng cách thay thế nội dung của script.js bằng các hằng số sau:
script.js
const STATUS = document.getElementById('status');
const VIDEO = document.getElementById('webcam');
const ENABLE_CAM_BUTTON = document.getElementById('enableCam');
const RESET_BUTTON = document.getElementById('reset');
const TRAIN_BUTTON = document.getElementById('train');
const MOBILE_NET_INPUT_WIDTH = 224;
const MOBILE_NET_INPUT_HEIGHT = 224;
const STOP_DATA_GATHER = -1;
const CLASS_NAMES = [];
Hãy cùng tìm hiểu mục đích của những thông tin này:
STATUSchỉ giữ một tham chiếu đến thẻ đoạn văn mà bạn sẽ viết thông tin cập nhật trạng thái.VIDEOsẽ tham chiếu đến phần tử video HTML sẽ kết xuất nguồn cấp dữ liệu webcam.ENABLE_CAM_BUTTON,RESET_BUTTONvàTRAIN_BUTTONlấy các tham chiếu DOM đến tất cả các nút chính trên trang HTML.MOBILE_NET_INPUT_WIDTHvàMOBILE_NET_INPUT_HEIGHTlần lượt xác định chiều rộng và chiều cao đầu vào dự kiến của mô hình MobileNet. Bằng cách lưu trữ giá trị này trong một hằng số ở gần đầu tệp như thế này, nếu sau này bạn quyết định sử dụng một phiên bản khác, thì bạn chỉ cần cập nhật giá trị một lần thay vì phải thay thế ở nhiều nơi khác nhau.STOP_DATA_GATHERđược đặt thành -1. Thao tác này lưu trữ một giá trị trạng thái để bạn biết thời điểm người dùng ngừng nhấp vào một nút để thu thập dữ liệu từ nguồn cấp dữ liệu webcam. Bằng cách đặt cho số này một tên có ý nghĩa hơn, mã sẽ dễ đọc hơn sau này.CLASS_NAMEShoạt động như một bảng tra cứu và lưu giữ tên mà người dùng có thể đọc được cho các dự đoán có thể có về lớp. Mảng này sẽ được điền sau.
Được rồi, giờ đây khi đã có các tham chiếu đến những phần tử chính, bạn có thể liên kết một số trình nghe sự kiện với các phần tử đó.
Thêm trình nghe sự kiện quan trọng
Bắt đầu bằng cách thêm trình xử lý sự kiện nhấp chuột vào các nút chính như minh hoạ:
script.js
ENABLE_CAM_BUTTON.addEventListener('click', enableCam);
TRAIN_BUTTON.addEventListener('click', trainAndPredict);
RESET_BUTTON.addEventListener('click', reset);
function enableCam() {
// TODO: Fill this out later in the codelab!
}
function trainAndPredict() {
// TODO: Fill this out later in the codelab!
}
function reset() {
// TODO: Fill this out later in the codelab!
}
ENABLE_CAM_BUTTON – gọi hàm enableCam khi được nhấp.
TRAIN_BUTTON – gọi trainAndPredict khi được nhấp vào.
RESET_BUTTON – cuộc gọi đặt lại khi được nhấp vào.
Cuối cùng, trong phần này, bạn có thể tìm thấy tất cả các nút có lớp "dataCollector" bằng cách sử dụng document.querySelectorAll(). Thao tác này sẽ trả về một mảng các phần tử tìm thấy trong tài liệu khớp với:
script.js
let dataCollectorButtons = document.querySelectorAll('button.dataCollector');
for (let i = 0; i < dataCollectorButtons.length; i++) {
dataCollectorButtons[i].addEventListener('mousedown', gatherDataForClass);
dataCollectorButtons[i].addEventListener('mouseup', gatherDataForClass);
// Populate the human readable names for classes.
CLASS_NAMES.push(dataCollectorButtons[i].getAttribute('data-name'));
}
function gatherDataForClass() {
// TODO: Fill this out later in the codelab!
}
Giải thích mã:
Sau đó, bạn lặp lại các nút đã tìm thấy và liên kết 2 trình nghe sự kiện với mỗi nút. Một cho "mousedown" và một cho "mouseup". Nhờ đó, bạn có thể tiếp tục ghi lại các mẫu miễn là bạn nhấn nút. Điều này rất hữu ích cho việc thu thập dữ liệu.
Cả hai sự kiện đều gọi một hàm gatherDataForClass mà bạn sẽ xác định sau.
Tại thời điểm này, bạn cũng có thể đẩy tên lớp mà con người có thể đọc được tìm thấy từ thuộc tính nút HTML data-name vào mảng CLASS_NAMES.
Tiếp theo, hãy thêm một số biến để lưu trữ những điều quan trọng sẽ được sử dụng sau này.
script.js
let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;
Hãy cùng tìm hiểu về những điểm này.
Trước tiên, bạn có một biến mobilenet để lưu trữ mô hình mobilenet đã tải. Ban đầu, hãy đặt giá trị này thành không xác định.
Tiếp theo, bạn có một biến có tên là gatherDataState. Nếu người dùng nhấn vào nút "dataCollector", thì giá trị này sẽ thay đổi thành 1 mã nhận dạng nóng của nút đó (được xác định trong HTML), nhờ đó bạn biết được lớp dữ liệu mà bạn đang thu thập tại thời điểm đó. Ban đầu, giá trị này được đặt thành STOP_DATA_GATHER để vòng lặp thu thập dữ liệu mà bạn viết sau này sẽ không thu thập bất kỳ dữ liệu nào khi không có nút nào được nhấn.
videoPlaying theo dõi xem luồng video từ webcam có tải và phát thành công hay không và có sẵn để sử dụng hay không. Ban đầu, chế độ này được đặt thành false vì webcam sẽ không bật cho đến khi bạn nhấn nút ENABLE_CAM_BUTTON.
Tiếp theo, hãy xác định 2 mảng, trainingDataInputs và trainingDataOutputs. Các nút này lưu trữ các giá trị dữ liệu huấn luyện đã thu thập, khi bạn nhấp vào các nút "dataCollector" cho các tính năng đầu vào do mô hình cơ sở MobileNet tạo và lớp đầu ra được lấy mẫu tương ứng.
Sau đó, một mảng cuối cùng, examplesCount,, sẽ được xác định để theo dõi số lượng ví dụ có trong mỗi lớp sau khi bạn bắt đầu thêm các ví dụ đó.
Cuối cùng, bạn có một biến gọi là predict để kiểm soát vòng lặp dự đoán. Ban đầu, giá trị này được đặt thành false. Bạn sẽ không thể dự đoán cho đến khi đặt giá trị này thành true vào lúc khác.
Giờ đây, khi tất cả các biến chính đã được xác định, hãy tải mô hình cơ sở MobileNet v3 đã được cắt sẵn, cung cấp các vectơ đặc trưng của hình ảnh thay vì phân loại.
9. Tải mô hình cơ sở MobileNet
Trước tiên, hãy xác định một hàm mới có tên là loadMobileNetFeatureModel như minh hoạ bên dưới. Đây phải là một hàm không đồng bộ vì hành động tải một mô hình là không đồng bộ:
script.js
/**
* Loads the MobileNet model and warms it up so ready for use.
**/
async function loadMobileNetFeatureModel() {
const URL =
'https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v3_small_100_224/feature_vector/5/default/1';
mobilenet = await tf.loadGraphModel(URL, {fromTFHub: true});
STATUS.innerText = 'MobileNet v3 loaded successfully!';
// Warm up the model by passing zeros through it once.
tf.tidy(function () {
let answer = mobilenet.predict(tf.zeros([1, MOBILE_NET_INPUT_HEIGHT, MOBILE_NET_INPUT_WIDTH, 3]));
console.log(answer.shape);
});
}
// Call the function immediately to start loading.
loadMobileNetFeatureModel();
Trong mã này, bạn xác định URL nơi mô hình cần tải nằm trong tài liệu TFHub.
Sau đó, bạn có thể tải mô hình bằng cách sử dụng await tf.loadGraphModel(), nhớ đặt thuộc tính đặc biệt fromTFHub thành true khi bạn đang tải mô hình từ trang web này của Google. Đây là trường hợp đặc biệt chỉ dành cho việc sử dụng các mô hình được lưu trữ trên TF Hub, trong đó bạn phải đặt thuộc tính bổ sung này.
Sau khi tải xong, bạn có thể đặt innerText của phần tử STATUS bằng một thông báo để có thể thấy trực quan rằng phần tử này đã tải đúng cách và bạn đã sẵn sàng bắt đầu thu thập dữ liệu.
Giờ đây, việc duy nhất bạn cần làm là khởi động mô hình. Với các mô hình lớn hơn như thế này, lần đầu tiên bạn sử dụng mô hình, có thể mất một chút thời gian để thiết lập mọi thứ. Do đó, bạn nên truyền các số 0 qua mô hình để tránh phải chờ đợi trong tương lai khi thời gian có thể quan trọng hơn.
Bạn có thể sử dụng tf.zeros() được bao bọc trong tf.tidy() để đảm bảo các tensor được xử lý đúng cách, với kích thước lô là 1 và chiều cao cũng như chiều rộng chính xác mà bạn đã xác định trong các hằng số ngay từ đầu. Cuối cùng, bạn cũng chỉ định các kênh màu. Trong trường hợp này, đó là 3 vì mô hình dự kiến sẽ có hình ảnh RGB.
Tiếp theo, hãy ghi lại hình dạng của tensor được trả về bằng cách sử dụng answer.shape() để giúp bạn hiểu rõ kích thước của các đặc điểm hình ảnh mà mô hình này tạo ra.
Sau khi xác định hàm này, bạn có thể gọi ngay để bắt đầu tải mô hình xuống khi tải trang.
Nếu xem bản xem trước trực tiếp ngay bây giờ, sau vài giây, bạn sẽ thấy văn bản trạng thái thay đổi từ "Awaiting TF.js load" (Đang chờ tải TF.js) thành "MobileNet v3 loaded successfully!" (Đã tải MobileNet v3 thành công!) như minh hoạ bên dưới. Đảm bảo rằng bạn có thể thực hiện việc này trước khi tiếp tục.

Bạn cũng có thể kiểm tra đầu ra của bảng điều khiển để xem kích thước được in của các đối tượng đầu ra mà mô hình này tạo ra. Sau khi chạy các số 0 thông qua mô hình MobileNet, bạn sẽ thấy một hình dạng [1, 1024] được in. Mục đầu tiên chỉ là kích thước lô là 1 và bạn có thể thấy rằng mục này thực sự trả về 1024 đối tượng mà sau đó có thể dùng để giúp bạn phân loại các đối tượng mới.
10. Xác định phần đầu của mô hình mới
Bây giờ là lúc xác định đầu mô hình, về cơ bản là một perceptron đa lớp rất tối giản.
script.js
let model = tf.sequential();
model.add(tf.layers.dense({inputShape: [1024], units: 128, activation: 'relu'}));
model.add(tf.layers.dense({units: CLASS_NAMES.length, activation: 'softmax'}));
model.summary();
// Compile the model with the defined optimizer and specify a loss function to use.
model.compile({
// Adam changes the learning rate over time which is useful.
optimizer: 'adam',
// Use the correct loss function. If 2 classes of data, must use binaryCrossentropy.
// Else categoricalCrossentropy is used if more than 2 classes.
loss: (CLASS_NAMES.length === 2) ? 'binaryCrossentropy': 'categoricalCrossentropy',
// As this is a classification problem you can record accuracy in the logs too!
metrics: ['accuracy']
});
Hãy cùng xem qua mã này. Bạn bắt đầu bằng cách xác định một mô hình tf.sequential mà bạn sẽ thêm các lớp mô hình vào.
Tiếp theo, hãy thêm một lớp dày đặc làm lớp đầu vào cho mô hình này. Điều này có hình dạng đầu vào là 1024 vì đầu ra từ các đối tượng MobileNet v3 có kích thước này. Bạn đã phát hiện ra điều này ở bước trước sau khi truyền các số 1 qua mô hình. Lớp này có 128 nơ-ron sử dụng hàm kích hoạt ReLU.
Nếu bạn mới làm quen với các hàm kích hoạt và lớp mô hình, hãy cân nhắc tham gia khoá học được trình bày chi tiết ở phần đầu của hội thảo này để hiểu rõ những thuộc tính này hoạt động như thế nào.
Lớp tiếp theo cần thêm là lớp đầu ra. Số lượng nơ-ron phải bằng số lượng lớp mà bạn đang cố gắng dự đoán. Để làm việc đó, bạn có thể sử dụng CLASS_NAMES.length để tìm hiểu số lượng lớp mà bạn dự định phân loại, bằng với số lượng nút thu thập dữ liệu có trong giao diện người dùng. Vì đây là vấn đề phân loại, nên bạn sử dụng hàm kích hoạt softmax trên lớp đầu ra này. Hàm này phải được dùng khi bạn cố gắng tạo một mô hình để giải quyết các vấn đề phân loại thay vì hồi quy.
Bây giờ, hãy in model.summary() để in thông tin tổng quan về mô hình mới xác định vào bảng điều khiển.
Cuối cùng, hãy biên dịch mô hình để sẵn sàng huấn luyện. Ở đây, trình tối ưu hoá được đặt thành adam và tổn thất sẽ là binaryCrossentropy nếu CLASS_NAMES.length bằng 2, hoặc sẽ sử dụng categoricalCrossentropy nếu có 3 lớp trở lên để phân loại. Các chỉ số về độ chính xác cũng được yêu cầu để có thể theo dõi trong nhật ký sau này cho mục đích gỡ lỗi.
Trong bảng điều khiển, bạn sẽ thấy nội dung như sau:

Xin lưu ý rằng mô hình này có hơn 130.000 tham số có thể huấn luyện. Nhưng vì đây là một lớp dày đặc đơn giản của các nơ-ron thông thường nên nó sẽ huấn luyện khá nhanh.
Khi dự án hoàn tất, bạn có thể thử thay đổi số lượng nơ-ron trong lớp đầu tiên để xem bạn có thể giảm số lượng này xuống mức thấp nhất mà vẫn đạt được hiệu suất tốt hay không. Thông thường, với công nghệ học máy, bạn cần thử nghiệm và mắc lỗi ở một mức độ nào đó để tìm ra các giá trị tham số tối ưu nhằm mang lại cho bạn sự cân bằng tốt nhất giữa mức sử dụng tài nguyên và tốc độ.
11. Bật webcam
Giờ là lúc bạn cần hoàn thiện hàm enableCam() mà bạn đã xác định trước đó. Thêm một hàm mới có tên là hasGetUserMedia() như minh hoạ bên dưới, sau đó thay thế nội dung của hàm enableCam() đã xác định trước đó bằng mã tương ứng bên dưới.
script.js
function hasGetUserMedia() {
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
}
function enableCam() {
if (hasGetUserMedia()) {
// getUsermedia parameters.
const constraints = {
video: true,
width: 640,
height: 480
};
// Activate the webcam stream.
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
VIDEO.srcObject = stream;
VIDEO.addEventListener('loadeddata', function() {
videoPlaying = true;
ENABLE_CAM_BUTTON.classList.add('removed');
});
});
} else {
console.warn('getUserMedia() is not supported by your browser');
}
}
Trước tiên, hãy tạo một hàm có tên là hasGetUserMedia() để kiểm tra xem trình duyệt có hỗ trợ getUserMedia() hay không bằng cách kiểm tra sự tồn tại của các thuộc tính API trình duyệt chính.
Trong hàm enableCam(), hãy dùng hàm hasGetUserMedia() mà bạn vừa xác định ở trên để kiểm tra xem hàm này có được hỗ trợ hay không. Nếu không, hãy in một cảnh báo vào bảng điều khiển.
Nếu hỗ trợ, hãy xác định một số ràng buộc cho lệnh gọi getUserMedia(), chẳng hạn như bạn chỉ muốn luồng video và bạn muốn width của video có kích thước 640 pixel và height là 480 pixel. Tại sao? Thật ra, không cần thiết phải có một video lớn hơn kích thước này vì video đó sẽ cần được đổi kích thước thành 224 x 224 pixel để được đưa vào mô hình MobileNet. Bạn cũng có thể tiết kiệm một số tài nguyên điện toán bằng cách yêu cầu độ phân giải nhỏ hơn. Hầu hết các camera đều hỗ trợ độ phân giải có kích thước này.
Tiếp theo, hãy gọi navigator.mediaDevices.getUserMedia() bằng constraints như mô tả ở trên, sau đó đợi stream được trả về. Sau khi stream được trả về, bạn có thể lấy phần tử VIDEO để phát stream bằng cách đặt phần tử đó làm giá trị srcObject.
Bạn cũng nên thêm một eventListener vào phần tử VIDEO để biết thời điểm stream đã tải và phát thành công.
Sau khi luồng tải, bạn có thể đặt videoPlaying thành true và xoá ENABLE_CAM_BUTTON để ngăn người dùng nhấp lại bằng cách đặt lớp của nút thành "removed".
Bây giờ, hãy chạy mã của bạn, nhấp vào nút bật camera và cho phép truy cập vào webcam. Nếu đây là lần đầu tiên làm việc này, bạn sẽ thấy hình ảnh của mình được kết xuất vào phần tử video trên trang như hình minh hoạ:

Được rồi, bây giờ là lúc thêm một hàm để xử lý các lượt nhấp vào nút dataCollector.
12. Trình xử lý sự kiện nút thu thập dữ liệu
Bây giờ, bạn cần điền vào hàm hiện đang trống có tên là gatherDataForClass().. Đây là hàm mà bạn đã chỉ định làm hàm xử lý sự kiện cho các nút dataCollector khi bắt đầu lớp học lập trình.
script.js
/**
* Handle Data Gather for button mouseup/mousedown.
**/
function gatherDataForClass() {
let classNumber = parseInt(this.getAttribute('data-1hot'));
gatherDataState = (gatherDataState === STOP_DATA_GATHER) ? classNumber : STOP_DATA_GATHER;
dataGatherLoop();
}
Trước tiên, hãy kiểm tra thuộc tính data-1hot trên nút hiện được nhấp bằng cách gọi this.getAttribute() bằng tên của thuộc tính, trong trường hợp này là data-1hot làm tham số. Vì đây là một chuỗi, nên bạn có thể dùng parseInt() để truyền chuỗi này thành một số nguyên và chỉ định kết quả này cho một biến có tên là classNumber.
Tiếp theo, hãy đặt biến gatherDataState cho phù hợp. Nếu gatherDataState hiện tại bằng STOP_DATA_GATHER (bạn đặt thành -1), thì điều đó có nghĩa là bạn hiện không thu thập bất kỳ dữ liệu nào và đó là sự kiện mousedown đã kích hoạt. Đặt gatherDataState thành classNumber mà bạn vừa tìm thấy.
Nếu không, điều này có nghĩa là bạn hiện đang thu thập dữ liệu và sự kiện đã kích hoạt là sự kiện mouseup, còn giờ đây bạn muốn ngừng thu thập dữ liệu cho lớp đó. Chỉ cần đặt lại trạng thái STOP_DATA_GATHER để kết thúc vòng lặp thu thập dữ liệu mà bạn sẽ xác định trong thời gian ngắn.
Cuối cùng, hãy bắt đầu lệnh gọi đến dataGatherLoop(), để thực sự ghi lại dữ liệu lớp.
13. Thu thập dữ liệu
Bây giờ, hãy xác định hàm dataGatherLoop(). Hàm này chịu trách nhiệm lấy mẫu hình ảnh từ video webcam, truyền các hình ảnh đó qua mô hình MobileNet và ghi lại đầu ra của mô hình đó (1024 vectơ đặc trưng).
Sau đó, ứng dụng sẽ lưu trữ các giá trị này cùng với mã nhận dạng gatherDataState của nút hiện đang được nhấn để bạn biết lớp này đại diện cho dữ liệu nào.
Hãy cùng tìm hiểu quy trình này:
script.js
function dataGatherLoop() {
if (videoPlaying && gatherDataState !== STOP_DATA_GATHER) {
let imageFeatures = tf.tidy(function() {
let videoFrameAsTensor = tf.browser.fromPixels(VIDEO);
let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor, [MOBILE_NET_INPUT_HEIGHT,
MOBILE_NET_INPUT_WIDTH], true);
let normalizedTensorFrame = resizedTensorFrame.div(255);
return mobilenet.predict(normalizedTensorFrame.expandDims()).squeeze();
});
trainingDataInputs.push(imageFeatures);
trainingDataOutputs.push(gatherDataState);
// Intialize array index element if currently undefined.
if (examplesCount[gatherDataState] === undefined) {
examplesCount[gatherDataState] = 0;
}
examplesCount[gatherDataState]++;
STATUS.innerText = '';
for (let n< = 0; n CLASS_NAMES.length; n++) {
STATUS.innerText += CLASS_NAMES[n] + ' data count: ' + examplesCount[n] + '. ';
}
window.requestAnimationFrame(dataGatherLoop);
}
}
Bạn chỉ tiếp tục thực thi hàm này nếu videoPlaying là true, tức là webcam đang hoạt động và gatherDataState không bằng STOP_DATA_GATHER, đồng thời một nút thu thập dữ liệu lớp đang được nhấn.
Tiếp theo, hãy gói mã của bạn trong một tf.tidy() để loại bỏ mọi tensor đã tạo trong mã sau. Kết quả của quá trình thực thi mã tf.tidy() này được lưu trữ trong một biến có tên là imageFeatures.
Giờ đây, bạn có thể lấy một khung hình của webcam VIDEO bằng cách sử dụng tf.browser.fromPixels(). Tensor kết quả chứa dữ liệu hình ảnh được lưu trữ trong một biến có tên là videoFrameAsTensor.
Tiếp theo, hãy đổi kích thước biến videoFrameAsTensor thành hình dạng phù hợp cho dữ liệu đầu vào của mô hình MobileNet. Sử dụng lệnh gọi tf.image.resizeBilinear() với tensor mà bạn muốn định hình lại làm tham số đầu tiên, sau đó là một hình dạng xác định chiều cao và chiều rộng mới theo các hằng số mà bạn đã tạo trước đó. Cuối cùng, hãy đặt align corners thành true bằng cách truyền tham số thứ ba để tránh mọi vấn đề về căn chỉnh khi đổi kích thước. Kết quả của thao tác đổi kích thước này được lưu trữ trong một biến có tên là resizedTensorFrame.
Xin lưu ý rằng thao tác đổi kích thước cơ bản này sẽ kéo giãn hình ảnh, vì hình ảnh từ webcam của bạn có kích thước 640 x 480 pixel và mô hình cần một hình ảnh vuông có kích thước 224 x 224 pixel.
Đối với mục đích của bản minh hoạ này, điều này sẽ hoạt động tốt. Tuy nhiên, sau khi hoàn thành lớp học lập trình này, bạn có thể muốn thử cắt một hình vuông từ hình ảnh này để có kết quả tốt hơn nữa cho mọi hệ thống phát hành chính thức mà bạn có thể tạo sau này.
Tiếp theo, hãy chuẩn hoá dữ liệu hình ảnh. Dữ liệu hình ảnh luôn nằm trong khoảng từ 0 đến 255 khi sử dụng tf.browser.frompixels(), vì vậy, bạn chỉ cần chia resizedTensorFrame cho 255 để đảm bảo tất cả các giá trị đều nằm trong khoảng từ 0 đến 1. Đây là những giá trị mà mô hình MobileNet mong đợi làm dữ liệu đầu vào.
Cuối cùng, trong phần tf.tidy() của mã, hãy đẩy tensor được chuẩn hoá này thông qua mô hình đã tải bằng cách gọi mobilenet.predict(), trong đó bạn truyền phiên bản mở rộng của normalizedTensorFrame bằng expandDims() để tạo thành một lô gồm 1, vì mô hình dự kiến sẽ có một lô đầu vào để xử lý.
Sau khi kết quả trả về, bạn có thể gọi ngay squeeze() trên kết quả được trả về đó để nén kết quả xuống thành một tensor 1D, sau đó trả về và chỉ định cho biến imageFeatures để nắm bắt kết quả từ tf.tidy().
Giờ đây, khi đã có imageFeatures từ mô hình MobileNet, bạn có thể ghi lại các imageFeatures đó bằng cách đẩy chúng vào mảng trainingDataInputs mà bạn đã xác định trước đó.
Bạn cũng có thể ghi lại những gì mà đầu vào này đại diện bằng cách đẩy gatherDataState hiện tại vào mảng trainingDataOutputs.
Xin lưu ý rằng biến gatherDataState sẽ được đặt thành mã số của lớp hiện tại mà bạn đang ghi dữ liệu khi người dùng nhấp vào nút trong hàm gatherDataForClass() đã xác định trước đó.
Tại thời điểm này, bạn cũng có thể tăng số lượng ví dụ cho một lớp nhất định. Để làm việc này, trước tiên, hãy kiểm tra xem chỉ mục trong mảng examplesCount đã được khởi chạy hay chưa. Nếu chưa xác định, hãy đặt giá trị này thành 0 để khởi tạo bộ đếm cho mã số của một lớp nhất định, sau đó bạn có thể tăng examplesCount cho gatherDataState hiện tại.
Bây giờ, hãy cập nhật văn bản của phần tử STATUS trên trang web để cho biết số lượng hiện tại của từng lớp khi chúng được ghi lại. Để thực hiện việc này, hãy lặp lại mảng CLASS_NAMES và in tên mà con người có thể đọc được kết hợp với số lượng dữ liệu ở cùng một chỉ mục trong examplesCount.
Cuối cùng, hãy gọi window.requestAnimationFrame() với dataGatherLoop được truyền dưới dạng một tham số để gọi lại hàm này theo cách đệ quy. Thao tác này sẽ tiếp tục lấy mẫu các khung hình từ video cho đến khi phát hiện thấy mouseup của nút và gatherDataState được đặt thành STOP_DATA_GATHER,. Tại thời điểm đó, vòng lặp thu thập dữ liệu sẽ kết thúc.
Nếu chạy mã ngay bây giờ, bạn sẽ có thể nhấp vào nút bật camera, đợi webcam tải, sau đó nhấp và giữ từng nút thu thập dữ liệu để thu thập các ví dụ cho từng lớp dữ liệu. Ở đây, bạn thấy tôi thu thập dữ liệu cho điện thoại di động và bàn tay của mình.

Bạn sẽ thấy văn bản trạng thái được cập nhật khi ứng dụng lưu trữ tất cả các tensor trong bộ nhớ như trong ảnh chụp màn hình ở trên.
14. Huấn luyện và dự đoán
Bước tiếp theo là triển khai mã cho hàm trainAndPredict() hiện đang trống của bạn. Đây là nơi diễn ra quá trình học chuyển giao. Hãy xem mã này:
script.js
async function trainAndPredict() {
predict = false;
tf.util.shuffleCombo(trainingDataInputs, trainingDataOutputs);
let outputsAsTensor = tf.tensor1d(trainingDataOutputs, 'int32');
let oneHotOutputs = tf.oneHot(outputsAsTensor, CLASS_NAMES.length);
let inputsAsTensor = tf.stack(trainingDataInputs);
let results = await model.fit(inputsAsTensor, oneHotOutputs, {shuffle: true, batchSize: 5, epochs: 10,
callbacks: {onEpochEnd: logProgress} });
outputsAsTensor.dispose();
oneHotOutputs.dispose();
inputsAsTensor.dispose();
predict = true;
predictLoop();
}
function logProgress(epoch, logs) {
console.log('Data for epoch ' + epoch, logs);
}
Trước tiên, hãy đảm bảo bạn dừng mọi hoạt động dự đoán hiện tại bằng cách đặt predict thành false.
Tiếp theo, hãy xáo trộn các mảng đầu vào và đầu ra bằng cách sử dụng tf.util.shuffleCombo() để đảm bảo thứ tự không gây ra vấn đề trong quá trình huấn luyện.
Chuyển đổi mảng đầu ra trainingDataOutputs, thành tensor1d thuộc loại int32 để sẵn sàng sử dụng trong mã hoá một lần nóng. Giá trị này được lưu trữ trong một biến có tên là outputsAsTensor.
Sử dụng hàm tf.oneHot() với biến outputsAsTensor này cùng với số lượng tối đa các lớp cần mã hoá, chỉ là CLASS_NAMES.length. Các đầu ra được mã hoá một lần nóng hiện được lưu trữ trong một tensor mới có tên là oneHotOutputs.
Xin lưu ý rằng hiện tại trainingDataInputs là một mảng các tensor đã ghi. Để sử dụng các tensor này cho việc huấn luyện, bạn cần chuyển đổi mảng tensor thành một tensor 2D thông thường.
Để làm điều đó, có một hàm tuyệt vời trong thư viện TensorFlow.js có tên là tf.stack(),
lấy một mảng gồm các tensor và xếp chúng lại với nhau để tạo ra một tensor có chiều cao hơn làm đầu ra. Trong trường hợp này, một tensor 2D sẽ được trả về, đó là một lô gồm các đầu vào 1 chiều, mỗi đầu vào có độ dài 1024 chứa các đặc điểm đã ghi lại, đây là những gì bạn cần để huấn luyện.
Tiếp theo, await model.fit() để huấn luyện phần đầu của mô hình tuỳ chỉnh. Tại đây, bạn sẽ truyền biến inputsAsTensor cùng với oneHotOutputs để biểu thị dữ liệu huấn luyện cần dùng cho các đầu vào mẫu và đầu ra mục tiêu tương ứng. Trong đối tượng cấu hình cho tham số thứ 3, hãy đặt shuffle thành true, dùng batchSize của 5, với epochs được đặt thành 10, sau đó chỉ định callback cho onEpochEnd thành hàm logProgress mà bạn sẽ xác định trong thời gian ngắn.
Cuối cùng, bạn có thể loại bỏ các tensor đã tạo vì mô hình hiện đã được huấn luyện. Sau đó, bạn có thể đặt predict về true để cho phép dự đoán diễn ra lại, rồi gọi hàm predictLoop() để bắt đầu dự đoán hình ảnh trực tiếp từ webcam.
Bạn cũng có thể xác định hàm logProcess() để ghi nhật ký trạng thái của quá trình huấn luyện. Hàm này được dùng trong model.fit() ở trên và in kết quả ra bảng điều khiển sau mỗi vòng huấn luyện.
Bạn sắp hoàn tất rồi! Đã đến lúc thêm hàm predictLoop() để đưa ra dự đoán.
Vòng lặp dự đoán cốt lõi
Tại đây, bạn triển khai vòng lặp dự đoán chính lấy mẫu các khung hình từ một webcam và liên tục dự đoán nội dung trong mỗi khung hình với kết quả theo thời gian thực trong trình duyệt.
Hãy kiểm tra mã:
script.js
function predictLoop() {
if (predict) {
tf.tidy(function() {
let videoFrameAsTensor = tf.browser.fromPixels(VIDEO).div(255);
let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor,[MOBILE_NET_INPUT_HEIGHT,
MOBILE_NET_INPUT_WIDTH], true);
let imageFeatures = mobilenet.predict(resizedTensorFrame.expandDims());
let prediction = model.predict(imageFeatures).squeeze();
let highestIndex = prediction.argMax().arraySync();
let predictionArray = prediction.arraySync();
STATUS.innerText = 'Prediction: ' + CLASS_NAMES[highestIndex] + ' with ' + Math.floor(predictionArray[highestIndex] * 100) + '% confidence';
});
window.requestAnimationFrame(predictLoop);
}
}
Trước tiên, hãy kiểm tra để đảm bảo predict là đúng, để chỉ đưa ra dự đoán sau khi một mô hình được huấn luyện và có thể sử dụng.
Tiếp theo, bạn có thể lấy các đặc điểm của hình ảnh cho hình ảnh hiện tại giống như trong hàm dataGatherLoop(). Về cơ bản, bạn sẽ lấy một khung hình từ webcam bằng cách sử dụng tf.browser.from pixels(), chuẩn hoá khung hình đó, đổi kích thước thành 224 x 224 pixel rồi truyền dữ liệu đó qua mô hình MobileNet để nhận được các đặc điểm hình ảnh thu được.
Tuy nhiên, giờ đây, bạn có thể sử dụng phần đầu mô hình mới huấn luyện để thực sự thực hiện một dự đoán bằng cách truyền imageFeatures kết quả vừa tìm được thông qua hàm predict() của mô hình đã huấn luyện. Sau đó, bạn có thể nén tensor kết quả để tạo lại tensor 1 chiều và chỉ định tensor đó cho một biến có tên là prediction.
Với prediction này, bạn có thể tìm thấy chỉ mục có giá trị cao nhất bằng cách sử dụng argMax(), sau đó chuyển đổi tensor kết quả này thành một mảng bằng cách sử dụng arraySync() để truy cập vào dữ liệu cơ bản trong JavaScript nhằm khám phá vị trí của phần tử có giá trị cao nhất. Giá trị này được lưu trữ trong biến có tên là highestIndex.
Bạn cũng có thể nhận được điểm số độ tin cậy dự đoán thực tế theo cách tương tự bằng cách gọi arraySync() trực tiếp trên tensor prediction.
Giờ đây, bạn đã có mọi thứ cần thiết để cập nhật văn bản STATUS bằng dữ liệu prediction. Để lấy chuỗi mà con người có thể đọc được cho lớp, bạn chỉ cần tra cứu highestIndex trong mảng CLASS_NAMES, rồi lấy giá trị độ tin cậy từ predictionArray. Để dễ đọc hơn dưới dạng tỷ lệ phần trăm, bạn chỉ cần nhân kết quả với 100 và math.floor().
Cuối cùng, bạn có thể dùng window.requestAnimationFrame() để gọi lại predictionLoop() khi đã sẵn sàng, nhằm phân loại luồng video theo thời gian thực. Quá trình này sẽ tiếp tục cho đến khi predict được đặt thành false nếu bạn chọn huấn luyện một mô hình mới bằng dữ liệu mới.
Điều này sẽ đưa bạn đến phần cuối cùng của câu đố. Triển khai nút đặt lại.
15. Triển khai nút đặt lại
Sắp hoàn tất! Mảnh ghép cuối cùng là triển khai nút đặt lại để bắt đầu lại. Mã cho hàm reset() hiện đang trống của bạn ở bên dưới. Hãy tiếp tục và cập nhật như sau:
script.js
/**
* Purge data and start over. Note this does not dispose of the loaded
* MobileNet model and MLP head tensors as you will need to reuse
* them to train a new model.
**/
function reset() {
predict = false;
examplesCount.length = 0;
for (let i = 0; i < trainingDataInputs.length; i++) {
trainingDataInputs[i].dispose();
}
trainingDataInputs.length = 0;
trainingDataOutputs.length = 0;
STATUS.innerText = 'No data collected';
console.log('Tensors in memory: ' + tf.memory().numTensors);
}
Trước tiên, hãy dừng mọi vòng lặp dự đoán đang chạy bằng cách đặt predict thành false. Tiếp theo, hãy xoá tất cả nội dung trong mảng examplesCount bằng cách đặt độ dài của mảng này thành 0. Đây là một cách thuận tiện để xoá tất cả nội dung khỏi một mảng.
Bây giờ, hãy xem xét tất cả trainingDataInputs đã ghi hiện tại và đảm bảo bạn dispose() từng tensor có trong đó để giải phóng bộ nhớ một lần nữa, vì Trình thu gom rác JavaScript không dọn dẹp Tensor.
Sau khi hoàn tất, bạn có thể đặt độ dài mảng thành 0 một cách an toàn trên cả mảng trainingDataInputs và trainingDataOutputs để xoá cả hai mảng đó.
Cuối cùng, hãy đặt văn bản STATUS thành một nội dung hợp lý và in ra các tensor còn lại trong bộ nhớ để kiểm tra tính hợp lý.
Xin lưu ý rằng vẫn sẽ có vài trăm tensor trong bộ nhớ vì cả mô hình MobileNet và perceptron nhiều lớp mà bạn xác định đều không bị loại bỏ. Bạn sẽ cần sử dụng lại các mô hình này với dữ liệu huấn luyện mới nếu quyết định huấn luyện lại sau khi đặt lại.
16. Hãy thử xem
Đã đến lúc bạn thử nghiệm phiên bản Dạy cho máy của riêng mình!
Chuyển đến bản xem trước trực tiếp, bật webcam, thu thập ít nhất 30 mẫu cho lớp 1 cho một số đối tượng trong phòng của bạn, sau đó làm tương tự cho lớp 2 cho một đối tượng khác, nhấp vào "train" (huấn luyện) và kiểm tra nhật ký bảng điều khiển để xem tiến trình. Mô hình này sẽ huấn luyện khá nhanh:

Sau khi được huấn luyện, hãy đưa các đối tượng vào camera để nhận được các dự đoán trực tiếp sẽ được in vào vùng văn bản trạng thái ở gần đầu trang web. Nếu bạn gặp vấn đề, hãy kiểm tra mã hoạt động đã hoàn tất của tôi để xem bạn có bỏ lỡ việc sao chép nội dung nào không.
17. Xin chúc mừng
Xin chúc mừng! Bạn vừa hoàn thành ví dụ đầu tiên về học chuyển giao bằng TensorFlow.js ngay trong trình duyệt.
Hãy thử nghiệm trên nhiều đối tượng. Bạn có thể nhận thấy một số đối tượng khó nhận dạng hơn những đối tượng khác, đặc biệt là nếu chúng tương tự như một đối tượng khác. Bạn có thể cần thêm nhiều lớp hoặc dữ liệu huấn luyện hơn để phân biệt chúng.
Tóm tắt
Trong lớp học lập trình này, bạn đã tìm hiểu:
- Học chuyển giao là gì và những lợi thế của phương pháp này so với việc huấn luyện một mô hình đầy đủ.
- Cách lấy các mô hình để sử dụng lại từ TensorFlow Hub.
- Cách thiết lập một ứng dụng web phù hợp cho việc học chuyển giao.
- Cách tải và sử dụng một mô hình cơ sở để tạo các đối tượng trong hình ảnh.
- Cách huấn luyện một đầu dự đoán mới có thể nhận dạng các đối tượng tuỳ chỉnh từ hình ảnh của webcam.
- Cách sử dụng các mô hình thu được để phân loại dữ liệu theo thời gian thực.
Tiếp theo là gì?
Giờ đây, bạn đã có một cơ sở hoạt động để bắt đầu, bạn có thể nghĩ ra những ý tưởng sáng tạo nào để mở rộng mẫu mô hình học máy này cho một trường hợp sử dụng thực tế mà bạn có thể đang thực hiện? Có thể bạn sẽ tạo ra một cuộc cách mạng trong ngành mà bạn đang làm việc để giúp mọi người tại công ty của bạn huấn luyện các mô hình phân loại những điều quan trọng trong công việc hằng ngày của họ? Có vô vàn hành động để bạn lựa chọn.
Để tìm hiểu thêm, hãy cân nhắc tham gia miễn phí khoá học đầy đủ này. Khoá học này sẽ hướng dẫn bạn cách kết hợp 2 mô hình mà bạn hiện có trong lớp học lập trình này thành 1 mô hình duy nhất để tăng hiệu quả.
Ngoài ra, nếu bạn muốn tìm hiểu thêm về lý thuyết đằng sau ứng dụng Teachable Machine ban đầu, hãy xem hướng dẫn này.
Chia sẻ những nội dung bạn tạo với chúng tôi
Bạn cũng có thể dễ dàng mở rộng những gì mình đã tạo hôm nay cho các trường hợp sử dụng sáng tạo khác. Chúng tôi khuyến khích bạn suy nghĩ sáng tạo và tiếp tục khám phá.
Đừng quên gắn thẻ chúng tôi trên mạng xã hội bằng hashtag #MadeWithTFJS để có cơ hội xuất hiện trên blog TensorFlow hoặc thậm chí là các sự kiện trong tương lai. Chúng tôi rất mong được xem những tác phẩm của bạn.
Các trang web cần xem
- Trang web chính thức của TensorFlow.js
- Các mô hình được tạo sẵn của TensorFlow.js
- TensorFlow.js API
- TensorFlow.js Show & Tell (TensorFlow.js Giới thiệu và chia sẻ) – tìm cảm hứng và xem những gì người khác đã tạo.