TensorFlow.js: 나만의 "Teachable Machine" 만들기 TensorFlow.js에서 전이 학습을 사용하여

1. 시작하기 전에

지난 몇 년간 TensorFlow.js 모델 사용량이 기하급수적으로 증가함에 따라 이제 많은 JavaScript 개발자들이 기존의 최첨단 모델을 가져와 업계에 고유한 커스텀 데이터로 작업하도록 다시 학습시키려고 합니다. 기존 모델 (기본 모델이라고도 함)을 유사하지만 다른 영역에서 사용하는 작업을 전이 학습이라고 합니다.

전이 학습은 완전히 비어 있는 모델에서 시작하는 것에 비해 많은 이점이 있습니다. 이전에 학습된 모델에서 이미 학습한 지식을 재사용할 수 있으며 분류하려는 새 항목에 대한 예시가 더 적게 필요합니다. 또한 전체 네트워크가 아닌 모델 아키텍처의 마지막 몇 개 레이어만 재학습하면 되므로 학습 속도가 훨씬 빠른 경우가 많습니다. 따라서 전이 학습은 실행 기기에 따라 리소스가 달라질 수 있지만 데이터를 쉽게 얻을 수 있도록 센서에 직접 액세스할 수 있는 웹브라우저 환경에 매우 적합합니다.

이 Codelab에서는 빈 캔버스에서 웹 앱을 빌드하여 Google의 인기 있는 ' Teachable Machine" 있습니다. 웹사이트를 통해 모든 사용자가 웹캠의 몇 가지 예시 이미지를 통해 맞춤 객체를 인식하는 데 사용할 수 있는 실용적인 웹 앱을 만들 수 있습니다. 이 Codelab의 머신러닝 측면에 집중할 수 있도록 웹사이트는 의도적으로 최소한으로 유지됩니다. 하지만 기존의 Teachable Machine 웹사이트와 마찬가지로 기존 웹 개발자 환경을 적용하여 UX를 개선할 수 있는 범위는 무궁무진합니다.

기본 요건

이 Codelab은 TensorFlow.js 사전 제작 모델과 기본적인 API 사용법에 어느 정도 익숙하고 TensorFlow.js에서 전이 학습을 시작하려는 웹 개발자를 위해 작성되었습니다.

  • 이 실습에서는 TensorFlow.js, HTML5, CSS, JavaScript에 관한 기본 지식을 숙지하고 있다고 가정합니다.

Tensorflow.js를 처음 사용하는 경우 먼저 이 무료 히어로 과정을 고려해 보세요. 이 과정은 머신러닝이나 TensorFlow.js에 대한 배경 지식이 없고 필요한 모든 내용을 짧은 단계로 학습합니다.

학습할 내용

  • TensorFlow.js의 정의와 다음 웹 앱에서 TensorFlow.js를 사용해야 하는 이유
  • Teachable Machine 사용자 환경을 그대로 재현한 간소화된 HTML/CSS /JS 웹페이지를 빌드하는 방법
  • TensorFlow.js를 사용하여 선행 학습된 기본 모델, 특히 MobileNet을 로드하여 전이 학습에 사용할 수 있는 이미지 특성을 생성하는 방법
  • 인식하려는 여러 데이터 클래스에 대해 사용자의 웹캠에서 데이터를 수집하는 방법
  • 이미지 특성을 가져와서 이를 사용하여 새 객체를 분류하는 방법을 학습하는 다층 퍼셉트론을 만들고 정의하는 방법

해킹을 시작해 보자...

필요한 항목

  • Glitch.com 계정을 사용하거나 직접 편집하고 실행할 수 있는 웹 제공 환경을 사용할 수 있습니다.

2. TensorFlow.js란 무엇인가요?

54e81d02971f53e8.png

TensorFlow.js는 JavaScript가 가능한 모든 곳에서 실행할 수 있는 오픈소스 머신러닝 라이브러리입니다. Python으로 작성된 원래 TensorFlow 라이브러리를 기반으로 하며 JavaScript 생태계용 개발자 환경과 API 세트를 다시 만드는 것을 목표로 합니다.

어디에서 사용할 수 있나요?

JavaScript의 이식성을 고려할 때, 이제 단일 언어로 작성하고 다음과 같은 모든 플랫폼에서 머신러닝을 쉽게 수행할 수 있습니다.

  • Vanilla JavaScript를 사용하는 웹브라우저의 클라이언트 측
  • Node.js를 사용하는 서버 측 및 IoT 기기(예: Raspberry Pi)
  • Electron을 사용하는 데스크톱 앱
  • React Native를 사용하는 네이티브 모바일 앱

또한 TensorFlow.js는 이러한 각 환경 (예: CPU 또는 WebGL) 내에서 실행할 수 있는 실제 하드웨어 기반 환경 내에서 여러 백엔드를 지원합니다. '백엔드' 이 맥락에서 서버 측 환경을 의미하지는 않습니다. 호환성을 보장하고 빠르게 실행하기 위해 실행을 위한 백엔드는 WebGL의 클라이언트 측일 수 있습니다. 현재 TensorFlow.js는 다음을 지원합니다.

  • 기기의 그래픽 카드에서 WebGL 실행 (GPU) - GPU 가속을 사용하여 더 큰 모델 (크기 3MB 이상)을 가장 빠르게 실행할 수 있습니다.
  • CPU에서 웹 어셈블리 (WASM) 실행 - 이전 세대의 휴대전화를 비롯한 여러 기기에서 CPU 성능을 개선합니다. 이는 그래픽 프로세서에 콘텐츠를 업로드하는 오버헤드로 인해 WebGL보다 WASM을 사용할 때 CPU에서 더 빠르게 실행될 수 있는 작은 모델 (크기 3MB 미만)에 더 적합합니다.
  • CPU 실행 - 다른 환경을 사용할 수 없어야 합니다. 이 세 가지 방법 중 가장 느리지만 언제나 도움이 됩니다.

참고: 실행할 기기를 알고 있는 경우 이러한 백엔드 중 하나를 강제하도록 선택하거나, 지정하지 않는 경우 TensorFlow.js가 자동으로 결정하도록 할 수 있습니다.

클라이언트 측 슈퍼 파워

클라이언트 컴퓨터의 웹브라우저에서 TensorFlow.js를 실행하면 고려할 만한 여러 이점을 얻을 수 있습니다.

개인 정보 보호

서드 파티 웹 서버로 데이터를 전송하지 않고도 클라이언트 시스템에서 데이터를 학습시키고 분류할 수 있습니다. 데이터 수집은 GDPR과 같은 현지 법규의 준수를 위해 요구되거나, 사용자가 제3자에게 보내지 않고 컴퓨터에 보관하고자 하는 데이터를 처리하는 경우일 수 있습니다.

