TensorFlow.js: Tạo "Máy giảng dạy"của riêng bạn" sử dụng phương pháp học chuyển giao bằng TensorFlow.js

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à nhiều nhà phát triển JavaScript hiện đang tìm cách sử dụng các mô hình tiên tiến hiện có và đào tạo lại để họ làm việc với dữ liệu tuỳ chỉnh đặc biệt cho ngành của họ. Hành động sử dụng 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 miền được gọi là học chuyển.

Phương pháp học chuyển giao có nhiều ưu điểm 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 từ mô hình đã huấn luyện trước đó và cần ít ví dụ hơn về mục mới mà bạn muốn phân loại. Ngoài ra, việc huấn luyện thường nhanh hơn đáng kể do chỉ phải đào tạo lại một vài lớp cuối cùng của kiến trúc mô hình thay vì toàn bộ mạng. Vì lý do này, phương pháp học chuyển dữ liệu rất phù hợp với môi trường trình duyệt web, nơi các tài nguyên có thể thay đổi tuỳ theo thiết bị thực thi, nhưng vẫn cho phép truy cập trực tiếp vào cảm biến để dễ dàng thu thập dữ liệu.

Lớp học lập trình này cho bạn biết cách tạo một ứng dụng web từ một canvas trống, tái tạo " Thiết bị giảng dạy" của bạn. Trang web này giúp bạn tạo một ứng dụng web hoạt động mà bất kỳ người dùng nào cũng có thể sử dụng để nhận dạng một đối tượng tuỳ chỉnh chỉ với một vài hình ảnh mẫu từ webcam của họ. Trang web này được thiết kế ở mức tối thiểu để 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, tương tự như trang web ban đầu của Teachable Machine, bạn có thể áp dụng trải nghiệm hiện tại 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 đã có phần quen thuộc với các mô hình tạo sẵn TensorFlow.js và cách sử dụng API cơ bản, và những người muốn bắt đầu chuyển đổi phương pháp học trong TensorFlow.js.

  • Thử nghiệm này giả định bạn đã có kiến thức cơ bản về TensorFlow.js, HTML5, CSS và JavaScript.

Nếu bạn mới sử dụng Tensorflow.js, trước tiên, hãy cân nhắc tham gia khoá học miễn phí từ 0 đến chủ chốt này. Khoá học này giả định rằng bạn không có kiến thức nền tảng về Học máy hoặc TensorFlow.js, đồng thời hướng dẫn bạn mọi thứ bạn cần biết trong các bước nhỏ hơn.

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 trang web HTML/CSS /JS đơn giản, sao chép trải nghiệm người dùng của Teachable Machine.
  • Cách sử dụng TensorFlow.js để tải mô hình cơ sở được huấn luyện trước, cụ thể là MobileNet, để tạo các tính năng hình ảnh có thể dùng trong phương pháp học chuyển giao.
  • Cách thu thập dữ liệu từ webcam của người dùng đối với nhiều lớp dữ liệu mà bạn muốn nhận dạng.
  • Cách tạo và định nghĩa một perceptron nhiều lớp nhận các đặc điểm hình ảnh, đồng thời học cách phân loại các đối tượng mới bằng cách sử dụng chúng.

Hãy cùng tấn công...

Bạn cần có

  • Bạn nên tiếp tục sử dụng tài khoản Glitch.com hoặc bạn có thể sử dụng môi trường phân phát web mà bạn có thể tự chỉnh sửa và điều hành.

2. TensorFlow.js là gì?

54e81d02971f53e8.pngs

TensorFlow.js là một thư viện máy học nguồn mở có thể chạy ở mọi nơi mà JavaScript có thể. Thư viện này 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 này cũng như bộ API cho nhà phát triển cho hệ sinh thái JavaScript.

Có thể sử dụng ở đâu?

Do tính có thể di chuyển của JavaScript, giờ đây bạn có thể viết bằng 1 ngôn ngữ và dễ dàng thực hiện công nghệ học máy trên tất cả các nền tảng sau đây:

  • Phía máy khách trong trình duyệt web bằng JavaScript vanilla
  • Phía máy chủ và thậm chí cả các thiết bị IoT như Raspberry Pi sử dụng Node.js
  • Ứng dụng máy tính sử dụng Electron
  • Ứng dụng gốc dành cho thiết bị di động sử dụng React Native

TensorFlow.js cũng hỗ trợ nhiều phần phụ trợ trong mỗi môi trường này (ví dụ: môi trường dựa trên phần cứng mà công cụ này có thể thực thi trong CPU hoặc WebGL. Một "máy chủ phụ trợ" trong trường hợp 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) để đảm bảo khả năng tương thích và giúp mọi thứ chạy nhanh. Hiện tại, TensorFlow.js 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 (kích thước trên 3 MB) bằng tính năng tăng tốc GPU.
  • Thực thi Web hội (WASM) trên CPU – để cải thiện hiệu suất của 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) thực sự có thể 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ý đồ họa.
  • Thực thi CPU – mô hình dự phòng không được có sẵn trong môi trường nào khác. Đây là tốc độ chậm nhất trong ba thử nghiệm nhưng luôn ở bên bạn.

