สร้างเครื่องตรวจจับห้องสำหรับอุปกรณ์ IoT บน Mac OS
เผยแพร่แล้ว: 2022-03-10การรู้ว่าคุณอยู่ในห้องไหนทำให้แอปพลิเคชัน IoT ต่างๆ ใช้งานได้ ตั้งแต่การเปิดไฟไปจนถึงการเปลี่ยนช่องทีวี แล้วเราจะตรวจจับช่วงเวลาที่คุณและโทรศัพท์ของคุณอยู่ในห้องครัว ห้องนอน หรือห้องนั่งเล่นได้อย่างไร ด้วยฮาร์ดแวร์สำหรับสินค้าโภคภัณฑ์ในปัจจุบัน มีความเป็นไปได้มากมาย:
ทางออกหนึ่งคือการ จัดเตรียมอุปกรณ์บลูทูธให้แต่ละห้อง เมื่อโทรศัพท์ของคุณอยู่ในระยะของอุปกรณ์บลูทูธ โทรศัพท์ของคุณจะรู้ว่าเป็นห้องไหน โดยอิงจากอุปกรณ์บลูทูธ อย่างไรก็ตาม การดูแลรักษาชุดอุปกรณ์บลูทูธถือเป็นค่าใช้จ่ายที่สำคัญ ตั้งแต่การเปลี่ยนแบตเตอรี่ไปจนถึงการเปลี่ยนอุปกรณ์ที่มีปัญหา นอกจากนี้ ความใกล้ชิดกับอุปกรณ์บลูทูธอาจไม่ใช่คำตอบเสมอไป หากคุณอยู่ในห้องนั่งเล่น ข้างผนังที่ใช้ร่วมกับห้องครัว เครื่องใช้ในครัวของคุณไม่ควรเริ่มปั่นอาหาร
อีกวิธีหนึ่งแม้ว่าจะใช้งานไม่ได้ แต่วิธีแก้ไขคือการ ใช้ GPS อย่างไรก็ตาม โปรดทราบว่าหมวก GPS ทำงานได้ไม่ดีในที่ร่ม ซึ่งกำแพงจำนวนมาก สัญญาณอื่นๆ และสิ่งกีดขวางอื่นๆ จะสร้างความเสียหายให้กับความแม่นยำของ GPS
แนวทางของเราคือ ใช้ประโยชน์จากเครือข่าย WiFi ที่อยู่ในระยะทั้งหมด — แม้กระทั่งเครือข่ายที่โทรศัพท์ของคุณไม่ได้เชื่อมต่อด้วย นี่คือวิธี: พิจารณาความแรงของ WiFi A ในห้องครัว สมมติว่าเป็น 5 เนื่องจากมีผนังกั้นระหว่างห้องครัวและห้องนอน เราจึงคาดว่าความแรงของ WiFi A ในห้องนอนจะแตกต่างกัน บอกว่าเป็น 2 เราสามารถใช้ประโยชน์จากความแตกต่างนี้เพื่อคาดการณ์ว่าเราอยู่ในห้องใด มีอะไรเพิ่มเติม: เครือข่าย WiFi B จากเพื่อนบ้านของเราสามารถตรวจพบได้จากห้องนั่งเล่นเท่านั้น แต่จะมองไม่เห็นจากห้องครัวอย่างมีประสิทธิภาพ ที่ทำให้การทำนายง่ายยิ่งขึ้น โดยสรุป รายการ WiFi ที่อยู่ในระยะทั้งหมดให้ข้อมูลมากมายแก่เรา
วิธีนี้มีข้อดีที่ชัดเจนของ:
- ไม่ต้องการฮาร์ดแวร์เพิ่มเติม
- อาศัยสัญญาณที่เสถียรกว่าเช่น WiFi
- ทำงานได้ดีเมื่อเทคนิคอื่นๆ เช่น GPS อ่อนแอ
ยิ่งมีกำแพงมากเท่าไร ยิ่งมีจุดแข็งของเครือข่าย WiFi แตกต่างกันมากเท่าไร ก็ยิ่งจำแนกห้องได้ง่ายขึ้นเท่านั้น คุณจะสร้างแอปเดสก์ท็อปที่เรียบง่ายซึ่งรวบรวมข้อมูล เรียนรู้จากข้อมูล และคาดการณ์ว่าคุณอยู่ในห้องใดในเวลาใดก็ตาม
อ่านเพิ่มเติม เกี่ยวกับ SmashingMag:
- การเพิ่มขึ้นของ UI การสนทนาอัจฉริยะ
- การประยุกต์ใช้แมชชีนเลิร์นนิงสำหรับนักออกแบบ
- วิธีการสร้างต้นแบบประสบการณ์ IoT: การสร้างฮาร์ดแวร์
- การออกแบบสำหรับอินเทอร์เน็ตของสิ่งต่าง ๆ ทางอารมณ์
ข้อกำหนดเบื้องต้น
สำหรับบทช่วยสอนนี้ คุณจะต้องมี Mac OSX ในขณะที่รหัสสามารถใช้ได้กับทุกแพลตฟอร์ม เราจะจัดเตรียมคำแนะนำในการติดตั้งการพึ่งพาสำหรับ Mac เท่านั้น
- Mac OSX
- Homebrew ตัวจัดการแพ็คเกจสำหรับ Mac OSX ในการติดตั้ง ให้คัดลอกและวางคำสั่งที่ brew.sh
- การติดตั้ง NodeJS 10.8.0+ และ npm
- การติดตั้ง Python 3.6+ และ pip ดู 3 ส่วนแรกของ “วิธีการติดตั้ง virtualenv การติดตั้งด้วย pip และการจัดการแพ็คเกจ”
ขั้นตอนที่ 0: ตั้งค่าสภาพแวดล้อมการทำงาน
แอพเดสก์ท็อปของคุณจะถูกเขียนใน NodeJS อย่างไรก็ตาม เพื่อใช้ประโยชน์จากไลบรารีการคำนวณที่มีประสิทธิภาพมากขึ้น เช่น numpy
โค้ดการฝึกอบรมและการทำนายจะถูกเขียนใน Python ในการเริ่มต้น เราจะตั้งค่าสภาพแวดล้อมของคุณและติดตั้งการพึ่งพา สร้างไดเร็กทอรีใหม่เพื่อใช้เป็นที่เก็บโครงการของคุณ
mkdir ~/riot
นำทางไปยังไดเร็กทอรี
cd ~/riot
ใช้ pip เพื่อติดตั้งตัวจัดการสภาพแวดล้อมเสมือนเริ่มต้นของ Python
sudo pip install virtualenv
สร้างสภาพแวดล้อมเสมือน Python3.6 ชื่อ riot
virtualenv riot --python=python3.6
เปิดใช้งานสภาพแวดล้อมเสมือน
source riot/bin/activate
ข้อความแจ้งของคุณนำหน้าด้วย (riot)
นี่แสดงว่าเราเข้าสู่สภาพแวดล้อมเสมือนสำเร็จแล้ว ติดตั้งแพ็คเกจต่อไปนี้โดยใช้ pip
:
-
numpy
: ห้องสมุดพีชคณิตเชิงเส้นที่มีประสิทธิภาพ -
scipy
: ห้องสมุดคอมพิวเตอร์ทางวิทยาศาสตร์ที่ใช้โมเดลการเรียนรู้ของเครื่องยอดนิยม
pip install numpy==1.14.3 scipy ==1.1.0
ด้วยการตั้งค่าไดเรกทอรีการทำงาน เราจะเริ่มต้นด้วยแอปเดสก์ท็อปที่บันทึกเครือข่าย WiFi ทั้งหมดในระยะ การบันทึกเหล่านี้จะประกอบเป็นข้อมูลการฝึกสำหรับโมเดลแมชชีนเลิร์นนิงของคุณ เมื่อเรามีข้อมูลในมือแล้ว คุณจะเขียนตัวแยกประเภทกำลังสองน้อยที่สุด ซึ่งฝึกเกี่ยวกับสัญญาณ WiFi ที่รวบรวมไว้ก่อนหน้านี้ สุดท้าย เราจะใช้โมเดลสี่เหลี่ยมจัตุรัสน้อยที่สุดในการคาดคะเนห้องที่คุณอยู่ โดยพิจารณาจากเครือข่าย WiFi ในระยะ
ขั้นตอนที่ 1: แอปพลิเคชันเดสก์ท็อปเริ่มต้น
ในขั้นตอนนี้ เราจะสร้างแอปพลิเคชันเดสก์ท็อปใหม่โดยใช้ Electron JS ในการเริ่มต้น เราจะใช้ตัวจัดการแพ็คเกจโหนด npm
และยูทิลิตี้ดาวน์โหลด wget
แทน
brew install npm wget
ในการเริ่มต้น เราจะสร้างโปรเจ็กต์โหนดใหม่
npm init
ซึ่งจะแจ้งให้คุณใส่ชื่อแพ็กเกจและหมายเลขเวอร์ชัน กด ENTER
เพื่อยอมรับชื่อเริ่มต้นของ riot
และรุ่นเริ่มต้น 1.0.0
package name: (riot) version: (1.0.0)
ซึ่งจะแจ้งให้คุณทราบถึงคำอธิบายโครงการ เพิ่มคำอธิบายที่ไม่ว่างเปล่าที่คุณต้องการ ด้านล่างคำอธิบายคือ room detector
description: room detector
ซึ่งจะแจ้งให้คุณทราบถึงจุดเริ่มต้นหรือไฟล์หลักที่จะเรียกใช้โครงการ ป้อน app.js
entry point: (index.js) app.js
ซึ่งจะแจ้งให้คุณทราบ test command
และ git repository
กด ENTER
เพื่อข้ามช่องเหล่านี้ไปก่อน
test command: git repository:
นี้พร้อมท์คุณสำหรับ keywords
และ author
กรอกค่าใด ๆ ที่คุณต้องการ ด้านล่างนี้ เราใช้ iot
, wifi
สำหรับคำหลัก และใช้ John Doe
สำหรับผู้แต่ง
keywords: iot,wifi author: John Doe
สิ่งนี้จะแจ้งให้คุณขอใบอนุญาต กด ENTER
เพื่อยอมรับค่าเริ่มต้นของ ISC
license: (ISC)
ณ จุดนี้ npm
จะแจ้งสรุปข้อมูลให้คุณทราบ ผลลัพธ์ของคุณควรคล้ายกับต่อไปนี้
{ "name": "riot", "version": "1.0.0", "description": "room detector", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "iot", "wifi" ], "author": "John Doe", "license": "ISC" }
กด ENTER
เพื่อยอมรับ npm
จะสร้าง package.json
แสดงรายการไฟล์ทั้งหมดเพื่อตรวจสอบอีกครั้ง
ls
สิ่งนี้จะส่งออกไฟล์เดียวในไดเร็กทอรีนี้ พร้อมกับโฟลเดอร์สภาพแวดล้อมเสมือน
package.json riot
ติดตั้งการพึ่งพา NodeJS สำหรับโครงการของเรา
npm install electron --global # makes electron binary accessible globally npm install node-wifi --save
เริ่มต้นด้วย main.js
จาก Electron Quick Start โดยการดาวน์โหลดไฟล์โดยใช้ด้านล่าง อาร์กิวเมนต์ -O
ต่อไปนี้เปลี่ยนชื่อ main.js
เป็น app.js
wget https://raw.githubusercontent.com/electron/electron-quick-start/master/main.js -O app.js
เปิด app.js
ใน nano
หรือโปรแกรมแก้ไขข้อความที่คุณชื่นชอบ
nano app.js
ในบรรทัดที่ 12 เปลี่ยน index.html เป็น static/index.html เนื่องจากเราจะสร้างไดเร็กทอรีส static
เพื่อให้มีเทมเพลต HTML ทั้งหมด
function createWindow () { // Create the browser window. win = new BrowserWindow({width: 1200, height: 800}) // and load the index.html of the app. win.loadFile('static/index.html') // Open the DevTools.
บันทึกการเปลี่ยนแปลงของคุณและออกจากตัวแก้ไข ไฟล์ของคุณควรตรงกับซอร์สโค้ดของไฟล์ app.js
ตอนนี้สร้างไดเร็กทอรีใหม่เพื่อจัดเก็บเทมเพลต HTML ของเรา
mkdir static
ดาวน์โหลดสไตล์ชีตที่สร้างขึ้นสำหรับโครงการนี้
wget https://raw.githubusercontent.com/alvinwan/riot/master/static/style.css?token=AB-ObfDtD46ANlqrObDanckTQJ2Q1Pyuks5bf79PwA%3D%3D -O static/style.css
เปิด static/index.html
ใน nano
หรือโปรแกรมแก้ไขข้อความที่คุณชื่นชอบ เริ่มต้นด้วยโครงสร้าง HTML มาตรฐาน
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Room Detector</title> </head> <body> <main> </main> </body> </html>
ต่อจากชื่อเรื่อง ให้ลิงก์แบบอักษรมอนต์เซอร์รัตที่ลิงก์โดย Google แบบอักษรและสไตล์ชีต
<title>Riot | Room Detector</title> <!-- start new code --> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet"> <link href="style.css" rel="stylesheet"> <!-- end new code --> </head>
ระหว่างแท็ก main
ให้เพิ่มช่องสำหรับชื่อห้องที่คาดคะเน
<main> <!-- start new code --> <p class="text">I believe you're in the</p> <h1 class="title">(I dunno)</h1> <!-- end new code --> </main>
สคริปต์ของคุณควรตรงกับสิ่งต่อไปนี้ทุกประการ ออกจากตัวแก้ไข
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Room Detector</title> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet"> <link href="style.css" rel="stylesheet"> </head> <body> <main> <p class="text">I believe you're in the</p> <h1 class="title">(I dunno)</h1> </main> </body> </html>
ตอนนี้ แก้ไขไฟล์แพ็คเกจให้มีคำสั่ง start
nano package.json
หลังจากบรรทัดที่ 7 ให้เพิ่มคำสั่ง start
ที่ใช้นามแฝงว่า electron .
. อย่าลืมใส่เครื่องหมายจุลภาคต่อท้ายบรรทัดก่อนหน้า
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "electron ." },
บันทึกและออก. ตอนนี้คุณพร้อมที่จะเปิดแอปเดสก์ท็อปของคุณใน Electron JS แล้ว ใช้ npm
เพื่อเปิดแอปพลิเคชันของคุณ
npm start
แอปพลิเคชันเดสก์ท็อปของคุณควรตรงกับรายการต่อไปนี้
การดำเนินการนี้จะทำให้แอปเดสก์ท็อปเริ่มต้นของคุณสมบูรณ์ หากต้องการออก ให้กลับไปที่เทอร์มินัลของคุณและกด CTRL+C ในขั้นตอนต่อไป เราจะบันทึกเครือข่าย wifi และทำให้ยูทิลิตี้การบันทึกสามารถเข้าถึงได้ผ่าน UI ของแอปพลิเคชันเดสก์ท็อป
ขั้นตอนที่ 2: บันทึกเครือข่าย WiFi
ในขั้นตอนนี้ คุณจะเขียนสคริปต์ NodeJS ที่บันทึกความแรงและความถี่ของเครือข่าย wifi ในระยะทั้งหมด สร้างไดเร็กทอรีสำหรับสคริปต์ของคุณ
mkdir scripts
เปิด scripts/observe.js
ใน nano
หรือโปรแกรมแก้ไขข้อความที่คุณชื่นชอบ
nano scripts/observe.js
นำเข้ายูทิลิตี้ NodeJS wifi และอ็อบเจ็กต์ระบบไฟล์
var wifi = require('node-wifi'); var fs = require('fs');
กำหนดฟังก์ชัน record
ที่ยอมรับตัวจัดการความสมบูรณ์
/** * Uses a recursive function for repeated scans, since scans are asynchronous. */ function record(n, completion, hook) { }
ภายในฟังก์ชันใหม่ ให้เริ่มต้นยูทิลิตี้ wifi ตั้งค่า iface
เป็น null เพื่อเริ่มต้นอินเทอร์เฟซ wifi แบบสุ่ม เนื่องจากค่านี้ไม่เกี่ยวข้องในขณะนี้
function record(n, completion, hook) { wifi.init({ iface : null }); }
กำหนดอาร์เรย์เพื่อเก็บตัวอย่างของคุณ ตัวอย่าง คือข้อมูลการฝึกอบรมที่เราจะใช้สำหรับแบบจำลองของเรา ตัวอย่างในบทช่วยสอนนี้คือรายการเครือข่าย wifi ในระยะและจุดแข็ง ความถี่ ชื่อ ฯลฯ ที่เกี่ยวข้อง
function record(n, completion, hook) { ... samples = [] }
กำหนดฟังก์ชันเรียกซ้ำ startScan
ซึ่งจะเริ่มต้นการสแกน wifi แบบอะซิงโครนัส เมื่อเสร็จสิ้น การสแกน wifi แบบอะซิงโครนัสจะเรียกใช้ startScan
แบบเรียกซ้ำ
function record(n, completion, hook) { ... function startScan(i) { wifi.scan(function(err, networks) { }); } startScan(n); }
ในการเรียกกลับ wifi.scan
ให้ตรวจหาข้อผิดพลาดหรือรายการเครือข่ายว่าง แล้วเริ่มการสแกนใหม่ หากมี
wifi.scan(function(err, networks) { if (err || networks.length == 0) { startScan(i); return } });
เพิ่มตัวพิมพ์พื้นฐานของฟังก์ชันแบบเรียกซ้ำ ซึ่งเรียกใช้ตัวจัดการความสมบูรณ์
wifi.scan(function(err, networks) { ... if (i <= 0) { return completion({samples: samples}); } });
ส่งออกการอัปเดตความคืบหน้า ต่อท้ายรายการตัวอย่าง และโทรซ้ำ
wifi.scan(function(err, networks) { ... hook(n-i+1, networks); samples.push(networks); startScan(i-1); });
ที่ส่วนท้ายของไฟล์ ให้เรียกใช้ฟังก์ชัน record
ด้วยการโทรกลับที่บันทึกตัวอย่างลงในไฟล์บนดิสก์
function record(completion) { ... } function cli() { record(1, function(data) { fs.writeFile('samples.json', JSON.stringify(data), 'utf8', function() {}); }, function(i, networks) { console.log(" * [INFO] Collected sample " + (21-i) + " with " + networks.length + " networks"); }) } cli();
ตรวจสอบอีกครั้งว่าไฟล์ของคุณตรงกับรายการต่อไปนี้:
var wifi = require('node-wifi'); var fs = require('fs'); /** * Uses a recursive function for repeated scans, since scans are asynchronous. */ function record(n, completion, hook) { wifi.init({ iface : null // network interface, choose a random wifi interface if set to null }); samples = [] function startScan(i) { wifi.scan(function(err, networks) { if (err || networks.length == 0) { startScan(i); return } if (i <= 0) { return completion({samples: samples}); } hook(n-i+1, networks); samples.push(networks); startScan(i-1); }); } startScan(n); } function cli() { record(1, function(data) { fs.writeFile('samples.json', JSON.stringify(data), 'utf8', function() {}); }, function(i, networks) { console.log(" * [INFO] Collected sample " + i + " with " + networks.length + " networks"); }) } cli();
บันทึกและออก. เรียกใช้สคริปต์
node scripts/observe.js
ผลลัพธ์ของคุณจะตรงกับสิ่งต่อไปนี้ พร้อมจำนวนตัวแปรของเครือข่าย
* [INFO] Collected sample 1 with 39 networks
ตรวจสอบตัวอย่างที่เพิ่งรวบรวม ไปป์ไปที่ json_pp
เพื่อพิมพ์ JSON และไพพ์ไปที่หัวเพื่อดู 16 บรรทัดแรก
cat samples.json | json_pp | head -16
ด้านล่างนี้เป็นตัวอย่างเอาต์พุตสำหรับเครือข่าย 2.4 GHz
{ "samples": [ [ { "mac": "64:0f:28:79:9a:29", "bssid": "64:0f:28:79:9a:29", "ssid": "SMASHINGMAGAZINEROCKS", "channel": 4, "frequency": 2427, "signal_level": "-91", "security": "WPA WPA2", "security_flags": [ "(PSK/AES,TKIP/TKIP)", "(PSK/AES,TKIP/TKIP)" ] },
นี่เป็นการสรุปสคริปต์การสแกน NodeJS wifi ของคุณ ซึ่งช่วยให้เราดูเครือข่าย WiFi ในระยะทั้งหมดได้ ในขั้นตอนต่อไป คุณจะต้องทำให้สคริปต์นี้สามารถเข้าถึงได้จากแอปเดสก์ท็อป
ขั้นตอนที่ 3: เชื่อมต่อสคริปต์สแกนกับแอปเดสก์ท็อป
ในขั้นตอนนี้ คุณจะต้องเพิ่มปุ่มลงในแอปเดสก์ท็อปก่อนเพื่อเรียกใช้สคริปต์ จากนั้น คุณจะอัปเดต UI ของแอปเดสก์ท็อปด้วยความคืบหน้าของสคริปต์
เปิด static/index.html
nano static/index.html
ใส่ปุ่ม "เพิ่ม" ดังที่แสดงด้านล่าง
<h1 class="title">(I dunno)</h1> <!-- start new code --> <div class="buttons"> <a href="add.html" class="button">Add new room</a> </div> <!-- end new code --> </main>
บันทึกและออก. เปิด static/add.html
nano static/add.html
วางเนื้อหาต่อไปนี้
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Add New Room</title> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet"> <link href="style.css" rel="stylesheet"> </head> <body> <main> <h1 class="title">0</h1> <p class="subtitle">of <span>20</span> samples needed. Feel free to move around the room.</p> <input type="text" class="text-field" placeholder="(room name)"> <div class="buttons"> <a href="#" class="button">Start recording</a> <a href="index.html" class="button light">Cancel</a> </div> <p class="text"></p> </main> <script> require('../scripts/observe.js') </script> </body> </html>
บันทึกและออก. เปิด scripts/observe.js
อีกครั้ง
nano scripts/observe.js
ใต้ฟังก์ชัน cli
ให้กำหนดฟังก์ชัน ui
ใหม่
function cli() { ... } // start new code function ui() { } // end new code cli();
อัปเดตสถานะแอปเดสก์ท็อปเพื่อระบุว่าฟังก์ชันเริ่มทำงานแล้ว
function ui() { var room_name = document.querySelector('#add-room-name').value; var status = document.querySelector('#add-status'); var number = document.querySelector('#add-title'); status.style.display = "block" status.innerHTML = "Listening for wifi..." }
แบ่งข้อมูลออกเป็นชุดข้อมูลการฝึกอบรมและการตรวจสอบความถูกต้อง
function ui() { ... function completion(data) { train_data = {samples: data['samples'].slice(0, 15)} test_data = {samples: data['samples'].slice(15)} var train_json = JSON.stringify(train_data); var test_json = JSON.stringify(test_data); } }
ยังอยู่ในการเรียกกลับที่ completion
ให้เขียนชุดข้อมูลทั้งสองลงในดิสก์
function ui() { ... function completion(data) { ... fs.writeFile('data/' + room_name + '_train.json', train_json, 'utf8', function() {}); fs.writeFile('data/' + room_name + '_test.json', test_json, 'utf8', function() {}); console.log(" * [INFO] Done") status.innerHTML = "Done." } }
เรียกใช้ record
ด้วยการโทรกลับที่เหมาะสมเพื่อบันทึก 20 ตัวอย่างและบันทึกตัวอย่างลงในดิสก์
function ui() { ... function completion(data) { ... } record(20, completion, function(i, networks) { number.innerHTML = i console.log(" * [INFO] Collected sample " + i + " with " + networks.length + " networks") }) }
สุดท้าย เรียกใช้ฟังก์ชัน cli
และ ui
ตามความเหมาะสม เริ่มต้นด้วยการลบ cli();
โทรที่ด้านล่างของไฟล์
function ui() { ... } cli(); // remove me
ตรวจสอบว่าวัตถุเอกสารสามารถเข้าถึงได้ทั่วโลกหรือไม่ ถ้าไม่ แสดงว่าสคริปต์กำลังถูกเรียกใช้จากบรรทัดคำสั่ง ในกรณีนี้ เรียกใช้ฟังก์ชัน cli
หากใช่ สคริปต์จะถูกโหลดจากภายในแอปเดสก์ท็อป ในกรณีนี้ ให้ผูก click listener กับฟังก์ชัน ui
if (typeof document == 'undefined') { cli(); } else { document.querySelector('#start-recording').addEventListener('click', ui) }
บันทึกและออก. สร้างไดเร็กทอรีเพื่อเก็บข้อมูลของเรา
mkdir data
เปิดแอปเดสก์ท็อป
npm start
คุณจะเห็นหน้าแรกต่อไปนี้ คลิกที่ "เพิ่มห้อง"
คุณจะเห็นแบบฟอร์มต่อไปนี้ พิมพ์ชื่อห้อง จำชื่อนี้ไว้เพราะเราจะใช้ชื่อนี้ในภายหลัง ตัวอย่างของเราคือ bedroom
.
คลิก “เริ่มการบันทึก” และคุณจะเห็นสถานะต่อไปนี้ “กำลังฟัง wifi…”
เมื่อบันทึกตัวอย่างทั้งหมด 20 ตัวอย่าง แอปของคุณจะตรงกับรายการต่อไปนี้ สถานะจะขึ้นว่า "เสร็จสิ้น"
คลิกที่ชื่อผิด “ยกเลิก” เพื่อกลับไปที่หน้าแรกซึ่งตรงกับรายการต่อไปนี้
ขณะนี้เราสามารถสแกนเครือข่าย wifi จาก UI เดสก์ท็อป ซึ่งจะบันทึกตัวอย่างที่บันทึกไว้ทั้งหมดไปยังไฟล์บนดิสก์ ต่อไป เราจะฝึกอัลกอริทึมการเรียนรู้ของเครื่องนอกกรอบที่มีกำลังสองน้อยที่สุดกับข้อมูลที่คุณรวบรวม
ขั้นตอนที่ 4: เขียนสคริปต์การฝึกอบรม Python
ในขั้นตอนนี้ เราจะเขียนสคริปต์การฝึกอบรมในภาษาไพทอน สร้างไดเร็กทอรีสำหรับยูทิลิตี้การฝึกอบรมของคุณ
mkdir model
เปิด model/train.py
nano model/train.py
ที่ด้านบนสุดของไฟล์ของคุณ นำเข้าไลบรารีการคำนวณ numpy
และ scipy
สำหรับโมเดลสี่เหลี่ยมที่น้อยที่สุด
import numpy as np from scipy.linalg import lstsq import json import sys
ยูทิลิตี้สามตัวถัดไปจะจัดการการโหลดและตั้งค่าข้อมูลจากไฟล์บนดิสก์ เริ่มต้นด้วยการเพิ่มฟังก์ชันยูทิลิตี้ที่ทำให้รายการที่ซ้อนกันเรียบ คุณจะใช้สิ่งนี้เพื่อทำให้รายการตัวอย่างเรียบขึ้น
import sys def flatten(list_of_lists): """Flatten a list of lists to make a list. >>> flatten([[1], [2], [3, 4]]) [1, 2, 3, 4] """ return sum(list_of_lists, [])
เพิ่มยูทิลิตี้ที่สองที่โหลดตัวอย่างจากไฟล์ที่ระบุ วิธีนี้จะสรุปข้อเท็จจริงที่ว่าตัวอย่างถูกกระจายไปทั่วหลายไฟล์ โดยส่งคืนเพียงตัวสร้างเดียวสำหรับตัวอย่างทั้งหมด สำหรับแต่ละตัวอย่าง ป้ายกำกับคือดัชนีของไฟล์ เช่น หากคุณเรียก get_all_samples('a.json', 'b.json')
ตัวอย่างทั้งหมดใน a.json
จะมีเลเบล 0 และตัวอย่างทั้งหมดใน b.json
จะมีเลเบล 1
def get_all_samples(paths): """Load all samples from JSON files.""" for label, path in enumerate(paths): with open(path) as f: for sample in json.load(f)['samples']: signal_levels = [ network['signal_level'].replace('RSSI', '') or 0 for network in sample] yield [network['mac'] for network in sample], signal_levels, label
ถัดไป เพิ่มยูทิลิตี้ที่เข้ารหัสตัวอย่างโดยใช้แบบจำลองแบบถุงคำ นี่คือตัวอย่าง: สมมติว่าเรารวบรวมสองตัวอย่าง
- เครือข่าย wifi A ที่ระดับ 10 และเครือข่าย wifi B ที่ระดับความแรง 15
- เครือข่าย wifi B ที่ระดับ 20 และเครือข่าย wifi C ที่ระดับ 25
ฟังก์ชันนี้จะสร้างรายการตัวเลขสามตัวสำหรับแต่ละตัวอย่าง: ค่าแรกคือความแรงของเครือข่าย wifi A ค่าที่สองสำหรับเครือข่าย B และค่าที่สามสำหรับ C รูปแบบคือ [A, B, C ].
- [10, 15, 0]
- [0, 20, 25]
def bag_of_words(all_networks, all_strengths, ordering): """Apply bag-of-words encoding to categorical variables. >>> samples = bag_of_words( ... [['a', 'b'], ['b', 'c'], ['a', 'c']], ... [[1, 2], [2, 3], [1, 3]], ... ['a', 'b', 'c']) >>> next(samples) [1, 2, 0] >>> next(samples) [0, 2, 3] """ for networks, strengths in zip(all_networks, all_strengths): yield [strengths[networks.index(network)] if network in networks else 0 for network in ordering]
โดยใช้ยูทิลิตี้ทั้งสามด้านบนนี้ เราสังเคราะห์กลุ่มตัวอย่างและป้ายกำกับ รวบรวมตัวอย่างและป้ายกำกับทั้งหมดโดยใช้ get_all_samples
กำหนดรูปแบบที่สอดคล้องกันโดย ordering
เพื่อเข้ารหัสตัวอย่างทั้งหมดโดยใช้ one-hot จากนั้นใช้การเข้ารหัส one_hot
กับตัวอย่าง สุดท้าย สร้าง data และ label เมทริกซ์ X
และ Y
ตามลำดับ
def create_dataset(classpaths, ordering=None): """Create dataset from a list of paths to JSON files.""" networks, strengths, labels = zip(*get_all_samples(classpaths)) if ordering is None: ordering = list(sorted(set(flatten(networks)))) X = np.array(list(bag_of_words(networks, strengths, ordering))).astype(np.float64) Y = np.array(list(labels)).astype(np.int) return X, Y, ordering
ฟังก์ชันเหล่านี้ทำให้ไปป์ไลน์ข้อมูลสมบูรณ์ ต่อไป เราจะสรุปการทำนายและประเมินแบบจำลอง เริ่มต้นด้วยการกำหนดวิธีการทำนาย ฟังก์ชันแรกจะทำให้เอาต์พุตของโมเดลของเราเป็นปกติ เพื่อให้ผลรวมของค่าทั้งหมดรวมเป็น 1 และค่าทั้งหมดไม่เป็นค่าลบ เพื่อให้แน่ใจว่าผลลัพธ์ที่ได้คือการแจกแจงความน่าจะเป็นที่ถูกต้อง ที่สองประเมินแบบจำลอง
def softmax(x): """Convert one-hotted outputs into probability distribution""" x = np.exp(x) return x / np.sum(x) def predict(X, w): """Predict using model parameters""" return np.argmax(softmax(X.dot(w)), axis=1)
ถัดไป ประเมินความถูกต้องของแบบจำลอง บรรทัดแรกรันการทำนายโดยใช้แบบจำลอง วินาทีนับจำนวนครั้งที่ทั้งค่าที่คาดการณ์และค่าจริงตกลงกัน จากนั้นทำให้เป็นมาตรฐานด้วยจำนวนตัวอย่างทั้งหมด
def evaluate(X, Y, w): """Evaluate model w on samples X and labels Y.""" Y_pred = predict(X, w) accuracy = (Y == Y_pred).sum() / X.shape[0] return accuracy
นี้สรุปอรรถประโยชน์การคาดการณ์และการประเมินผลของเรา หลังจากยูทิลิตี้เหล่านี้ ให้กำหนดฟังก์ชัน main
ที่จะรวบรวมชุดข้อมูล ฝึกฝน และประเมินผล เริ่มต้นด้วยการอ่านรายการอาร์กิวเมนต์จากบรรทัดคำสั่ง sys.argv
; เหล่านี้เป็นห้องที่จะรวมในการฝึกอบรม จากนั้นสร้างชุดข้อมูลขนาดใหญ่จากห้องที่ระบุทั้งหมด
def main(): classes = sys.argv[1:] train_paths = sorted(['data/{}_train.json'.format(name) for name in classes]) test_paths = sorted(['data/{}_test.json'.format(name) for name in classes]) X_train, Y_train, ordering = create_dataset(train_paths) X_test, Y_test, _ = create_dataset(test_paths, ordering=ordering)
ใช้การเข้ารหัสแบบ one-hot กับฉลาก การ เข้ารหัสแบบใช้ครั้ง เดียวจะคล้ายกับรูปแบบคำศัพท์ด้านบน เราใช้การเข้ารหัสนี้เพื่อจัดการกับตัวแปรหมวดหมู่ สมมติว่าเรามีป้ายกำกับที่เป็นไปได้ 3 รายการ แทนที่จะติดป้ายกำกับ 1, 2 หรือ 3 เราติดป้ายกำกับข้อมูลด้วย [1, 0, 0], [0, 1, 0] หรือ [0, 0, 1] สำหรับบทช่วยสอนนี้ เราจะสำรองคำอธิบายว่าเหตุใดการเข้ารหัสแบบใช้ครั้งเดียวจึงมีความสำคัญ ฝึกโมเดล และประเมินทั้งชุดฝึกและชุดตรวจสอบ
def main(): ... X_test, Y_test, _ = create_dataset(test_paths, ordering=ordering) Y_train_oh = np.eye(len(classes))[Y_train] w, _, _, _ = lstsq(X_train, Y_train_oh) train_accuracy = evaluate(X_train, Y_train, w) test_accuracy = evaluate(X_test, Y_test, w)
พิมพ์ความแม่นยำทั้งสอง และบันทึกโมเดลลงดิสก์
def main(): ... print('Train accuracy ({}%), Validation accuracy ({}%)'.format(train_accuracy*100, test_accuracy*100)) np.save('w.npy', w) np.save('ordering.npy', np.array(ordering)) sys.stdout.flush()
ที่ส่วนท้ายของไฟล์ ให้เรียกใช้ฟังก์ชัน main
if __name__ == '__main__': main()
บันทึกและออก. ตรวจสอบอีกครั้งว่าไฟล์ของคุณตรงกับรายการต่อไปนี้:
import numpy as np from scipy.linalg import lstsq import json import sys def flatten(list_of_lists): """Flatten a list of lists to make a list. >>> flatten([[1], [2], [3, 4]]) [1, 2, 3, 4] """ return sum(list_of_lists, []) def get_all_samples(paths): """Load all samples from JSON files.""" for label, path in enumerate(paths): with open(path) as f: for sample in json.load(f)['samples']: signal_levels = [ network['signal_level'].replace('RSSI', '') or 0 for network in sample] yield [network['mac'] for network in sample], signal_levels, label def bag_of_words(all_networks, all_strengths, ordering): """Apply bag-of-words encoding to categorical variables. >>> samples = bag_of_words( ... [['a', 'b'], ['b', 'c'], ['a', 'c']], ... [[1, 2], [2, 3], [1, 3]], ... ['a', 'b', 'c']) >>> next(samples) [1, 2, 0] >>> next(samples) [0, 2, 3] """ for networks, strengths in zip(all_networks, all_strengths): yield [int(strengths[networks.index(network)]) if network in networks else 0 for network in ordering] def create_dataset(classpaths, ordering=None): """Create dataset from a list of paths to JSON files.""" networks, strengths, labels = zip(*get_all_samples(classpaths)) if ordering is None: ordering = list(sorted(set(flatten(networks)))) X = np.array(list(bag_of_words(networks, strengths, ordering))).astype(np.float64) Y = np.array(list(labels)).astype(np.int) return X, Y, ordering def softmax(x): """Convert one-hotted outputs into probability distribution""" x = np.exp(x) return x / np.sum(x) def predict(X, w): """Predict using model parameters""" return np.argmax(softmax(X.dot(w)), axis=1) def evaluate(X, Y, w): """Evaluate model w on samples X and labels Y.""" Y_pred = predict(X, w) accuracy = (Y == Y_pred).sum() / X.shape[0] return accuracy def main(): classes = sys.argv[1:] train_paths = sorted(['data/{}_train.json'.format(name) for name in classes]) test_paths = sorted(['data/{}_test.json'.format(name) for name in classes]) X_train, Y_train, ordering = create_dataset(train_paths) X_test, Y_test, _ = create_dataset(test_paths, ordering=ordering) Y_train_oh = np.eye(len(classes))[Y_train] w, _, _, _ = lstsq(X_train, Y_train_oh) train_accuracy = evaluate(X_train, Y_train, w) validation_accuracy = evaluate(X_test, Y_test, w) print('Train accuracy ({}%), Validation accuracy ({}%)'.format(train_accuracy*100, validation_accuracy*100)) np.save('w.npy', w) np.save('ordering.npy', np.array(ordering)) sys.stdout.flush() if __name__ == '__main__': main()
บันทึกและออก. จำชื่อห้องที่ใช้ด้านบนเมื่อบันทึกตัวอย่าง 20 ตัวอย่าง ใช้ชื่อนั้นแทน bedroom
ด้านล่าง ตัวอย่างของเราคือ bedroom
. เราใช้ -W ignore
เพื่อละเว้นคำเตือนจากจุดบกพร่องของ LAPACK
python -W ignore model/train.py bedroom
เนื่องจากเราได้รวบรวมตัวอย่างการฝึกอบรมไว้สำหรับหนึ่งห้องเท่านั้น คุณควรเห็นการฝึกอบรมและการตรวจสอบความถูกต้อง 100%
Train accuracy (100.0%), Validation accuracy (100.0%)
ต่อไป เราจะเชื่อมโยงสคริปต์การฝึกอบรมนี้กับแอปเดสก์ท็อป
ขั้นตอนที่ 5: เชื่อมโยงสคริปต์รถไฟ
ในขั้นตอนนี้ เราจะฝึกโมเดลใหม่โดยอัตโนมัติทุกครั้งที่ผู้ใช้รวบรวมกลุ่มตัวอย่างใหม่ เปิด scripts/observe.js
nano scripts/observe.js
หลังจากนำเข้า fs
แล้ว ให้นำเข้า spawner โปรเซสลูกและยูทิลิตี้
var fs = require('fs'); // start new code const spawn = require("child_process").spawn; var utils = require('./utils.js');
ในฟังก์ชัน ui
ให้เพิ่มการเรียกต่อไปนี้เพื่อ retrain
เมื่อสิ้นสุดตัวจัดการการเสร็จสิ้น
function ui() { ... function completion() { ... retrain((data) => { var status = document.querySelector('#add-status'); accuracies = data.toString().split('\n')[0]; status.innerHTML = "Retraining succeeded: " + accuracies }); } ... }
หลังจากฟังก์ชัน ui
ให้เพิ่มฟังก์ชัน retrain
ต่อไปนี้ สิ่งนี้ทำให้เกิดกระบวนการย่อยที่จะเรียกใช้สคริปต์หลาม เมื่อเสร็จสิ้น กระบวนการจะเรียกตัวจัดการความสมบูรณ์ เมื่อล้มเหลว มันจะบันทึกข้อความแสดงข้อผิดพลาด
function ui() { .. } function retrain(completion) { var filenames = utils.get_filenames() const pythonProcess = spawn('python', ["./model/train.py"].concat(filenames)); pythonProcess.stdout.on('data', completion); pythonProcess.stderr.on('data', (data) => { console.log(" * [ERROR] " + data.toString()) }) }
บันทึกและออก. เปิด scripts/utils.js
nano scripts/utils.js
เพิ่มยูทิลิตี้ต่อไปนี้เพื่อดึงชุดข้อมูลทั้งหมดใน data/
var fs = require('fs'); module.exports = { get_filenames: get_filenames } function get_filenames() { filenames = new Set([]); fs.readdirSync("data/").forEach(function(filename) { filenames.add(filename.replace('_train', '').replace('_test', '').replace('.json', '' )) }); filenames = Array.from(filenames.values()) filenames.sort(); filenames.splice(filenames.indexOf('.DS_Store'), 1) return filenames }
บันทึกและออก. สำหรับการสิ้นสุดของขั้นตอนนี้ ให้ย้ายไปยังตำแหน่งใหม่ ควรมีกำแพงกั้นระหว่างตำแหน่งเดิมกับตำแหน่งใหม่ของคุณ ยิ่งมีอุปสรรคมากเท่าไร แอปเดสก์ท็อปของคุณก็จะยิ่งทำงานได้ดีขึ้นเท่านั้น
เรียกใช้แอปเดสก์ท็อปของคุณอีกครั้ง
npm start
เช่นเดียวกับก่อนหน้านี้ ให้รันสคริปต์การฝึกอบรม คลิกที่ "เพิ่มห้อง"
พิมพ์ชื่อห้องที่แตกต่างจากห้องแรกของคุณ เราจะใช้ living room
คลิก “เริ่มการบันทึก” และคุณจะเห็นสถานะต่อไปนี้ “กำลังฟัง wifi…”
เมื่อบันทึกตัวอย่างทั้งหมด 20 ตัวอย่าง แอปของคุณจะตรงกับรายการต่อไปนี้ สถานะจะขึ้นว่า "เสร็จสิ้น โมเดลฝึกหัด…”
ในขั้นตอนต่อไป เราจะใช้แบบจำลองที่ได้รับการฝึกใหม่นี้เพื่อคาดการณ์ห้องที่คุณอยู่ในทันที
ขั้นตอนที่ 6: เขียนสคริปต์ประเมินผล Python
ในขั้นตอนนี้ เราจะโหลดพารามิเตอร์โมเดลที่ฝึกไว้ล่วงหน้า สแกนหาเครือข่าย wifi และคาดการณ์ห้องตามการสแกน
เปิด model/eval.py
nano model/eval.py
นำเข้าไลบรารีที่ใช้และกำหนดไว้ในสคริปต์ล่าสุดของเรา
import numpy as np import sys import json import os import json from train import predict from train import softmax from train import create_dataset from train import evaluate
กำหนดยูทิลิตี้เพื่อแยกชื่อของชุดข้อมูลทั้งหมด ฟังก์ชันนี้ถือว่าชุดข้อมูลทั้งหมดถูกจัดเก็บไว้ใน data/
เป็น <dataset>_train.json
และ <dataset>_test.json
from train import evaluate def get_datasets(): """Extract dataset names.""" return sorted(list({path.split('_')[0] for path in os.listdir('./data') if '.DS' not in path}))
กำหนดฟังก์ชัน main
และเริ่มต้นด้วยการโหลดพารามิเตอร์ที่บันทึกไว้จากสคริปต์การฝึก
def get_datasets(): ... def main(): w = np.load('w.npy') ordering = np.load('ordering.npy')
สร้างชุดข้อมูลและคาดการณ์
def main(): ... classpaths = [sys.argv[1]] X, _, _ = create_dataset(classpaths, ordering) y = np.asscalar(predict(X, w))
คำนวณคะแนนความเชื่อมั่นตามความแตกต่างระหว่างความน่าจะเป็นสองอันดับแรก
def main(): ... sorted_y = sorted(softmax(X.dot(w)).flatten()) confidence = 1 if len(sorted_y) > 1: confidence = round(sorted_y[-1] - sorted_y[-2], 2)
สุดท้าย แยกหมวดหมู่และพิมพ์ผลลัพธ์ ในการสรุปสคริปต์ ให้เรียกใช้ฟังก์ชัน main
def main() ... category = get_datasets()[y] print(json.dumps({"category": category, "confidence": confidence})) if __name__ == '__main__': main()
บันทึกและออก. ตรวจสอบรหัสของคุณอีกครั้งว่าตรงกับสิ่งต่อไปนี้ (ซอร์สโค้ด):
import numpy as np import sys import json import os import json from train import predict from train import softmax from train import create_dataset from train import evaluate def get_datasets(): """Extract dataset names.""" return sorted(list({path.split('_')[0] for path in os.listdir('./data') if '.DS' not in path})) def main(): w = np.load('w.npy') ordering = np.load('ordering.npy') classpaths = [sys.argv[1]] X, _, _ = create_dataset(classpaths, ordering) y = np.asscalar(predict(X, w)) sorted_y = sorted(softmax(X.dot(w)).flatten()) confidence = 1 if len(sorted_y) > 1: confidence = round(sorted_y[-1] - sorted_y[-2], 2) category = get_datasets()[y] print(json.dumps({"category": category, "confidence": confidence})) if __name__ == '__main__': main()
ต่อไป เราจะเชื่อมต่อสคริปต์การประเมินนี้กับแอปเดสก์ท็อป แอปเดสก์ท็อปจะเรียกใช้การสแกน wifi อย่างต่อเนื่องและอัปเดต UI ด้วยห้องที่คาดคะเน
ขั้นตอนที่ 7: เชื่อมต่อการประเมินกับแอปเดสก์ท็อป
ในขั้นตอนนี้ เราจะอัปเดต UI ด้วยการแสดง "ความมั่นใจ" จากนั้น สคริปต์ NodeJS ที่เกี่ยวข้องจะเรียกใช้การสแกนและการคาดคะเนอย่างต่อเนื่อง โดยอัปเดต UI ตามนั้น
เปิด static/index.html
nano static/index.html
เพิ่มบรรทัดเพื่อความมั่นใจหลังชื่อเรื่องและหน้าปุ่ม
<h1 class="title">(I dunno)</h1> <!-- start new code --> <p class="subtitle">with <span>0%</span> confidence</p> <!-- end new code --> <div class="buttons">
หลัง main
แต่ก่อนส่วนท้ายของ body
ให้เพิ่มสคริปต์ใหม่ predict.js
</main> <!-- start new code --> <script> require('../scripts/predict.js') </script> <!-- end new code --> </body>
บันทึกและออก. เปิด scripts/predict.js
nano scripts/predict.js
อิมพอร์ตยูทิลิตี NodeJS ที่จำเป็นสำหรับระบบไฟล์ ยูทิลิตี และโปรเซสลูก
var fs = require('fs'); var utils = require('./utils'); const spawn = require("child_process").spawn;
กำหนดฟังก์ชัน predict
ซึ่งเรียกใช้กระบวนการโหนดแยกต่างหากเพื่อตรวจจับเครือข่าย wifi และกระบวนการ Python แยกต่างหากเพื่อทำนายห้อง
function predict(completion) { const nodeProcess = spawn('node', ["scripts/observe.js"]); const pythonProcess = spawn('python', ["-W", "ignore", "./model/eval.py", "samples.json"]); }
หลังจากที่กระบวนการทั้งสองเกิดขึ้นแล้ว ให้เพิ่มการเรียกกลับไปยังกระบวนการ Python สำหรับทั้งความสำเร็จและข้อผิดพลาด การโทรกลับที่ประสบความสำเร็จจะบันทึกข้อมูล เรียกใช้การโทรกลับที่เสร็จสิ้น และอัปเดต UI ด้วยการคาดการณ์และความมั่นใจ การเรียกกลับข้อผิดพลาดจะบันทึกข้อผิดพลาด
function predict(completion) { ... pythonProcess.stdout.on('data', (data) => { information = JSON.parse(data.toString()); console.log(" * [INFO] Room '" + information.category + "' with confidence '" + information.confidence + "'") completion() if (typeof document != "undefined") { document.querySelector('#predicted-room-name').innerHTML = information.category document.querySelector('#predicted-confidence').innerHTML = information.confidence } }); pythonProcess.stderr.on('data', (data) => { console.log(data.toString()); }) }
กำหนดฟังก์ชันหลักเพื่อเรียกใช้ฟังก์ชัน predict
แบบวนซ้ำตลอดไป
function main() { f = function() { predict(f) } predict(f) } main();
เป็นครั้งสุดท้าย เปิดแอปเดสก์ท็อปเพื่อดูการคาดคะเนแบบสด
npm start
ทุกๆ วินาที การสแกนจะเสร็จสมบูรณ์ และอินเทอร์เฟซจะได้รับการอัปเดตด้วยความมั่นใจล่าสุดและห้องที่คาดการณ์ไว้ ยินดีด้วย; คุณได้เสร็จสิ้นเครื่องตรวจจับห้องอย่างง่ายโดยอิงตามเครือข่าย WiFi ทั้งหมดในช่วง
บทสรุป
ในบทช่วยสอนนี้ เราได้สร้างโซลูชันโดยใช้เดสก์ท็อปของคุณเท่านั้นในการตรวจจับตำแหน่งของคุณภายในอาคาร เราสร้างแอปเดสก์ท็อปอย่างง่ายโดยใช้ Electron JS และใช้วิธีการเรียนรู้ของเครื่องอย่างง่ายบนเครือข่าย WiFi ทุกเครือข่าย สิ่งนี้เป็นการปูทางสำหรับแอปพลิเคชั่นอินเทอร์เน็ตของสิ่งต่าง ๆ โดยไม่ต้องใช้อาร์เรย์ของอุปกรณ์ที่มีค่าใช้จ่ายสูงในการบำรุงรักษา (ค่าใช้จ่ายไม่ใช่ในแง่ของเงิน แต่ในแง่ของเวลาและการพัฒนา)
หมายเหตุ : คุณสามารถดูซอร์สโค้ดได้อย่างครบถ้วนบน Github
เมื่อเวลาผ่านไป คุณอาจพบว่าช่องสี่เหลี่ยมที่น้อยที่สุดนี้ไม่ได้ทำงานอย่างน่าทึ่ง ลองหาสถานที่สองแห่งภายในห้องเดียวหรือยืนอยู่ที่ประตู สี่เหลี่ยมที่เล็กที่สุดจะมีขนาดใหญ่ไม่สามารถแยกแยะระหว่างเคสขอบได้ เราสามารถทำได้ดีกว่านี้หรือไม่? ปรากฎว่าเราทำได้ และในบทเรียนต่อๆ ไป เราจะใช้ประโยชน์จากเทคนิคอื่นๆ และพื้นฐานของแมชชีนเลิร์นนิงเพื่อประสิทธิภาพที่ดีขึ้น บทช่วยสอนนี้ทำหน้าที่เป็นเตียงทดสอบอย่างรวดเร็วสำหรับการทดลองที่จะมาถึง