Speed

데이터를 원격 서버로 전송할 필요가 없으므로 추론 (데이터를 분류하는 작업)이 더 빠를 수 있습니다. 더 좋은 점은 사용자가 액세스 권한을 부여하면 카메라, 마이크, GPS, 가속도계와 같은 기기의 센서에 직접 액세스할 수 있다는 것입니다.

도달범위 및 확장

전 세계 누구나 클릭 한 번으로 내가 보낸 링크를 클릭하고, 자신의 브라우저에서 웹페이지를 열고, 내가 만든 것을 활용할 수 있습니다. CUDA 드라이버를 사용하는 복잡한 서버 측 Linux 설정이 필요 없으며 머신러닝 시스템만 사용하기만 하면 됩니다.

비용

서버가 없다는 것은 HTML, CSS, JS 및 모델 파일을 호스팅하는 CDN만 비용을 지불하면 됨을 의미합니다. CDN 비용은 연중무휴 24시간 실행되는 서버 (그래픽 카드가 연결될 수 있음)보다 훨씬 저렴합니다.

서버 측 기능

TensorFlow.js의 Node.js 구현을 활용하면 다음 기능을 사용할 수 있습니다.

완벽한 CUDA 지원

서버 측에서 그래픽 카드 가속을 위해 NVIDIA CUDA 드라이버를 설치하여 TensorFlow가 그래픽 카드와 호환할 수 있도록 해야 합니다 (WebGL을 사용하는 브라우저와 달리 설치 필요 없음). 하지만 완전한 CUDA 지원을 통해 그래픽 카드의 하위 수준 기능을 최대한 활용하여 학습 및 추론 시간을 단축할 수 있습니다. 둘 다 동일한 C++ 백엔드를 공유하므로 Python TensorFlow 구현과 동일한 성능을 발휘합니다.

모델 크기

연구 중인 최첨단 모델의 경우, 크기가 기가바이트에 달할 정도로 매우 큰 모델로 작업하게 될 수도 있습니다. 이 모델은 브라우저 탭당 메모리 사용량 한도로 인해 현재 웹브라우저에서 실행할 수 없습니다. 이러한 더 큰 모델을 실행하려면 이러한 모델을 효율적으로 실행하는 데 필요한 하드웨어 사양을 갖춘 Node.js를 자체 서버에서 사용하면 됩니다.

IOT

Node.js는 Raspberry Pi와 같이 널리 사용되는 단일 보드 컴퓨터에서 지원되므로 이러한 기기에서도 TensorFlow.js 모델을 실행할 수 있습니다.

Speed

Node.js는 자바스크립트로 작성되므로 적시 컴파일의 이점을 활용할 수 있습니다. 즉, Node.js를 사용하면 런타임에 최적화되므로 특히 실행 중인 전처리에 대해 성능이 향상되는 경우가 많습니다. 이에 대한 좋은 예는 Hugging Face가 Node.js를 사용하여 자연어 처리 모델의 성능을 2배 향상한 방법을 보여주는 우수사례에서 확인할 수 있습니다.

이제 TensorFlow.js의 기본사항과 실행 위치, 몇 가지 이점을 이해했습니다. 이제 TensorFlow.js로 유용한 작업을 해보겠습니다.

3. 전이 학습

전이 학습이란 정확히 무엇인가요?

전이 학습은 이미 학습된 지식을 활용하여 다르지만 비슷한 내용을 학습하는 데 도움이 됩니다.

우리 인간은 항상 이런 일을 합니다. 이전에 본 적 없는 새로운 것을 인식하는 데 사용할 수 있는 평생 경험이 뇌에 포함되어 있습니다. 다음 버드나무 나무를 예로 들어보겠습니다.

e28070392cd4afb9.png

전 세계 어디에 있는지에 따라 전에 이런 종류의 나무를 본 적이 없을 가능성이 있습니다.

하지만 아래 새 이미지에서 버드나무가 있는지 알려달라고 하면, 각도가 다르고 앞서 보여드린 버드나무와 약간 다르더라도 꽤 빨리 찾을 수 있을 것입니다.

d9073a0d5df27222.png

이미 뇌에는 나무 같은 물체를 식별하는 방법을 아는 많은 뉴런과 긴 직선을 찾는 데 좋은 다른 뉴런이 있습니다. 이 지식을 활용하여 버드나무를 빠르게 분류할 수 있습니다. 버드나무는 길고 곧은 세로 가지를 가진 나무와 같은 객체입니다.

마찬가지로, 이미지에서 이미 학습한 머신러닝 모델(예: 이미지 인식)이 있는 경우 해당 모델을 재사용해 서로 다르지만 관련된 작업을 수행할 수 있습니다.

1, 000개의 서로 다른 객체 유형에 대해 영상 인식을 수행할 수 있는 매우 널리 사용되는 연구 모델인 MobileNet과 같은 고급 모델도 동일한 작업을 수행할 수 있습니다. 개부터 자동차에 이르기까지 라벨이 지정된 수백만 개의 이미지가 포함된 ImageNet으로 알려진 방대한 데이터 세트로 학습되었습니다.

이 애니메이션에서는 이 MobileNet V1 모델에 포함된 수많은 레이어를 볼 수 있습니다.

7d4e1e35c1a89715.gif

학습 과정에서 이 모델은 1, 000개의 모든 객체에 중요한 공통 특성을 추출하는 방법을 학습했으며, 이러한 객체를 식별하는 데 사용하는 많은 하위 수준 특성은 이전에 본 적 없는 새로운 객체를 감지하는 데 유용할 수 있습니다. 결국 모든 것은 결국 선, 질감, 도형의 조합일 뿐입니다.

MobileNet과 유사한 기존의 컨볼루셔널 신경망(CNN) 아키텍처를 살펴보고 전이 학습이 이 학습된 네트워크를 활용하여 새로운 것을 학습하는 방법을 알아보겠습니다. 아래 이미지는 0부터 9까지의 필기 입력 숫자를 인식하도록 학습된 CNN의 일반적인 모델 아키텍처를 보여줍니다.

baf4e3d434576106.png

왼쪽에 보이는 것처럼 학습된 기존 모델의 선행 학습된 하위 수준 레이어를 오른쪽에 표시된 모델 끝 부분 근처의 분류 레이어 (모델의 분류 헤드라고도 함)에서 분리할 수 있다면 하위 수준 레이어를 사용하여 학습의 기반이 된 원본 데이터를 기반으로 특정 이미지에 대한 출력 특성을 생성할 수 있습니다. 다음은 분류 헤드가 삭제된 동일한 네트워크입니다.