Lưu ý: Bạn có thể chọn buộc thực hiện một trong các phần phụ trợ này nếu biết mình sẽ thực thi trên thiết bị nào, hoặc bạn chỉ cần để TensorFlow.js quyết định cho bạn nếu bạn không chỉ định điều này.

Siêu năng lực 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ể vừa huấn luyện vừa 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à yêu cầu về việc tuân thủ luật đị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 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áy chủ từ xa nên suy luận (hoạt động phân loại dữ liệu) có thể nhanh hơn. Hơn nữa, bạn có quyền truy cập trực tiếp vào các cảm biến của thiết bị như máy ảnh, micrô, GPS, gia tốc kế và các 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ỉ với một cú nhấp chuột, bất kỳ ai trên thế giới cũng có thể nhấp vào liên kết bạn gửi cho họ, mở trang web trong trình duyệt của họ và sử dụng những gì bạn đã tạo. Không cần phải thiết lập Linux phía máy chủ phức tạp bằng trình điều khiển CUDA và nhiều công việc 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 để có một CDN để lưu trữ các tệp HTML, CSS, JS và mô hình của mình. Chi phí của một CDN rẻ hơn nhiều so với việc duy trì một máy chủ (có thể có kèm theo thẻ đồ hoạ) chạy 24/7.

Tính năng phía máy chủ

Việc tận dụng phương thức triển khai Node.js của TensorFlow.js sẽ giúp các tính năng sau hoạt động.

Hỗ trợ đầy đủ CUDA

Về phía máy chủ, để tăng tốc thẻ đồ 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 thẻ đồ 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 sự hỗ trợ CUDA đầy đủ, bạn hoàn toàn có thể tận dụng các khả năng cấp thấp hơn của thẻ đồ hoạ, nhờ đó rút ngắn thời gian huấn luyện và suy luận. Hiệu suất tương đương với quá trình triển khai TensorFlow của Python vì cả hai đều có cùng phần phụ trợ C++.

Quy mô mô hình

Để biết 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 gigabyte. Các mô hình này hiện không thể chạy trong trình duyệt web do hạn chế về việc sử dụng bộ nhớ trên 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 thông số kỹ thuật phần cứng mà bạn cần để chạy mô hình như vậy 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, theo đó, 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, có nghĩa là ứng dụng này được hưởng lợi từ việc biên dịch kịp thời. Điều này có nghĩa là bạn thường có thể nhận thấy mức tăng hiệu suất khi sử dụng Node.js vì Node.js sẽ được tối ưu hoá trong thời gian chạy, đặc biệt là đối với bất kỳ quá trình xử lý trước nào 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, trong đó 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ọ.

