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

해킹을 시작해 보겠습니다.

필요한 항목

  • Glitch.com 계정을 사용하는 것이 좋지만, 직접 편집하고 실행할 수 있는 웹 제공 환경을 사용해도 됩니다.

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

54e81d02971f53e8.png

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

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

JavaScript의 이식성을 고려할 때 이제 하나의 언어로 작성하고 다음 플랫폼에서 모두 쉽게 머신러닝을 실행할 수 있습니다.

  • 일반 JavaScript를 사용하여 웹브라우저의 클라이언트 측
  • Node.js를 사용하는 서버 측 및 Raspberry Pi와 같은 IoT 기기
  • Electron을 사용하는 데스크톱 앱
  • React Native를 사용하는 기본 모바일 앱

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

  • 기기의 그래픽 카드 (GPU)에서 WebGL 실행 - GPU 가속을 사용하여 더 큰 모델 (크기 3MB 이상)을 실행하는 가장 빠른 방법입니다.
  • CPU에서 Web Assembly (WASM) 실행: 예를 들어 이전 세대 휴대전화를 비롯한 여러 기기에서 CPU 성능을 개선합니다. 이는 그래픽 프로세서에 콘텐츠를 업로드하는 오버헤드로 인해 WebGL보다 WASM에서 CPU로 더 빠르게 실행할 수 있는 소규모 모델 (크기 3MB 미만)에 더 적합합니다.
  • CPU 실행 - 다른 환경을 사용할 수 없는 경우 대체해야 합니다. 세 가지 방법 중 가장 느리지만 항상 사용할 수 있습니다.

참고: 실행할 기기를 알고 있다면 이러한 백엔드 중 하나를 강제할 수 있습니다. 이를 지정하지 않으면 TensorFlow.js가 자동으로 결정합니다.

클라이언트 측 슈퍼 파워

클라이언트 시스템의 웹브라우저에서 TensorFlow.js를 실행하면 고려해 볼 만한 여러 이점이 있습니다.

개인 정보 보호

데이터를 서드 파티 웹 서버로 전송하지 않고도 클라이언트 머신에서 데이터를 학습시키고 분류할 수 있습니다. 예를 들어 GDPR과 같은 현지 법규를 준수해야 하거나 사용자가 기기에 보관하고 서드 파티에 전송하지 않기를 원하는 데이터를 처리하는 경우 이러한 요구사항이 있을 수 있습니다.

속도

원격 서버로 데이터를 전송하지 않아도 되므로 추론 (데이터 분류 행위)이 더 빨라질 수 있습니다. 사용자가 액세스 권한을 부여하면 카메라, 마이크, GPS, 가속도계 등 기기의 센서에 직접 액세스할 수 있습니다.

도달범위 및 확장성

전 세계 누구나 내가 보낸 링크를 클릭하고 브라우저에서 웹페이지를 열어 내가 만든 것을 활용할 수 있습니다. 머신러닝 시스템을 사용하기 위해 CUDA 드라이버 등이 포함된 복잡한 서버 측 Linux 설정을 할 필요가 없습니다.

비용

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

서버 측 기능

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

전체 CUDA 지원

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

모델 크기

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

IOT

Node.js는 Raspberry Pi와 같은 인기 있는 싱글 보드 컴퓨터에서 지원되므로 이러한 기기에서도 TensorFlow.js 모델을 실행할 수 있습니다.

속도

Node.js는 JavaScript로 작성되어 있으므로 JIT(Just-In-Time) 컴파일의 이점을 누릴 수 있습니다. 즉, Node.js는 런타임에 최적화되므로 특히 전처리 작업을 실행할 때 성능이 향상되는 경우가 많습니다. 이 사례 연구에서 Hugging Face가 Node.js를 사용하여 자연어 처리 모델의 성능을 2배 향상한 방법을 확인할 수 있습니다.

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

3. 전이 학습

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

전이 학습은 이미 학습한 지식을 활용하여 다른 유사한 것을 학습하는 것을 의미합니다.

인간은 항상 이렇게 합니다. 뇌에는 이전에 본 적 없는 새로운 것을 인식하는 데 도움이 되는 평생의 경험이 담겨 있습니다. 예를 들어 이 버드나무를 살펴보세요.

e28070392cd4afb9.png

세계 어느 곳에 거주하는지에 따라 이 유형의 나무를 본 적이 없을 수도 있습니다.

하지만 아래의 새 이미지에 버드나무가 있는지 알려달라고 하면 각도가 다르고 제가 보여드린 원래 이미지와 약간 다르더라도 버드나무를 꽤 빨리 발견할 수 있을 것입니다.

d9073a0d5df27222.png

뇌에는 이미 나무 모양의 물체를 식별하는 방법을 아는 뉴런과 긴 직선을 찾는 데 능숙한 뉴런이 많이 있습니다. 이 지식을 재사용하여 긴 직선형 수직 가지가 많은 나무와 같은 물체인 버드나무를 빠르게 분류할 수 있습니다.

마찬가지로 이미지 인식과 같은 도메인에서 이미 학습된 머신러닝 모델이 있는 경우 이를 재사용하여 관련이 있지만 다른 작업을 수행할 수 있습니다.