369a8a9041c6917d.png

인식하려는 새로운 대상이 이전 모델이 학습한 이러한 출력 특성도 사용할 수 있다고 가정하면 새로운 목적으로 재사용할 가능성이 높습니다.

위의 다이어그램에서 이 가상 모델은 숫자로 학습되었기 때문에 숫자에 대해 학습한 내용을 a, b, c와 같은 문자에도 적용할 수 있습니다.

따라서 다음과 같이 대신 a, b 또는 c를 예측하려고 하는 새 분류 헤드를 추가할 수 있습니다.

db97e5e60ae73bbd.png

여기서 하위 수준 레이어는 동결되고 학습되지 않습니다. 새 분류 헤드만 왼쪽에 있는 선행 학습된 잘린 모델에서 제공된 특성을 학습하도록 자동으로 업데이트됩니다.

이를 전이 학습이라고 하며 Teachable Machine이 백그라운드에서 수행하는 작업입니다.

또한 신경망의 맨 끝에서 멀티 레이어 퍼셉트론을 학습시키기만 하면 처음부터 전체 네트워크를 학습시킬 때보다 훨씬 빠르게 학습한다는 것을 알 수 있습니다.

하지만 모델의 하위 부분은 어떻게 구할 수 있을까요? 다음 섹션으로 이동하여 알아보세요.

4. TensorFlow Hub - 기본 모델

사용하기에 적합한 기본 모델 찾기

MobileNet과 같이 널리 사용되는 고급 연구 모델의 경우 TensorFlow 허브로 이동한 다음 MobileNet v3 아키텍처를 사용하는 TensorFlow.js에 적합한 모델을 필터링하여 여기에 표시된 것과 같은 결과를 찾을 수 있습니다.

c5dc1420c6238c14.png

이러한 검색 결과 중 일부는 '이미지 분류' 유형입니다. (각 모델 카드 결과의 왼쪽 상단에 자세히 설명됨), 나머지는 '이미지 특성 벡터' 유형입니다.

이러한 이미지 특징 벡터 결과는 기본적으로 최종 분류 대신 이미지 특징 벡터를 얻는 데 사용할 수 있는 MobileNet의 미리 잘린 버전입니다.

이와 같은 모델을 '기본 모델'이라고 부르기도 합니다. 그런 다음 새 분류 헤드를 추가하고 자체 데이터로 학습시켜 이전 섹션에서 본 것과 동일한 방식으로 전이 학습을 수행하는 데 사용할 수 있습니다.

다음으로 확인할 사항은 모델이 관심 있는 특정 기본 모델에 대해 모델이 어떤 TensorFlow.js 형식으로 릴리스되는지입니다. 이러한 특성 벡터 MobileNet v3 모델 중 하나의 페이지를 열면 JS 문서에서 tf.loadGraphModel()를 사용하는 문서의 예시 코드 스니펫을 기반으로 한 그래프 모델 형식임을 확인할 수 있습니다.

f97d903d2e46924b.png

그래프 형식이 아닌 레이어 형식으로 모델을 찾은 경우 학습을 위해 고정할 레이어와 고정 해제할 레이어를 선택할 수 있습니다. 이 방식은 새 작업을 위한 모델을 만들 때 매우 강력할 수 있으며, 이를 '전송 모델'이라고 합니다. 그러나 지금은 이 튜토리얼에서는 대부분의 TF Hub 모델이 배포되는 기본 그래프 모델 유형을 사용합니다. 레이어 모델 작업에 대해 자세히 알아보려면 zero to hero TensorFlow.js 과정을 확인하세요.

전이 학습의 이점

전체 모델 아키텍처를 처음부터 학습시키는 대신 전이 학습을 사용하면 어떤 이점이 있나요?

첫째, 학습 시간은 이미 빌드할 기본 모델이 이미 있으므로 전이 학습 접근 방식을 사용할 때 얻을 수 있는 주요 이점입니다.

둘째, 이미 진행된 학습으로 인해 분류하려는 새로운 항목의 예시를 훨씬 적게 보여주는 것만으로도 벗어날 수 있습니다.

이는 분류하려는 항목의 예시 데이터를 수집할 시간과 리소스가 제한되어 있고 더 강력하게 만들기 위해 더 많은 학습 데이터를 수집하기 전에 신속하게 프로토타입을 만들어야 하는 경우에 매우 유용합니다.

더 적은 데이터를 필요로 하고 더 작은 네트워크의 학습 속도를 감안할 때 전이 학습은 리소스 사용이 덜 필요합니다. 그렇기 때문에 브라우저 환경에 매우 적합하며, 최신 머신에서 전체 모델을 학습시키는 데 몇 시간, 며칠, 몇 주가 걸리던 것이 수십 초밖에 걸리지 않습니다.

좋습니다. 전이 학습의 핵심을 이해하셨으니 이제 나만의 Teachable Machine 버전을 만들어 보겠습니다. 그럼 시작해 볼까요?

5. 코딩 설정

필요한 항목

  • 최신 웹브라우저입니다.
  • HTML, CSS, JavaScript, Chrome DevTools (콘솔 출력 보기)에 관한 기본 지식

코딩을 시작해 볼까요?

Glitch.com 또는 Codepen.io용으로 만든 상용구 템플릿이 있습니다. 클릭 한 번으로 간단히 이 Codelab의 기본 상태로 두 템플릿 중 하나를 클론할 수 있습니다.

Glitch에서 '리믹스' 버튼을 클릭하여 포크하고 수정할 수 있는 새로운 파일 세트를 만듭니다.

또는 Codepen에서 fork"를 사용합니다.

이 매우 간단한 구조는 다음과 같은 파일을 제공합니다.

  • HTML 페이지 (index.html)
  • 스타일시트 (style.css)
  • JavaScript 코드 (script.js)를 작성할 파일

편의를 위해 TensorFlow.js 라이브러리의 HTML 파일에 추가된 가져오기가 있습니다. 상태 메시지가 표시됩니다.

index.html

<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>

대안: 선호하는 웹편집기 사용 또는 로컬에서 작업

코드를 다운로드하여 로컬에서 작업하거나 다른 온라인 편집기에서 작업하려면 위에 나온 3개의 파일을 같은 디렉터리에 만들고 Glitch 상용구에서 코드를 복사하여 각각에 붙여넣으면 됩니다.

6. 앱 HTML 상용구

어디서부터 시작해야 하나요?