Bây giờ, bạn đã nắm được những thông tin cơ bản về TensorFlow.js (nơi mã 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!

3. Chuyển đổi nội dung học tập

Chính xác thì chuyển đổi học tập là gì?

Học chuyển giao bao gồm việc dùng kiến thức đã học để giúp học một điều khác nhưng tương tự.

Con người chúng ta lúc nào cũng vậy. Bộ não của bạn có cả một đời trải nghiệm mà bạn có thể sử dụng để giúp nhận ra những điều mới mẻ mà bạn chưa từng thấy trước đây. Hãy lấy cây liễu này làm ví dụ:

e28070392cd4afb9.png

Tuỳ thuộc vào nơi bạn đang ở trên thế giới, có khả năng bạn chưa từng thấy loại cây này trước đây.

Tuy nhiên, nếu bạn cho tôi biết liệu có cây liễu nào trong hình ảnh mới dưới đây hay không, bạn có thể nhậ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 ảnh gốc mà tôi cho bạn xem.

d9073a0d5df27222.png

Bộ não của bạn đã có sẵn một loạt các nơron biết cách xác định các vật thể giống cây cối và các nơron khác có khả năng tìm ra các đường thẳng dài. Bạn có thể sử dụng lại kiến thức đó để nhanh chóng phân loại cây liễu. Đây là một vật thể giống cây với nhiều nhá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, thì 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 tiên tiến như MobileNet, một mô hình nghiên cứu rất phổ biến có thể thực hiện nhận dạng hình ảnh trên 1000 loại đối tượng khác nhau. Từ chó đến ô tô, họ đã được huấn luyện dựa trên một tập dữ liệu khổng lồ tên là ImageNet, trong đó có 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 có trong mô hình MobileNet V1 này:

7d4e1e35c1a89715.gif

Trong quá trình huấn luyện, mô hình này đã học cách trích xuất các đối tượng phổ biến quan trọng đối với toàn bộ 1.000 đối tượng đó. Ngoài ra, nhiều tính năng cấp thấp hơn mà nó 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à nó chưa từng thấy trước đây. Suy cho cùng, mọi thứ hoàn toàn chỉ là sự kết hợp của các đường nét, hoạ tiết và hình dạng.

Hãy cùng tìm hiểu kiến trúc Mạng nơron tích chập (CNN) truyền thống (tương tự như MobileNet) và xem cách công nghệ học chuyển tiếp có thể tận dụng mạng đã được huấn luyện này để học hỏi điều mới. Hình ảnh dưới đây cho thấy kiến trúc mô hình điển hình của CNN mà trong trường hợp này đã được huấn luyện để nhận dạng các chữ số viết tay từ 0 đến 9:

baf4e3d434576106.png

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

369a8a9041c6917d.png.

Giả sử đối tượng mới mà bạn đang cố gắng nhận ra 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ì bạn có thể sử dụng lại những tính năng đó cho mục đích mới.

Trong biểu đồ trên, mô hình giả định này đã được huấn luyện dựa trên các chữ số. Vì vậy, những gì đã học về chữ số cũng có thể được áp dụng cho các chữ cái như a, b và c.

Vì vậy, giờ đây bạn có thể thêm một phần đầu phân loại mới để cố gắng dự đoán a, b hoặc c như sau:

db97e5e60ae73bbd.png

Ở đây, các lớp cấp thấp hơn được cố định và không được huấn luyện, chỉ có trưởng nhóm phân loại mới sẽ tự cập nhật để học từ các tính năng được cung cấp từ mô hình phân loại được huấn luyện trước ở bên trái.

Việc này được gọi là học chuyển và Teachable Machine cũng thực hiện việc này.

Bạn cũng có thể thấy rằng bằng cách chỉ phải huấn luyện perceptron nhiều lớp ở cuối mạng, nó 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 thế nào để có thể thao tác trên các 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ô hình cơ sở phù hợp để sử dụng

Để xem các mô hình nghiên cứu nâng cao và phổ biến hơn như MobileNet, bạn có thể truy cập trung tâm TensorFlow, sau đó lọc các mô hình phù hợp với TensorFlow.js sử dụng kiến trúc MobileNet v3 để tìm được kết quả tương tự như ở đây:

c5dc1420c6238c14.png

Xin lưu ý rằng một số kết quả trong số này thuộc loại "phân loại hình ảnh" (chi tiết ở trên cùng bên trái của mỗi kết quả thẻ mô hình) và các kết quả khác thuộc loại "vectơ đặc điểm hình ảnh".

Các kết quả vectơ đối tượng của hình ảnh này về cơ bản là các phiên bản cắt sẵn của MobileNet mà bạn có thể sử dụng để lấy vectơ đặc điểm của hình ảnh thay vì phân loại cuối cùng.

Những mô hình như thế này thường được gọi là "mô hình cơ sở", mà sau đó bạn có thể sử dụng để thực hiện chuyển đổi học tập theo cách tương tự như đã trình bày trong phần trước bằng cách thêm một đầu phân loại mới và huấn luyện nó 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ở được quan tâm nhất định có định dạng TensorFlow.js mà mô hình được phát hành. Nếu mở trang của một trong các mô hình vectơ tính năng MobileNet v3, bạn có thể thấy trong tài liệu về JS rằng mô hình đó ở dạng mô hình biểu đồ dựa trên đoạn mã mẫu trong tài liệu sử dụng tf.loadGraphModel().

f97d903d2e46924b.png

Cũng xin 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 nào cần cố định và lớp nào cần huỷ cố định để huấn luyện. Điều này có thể rất hiệu quả khi tạo mô hình cho công việc 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 biểu đồ 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. Để tìm hiểu thêm về cách sử dụng các mô hình Lớp, hãy tham khảo khoá học zero to hero TensorFlow.js (từ 0 đến hero).

Ưu điểm của phương pháp học chuyển giao

Việc sử dụng phương pháp học chuyển đổi có những ưu điểm gì thay vì huấn luyện toàn bộ kiến trúc mô hình từ đầu?

Thứ nhất, thời gian đào tạo là một lợi thế chính khi áp dụng phương pháp học chuyển đổi vì bạn đã có mô hình cơ sở đã huấn luyện để xây dựng dựa trên đó.

Thứ hai, bạn có thể không cần đưa ra nhiều ví dụ về nội dung mới mà bạn đang cố gắng phân loại do quá trình đào tạo đã diễn ra.

Điều này thực sự hữu ích nếu bạn có thời gian và tài nguyên hạn chế để thu thập dữ liệu mẫu về thứ bạn muốn phân loại và 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 để cải thiện kết quả mạnh mẽ hơn.

Do nhu cầu cần ít dữ liệu hơn và tốc độ đào tạo một mạng nhỏ hơn, nên phương pháp học chuyển dữ liệu sẽ tốn ít nguồn lực hơn. Điều đó làm cho chương trình này rất phù hợp với môi trường trình duyệt, chỉ mất hàng chục giây trên một cỗ máy hiện đại thay vì hàng giờ, nhiều ngày hoặc hàng tuần để huấn luyện mô hình đầy đủ.

Đã xong! Giờ bạn đã nắm được bản chất của Transfer Learning là gì, đã đến lúc tạo phiên bản Teachable Machine của riêng bạn. Hãy bắt đầu!

5. Thiết lập để viết mã

Bạn cần có

Hãy lập trình

Các mẫu tạo sẵn để bắt đầu đã đượ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 "phối lại nội dung này" để phát triển nhánh và tạo một nhóm tệp mới mà bạn có thể chỉnh sửa.

Hoặc, trên Codepen, hãy nhấp vào" nĩa" ở góc dưới bên phải của 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 để viết mã JavaScript (script.js)

Để thuận tiện cho bạn, chúng tôi đã thêm một tệp nhập 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>

Phương án thay thế: Sử dụng trình chỉnh sửa web mà bạn ưu tiên hoặc làm việc trên máy tính

Nếu muốn tải mã xuống và thao tác trên thiết bị cục bộ hoặc trên một trình chỉnh sửa trực tuyến khác, bạn chỉ cần tạo 3 tệp có tên ở trên trong cùng thư mục đó, rồi sao chép và dán mã từ bản mẫu Glitch vào từng tệp.

6. Mẫu HTML ứ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ố khung HTML cơ bản để bạn có thể trình bày những phát hiện của mình. Thiết lập ngay bây giờ. Bạn sẽ thêm:

  • Tiêu đề cho trang.
  • Một số văn bản mô tả.
  • Đoạn trạng thái.
  • Video để lưu nguồn cấp dữ liệu webcam khi đã sẵn sàng.
  • Một số nút để khởi động máy ảnh, thu thập dữ liệu hoặc đặt lại trải nghiệm.
  • Các tệp nhập cho các tệp TensorFlow.js và JS mà bạn sẽ lập trình sau này.

Mở index.html rồi dán lên mã hiện có bằng đoạn mã sau để thiết lập các tính năng ở 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 &amp; 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ỏ

Hãy phân tích một số mã HTML ở trên để làm nổi bật một số nội dung quan trọng bạn đã thêm.

  • Bạn đã thêm một thẻ <h1> cho tiêu đề trang cùng với một thẻ <p> có mã nhận dạng "trạng thái". đây là nơi bạn sẽ in thông tin khi 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" mà bạn sẽ kết xuất luồng webcam của mình sau này.
  • Bạn đã thêm 5 phần tử <button>. Đầu tiên, với mã nhận dạng là "enableCam", bật máy ảnh. Hai nút tiếp theo có một lớp "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 số lượng nút bất kỳ và chúng sẽ tự động hoạt động như dự kiến.

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ẽ sử dụng để biểu thị dữ liệu của một lớp nhất định. Chỉ mục sẽ được dùng để mã hoá các lớp đầu ra một cách chính xác bằng cách biểu diễn bằng số thay vì chuỗi, vì mô hình học máy chỉ có thể hoạt động với số.

Ngoài ra, còn có thuộc tính tên dữ liệu chứa tên dễ đọc mà bạn muốn sử dụng cho lớp này, cho phép bạn cung cấp tên có ý nghĩa hơn cho người dùng thay vì giá trị chỉ mục bằng số trong mã hóa 1 nóng.

Cuối cùng, bạn có nút huấn luyện và đặt lại để bắt đầu quá trình huấn luyện sau khi dữ liệu đã được thu thập 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 mã cho TensorFlow.js và một cho script.js mà bạn sắp xác định được.

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 bạn vừa thêm để đảm bảo chúng hiển thị chính xác. Dưới đây là một số kiểu được thêm vào vị trí và kích thước các phần tử một cách chính xác. Không có gì quá đặc biệt. Bạn chắc chắn có thể bổ sung vào phần này sau để cải thiện trải nghiệm người dùng, như bạn đã thấy trong video dạy máy học.

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 làm. Nếu bạn xem trước kết quả ngay bây giờ, kết quả sẽ có dạng như sau:

81909685d7566dcb.png.

8. JavaScript: Hằng số và trình nghe chính

Xác định các hằng số chính

Trước tiên, hãy thêm một số hằng số chính mà bạn sẽ sử dụng xuyên 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 phân tích những mục này:

  • STATUS chỉ chứa thông tin tham chiếu đến thẻ đoạn mà bạn sẽ viết nội dung cập nhật trạng thái.
  • VIDEO chứa thông tin 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_BUTTONTRAIN_BUTTON lấy thông tin tham chiếu DOM đến tất cả các nút chính trên trang HTML.
  • MOBILE_NET_INPUT_WIDTHMOBILE_NET_INPUT_HEIGHT lầ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ữ thuộc tính 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, bạn sẽ dễ dàng cập nhật các giá trị một lần thay vì phải thay thế các giá trị đó ở nhiều vị trí.
  • STOP_DATA_GATHER được đặt thành – 1. Công cụ này lưu trữ một giá trị trạng thái để bạn biết khi nào người dùng dừng nhấp vào một nút để thu thập dữ liệu từ nguồn cấp dữ liệu webcam. Việc đặt một tên có ý nghĩa hơn cho số này giúp mã dễ đọc hơn sau này.
  • CLASS_NAMES đóng vai trò như một tra cứu và chứa tên mà con người 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ờ bạn đã tham chiếu đến các phần tử chính, đã đến lúc 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 chính

Bắt đầu bằng cách thêm trình xử lý sự kiện nhấp vào các nút chính như sau:

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 vào.

TRAIN_BUTTON – gọi TrainAndPredict khi được nhấp vào.

RESET_BUTTON – cuộc gọi được đặ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" đang sử dụng document.querySelectorAll(). Hàm 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 yêu cầu sau:

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!
}