1, 000개의 서로 다른 객체 유형에 대해 이미지 인식을 실행할 수 있는 매우 인기 있는 연구 모델인 MobileNet과 같은 고급 모델을 사용해도 됩니다. 개부터 자동차까지 수백만 개의 라벨이 지정된 이미지가 포함된 ImageNet이라는 대규모 데이터 세트를 기반으로 학습되었습니다.

이 애니메이션에서는 MobileNet V1 모델에 있는 엄청난 수의 레이어를 확인할 수 있습니다.

7d4e1e35c1a89715.gif

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

기존 컨볼루션 신경망 (CNN) 아키텍처 (MobileNet과 유사)를 살펴보고 전이 학습이 이 학습된 네트워크를 활용하여 새로운 것을 학습하는 방법을 알아보겠습니다. 아래 이미지는 이 경우 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 모델이 배포되는 기본 그래프 모델 유형을 이 튜토리얼에서 사용합니다. 레이어 모델 작업에 대해 자세히 알아보려면 TensorFlow.js 초급부터 고급까지 과정을 확인하세요.

전이 학습의 장점

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

첫째, 학습된 기본 모델을 기반으로 빌드할 수 있으므로 전이 학습 접근 방식을 사용하면 학습 시간이 단축됩니다.

둘째, 이미 진행된 학습 덕분에 분류하려는 새로운 항목의 예시를 훨씬 적게 표시해도 됩니다.

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

더 적은 데이터가 필요하고 더 작은 네트워크를 빠르게 학습시킬 수 있으므로 전이 학습은 리소스 집약도가 낮습니다. 따라서 브라우저 환경에 매우 적합하며, 전체 모델 학습에 몇 시간, 며칠 또는 몇 주가 걸리는 대신 최신 컴퓨터에서 수십 초만 걸립니다.

좋습니다. 이제 전이 학습의 본질을 알았으니 나만의 Teachable Machine 버전을 만들어 볼 차례입니다. 지금 시작해 보세요.

5. 코딩을 위한 설정

필요한 항목

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

코딩 시작하기

Glitch.com 또는 Codepen.io에서 시작할 수 있는 상용구 템플릿이 생성되었습니다. 클릭 한 번으로 템플릿을 클론하여 이 코드 실습의 기본 상태로 사용할 수 있습니다.

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

또는 Codepen에서 화면 오른쪽 하단의 포크를 클릭합니다.

이 매우 간단한 스켈레톤은 다음 파일을 제공합니다.

  • 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 코드 중 일부를 분석하여 추가한 주요 사항을 강조해 보겠습니다.

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

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

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

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

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

7. 스타일 추가

요소 기본값

방금 추가한 HTML 요소가 올바르게 렌더링되도록 스타일을 추가합니다. 다음은 요소를 올바르게 배치하고 크기를 조정하기 위해 추가된 스타일입니다. 특별한 건 없어. 나중에 이 기능을 추가하여 학습 가능한 머신 동영상에서 본 것처럼 더 나은 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 - 클릭 시 재설정을 호출합니다.

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

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 모델을 저장하는 변수 mobilenet이 있습니다. 처음에는 이를 정의되지 않음으로 설정합니다.

다음으로 gatherDataState이라는 변수가 있습니다. 'dataCollector' 버튼을 누르면 HTML에 정의된 대로 해당 버튼의 1 핫 ID로 변경되므로 해당 시점에 수집 중인 데이터 클래스를 알 수 있습니다. 처음에는 STOP_DATA_GATHER로 설정되어 나중에 작성할 데이터 수집 루프가 버튼을 누르지 않을 때 데이터를 수집하지 않습니다.

videoPlaying는 웹캠 스트림이 성공적으로 로드되고 재생 중이며 사용할 수 있는지 여부를 추적합니다. 처음에는 false로 설정됩니다. ENABLE_CAM_BUTTON.을 누르기 전에는 웹캠이 켜져 있지 않기 때문입니다.

다음으로 trainingDataInputstrainingDataOutputs의 두 배열을 정의합니다. 이러한 값은 MobileNet 기본 모델에서 생성된 입력 특성과 샘플링된 출력 클래스에 대해 'dataCollector' 버튼을 클릭할 때 수집된 학습 데이터 값을 저장합니다.

그런 다음 예시를 추가하기 시작하면 각 클래스에 포함된 예시의 수를 추적하기 위해 최종 배열 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();

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

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

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

이제 모델을 워밍업하기만 하면 됩니다. 이와 같은 대형 모델을 처음 사용할 때는 모든 것을 설정하는 데 시간이 걸릴 수 있습니다. 따라서 타이밍이 더 중요할 수 있는 향후 대기 시간을 방지하기 위해 모델을 통해 0을 전달하는 것이 좋습니다.

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

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

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

지금 실시간 미리보기를 보면 잠시 후 상태 텍스트가 아래와 같이 'Awaiting TF.js load'에서 'MobileNet v3 loaded successfully!'로 변경됩니다. 계속하기 전에 이 기능이 작동하는지 확인하세요.