모든 프로토타입에는 결과를 렌더링할 수 있는 몇 가지 기본 HTML 스캐폴딩이 필요합니다. 지금 설정하세요. 추가할 항목:

  • 페이지의 제목입니다.
  • 설명 텍스트
  • 상태 단락입니다.
  • 준비되었을 때 웹캠 피드를 보관할 동영상입니다.
  • 카메라를 시작하거나 데이터를 수집하거나 환경을 재설정할 수 있는 몇 가지 버튼
  • 나중에 코딩할 TensorFlow.js 및 JS 파일의 가져오기입니다.

index.html를 열고 기존 코드를 다음과 함께 붙여넣어 위의 기능을 설정합니다.

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>

분류하기

위의 HTML 코드 중 일부를 세분화하여 추가한 주요 사항을 강조해 보겠습니다.

  • ID가 'status'인 <p> 태그와 함께 페이지 제목에 <h1> 태그를 추가했습니다. 시스템의 다른 부분을 사용하여 출력을 볼 때 정보를 출력하게 됩니다.
  • ID가 '웹캠'인 <video> 요소를 추가했습니다. 나중에 웹캠 스트림을 렌더링할 것입니다.
  • <button> 요소 5개를 추가했습니다. 첫 번째는 'enableCam'이라는 ID로 카메라를 사용 설정합니다. 다음 두 버튼에는 ‘dataCollector’ 클래스가 있습니다. 인식하려는 대상의 예시 이미지를 수집할 수 있습니다. 나중에 작성하는 코드는 원하는 만큼 버튼을 추가할 수 있도록 설계되며, 버튼이 의도대로 자동으로 작동합니다.

이러한 버튼에는 data-1hot이라는 특수한 사용자 정의 속성도 있으며 첫 번째 클래스의 경우 정수 값이 0부터 시작됩니다. 특정 클래스의 데이터를 나타내는 데 사용할 숫자 색인입니다. ML 모델은 숫자만 사용할 수 있기 때문에 색인은 출력 클래스를 문자열 대신 숫자 표현으로 올바르게 인코딩하는 데 사용됩니다.

이 클래스에 사용하고자 하는 사람이 읽을 수 있는 이름이 포함된 data-name 특성도 있습니다. 이 속성을 사용하면 원-핫 인코딩의 숫자 색인 값 대신 더 의미 있는 이름을 사용자에게 제공할 수 있습니다.

마지막으로 데이터가 수집되면 학습 프로세스를 시작하거나 앱을 각각 재설정할 수 있는 학습 버튼과 재설정 버튼이 있습니다.

  • <script> 가져오기도 2개 추가했습니다. 하나는 TensorFlow.js용이고 다른 하나는 곧 정의할script.js용입니다.

7. 스타일 추가

요소 기본값

올바르게 렌더링되도록 방금 추가한 HTML 요소의 스타일을 추가합니다. 다음은 요소의 위치와 크기를 올바르게 지정하기 위해 추가된 스타일입니다. 그다지 특별할 것이 없어요. Teachable Machine 동영상에서 봤던 것처럼 나중에 이를 추가하여 훨씬 더 나은 UX를 만들 수 있습니다.

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%;
}

좋습니다. 완료되었습니다. 지금 출력을 미리 보면 다음과 같이 표시됩니다.

81909685d7566dcb.png

8. JavaScript: 주요 상수 및 리스너

키 상수 정의

먼저 앱 전체에서 사용할 몇 가지 주요 상수를 추가합니다. 먼저 script.js의 콘텐츠를 다음 상수로 바꿉니다.

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 = [];

각각의 용도를 자세히 살펴보겠습니다.

  • STATUS는 상태 업데이트를 작성할 단락 태그에 관한 참조를 보유합니다.
  • VIDEO는 웹캠 피드를 렌더링할 HTML 동영상 요소에 대한 참조를 보유합니다.
  • ENABLE_CAM_BUTTON, RESET_BUTTON, TRAIN_BUTTON는 HTML 페이지의 모든 키 버튼에 대한 DOM 참조를 가져옵니다.
  • MOBILE_NET_INPUT_WIDTHMOBILE_NET_INPUT_HEIGHT는 MobileNet 모델의 예상 입력 너비와 높이를 각각 정의합니다. 이와 같이 파일 상단 근처의 상수에 저장하면 나중에 다른 버전을 사용하기로 한 경우 여러 위치에서 값을 바꿀 필요 없이 값을 한 번에 쉽게 업데이트할 수 있습니다.
  • STOP_DATA_GATHER는 - 1로 설정됩니다. 상태 값이 저장되므로 사용자가 웹캠 피드에서 데이터 수집을 위한 버튼 클릭을 중단한 시점을 알 수 있습니다. 이 번호에 더 의미 있는 이름을 지정하면 나중에 코드를 더 쉽게 읽을 수 있습니다.
  • CLASS_NAMES는 조회 역할을 하며 가능한 클래스 예측을 위해 사람이 읽을 수 있는 이름을 보유합니다. 이 배열은 나중에 채워집니다.

이제 주요 요소에 대한 참조가 생겼으므로 일부 이벤트 리스너를 이러한 요소에 연결해 보겠습니다.

주요 이벤트 리스너 추가하기

먼저 다음과 같이 클릭 이벤트 핸들러를 키 버튼에 추가합니다.

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 - 클릭 시 enableCam 함수를 호출합니다.

TRAIN_BUTTON - 클릭 시 trainAndPredict를 호출합니다.

RESET_BUTTON - 클릭하면 통화가 재설정됩니다.

마지막으로 이 섹션에서는 'dataCollector' 클래스가 있는 모든 버튼을 찾을 수 있습니다. document.querySelectorAll() 사용. 이렇게 하면 다음과 일치하는 문서에서 찾은 요소의 배열이 반환됩니다.

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

코드 설명:

그런 다음 찾은 버튼을 반복하고 각각에 2개의 이벤트 리스너를 연결합니다. 하나는 ‘mousedown’, 다른 하나는 ‘mouseup’입니다. 이렇게 하면 버튼을 누르는 동안 샘플을 계속 기록할 수 있어 데이터 수집에 유용합니다.

두 이벤트 모두 나중에 정의할 gatherDataForClass 함수를 호출합니다.

이 시점에서, 사람이 읽을 수 있는 발견된 클래스 이름을 HTML 버튼 속성 data-name에서 CLASS_NAMES 배열로 푸시할 수도 있습니다.

다음으로, 나중에 사용할 주요 항목을 저장하기 위해 몇 가지 변수를 추가합니다.

script.js

let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;

자세히 살펴보겠습니다

먼저 로드된 모바일넷 모델을 저장할 mobilenet 변수가 있습니다. 처음에는 이 속성을 정의되지 않음으로 설정합니다.