Nội dung giải thích về đoạn mã:

Sau đó, bạn lặp lại lần lượt 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 mã cho "nhấp chuột" và một thẻ cho "nhấp chuột". Điều này cho phép bạn tiếp tục ghi 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ừ tên dữ liệu của thuộc tính nút HTML vào mảng CLASS_NAMES.

Tiếp theo, hãy thêm một số biến để lưu trữ những nội dung quan trọng 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 các giải pháp đó.

Trước tiên, bạn có một biến mobilenet để lưu trữ mô hình mobilenet đã tải. Ban đầu, đặt giá trị này là không xác định.

Tiếp theo, bạn có một biến tên là gatherDataState. Nếu một "dataCollectionor" được nhấn, thì thay đổi này sẽ trở thành ID nóng 1 của nút đó, như được xác định trong HTML, để bạn biết loại dữ liệu nào mình đang thu thập tại thời điểm đó. Ban đầu, thuộc tính này được đặt thành STOP_DATA_GATHER để dữ liệu thu thập vòng lặp 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 webcam có đượ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, đơn vị 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, trainingDataInputstrainingDataOutputs. Các khoá này lưu trữ các giá trị dữ liệu huấn luyện được thu thập khi bạn nhấp vào "dataCollection" các nút cho tính năng đầu vào được tạo bởi mô hình cơ sở MobileNet và lớp đầu ra được lấy mẫu tương ứng.

