在這篇文章中,我們將討論游戲 Emoji Scavenger Hunt 的內(nèi)部運(yùn)作方式。我們將向您展示如何使用 TensorFlow 訓(xùn)練用于對(duì)象識(shí)別的自定義模型以及如何在 Web 前端使用 TensorFlow.js 使用該模型。在使用瀏覽器 API 進(jìn)行攝像頭訪問(wèn)和文本到語(yǔ)音的轉(zhuǎn)換時(shí),我們還將介紹一些挑戰(zhàn)和解決方法。這個(gè)游戲的所有代碼都是開(kāi)源的,可以在 Github 上找到(https://github.com/google/emoji-scavenger-hunt)。
介紹游戲 Emoji Scavenger Hunt
Emoji Scavenger Hunt 是一個(gè)有趣的游戲,你會(huì)看到一個(gè)表情符合,并在秒數(shù)之內(nèi)找到真實(shí)世界的等效物體。當(dāng)你在現(xiàn)實(shí)世界中發(fā)現(xiàn)表情符號(hào)時(shí),隨后的表情符號(hào)顯示難度增加。從你可能擁有的物品開(kāi)始,比如鞋子,書(shū)本或者你自己的手以及像香蕉,蠟燭甚至踏板車這樣的東西。
我們的目標(biāo)是以有趣,互動(dòng)的方式展示機(jī)器學(xué)習(xí)技術(shù)。
訓(xùn)練對(duì)象識(shí)別模型
Emoji Scavenger Hunt 游戲的核心功能是識(shí)別您的相機(jī)所看到的物體,并將其與游戲要求您找到的物體(表情符號(hào))相匹配。但相機(jī)如何知道它看到了什么?我們需要一個(gè)可以幫助識(shí)別物體的模型。最初我們開(kāi)始使用名為 MobileNet 的預(yù)訓(xùn)練模型。這個(gè)模型是輕量級(jí)的,并針對(duì)移動(dòng)設(shè)備進(jìn)行了優(yōu)化,但其中的對(duì)象太具體,不適合我們的游戲。例如,確定了像 “金毛獵犬” 這樣的犬種,但沒(méi)有 “狗” 的通用對(duì)象類。我們逐漸意識(shí)到需要訓(xùn)練自定義的圖像識(shí)別模型。
這是轉(zhuǎn)移學(xué)習(xí)可以派上用場(chǎng)的地方。轉(zhuǎn)移學(xué)習(xí)是一種技術(shù),它通過(guò)將其用于另一個(gè)目標(biāo)任務(wù)來(lái)重用針對(duì)特定任務(wù)而訓(xùn)練的機(jī)器學(xué)習(xí)模型。我們通過(guò)使用此教程中描述的過(guò)程重新訓(xùn)練基于 MobileNet 的模型來(lái)構(gòu)建我們自己的自定義模型。我們添加了一個(gè)全連接層,它將默認(rèn)輸出 logits 映射到我們想要的表情符號(hào)對(duì)象,如 “手” 和 “鍵盤(pán)” 等。我們列出了大約400個(gè)對(duì)象用于物體識(shí)別并收集 100-1000 個(gè)圖像作為每個(gè)對(duì)象的訓(xùn)練數(shù)據(jù)。添加的全連接層通過(guò)組合來(lái)自 MobileNet 輸出層的 1000 個(gè)信號(hào)來(lái)推斷這 400 個(gè)對(duì)象。
注:教程 鏈接
https://www.tensorflow.org/hub/tutorials/image_retraining
訓(xùn)練腳本可在 TensorFlow Github 存儲(chǔ)庫(kù)中找到(https://github.com/tensorflow/hub/blob/master/examples/image_retraining/retrain.py)。我們將訓(xùn)練過(guò)程編譯為 Dockerfile,以便您可以通過(guò)指向自己的圖像數(shù)據(jù)集來(lái)訓(xùn)練自己的模型
我們運(yùn)行腳本將訓(xùn)練圖像數(shù)據(jù)提供給模型。為了簡(jiǎn)化我們的訓(xùn)練流程,我們?cè)?Google Cloud Platform 上構(gòu)建了整個(gè)管道。所有的訓(xùn)練數(shù)據(jù)都將存儲(chǔ)在 Google Cloud 存儲(chǔ)桶中。通過(guò)在 Google Functions 設(shè)置云存儲(chǔ)觸發(fā)器,一旦在存儲(chǔ)桶中檢測(cè)到任何變化,就會(huì)啟動(dòng)計(jì)算引擎上的 GPU 實(shí)例。GPU 實(shí)例以 TensorFlow SavedModel 格式輸出再訓(xùn)練模型,并將其保存在云存儲(chǔ)上的另一個(gè)存儲(chǔ)桶中。
模型培訓(xùn)的數(shù)據(jù)管道
我們?nèi)绾闻c TensorFlow.js 集成
完成上述模型訓(xùn)練中的步驟后,我們最終得到了一個(gè)用于對(duì)象識(shí)別的 TensorFlow SavedModel。為了通過(guò) TensorFlow.js 在瀏覽器中訪問(wèn)和使用此模型,我們使用 TensorFlow.js 轉(zhuǎn)換器將此 SavedModel 轉(zhuǎn)換為 TensorFlow.js 可以加載的格式。
識(shí)別對(duì)象的行為可以分為兩個(gè)子任務(wù)。首先,從相機(jī)中抓取像素,然后將圖像數(shù)據(jù)發(fā)送到 TensorFlow.js,以根據(jù)我們之前訓(xùn)練過(guò)的模型預(yù)測(cè)它的想法。
相機(jī)和模型設(shè)置
在我們開(kāi)始預(yù)測(cè)對(duì)象之前,我們需要確保相機(jī)(通過(guò) MediaDevices.getUserMedia)準(zhǔn)備好顯示內(nèi)容,并且我們的機(jī)器學(xué)習(xí)模型已加載并準(zhǔn)備好開(kāi)始預(yù)測(cè)。在我們開(kāi)始預(yù)測(cè)之前,我們使用以下代碼段來(lái)啟動(dòng)這兩個(gè)代碼并執(zhí)行一些任務(wù)設(shè)置。
1Promise.all([
2this.emojiScavengerMobileNet.load().then(() => this.warmUpModel()),
3camera.setupCamera().then((value: CameraDimentions) => {
4camera.setupVideoDimensions(value[0], value[1]);
5}),
6]).then(values => {
7// Both the camera and model are loaded, we can start predicting
8this.predict();
9}).catch(error => {
10// Some errors occurred and we need to handle them
11});
一旦成功完成,相機(jī)設(shè)置和模型加載都將以 Promise 解析。您會(huì)注意到,一旦加載了模型,我們就會(huì)調(diào)用 this.warmUpModel()。這個(gè)函數(shù)只是做一個(gè)預(yù)測(cè)調(diào)用來(lái)編譯程序并將權(quán)重上傳到 GPU,這樣當(dāng)我們想要傳遞真實(shí)數(shù)據(jù)進(jìn)行預(yù)測(cè)時(shí),模型就會(huì)準(zhǔn)備就緒。
將圖像數(shù)據(jù)發(fā)送到 TensorFlow.js
以下代碼片段(已刪除注釋)是我們的預(yù)測(cè)函數(shù)調(diào)用,它從相機(jī)中獲取數(shù)據(jù),將其解析為正確的圖像大小,將其發(fā)送到我們的 TensorFlow.js 并使用生成的識(shí)別對(duì)象來(lái)查看我們是否找到了表情符號(hào)。
1async predict() {
2if (this.isRunning) {
3const result = tfc.tidy(() => {
4
5const pixels = tfc.fromPixels(camera.videoElement);
6const centerHeight = pixels.shape[0] / 2;
7const beginHeight = centerHeight - (VIDEO_PIXELS / 2);
8const centerWidth = pixels.shape[1] / 2;
9const beginWidth = centerWidth - (VIDEO_PIXELS / 2);
10const pixelsCropped =
11pixels.slice([beginHeight, beginWidth, 0],
12[VIDEO_PIXELS, VIDEO_PIXELS, 3]);
13
14return this.emojiScavengerMobileNet.predict(pixelsCropped);
15});
16
17const topK =
18await this.emojiScavengerMobileNet.getTopKClasses(result, 10);
19
20this.checkEmojiMatch(topK[0].label, topK[1].label);
21}
22requestAnimationFrame(() => this.predict());
21}
讓我們更詳細(xì)地看一下這個(gè)片段。我們將整個(gè)預(yù)測(cè)代碼邏輯包裝在 requestAnimationFrame 調(diào)用中,以確保瀏覽器在進(jìn)行屏幕繪制更新時(shí)以最有效的方式執(zhí)行此邏輯。如果游戲處于運(yùn)行狀態(tài),我們只執(zhí)行預(yù)測(cè)邏輯。通過(guò)這種方式,我們可以確保在執(zhí)行屏幕動(dòng)畫(huà)(如結(jié)束和贏取屏幕)時(shí),我們不會(huì)運(yùn)行任何 GPU 密集型預(yù)測(cè)代碼。
另一個(gè)小而重要的性能改進(jìn)是將 TensorFlow.js 邏輯包裝在對(duì) tf.tidy() 的調(diào)用中。這將確保在執(zhí)行該邏輯期間創(chuàng)建的所有 TensorFlow.js 張量都將在之后得到清理,從而確保更好的長(zhǎng)期運(yùn)行性能。請(qǐng)參閱https://js.tensorflow.org/api/latest/#tidy
我們預(yù)測(cè)邏輯的核心與從相機(jī)中提取圖像以發(fā)送到 TensorFlow.js 有關(guān)。我們不是簡(jiǎn)單地拍攝整個(gè)相機(jī)圖像并將其發(fā)送出去,而是從相機(jī)中心切出一部分屏幕并將其發(fā)送到 TensorFlow.js。在我們的游戲中,我們使用 224 像素x 224 像素的參考圖像訓(xùn)練我們的模型。將與我們的參考訓(xùn)練數(shù)據(jù)具有相同尺寸的圖像發(fā)送到 TensorFlow.js,從而確保更好的預(yù)測(cè)性能。我們的相機(jī)元素(它只是一個(gè) HTML 視頻元素)不是 224 像素的原因是因?yàn)槲覀兿胍_保用戶的全屏體驗(yàn),這意味著使用 CSS 將相機(jī)元素?cái)U(kuò)展到 100% 的屏幕。
以下參考圖像顯示左上角的切片,該切片將發(fā)送到 TensorFlow.js。
然后,模型使用該圖像數(shù)據(jù)生成前 10 個(gè)最可能項(xiàng)目的列表。您會(huì)注意到我們獲取前 2 個(gè)值并將其傳遞給 checkEmojiMatch 以確定我們是否找到了匹配項(xiàng)。我們選擇使用前 2 個(gè)匹配而不是最頂級(jí)的項(xiàng)目,因?yàn)樗褂螒蚋腥ぃ⒃试S我們根據(jù)模型在匹配中留有一些余地。擁有一個(gè)過(guò)于準(zhǔn)確和嚴(yán)格的模型會(huì)導(dǎo)致用戶在無(wú)法識(shí)別對(duì)象時(shí)感到沮喪。
在上面的圖像示例中,您可以看到我們目前的任務(wù)是找到 “鍵盤(pán)” 表情符號(hào)。在此示例中,我們還顯示了一些調(diào)試信息,因此您可以根據(jù)輸入圖像查看模型預(yù)測(cè)的所有 10 個(gè)可能項(xiàng)目。這里的前兩個(gè)匹配是 “鍵盤(pán)” 和 “手”,它們都在圖像中,而 “手” 具有稍大的可能性。雖然 “鍵盤(pán)” 在第二個(gè)檢測(cè)到的位置,但游戲在這里檢測(cè)到匹配,因?yàn)槲覀兪褂们皟蓚€(gè)匹配進(jìn)行檢查。
為我們的模型提供文本到語(yǔ)音的轉(zhuǎn)換
作為游戲的一個(gè)有趣的補(bǔ)充,我們實(shí)施了 SpeechSynthesis API。從而當(dāng)你在尋找表情符號(hào)的時(shí)候,大聲朗讀出模型預(yù)測(cè)。在 Android 上的 Chrome 中,通過(guò)以下代碼實(shí)現(xiàn)這一點(diǎn)非常簡(jiǎn)單:
1speak(msg: string) {
2if (this.topItemGuess) {
3if ('speechSynthesis' in window) {
4let msgSpeak = new SpeechSynthesisUtterance();
5msgSpeak.voice = this.sleuthVoice['activeVoice'];
6
7msgSpeak.text = msg;
8speechSynthesis.speak(msgSpeak);
9}
10}
11}
此 API 在 Android 上即時(shí)運(yùn)行,但 iOS 將任何 SpeechSynthesis 調(diào)用限制為直接與用戶操作相關(guān)的調(diào)用(例如點(diǎn)擊事件),因此我們需要為該平臺(tái)找到替代解決方案。我們已經(jīng)熟悉 iOS 將音頻播放事件綁定到用戶操作的要求,我們通過(guò)啟動(dòng)用戶最初單擊 “播放” 按鈕時(shí)播放的所有音頻文件來(lái)處理我們游戲中的其他聲音,然后立即暫停所有這些音頻文件。最后,我們最終制作了一個(gè)音頻精靈,其中包含了所有 “成功” 的語(yǔ)音線(例如,“嘿,你找到了啤酒”)。這種方法的缺點(diǎn)是這個(gè)音頻精靈文件變得非常大,對(duì)話需要更多。
我們嘗試過(guò)的一種方法是將音頻精靈分解為前綴(“嘿,你找到了”,“是那個(gè)”)和后綴(“啤酒”,“香蕉” 等),但我們發(fā)現(xiàn) iOS 在播放一個(gè)音頻文件的片段,暫停,移動(dòng)播放頭,然后播放同一文件的另一個(gè)片段之間增加了不可避免的一秒延遲。前綴和后綴之間的差距很長(zhǎng),以至于感覺(jué)很刺耳,我們經(jīng)常發(fā)現(xiàn)語(yǔ)音會(huì)遠(yuǎn)遠(yuǎn)落后于實(shí)際的游戲玩法。我們?nèi)栽谡{(diào)查 iOS 上語(yǔ)音改進(jìn)的其他選項(xiàng)。
下面是我們播放音頻文件的函數(shù),其中包含通過(guò)開(kāi)始和停止時(shí)間戳處理播放音頻精靈片段的附加代碼:
1playAudio(audio: string, loop = false, startTime = 0,
2endTime:number = undefined) {
3let audioElement = this.audioSources[audio];
4if (loop) {
5audioElement.loop = true;
6}
7if (!this.audioIsPlaying(audio)) {
8audioElement.currentTime = startTime;
9let playPromise = audioElement.play();
10if (endTime !== undefined) {
11const timeUpdate = (e: Event) => {
12if (audioElement.currentTime >= endTime) {
13audioElement.pause();
14audioElement.removeEventListener('timeupdate', timeUpdate);
15}
16};
17audioElement.addEventListener('timeupdate', timeUpdate);
18}
19if (playPromise !== undefined) {
20playPromise.catch(error => {
21console.log('Error in playAudio: ' + error);
22});
23}
24}
25}
通過(guò) getUserMedia 進(jìn)行相機(jī)訪問(wèn)時(shí)可能存在的風(fēng)險(xiǎn)
Emoji Scavenger Hunt 在很大程度上依賴于能夠通過(guò)瀏覽器中的 Javascript 訪問(wèn)相機(jī)。我們?cè)跒g覽器中使用 MediaDevices.getUserMedia API 來(lái)訪問(wèn)攝像頭。并非所有瀏覽器都支持此 API,但大多數(shù)主流瀏覽器的最新版本都有很好的支持。
要通過(guò)此 API 訪問(wèn)相機(jī),我們使用以下代碼段:
1if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
2const stream = await navigator.mediaDevices.getUserMedia({
3'audio': false,
4'video': {facingMode: 'environment'}
5});
6(
7this.videoElement.srcObject = stream;
8}
此 API 提供了一種通過(guò)傳入配置對(duì)象并指定 facingMode 來(lái)訪問(wèn)前置和后置攝像頭的方法。
無(wú)法通過(guò) UIWebViews 訪問(wèn)
在測(cè)試期間,我們意識(shí)到 Apple 不支持任何基于 webkit 瀏覽器的 UIWebView 使用 getUserMedia API,這意味著 iOS 上任何實(shí)現(xiàn)自己瀏覽器的應(yīng)用程序,如第三方 Twitter 客戶端或 iOS 上的 Chrome,都無(wú)法訪問(wèn)攝像頭。
為解決此問(wèn)題,我們會(huì)檢測(cè)到相機(jī)初始化失敗,并提示用戶在本機(jī) Safari 瀏覽器中打開(kāi)體驗(yàn)。
致謝
通過(guò)這個(gè)實(shí)驗(yàn),我們想要?jiǎng)?chuàng)建一個(gè)有趣和愉快的游戲,利用當(dāng)今瀏覽器中提供的驚人的機(jī)器學(xué)習(xí)技術(shù)。這只是一個(gè)開(kāi)始,我們希望您能使用 TensorFlow.js 和 TensorFlow.js 轉(zhuǎn)換器實(shí)現(xiàn)您所有的想法。如上所述,我們的代碼可以在 Github 上找到(https://github.com/google/emoji-scavenger-hunt),所以請(qǐng)用它來(lái)開(kāi)始你自己的想法。
在構(gòu)建這個(gè)實(shí)驗(yàn)的過(guò)程中,我們要感謝 Takashi Kawashima,Daniel Smilkov,Nikhil Thorat 和 Ping Yu 的幫助。
-
圖像
+關(guān)注
關(guān)注
2文章
1079瀏覽量
40375 -
機(jī)器學(xué)習(xí)
+關(guān)注
關(guān)注
66文章
8353瀏覽量
132315 -
tensorflow
+關(guān)注
關(guān)注
13文章
328瀏覽量
60474
原文標(biāo)題:讓我們看看,如何使用 TensorFlow.js 構(gòu)建 Emoji Scavenger Hunt
文章出處:【微信號(hào):tensorflowers,微信公眾號(hào):Tensorflowers】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論