다음으로 gatherDataState라는 변수가 있습니다. ‘dataCollector’가 버튼을 누르면 이 값은 HTML에 정의된 대로 해당 버튼의 핫 ID로 변경되므로 이 시점에 어떤 유형의 데이터를 수집하고 있는지 알 수 있습니다. 처음에는 버튼을 누르지 않으면 나중에 작성하는 데이터 수집 루프에서 데이터를 수집하지 않도록 STOP_DATA_GATHER로 설정됩니다.

videoPlaying는 웹캠 스트림이 성공적으로 로드 및 재생되고 사용할 수 있는지 추적합니다. ENABLE_CAM_BUTTON. 키를 누를 때까지 웹캠이 켜지지 않으므로 처음에는 false로 설정되어 있습니다.

다음으로 배열 2개(trainingDataInputs, trainingDataOutputs)를 정의합니다. 'dataCollector'를 클릭하면 수집된 학습 데이터 값이 저장됩니다. 각각 샘플링된 출력 클래스와 MobileNet 기본 모델이 생성한 입력 특성의 버튼과 비교됩니다.

그런 다음 마지막 배열 examplesCount,는 클래스를 추가하기 시작하면 각 클래스에 포함된 예 수를 추적하도록 정의됩니다.

마지막으로 예측 루프를 제어하는 predict라는 변수가 있습니다. 처음에는 false로 설정됩니다. 나중에 true로 설정할 때까지 예측을 실행할 수 없습니다.

모든 주요 변수가 정의되었으므로 분류 대신 이미지 특성 벡터를 제공하는 미리 잘린 MobileNet v3 기본 모델을 로드해 보겠습니다.

9. MobileNet 기본 모델 로드

먼저 아래와 같이 loadMobileNetFeatureModel라는 새 함수를 정의합니다. 모델을 로드하는 작업은 비동기적이므로 비동기 함수여야 합니다.

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();

이 코드에서는 로드할 모델이 TFHub 문서에 있는 URL를 정의합니다.

그런 다음 await tf.loadGraphModel()를 사용하여 모델을 로드할 수 있습니다. 이 Google 웹사이트에서 모델을 로드할 때 특수 속성 fromTFHubtrue로 설정해야 합니다. 이는 이 추가 속성을 설정해야 하는 TF Hub에서 호스팅되는 모델을 사용하는 경우에만 해당됩니다.

로드가 완료되면 메시지로 STATUS 요소의 innerText를 설정하여 올바르게 로드되었고 데이터 수집을 시작할 준비가 되었음을 시각적으로 확인할 수 있습니다.

이제 남은 작업은 모델을 준비하는 것입니다. 이와 같이 더 큰 모델의 경우 모델을 처음 사용할 때 모든 항목을 설정하는 데 시간이 걸릴 수 있습니다. 따라서 향후 타이밍이 더 중요할 수 있는 상황을 방지하려면 모델을 통해 0을 전달하는 것이 좋습니다.

tf.tidy()에 래핑된 tf.zeros()를 사용하여 배치 크기가 1이고 시작 시 상수에 정의한 올바른 높이와 너비로 텐서가 올바르게 처리되도록 할 수 있습니다. 마지막으로 색상 채널도 지정합니다. 이 경우에는 모델이 RGB 이미지를 예상하므로 3입니다.

다음으로, answer.shape()를 사용하여 반환된 텐서의 결과 모양을 기록하여 이 모델이 생성하는 이미지 특성의 크기를 파악합니다.

이 함수를 정의한 후 바로 호출하여 페이지 로드 시 모델 다운로드를 시작할 수 있습니다.

지금 실시간 미리보기를 보면 잠시 후 상태 텍스트가 'TF.js 로드 대기 중'에서 변경됩니다. 'MobileNet v3loaded successfully!(MobileNet v3이 성공적으로 로드되었습니다!)' 변경할 수 있습니다. 계속하기 전에 이 코드가 작동하는지 확인하세요.

a28b734e190afff.png

콘솔 출력을 확인하여 이 모델이 생성하는 출력 특성의 인쇄된 크기를 확인할 수도 있습니다. MobileNet 모델을 통해 0을 실행하면 [1, 1024] 모양이 출력됩니다. 첫 번째 항목은 배치 크기가 1이며, 실제로 1,024개의 특성을 반환한 다음 새 객체를 분류하는 데 사용할 수 있음을 알 수 있습니다.

10. 새 모델 헤드 정의

이제 본질적으로 최소한의 다중 레이어 퍼셉트론인 모델 헤드를 정의할 차례입니다.

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']  
});

이 코드를 살펴보겠습니다. 먼저 모델 레이어를 추가할 tf.sequence 모델을 정의합니다.

다음으로 밀집 레이어를 이 모델에 입력 레이어로 추가합니다. MobileNet v3 특성의 출력이 이 크기이므로 입력 형태는 1024입니다. 이전 단계에서 모델을 통해 값을 전달한 후 이를 발견했습니다. 이 레이어에는 ReLU 활성화 함수를 사용하는 뉴런 128개가 있습니다.

활성화 함수 및 모델 레이어를 처음 사용하는 경우 이 워크숍 초반에 자세히 설명된 과정을 수강하여 이러한 속성이 이면에서 어떤 작업을 수행하는지 이해하는 것이 좋습니다.

다음으로 추가할 레이어는 출력 레이어입니다. 뉴런의 수는 예측하려는 클래스 수와 같아야 합니다. 이를 위해 CLASS_NAMES.length를 사용하여 분류할 클래스 수를 찾을 수 있습니다. 이는 사용자 인터페이스에 있는 데이터 수집 버튼 수와 같습니다. 이는 분류 문제이므로 이 출력 레이어에서 softmax 활성화를 사용합니다. 이 활성화 함수는 회귀 대신 분류 문제를 해결하기 위해 모델을 만들 때 사용해야 합니다.

이제 model.summary()를 출력하여 새로 정의된 모델의 개요를 콘솔에 출력합니다.

마지막으로 학습할 준비가 되도록 모델을 컴파일합니다. 여기서 옵티마이저는 adam로 설정되며 손실은 CLASS_NAMES.length2와 같으면 binaryCrossentropy가 되고 분류할 클래스가 3개 이상 있으면 categoricalCrossentropy를 사용합니다. 정확성 측정항목도 요청되므로 나중에 디버깅 목적으로 로그에서 모니터링할 수 있습니다.

콘솔에 다음과 같이 표시됩니다.

22eaf32286fea4bb.png

여기에는 13만 개가 넘는 학습 가능한 매개변수가 있습니다. 하지만 이는 단순하고 밀집된 일반 뉴런 레이어이므로 매우 빠르게 학습합니다.