Một mảng cuối cùng, examplesCount, sau đó được định nghĩa để 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 tên là predict kiểm soát vòng lặp dự đoán. Ban đầu, thuộc tính này được đặt thành false. Không có dự đoán nào có thể diễn ra cho đến khi bạn đặt giá trị này thành true sau.

Bây giờ, 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. Mô hình này cung cấp các vectơ đặc điểm 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ạ dưới đây. Đây phải là hàm không đồng bộ vì hành động tải 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 trong tài liệu TFHub.

Sau đó, bạn có thể tải mô hình bằng await tf.loadGraphModel() và hãy nhớ đặt thuộc tính đặc biệt fromTFHub thành true khi tải một mô hình từ trang web này của Google. Đây là trường hợp đặc biệt chỉ khi 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 kèm theo một thông báo để bạn có thể thấy rằng phần tử đã tải đúng cách và sẵn sàng bắt đầu thu thập dữ liệu.

Việc duy nhất bạn cần làm bây giờ là khởi động mô hình này. 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 này, có thể mất chút thời gian để thiết lập mọi thứ. Do đó, giúp chuyển các giá trị 0 qua mô hình này để 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 gói trong tf.tidy() để đảm bảo các tensor được xử lý đúng cách, với kích thước lô là 1, cũng như chiều cao và 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 yêu cầu hình ảnh RGB.

Tiếp theo, hãy ghi lại hình dạng kết quả của tensor được trả về bằng cách sử dụng answer.shape() để giúp bạn nắm được 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 định nghĩa hàm này, bạn có thể gọi hàm ngay để bắt đầu tải mô hình xuống khi tải trang.

Nếu bạn xem trước trực tiếp ngay bây giờ, thì sau vài phút, bạn sẽ thấy văn bản trạng thái thay đổi từ "Đang chờ tải TF.js" trở thành "MobileNet v3 đã tải thành công!" như minh hoạ dưới đây. Hãy đảm bảo mã này hoạt động bình thường trước khi tiếp tục.

a28b734e190afff.png

Bạn cũng có thể kiểm tra kết quả của bảng điều khiển để xem kích thước in của các tính năng đầu ra mà mô hình này tạo ra. Sau khi chạy các số 0 trong mô hình MobileNet, bạn sẽ thấy hình dạng [1, 1024] được in ra. Mục đầu tiên chỉ có kích thước lô là 1 và bạn có thể thấy mục này thực sự trả về 1024 tính năng sau đó có thể được 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

Giờ là lúc xác định phần đầu mô hình của bạn, về cơ bản là một perceptron nhiều lớp rất nhỏ.

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 tìm hiểu mã này. Bạn bắt đầu bằng cách xác định một mô hình tf.IMAP mà bạn sẽ thêm 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 vào mô hình này. Hàm này có hình dạng đầu vào là 1024 vì dữ liệu đầu ra của các tính năng MobileNet v3 đều có kích thước như vậy. Bạn đã phát hiện điều này ở bước trước sau khi truyền các sự kiện thông 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 chưa 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 nêu chi tiết ở đầu hội thảo này để hiểu rõ các thuộc tính này có chức năng gì đằng sau.

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ớp bạn đang cố gắng dự đoán. Để làm việc này, bạn có thể sử dụng CLASS_NAMES.length để biết số lượng lớp mà bạn đị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. Do đây là một vấn đề phân loại, nên bạn cần sử dụng thao tác kích hoạt softmax trên lớp đầu ra này. Bạn phải dùng thao tác này khi tạo 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() để xuất thông tin tổng quan về mô hình mới xác định ra bảng điều khiển.

Cuối cùng, hãy biên dịch mô hình để mô hình sẵn sàng đưa vào 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. Bạn cũng cần yêu cầu các chỉ số về độ chính xác để có thể theo dõi các chỉ số này 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 như sau:

22eaf32286fea4bb.png.