a28b734e190afff.png

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

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.sequential 모델을 정의합니다.

그런 다음 이 모델에 밀도 레이어를 입력 레이어로 추가합니다. MobileNet v3 기능의 출력이 이 크기이므로 입력 형태는 1024입니다. 이전 단계에서 모델을 통해 1을 전달한 후 이를 확인했습니다. 이 레이어에는 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');
  }
}

먼저 주요 브라우저 API 속성의 존재를 확인하여 브라우저가 getUserMedia()을 지원하는지 확인하는 hasGetUserMedia()라는 함수를 만듭니다.

enableCam() 함수에서 방금 정의한 hasGetUserMedia() 함수를 사용하여 지원되는지 확인합니다. 그렇지 않으면 콘솔에 경고를 출력합니다.

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

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

stream이 로드되어 재생되는 시점을 알기 위해 VIDEO 요소에 eventListener도 추가해야 합니다.

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

이제 코드를 실행하고 카메라 사용 설정 버튼을 클릭하여 웹캠에 대한 액세스를 허용합니다. 처음 사용하는 경우 페이지의 동영상 요소에 다음과 같이 렌더링된 내 모습이 표시됩니다.

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

먼저 속성 이름(이 경우 data-1hot)을 매개변수로 사용하여 this.getAttribute()를 호출하여 현재 클릭한 버튼의 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라는 변수에 저장됩니다.

다음으로 MobileNet 모델의 입력에 맞는 모양이 되도록 videoFrameAsTensor 변수의 크기를 조정합니다. 재구성하려는 텐서를 첫 번째 매개변수로 사용하고, 이전에 이미 만든 상수로 정의된 새 높이와 너비를 정의하는 모양을 사용하여 tf.image.resizeBilinear() 호출을 사용합니다. 마지막으로 크기를 조절할 때 정렬 문제가 발생하지 않도록 세 번째 매개변수를 전달하여 align corners를 true로 설정합니다. 이 크기 조절의 결과는 resizedTensorFrame라는 변수에 저장됩니다.

이 기본 크기 조정은 이미지를 늘립니다. 웹캠 이미지의 크기는 640x480픽셀인데 모델에는 224x224픽셀의 정사각형 이미지가 필요하기 때문입니다.

이 데모에서는 이 방법이 잘 작동합니다. 하지만 이 Codelab을 완료한 후에는 나중에 만들 수 있는 프로덕션 시스템에서 더 나은 결과를 얻기 위해 이 이미지에서 정사각형을 잘라내 보는 것이 좋습니다.

다음으로 이미지 데이터를 정규화합니다. tf.browser.frompixels()를 사용할 때 이미지 데이터는 항상 0~255 범위에 있으므로, 크기가 조정된 텐서 프레임을 255로 나누어 모든 값이 0~1 사이에 있도록 하면 됩니다. 이는 MobileNet 모델이 입력으로 예상하는 값입니다.

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

결과가 반환되면 반환된 결과에서 squeeze()를 즉시 호출하여 1D 텐서로 다시 압축한 후 이를 반환하고 tf.tidy()의 결과를 캡처하는 imageFeatures 변수에 할당할 수 있습니다.

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

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

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

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

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

마지막으로 dataGatherLoop이 매개변수로 전달된 window.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이라는 변수에 저장됩니다.

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

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

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

텐서 배열을 가져와 함께 스택하여 더 높은 차원의 텐서를 출력으로 생성합니다. 이 경우 텐서 2D가 반환됩니다. 이는 기록된 특징을 포함하는 길이가 각각 1024인 1차원 입력의 배치이며 학습에 필요한 것입니다.

그런 다음 await model.fit()을 실행하여 맞춤 모델 헤드를 학습시킵니다. 여기서는 inputsAsTensor 변수를 oneHotOutputs와 함께 전달하여 각각 예시 입력과 타겟 출력을 사용하는 학습 데이터를 나타냅니다. 세 번째 매개변수의 구성 객체에서 shuffletrue로 설정하고, epochs10로 설정된 5batchSize를 사용한 다음, 곧 정의할 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()를 호출하여 실제 예측 신뢰도 점수를 동일한 방식으로 가져올 수도 있습니다.

이제 prediction 데이터를 사용하여 STATUS 텍스트를 업데이트하는 데 필요한 모든 것이 준비되었습니다. 클래스의 사람이 읽을 수 있는 문자열을 가져오려면 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 애플리케이션의 배경 이론에 대해 자세히 알아보려면 이 튜토리얼을 참고하세요.

결과물 공유

오늘 만든 콘텐츠를 다른 창의적인 사용 사례로 쉽게 확장할 수 있으며, 틀에 박히지 않은 생각을 하고 계속해서 실험해 보시기 바랍니다.

TensorFlow 블로그 또는 향후 이벤트에서 여러분의 프로젝트가 추천되는 기회를 얻으려면 소셜 미디어에서 #MadeWithTFJS 해시태그를 사용해 태그해 주세요. 여러분이 만든 작품을 보고 싶습니다.

확인할 웹사이트