프로젝트가 완료된 후 해야 할 활동으로, 첫 번째 레이어의 뉴런 수를 변경하여 적절한 성능을 얻으면서 얼마나 낮게 만들 수 있는지 확인해 볼 수 있습니다. 머신러닝에서는 리소스 사용량과 속도 사이에서 최적의 매개변수 값을 찾기 위해 어느 정도의 시행착오를 겪을 수 있는 경우가 많습니다.

11. 웹캠 사용

이제 앞에서 정의한 enableCam() 함수를 구체화할 차례입니다. 아래와 같이 hasGetUserMedia()라는 새 함수를 추가한 다음 이전에 정의된 enableCam() 함수의 콘텐츠를 아래의 상응하는 코드로 바꿉니다.

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');
  }
}

먼저 hasGetUserMedia()라는 함수를 만들어 주요 브라우저 API 속성이 있는지 확인하여 브라우저가 getUserMedia()를 지원하는지 확인합니다.

enableCam() 함수에서 위에서 정의한 hasGetUserMedia() 함수를 사용하여 이 함수가 지원되는지 확인합니다. 그렇지 않은 경우 콘솔에 경고를 출력합니다.

지원하는 경우 getUserMedia() 호출에 몇 가지 제약 조건을 정의합니다(예: 동영상 스트림만 원하고 동영상의 width 크기를 640 픽셀로, height480 픽셀로 지정). 왜냐하면 이보다 큰 동영상은 MobileNet 모델에 피드하기 위해 224x224픽셀로 크기를 조정해야 하므로 의미가 없습니다. 더 작은 해상도를 요청하여 일부 컴퓨팅 리소스를 절약할 수도 있습니다. 대부분의 카메라는 이 크기의 해상도를 지원합니다.

그런 다음 위에 설명된 constraints를 사용하여 navigator.mediaDevices.getUserMedia()를 호출하고 stream가 반환될 때까지 기다립니다. stream가 반환되면 srcObject 값으로 설정하여 VIDEO 요소가 stream를 재생하도록 할 수 있습니다.

또한 VIDEO 요소에 eventListener도 추가하여 stream가 로드되어 성공적으로 재생되는지 알아야 합니다.

스트림이 로드되면 클래스를 'removed'로 설정하여 videoPlaying를 true로 설정하고 ENABLE_CAM_BUTTON를 삭제하여 다시 클릭되지 않도록 할 수 있습니다.

이제 코드를 실행하고 '카메라 사용' 버튼을 클릭한 다음 웹캠에 대한 액세스를 허용합니다. 이 작업을 처음 수행하는 경우 다음과 같이 페이지의 동영상 요소에 렌더링된 것을 볼 수 있습니다.

b378eb1affa9b883.png

이제 dataCollector 버튼 클릭을 처리하는 함수를 추가해 보겠습니다.

12. 데이터 수집 버튼 이벤트 핸들러

이제 현재 비어 있는 gatherDataForClass().라는 함수를 작성할 차례입니다. Codelab 시작 부분에서 dataCollector 버튼의 이벤트 핸들러 함수로 할당한 함수입니다.

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();
}

먼저 속성 이름으로 this.getAttribute()를 호출하여 현재 클릭된 버튼에서 data-1hot 속성을 확인합니다. 이 경우 data-1hot를 매개변수로 사용합니다. 문자열이므로 parseInt()를 사용하여 정수로 변환하고 이 결과를 classNumber.이라는 변수에 할당할 수 있습니다.

다음으로 gatherDataState 변수를 적절하게 설정합니다. 현재 gatherDataStateSTOP_DATA_GATHER(-1로 설정)과 같다면 현재 데이터를 수집하고 있지 않으며 실행된 mousedown 이벤트임을 의미합니다. gatherDataState를 방금 찾은 classNumber로 설정합니다.

그렇지 않으면 현재 데이터를 수집하고 있고 실행된 이벤트가 mouseup 이벤트였으며 이제 이 클래스의 데이터 수집을 중지하려는 것입니다. STOP_DATA_GATHER 상태로 다시 설정하면 곧 정의할 데이터 수집 루프가 종료됩니다.

마지막으로, 실제로 클래스 데이터를 기록하는 dataGatherLoop(), 호출을 시작합니다.

13. 데이터 수집

이제 dataGatherLoop() 함수를 정의합니다. 이 함수는 웹캠 동영상에서 이미지를 샘플링하여 MobileNet 모델을 통해 전달하고 해당 모델의 출력 (1024 특성 벡터)을 캡처합니다.

그런 다음 현재 누르고 있는 버튼의 gatherDataState ID와 함께 저장하므로 이 데이터가 나타내는 클래스를 알 수 있습니다.

이제 자세히 살펴보겠습니다.

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);
  }
}

videoPlaying이 true인 경우에만(즉, 웹캠이 활성 상태이고, gatherDataStateSTOP_DATA_GATHER와 같지 않고, 클래스 데이터 수집 버튼을 현재 누르고 있는 경우) 이 함수의 실행을 계속 진행합니다.

다음으로 코드를 tf.tidy()에 래핑하여 다음에 오는 코드에서 생성된 텐서를 삭제합니다. 이 tf.tidy() 코드 실행 결과는 imageFeatures라는 변수에 저장됩니다.

이제 tf.browser.fromPixels()를 사용하여 웹캠 VIDEO의 프레임을 가져올 수 있습니다. 이미지 데이터를 포함하는 결과 텐서는 videoFrameAsTensor라는 변수에 저장됩니다.

다음으로 videoFrameAsTensor 변수의 크기를 MobileNet 모델의 입력에 맞는 올바른 모양으로 조정합니다. 형태를 변경할 텐서와 함께 tf.image.resizeBilinear() 호출을 첫 번째 매개변수로 사용한 다음 이전에 이미 만든 상수로 정의된 대로 새 높이와 너비를 정의하는 도형을 사용합니다. 마지막으로 크기를 조절할 때 정렬 문제를 방지하려면 세 번째 매개변수를 전달하여 정렬 모서리를 true로 설정합니다. 이 크기 조절 결과는 resizedTensorFrame라는 변수에 저장됩니다.

웹캠 이미지의 크기가 640x480픽셀이고 모델에 224x224픽셀의 정사각형 이미지가 필요하므로 이 프리미티브 크기 조절은 이미지를 늘립니다.

이 데모의 목적에는 잘 작동합니다. 그러나 이 Codelab을 완료한 후에는 나중에 만들 수 있는 프로덕션 시스템에서 더 나은 결과를 얻기 위해 이 이미지에서 정사각형을 자를 수 있습니다.