Xin lưu ý rằng tệp này có hơn 130 nghìn thông số có thể huấn luyện. Nhưng vì đây là một lớp nơron thông thường dày đặc đơn giản nên nó sẽ huấn luyện khá nhanh.

Là một hoạt động cần thực hiện sau 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 mình có thể hạ thấp đến mức nào trong khi vẫn đạt được hiệu suất tốt. Thông thường, công nghệ học máy có thể thử và sai ở một mức độ nào đó để tìm ra các giá trị thông 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

Đã đến lúc bổ sung hàm enableCam() mà bạn định nghĩa 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() được 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 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 chính của API trình duyệt.

Trong hàm enableCam(), hãy dùng hàm hasGetUserMedia() 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 ra bảng điều khiển.

Nếu ứng dụng này hỗ trợ chức năng này, 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 có kích thước 480 pixel. Tại sao? Chà, không có nhiều điểm để nhận được một video lớn hơn thế này vì nó cần được thay đổ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 một độ phân giải nhỏ hơn. Hầu hết các máy ảnh đề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ư đã trình bày chi tiết ở trên, sau đó đợi stream được trả về. Sau khi stream được trả về, bạn có thể yêu cầu phần tử VIDEO phát stream bằng cách đặt phần tử này 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 tải hơi nước, bạn có thể đặt videoPlaying thành true (đúng) và xoá ENABLE_CAM_BUTTON để ngăn người dùng nhấp vào luồng này một lần nữa bằng cách đặt lớp (class) thành "removed".

Bây giờ, hãy chạy mã của bạn, nhấp vào nút bật máy ảnh 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 kết xuất cho phần tử video trên trang như sau:

b378eb1affa9b883.png