다음으로 이미지 데이터를 정규화합니다. tf.browser.frompixels()를 사용할 때 이미지 데이터는 항상 0에서 255 사이이므로 크기 조절된 TensorFrame을 255로 간단히 나누어 모든 값이 0과 1 사이가 되도록 할 수 있습니다. 이는 MobileNet 모델이 입력으로 예상하는 결과입니다.

마지막으로 코드의 tf.tidy() 섹션에서 mobilenet.predict()를 호출하여 로드된 모델을 통해 이 정규화된 텐서를 푸시합니다. 이때 expandDims()를 사용하여 확장된 normalizedTensorFrame 버전을 전달하여 1개 배치가 되도록 합니다. 모델에서 처리할 입력 배치를 예상하기 때문입니다.

결과가 나오면 반환된 결과에 대해 즉시 squeeze()를 호출하여 1차원 텐서로 다시 스쿼시할 수 있습니다. 그런 다음 반환한 후 tf.tidy()에서 결과를 캡처하는 imageFeatures 변수에 할당합니다.

이제 MobileNet 모델의 imageFeatures가 있으므로 이전에 정의한 trainingDataInputs 배열로 푸시하여 이를 기록할 수 있습니다.

또한 현재 gatherDataStatetrainingDataOutputs 배열로 푸시하여 이 입력이 나타내는 내용을 기록할 수도 있습니다.

gatherDataState 변수는 이전에 정의된 gatherDataForClass() 함수에서 버튼을 클릭할 때 데이터를 기록하는 현재 클래스의 숫자 ID로 설정됩니다.

이 시점에서 특정 클래스에 관한 예시의 수를 늘릴 수도 있습니다. 이렇게 하려면 먼저 examplesCount 배열 내의 색인이 이전에 초기화되었는지 여부를 확인합니다. 정의되지 않은 경우 0으로 설정하여 지정된 클래스의 숫자 ID의 카운터를 초기화한 다음 현재 gatherDataStateexamplesCount를 늘릴 수 있습니다.

이제 웹페이지에서 STATUS 요소의 텍스트를 업데이트하여 캡처되는 각 클래스의 현재 개수를 표시합니다. 이렇게 하려면 CLASS_NAMES 배열을 반복하고 examplesCount의 동일한 색인에 있는 데이터 수와 사람이 읽을 수 있는 이름을 결합하여 출력합니다.

마지막으로 매개변수로 전달된 dataGatherLoopwindow.requestAnimationFrame()를 호출하여 이 함수를 다시 호출합니다. 이렇게 하면 버튼의 mouseup가 감지될 때까지 동영상에서 프레임이 계속 샘플링되며 gatherDataStateSTOP_DATA_GATHER,로 설정되며, 이때 데이터 수집 루프가 종료됩니다.

지금 코드를 실행하면 '카메라 사용' 버튼을 클릭하고 웹캠이 로드될 때까지 기다린 다음 각 데이터 수집 버튼을 길게 클릭하여 각 데이터 클래스에 대한 예시를 수집할 수 있습니다. 여기서 제 휴대전화와 손에 대한 데이터를 각각 수집하는 것을 볼 수 있습니다.

541051644a45131f.gif

위의 화면 캡처와 같이 모든 텐서가 메모리에 저장되므로 상태 텍스트가 업데이트됩니다.

14. 학습 및 예측

다음 단계는 전이 학습이 발생하는 현재 비어 있는 trainAndPredict() 함수의 코드를 구현하는 것입니다. 코드를 살펴보겠습니다.

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);
}

먼저 predictfalse로 설정하여 현재 예측이 실행되지 않도록 합니다.

다음으로, 순서가 학습에 문제를 일으키지 않도록 tf.util.shuffleCombo()를 사용하여 입력 및 출력 배열을 셔플합니다.

출력 배열 trainingDataOutputs,를 int32 유형의 tensor1d로 변환하여 원-핫 인코딩에서 사용할 수 있도록 합니다. 이는 outputsAsTensor라는 변수에 저장됩니다.

인코딩할 최대 클래스 수(CLASS_NAMES.length)와 함께 이 outputsAsTensor 변수와 함께 tf.oneHot() 함수를 사용합니다. 이제 하나의 핫 인코딩된 출력이 oneHotOutputs라는 새 텐서에 저장됩니다.

현재 trainingDataInputs는 기록된 텐서의 배열입니다. 이를 학습에 사용하려면 텐서 배열을 일반 2D 텐서로 변환해야 합니다.

이를 위해 TensorFlow.js 라이브러리 내에 tf.stack()라는 훌륭한 함수가 있습니다.

이 함수는 텐서 배열을 취하고 이를 함께 쌓아 더 높은 차원의 텐서를 출력으로 생성합니다. 이 경우 텐서 2D가 반환됩니다. 이는 각각 길이가 1, 024이고 기록된 특성이 포함된 1차원 입력 배치이며, 이는 학습에 필요합니다.

다음으로 await model.fit()를 사용하여 커스텀 모델 헤드를 학습시킵니다. 여기서 inputsAsTensor 변수를 oneHotOutputs와 함께 전달하여 예시 입력과 타겟 출력에 각각 사용할 학습 데이터를 나타냅니다. 세 번째 매개변수의 구성 객체에서 shuffletrue로 설정하고 batchSize5를 사용하고 epochs10로 설정한 후 곧 정의할 logProgress 함수에 onEpochEndcallback를 지정합니다.

마지막으로, 이제 모델이 학습되면 생성된 텐서를 폐기할 수 있습니다. 그런 다음 predict를 다시 true로 설정하여 예측이 다시 실행되도록 한 다음 predictLoop() 함수를 호출하여 라이브 웹캠 이미지 예측을 시작할 수 있습니다.

학습 상태를 기록하도록 logProcess() 함수를 정의할 수도 있습니다. 이 함수는 위의 model.fit()에서 사용되며 각 학습 라운드가 끝난 후 콘솔에 결과를 출력합니다.

거의 완료되었습니다. 이제 predictLoop() 함수를 추가하여 예측을 수행해 보겠습니다.

핵심 예측 루프

여기에서는 웹캠에서 프레임을 샘플링하고 각 프레임의 내용을 지속적으로 예측하는 기본 예측 루프를 구현하여 브라우저에 실시간으로 표시합니다.

코드를 확인해 보겠습니다.

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);
  }
}

먼저 모델이 학습되고 사용할 수 있게 된 후에만 예측이 이루어지도록 predict가 true인지 확인합니다.