Bây giờ, đã đến 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ờ, đã đến lúc đ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 trình 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() với tên của thuộc tính, trong trường hợp này là data-1hot dưới dạng tham số. Vì đây là một chuỗi nên bạn có thể dùng parseInt() để truyền chuỗi đó đến một số nguyên và gán kết quả này cho 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 (mà bạn đặt là -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 đó có nghĩa là bạn hiện đang thu thập dữ liệu và sự kiện được kích hoạt là sự kiện mouseup, và bây giờ bạn muốn ngừng thu thập dữ liệu cho lớp đó. Chỉ cần đặt lại mã về 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ẽ sớm xác định.

Cuối cùng, hãy bắt đầu lệnh gọi đến dataGatherLoop(), để thực sự ghi lại dữ liệu của lớp.

13. Thu thập dữ liệu

Bây giờ, hãy định nghĩa 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 chúng qua mô hình MobileNet và thu thập kết quả của mô hình đó (vectơ đặc trưng 1024).

Sau đó, phương thức này lưu trữ các lớp này cùng với mã nhận dạng gatherDataState của nút đang được nhấn để bạn biết dữ liệu này đại diện cho lớp nào.

Hãy cùng tìm hiểu:

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 sẽ chỉ tiếp tục quá trình thực thi hàm này nếu videoPlaying là true, nghĩa là webcam đang hoạt động và gatherDataState không bằng STOP_DATA_GATHER và một nút để thu thập dữ liệu lớp hiện đang được nhấn.

Tiếp theo, hãy gói mã của bạn vào một tf.tidy() để loại bỏ mọi tensor đã tạo trong đoạn mã theo sau. Kết quả của lần 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 khung hình webcam VIDEO bằng tf.browser.fromPixels(). tensor thu được chứa dữ liệu hình ảnh được lưu trữ trong một biến tên là videoFrameAsTensor.

Tiếp theo, hãy đổi kích thước biến videoFrameAsTensor thành hình dạng chính xác 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 đổi hình dạng làm tham số đầu tiên, sau đó sử dụng một hình dạng xác định chiều cao và chiều rộng mới như được xác định bằng các hằng số bạn đã tạo trước đó. Cuối cùng, đặt các góc căn chỉnh thành true bằng cách truyền tham số thứ ba để tránh mọi vấn đề căn chỉnh khi đổi kích thước. Kết quả của việc đổi kích thước này được lưu trữ trong một biến có tên là resizedTensorFrame.

Lưu ý rằng việc đổi kích thước nguyên gốc này sẽ kéo dài hình ảnh, vì hình ảnh webcam của bạn có kích thước 640 x 480 pixel và mô hình cần hình ảnh vuông 224 x 224 pixel.

Theo mục đích của bản minh hoạ này, tính năng này sẽ hoạt động tốt. Tuy nhiên, sau khi hoàn tất lớp học lập trình này, bạn nên thử cắt một hình vuông trong hình ảnh này để có kết quả tốt hơn nữa cho mọi hệ thống sản xuất mà bạn có thể sẽ 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 kích thước của TensorFrame cho 255 để đảm bảo tất cả các giá trị đều nằm trong khoảng từ 0 đến 1, đây là điều mà mô hình MobileNet muốn dùng làm đầu vào.

Cuối cùng, trong phần tf.tidy() của mã, hãy đẩy tensor đã chuẩn hoá này qua mô hình đã tải bằng cách gọi mobilenet.predict(). Bạn sẽ truyền phiên bản mở rộng của normalizedTensorFrame bằng expandDims() để đây là một lô 1, vì mô hình dự kiến có một loạt dữ liệu đầu vào để xử lý.

Sau khi kết quả xuất hiện trở lại, bạn có thể gọi ngay squeeze() trên kết quả được trả về đó để nén kết quả xuống tensor 1D, sau đó bạn trả về và gán cho biến imageFeatures để thu thập 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 lượt chuyển đổi đó 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 nội dung đầu vào này 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 lại dữ liệu khi người dùng nhấp vào nút này trong hàm gatherDataForClass() được 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 tạo trước đó hay chưa. Nếu thuộc tính này không xác định, hãy đặt thành 0 để khởi chạy bộ đếm cho một 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 thấy số lượng hiện tại của mỗi lớp khi các lớp đó được chụp. Để thực hiện việc này, hãy lặp qua mảng CLASS_NAMES và xuất 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 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 trong video cho đến khi phát hiện thấy mouseup của nút và gatherDataState được đặt thành STOP_DATA_GATHER,. Lúc này, vòng lặp thu thập dữ liệu sẽ kết thúc.

Nếu chạy mã của mình ngay bây giờ, bạn có thể nhấp vào nút bật máy ảnh, chờ webcam tải, sau đó nhấp và giữ từng nút thu thập dữ liệu để thu thập ví dụ cho từng lớp dữ liệu. Ở đây, bạn thấy tôi thu thập dữ liệu tương ứng về điện thoại di động và tay của tôi.

541051644a45131f.gif

Bạn sẽ thấy văn bản trạng thái được cập nhật vì nó lưu trữ tất cả tensor trong bộ nhớ như trong ảnh chụp màn hình bên 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. Đây là nơi diễn ra quá trình học chuyển. Hãy cùng xem mã:

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 ngừng mọi dự đoán hiện tại đang diễn ra 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 sự cố trong quá trình huấn luyện.

Chuyển đổi mảng đầu ra, trainingDataOutputs, thành tensor1d thuộc kiểu int32 để mảng đó sẵn sàng sử dụng trong phương thức mã hoá nóng. Dữ liệu này được lưu trữ trong một biến có tên là outputsAsTensor.

Dùng hàm tf.oneHot() với biến outputsAsTensor này cùng với số lượng lớp tối đa để mã hoá, đây chỉ là CLASS_NAMES.length. Một dữ liệu đầu ra được mã hoá nóng hiện được lưu trữ trong một tensor mới có tên là oneHotOutputs.

Lưu ý rằng trainingDataInputs hiện là một mảng các tensor được ghi lại. Để dùng các tensor này để 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 được việc đó, có một hàm tuyệt vời trong thư viện TensorFlow.js có tên là tf.stack(),

Phương thức này lấy một mảng tensor và xếp chồng chúng lại với nhau để tạo ra một đầu ra một tensor nhiều chiều cao hơn. Trong trường hợp này, một tensor 2D được trả về, đó là một lô gồm các đầu vào 1 chiều có độ dài mỗi 1024 chứa các tính năng được ghi lại, đó là những gì bạn cần để đào tạo.

Tiếp theo, hãy await model.fit() để huấn luyện phần đầu của mô hình tuỳ chỉnh. Ở đây, bạn truyền biến inputsAsTensor cùng với oneHotOutputs để đại diện cho dữ liệu huấn luyện dùng cho mẫu đầu vào 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, sử dụng batchSize của 5, với epochs được đặt thành 10, sau đó chỉ định callback cho onEpochEnd cho hàm logProgress mà bạn sẽ xác định ngay.

Cuối cùng, bạn có thể loại bỏ các tensor đã tạo vì mô hình này hiện đã được huấn luyện. Sau đó, bạn có thể đặt lại predict về true để cho phép dự đoán tiếp tục diễn ra, rồi gọi hàm predictLoop() để bắt đầu dự đoán hình ảnh webcam trực tiếp.

Bạn cũng có thể xác định hàm logProcess() để ghi lại trạng thái huấn luyện, 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 chính

Tại đây, bạn triển khai vòng lặp dự đoán chính lấy mẫu khung hình 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 cùng 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 rằng predict là đúng, để các dự đoán chỉ được đưa ra sau khi một mô hình được huấn luyện và có thể sử dụng được.

Tiếp theo, bạn có thể nhận các tính năng hình ảnh cho hình ảnh hiện tại giống như bạn đã thực hiện trong hàm dataGatherLoop(). Về cơ bản, bạn lấy 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 khung hình thành kích thước 224 x 224 pixel, sau đó truyền dữ liệu đó qua mô hình MobileNet để lấy các tính năng hình ảnh thu được.

Tuy nhiên, giờ đây, bạn có thể sử dụng phần đầu của mô hình mới được huấn luyện để thực sự 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ể ép lại tensor thu được để tạo lại 1 chiều rồi gán lại cho một biến tên là prediction.

Với prediction này, bạn có thể tìm chỉ mục có giá trị cao nhất bằng cách sử dụng argMax(), sau đó chuyển đổi tensor thu được này thành một mảng bằng arraySync() để lấy dữ liệu cơ bản trong JavaScript, qua đó 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 điểm số tin cậy của dự đoán thực tế theo cách tương tự bằng cách gọi trực tiếp arraySync() trên tensor prediction.

Bây giờ, bạn đã có mọi thông tin cần thiết để cập nhật văn bản STATUS bằng dữ liệu prediction. Để có chuỗi mà con người có thể đọc được cho lớp này, bạn chỉ cần tra cứu highestIndex trong mảng CLASS_NAMES, sau đó lấy giá trị tin cậy từ predictionArray. Để kết quả dưới dạng phần trăm dễ đọc hơn, bạn chỉ cần nhân với 100 rồi math.floor().

Cuối cùng, bạn có thể sử dụng window.requestAnimationFrame() để gọi lại toàn bộ predictionLoop() khi đã sẵn sàng, nhằm phân loại luồng video của bạn theo thời gian thực. Việc 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 đư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! Phần cuối cùng của vấn đề cần giải quyết là triển khai nút đặt lại để bắt đầu lại. Bên dưới là mã cho hàm reset() hiện đang trống. Hãy tiếp tục và cập nhật mã 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 nội dung đó thành 0. Đây là một cách tiện lợi để xoá tất cả nội dung trong một mảng.

Bây giờ, hãy xem qua tất cả trainingDataInputs được ghi lại hiện tại và đảm bảo bạn dispose() của mỗi tensor có trong đó để giải phóng bộ nhớ một lần nữa, vì Tensor không được dọn dẹp bằng trình thu thập rác JavaScript.

Sau khi hoàn tất, bạn có thể đặt độ dài mảng thành 0 trên cả hai mảng trainingDataInputstrainingDataOutputs để xoá cả những mảng đó.

Cuối cùng, hãy thiết lập văn bản STATUS thành một điều gì đó hợp lý và in các tensor còn lại trong bộ nhớ để kiểm tra tính hợp lý.

Lưu ý rằng sẽ vẫn còn 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 được loại bỏ. Bạn sẽ cần sử dụng lại chúng với dữ liệu huấn luyện mới nếu quyết định huấn luyện lại sau lần đặt lại này.

16. Hãy thử xem

Đã đến lúc trải nghiệm phiên bản Teachable Machine của riêng bạn!

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 loại 1 cho đối tượng nào đó trong phòng của bạn, sau đó làm tương tự cho lớp 2 cho đối tượng khác, nhấp vào huấn luyện và kiểm tra nhật ký bảng điều khiển để xem tiến trình. Mã sẽ huấn luyện khá nhanh:

bf1ac3cc5b15740.gif

Sau khi huấn luyện, hãy cho các đối tượng trước camera để nhận thông tin dự đoán trực tiếp sẽ được in vào vùng văn bản trạng thái trên trang web ở gần phía trên cùng. Nếu bạn gặp sự cố, hãy kiểm tra mã đang làm việc đã hoàn tất của tôi để xem bạn có bỏ lỡ quá trình sao chép bất kỳ 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ụ học chuyển đầu tiên bằng cách sử dụng TensorFlow.js trong trình duyệt.

Hãy thử dùng tính năng này trên nhiều vật thể, bạn có thể nhận thấy một số thứ khó nhận ra hơn so với các vật thể khác, đặc biệt là khi chúng tương tự với vật thể khác. Bạn có thể cần thêm nhiều lớp hoặc dữ liệu huấn luyện khác để có thể 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:

  1. Học chuyển đổi là gì và ưu điểm của nó so với đào tạo một mô hình đầy đủ.
  2. Cách lấy các mô hình để tái sử dụng từ TensorFlow Hub.
  3. Cách thiết lập một ứng dụng web phù hợp với phương pháp học chuyển.
  4. Cách tải và sử dụng mô hình cơ sở để tạo các đặc điểm của hình ảnh.
  5. Cách huấn luyện một đầu dự đoán mới có khả năng nhận dạng đối tượng tuỳ chỉnh qua hình ảnh webcam.
  6. 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 khi đã có cơ sở để làm việc, bạn có thể nảy ra ý tưởng sáng tạo nào để mở rộng mẫu mô hình học máy này cho trường hợp sử dụng thực tế mà bạn có thể đang nghiên cứu? Hay bạn có thể cách mạng hoá ngành nghề mà mình đang làm việc để giúp nhân viên ở công ty đào tạo các mô hình phân loại những yếu tố 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, bạn có thể tham gia hoàn toàn miễn phí toàn bộ khoá học này. Qua đó, bạn sẽ biết cách kết hợp 2 mô hình mà bạn hiện đang có trong lớp học lập trình này thành 1 mô hình duy nhất để tăng tính hiệu quả.

Ngoài ra, nếu bạn muốn biết thêm về lý thuyết đằng sau ứng dụng máy học ban đầu, hãy tham khảo hướng dẫn này.

Chia sẻ với chúng tôi về những video bạn làm ra

Bạn cũng có thể dễ dàng mở rộng những nội dung mình làm hôm nay cho các trường hợp sử dụng sáng tạo khác. Bạn cũng nên có tư duy sáng tạo và tiếp tục đột nhập.

Đừ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 nổi bật trên blog của TensorFlow hoặc thậm chí là các sự kiện trong tương lai. Chúng tôi rất muốn xem thành quả của bạn.

Trang web nên thanh toán