다음으로, dataGatherLoop() 함수에서 한 것처럼 현재 이미지의 이미지 특징을 가져올 수 있습니다. 기본적으로 tf.browser.from pixels()를 사용하여 웹캠에서 프레임을 가져와 정규화하고 크기가 224x224픽셀이 되도록 크기를 조절하고 MobileNet 모델을 통해 이 데이터를 전달하여 결과 이미지 특성을 얻습니다.

하지만 이제 새로 학습된 모델 헤드를 사용하여 학습된 모델의 predict() 함수를 통해 방금 찾은 결과 imageFeatures를 전달하여 실제로 예측을 수행할 수 있습니다. 그런 다음 결과 텐서를 압착하여 다시 1차원으로 만들고 prediction라는 변수에 할당할 수 있습니다.

prediction의 경우 argMax()를 사용하여 가장 높은 값을 가진 색인을 찾은 다음 이 결과 텐서를 arraySync()을 사용하여 배열로 변환하여 JavaScript의 기본 데이터에서 가져와 가장 값이 높은 요소의 위치를 찾을 수 있습니다. 이 값은 highestIndex라는 변수에 저장됩니다.

prediction 텐서에서 직접 arraySync()를 호출하여 동일한 방식으로 실제 예측 신뢰도 점수를 가져올 수도 있습니다.

이제 STATUS 텍스트를 prediction 데이터로 업데이트하는 데 필요한 모든 것을 갖추었습니다. 사람이 읽을 수 있는 클래스 문자열을 가져오려면 CLASS_NAMES 배열에서 highestIndex를 찾은 다음 predictionArray에서 신뢰값을 가져오면 됩니다. 백분율로 읽기 쉽게 만들려면 100을 곱하고 결과를 math.floor()하기만 하면 됩니다.

마지막으로, 준비가 되면 window.requestAnimationFrame()를 사용하여 predictionLoop()를 다시 호출하여 동영상 스트림에서 실시간으로 분류할 수 있습니다. 새 데이터로 새 모델을 학습시키도록 선택한 경우 predictfalse로 설정될 때까지 계속됩니다.

이제 퍼즐의 마지막 조각에 도달합니다. 재설정 버튼 구현

15. 재설정 버튼 구현

거의 완료되었습니다. 퍼즐의 마지막 조각은 처음부터 다시 시작할 수 있는 재설정 버튼을 구현하는 것입니다. 다음은 현재 비어 있는 reset() 함수의 코드입니다. 다음과 같이 업데이트합니다.

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);
}

먼저 predictfalse로 설정하여 실행 중인 예측 루프를 중지합니다. 다음으로 길이를 0으로 설정하여 examplesCount 배열의 모든 콘텐츠를 삭제합니다. 이 방법은 배열에서 모든 콘텐츠를 간편하게 지울 수 있는 방법입니다.

이제 현재 기록된 모든 trainingDataInputs를 살펴보고 텐서가 JavaScript 가비지 컬렉터에 의해 삭제되지 않으므로 내부에 포함된 각 텐서의 dispose()를 수행하여 메모리를 다시 확보합니다.

이렇게 하면 이제 trainingDataInputstrainingDataOutputs 배열 모두에서 배열 길이를 0으로 안전하게 설정하여 이 배열도 지울 수 있습니다.

마지막으로 STATUS 텍스트를 적절한 값으로 설정하고 상태 검사로 메모리에 남은 텐서를 출력합니다.

MobileNet 모델과 정의한 멀티 레이어 퍼셉트론이 모두 처리되지 않으므로 메모리에는 수백 개의 텐서가 여전히 남아 있습니다. 재설정 후 다시 학습하려면 새 학습 데이터로 재사용해야 합니다.

16. 한번 해 보자

나만의 Teachable Machine 버전을 테스트해 보세요.

실시간 미리보기로 이동하여 웹캠을 사용 설정하고 방에 있는 일부 객체에 대해 클래스 1의 샘플을 30개 이상 수집한 다음 다른 객체에 대해 클래스 2에도 동일한 작업을 수행한 다음 트레인을 클릭하고 콘솔 로그를 확인하여 진행 상황을 확인합니다. 아주 빠르게 학습됩니다.

bf1ac3cc5b15740.gif

학습된 후에 객체를 카메라에 보여주면 실시간 예측이 웹페이지 상단 근처의 상태 텍스트 영역에 인쇄됩니다. 문제가 있는 경우 완료된 작업 코드를 확인하여 복사하지 못한 항목이 있는지 확인하세요.

17. 축하합니다

축하합니다. 브라우저에서 실시간으로 TensorFlow.js를 사용하여 첫 번째 전이 학습 예를 완료했습니다.

다양한 사물에서 테스트해 보세요. 특히 다른 사물과 유사한 사물보다 인식하기가 더 어려울 수 있습니다. 구별하기 위해 클래스나 학습 데이터를 더 추가해야 할 수도 있습니다.

요약

이 Codelab에서 알아본 내용은 다음과 같습니다.

  1. 전이 학습의 정의 및 전체 모델 학습에 비해 장점
  2. TensorFlow Hub에서 재사용할 모델을 가져오는 방법
  3. 전이 학습에 적합한 웹 앱을 설정하는 방법
  4. 기본 모델을 로드하고 사용하여 이미지 특성을 생성하는 방법
  5. 웹캠 이미지에서 맞춤 객체를 인식할 수 있는 새로운 예측 헤드를 학습시키는 방법
  6. 결과 모델을 사용하여 실시간으로 데이터를 분류하는 방법

다음 단계

이제 작업 기반이 마련되었으니 작업 중인 실제 사용 사례에 머신러닝 모델 상용구를 확장하기 위해 어떤 창의적인 아이디어를 떠올릴 수 있을까요? 현재 일하고 있는 산업을 혁신하여 회사 직원이 일상 업무에서 중요한 것을 분류하도록 모델을 학습시킬 수도 있을 것입니다. 가능성은 무한합니다.

더 나아가 전체 과정을 무료로 수강하는 것을 고려해 보세요. 현재 이 Codelab에 있는 두 가지 모델을 효율성을 위해 하나의 단일 모델로 결합하는 방법을 보여줍니다.

기존 Teachable Machine 애플리케이션의 이론에 관해 자세히 알아보려면 이 튜토리얼을 확인하세요.

결과물 공유

오늘 만든 콘텐츠를 다른 창의적인 사용 사례에도 쉽게 확장할 수 있습니다. 고정관념에서 벗어난 생각을 계속하고 해킹을 이어가시기 바랍니다.

소셜 미디어에서 #MadeWithTFJS 해시태그를 사용하여 Google을 태그하고 TensorFlow 블로그향후 이벤트에 프로젝트가 소개될 기회를 잡으세요. 여러분의 결과물을 기대하겠습니다.

확인할 웹사이트