Membangun Detektor Ruangan Untuk Perangkat IoT Di Mac OS
Diterbitkan: 2022-03-10Mengetahui ruangan tempat Anda berada memungkinkan berbagai aplikasi IoT — mulai dari menyalakan lampu hingga mengganti saluran TV. Jadi, bagaimana kami bisa mendeteksi saat Anda dan ponsel Anda berada di dapur, atau kamar tidur, atau ruang tamu? Dengan perangkat keras komoditas saat ini, ada banyak kemungkinan:
Salah satu solusinya adalah melengkapi setiap ruangan dengan perangkat bluetooth . Setelah ponsel Anda berada dalam jangkauan perangkat bluetooth, ponsel Anda akan mengetahui ruangan mana, berdasarkan perangkat bluetooth. Namun, mempertahankan serangkaian perangkat Bluetooth adalah biaya tambahan yang signifikan — mulai dari mengganti baterai hingga mengganti perangkat yang tidak berfungsi. Selain itu, kedekatan dengan perangkat Bluetooth tidak selalu menjadi jawaban: jika Anda berada di ruang tamu, di dekat dinding yang digunakan bersama dengan dapur, peralatan dapur Anda seharusnya tidak mulai mengaduk makanan.
Solusi lain, meskipun tidak praktis, adalah menggunakan GPS . Namun, perlu diingat GPS topi bekerja buruk di dalam ruangan di mana banyak dinding, sinyal lain, dan hambatan lain mendatangkan malapetaka pada presisi GPS.
Pendekatan kami sebagai gantinya adalah memanfaatkan semua jaringan WiFi dalam jangkauan — bahkan yang tidak terhubung dengan ponsel Anda. Begini caranya: pertimbangkan kekuatan WiFi A di dapur; katakanlah 5. Karena ada dinding antara dapur dan kamar tidur, kita dapat memperkirakan kekuatan WiFi A di kamar tidur berbeda; katakanlah 2. Kita dapat memanfaatkan perbedaan ini untuk memprediksi di ruangan mana kita berada. Terlebih lagi: Jaringan WiFi B dari tetangga kita hanya dapat dideteksi dari ruang tamu tetapi secara efektif tidak terlihat dari dapur. Itu membuat prediksi lebih mudah. Singkatnya, daftar semua WiFi dalam jangkauan memberi kami banyak informasi.
Metode ini memiliki keunggulan yang berbeda dari:
- tidak membutuhkan lebih banyak perangkat keras;
- mengandalkan sinyal yang lebih stabil seperti WiFi;
- bekerja dengan baik di mana teknik lain seperti GPS lemah.
Semakin banyak dinding semakin baik, karena semakin berbeda kekuatan jaringan WiFi, semakin mudah ruangan untuk diklasifikasikan. Anda akan membangun aplikasi desktop sederhana yang mengumpulkan data, belajar dari data, dan memprediksi di ruangan mana Anda berada pada waktu tertentu.
Bacaan Lebih Lanjut tentang SmashingMag:
- Munculnya UI Percakapan Cerdas
- Aplikasi Pembelajaran Mesin Untuk Desainer
- Cara Membuat Prototipe Pengalaman IoT: Membangun Perangkat Keras
- Merancang Untuk Internet Hal-Hal Emosional
Prasyarat
Untuk tutorial ini, Anda memerlukan Mac OSX. Sedangkan kode dapat berlaku untuk platform apapun, kami hanya akan memberikan instruksi instalasi ketergantungan untuk Mac.
- Mac OSX
- Homebrew, manajer paket untuk Mac OSX. Untuk menginstal, salin dan tempel perintah di brew.sh
- Instalasi NodeJS 10.8.0+ dan npm
- Instalasi Python 3.6+ dan pip. Lihat 3 bagian pertama dari “Cara Menginstal virtualenv, Menginstal Dengan pip, dan Mengelola Paket”
Langkah 0: Siapkan Lingkungan Kerja
Aplikasi desktop Anda akan ditulis dalam NodeJS. Namun, untuk memanfaatkan pustaka komputasi yang lebih efisien seperti numpy
, kode pelatihan dan prediksi akan ditulis dengan Python. Untuk memulai, kami akan mengatur lingkungan Anda dan menginstal dependensi. Buat direktori baru untuk menampung proyek Anda.
mkdir ~/riot
Arahkan ke direktori.
cd ~/riot
Gunakan pip untuk menginstal pengelola lingkungan virtual default Python.
sudo pip install virtualenv
Buat lingkungan virtual Python3.6 bernama riot
.
virtualenv riot --python=python3.6
Aktifkan lingkungan virtual.
source riot/bin/activate
Prompt Anda sekarang didahului oleh (riot)
. Ini menandakan kita telah berhasil memasuki virtual environment. Instal paket-paket berikut menggunakan pip
:
-
numpy
: Pustaka aljabar linier yang efisien -
scipy
: Pustaka komputasi ilmiah yang mengimplementasikan model pembelajaran mesin populer
pip install numpy==1.14.3 scipy ==1.1.0
Dengan pengaturan direktori kerja, kita akan mulai dengan aplikasi desktop yang merekam semua jaringan WiFi dalam jangkauan. Rekaman ini akan menjadi data pelatihan untuk model pembelajaran mesin Anda. Setelah kami memiliki data, Anda akan menulis pengklasifikasi kuadrat terkecil, yang dilatih tentang sinyal WiFi yang dikumpulkan sebelumnya. Terakhir, kami akan menggunakan model kuadrat terkecil untuk memprediksi ruangan tempat Anda berada, berdasarkan jaringan WiFi dalam jangkauan.
Langkah 1: Aplikasi Desktop Awal
Pada langkah ini, kita akan membuat aplikasi desktop baru menggunakan Electron JS. Untuk memulai, kami akan mengganti manajer paket Node npm
dan utilitas unduhan wget
.
brew install npm wget
Untuk memulai, kita akan membuat proyek Node baru.
npm init
Ini meminta Anda untuk nama paket dan kemudian nomor versi. Tekan ENTER
untuk menerima nama default riot
dan versi default 1.0.0
.
package name: (riot) version: (1.0.0)
Ini meminta Anda untuk deskripsi proyek. Tambahkan deskripsi yang tidak kosong yang Anda inginkan. Di bawah ini, deskripsinya adalah room detector
description: room detector
Ini meminta Anda untuk titik masuk, atau file utama untuk menjalankan proyek. Masukkan app.js
.
entry point: (index.js) app.js
Ini meminta Anda untuk test command
dan git repository
. Tekan ENTER
untuk melewati bidang ini untuk saat ini.
test command: git repository:
Ini meminta Anda untuk keywords
dan author
. Isi nilai yang Anda inginkan. Di bawah ini, kami menggunakan iot
, wifi
untuk kata kunci dan menggunakan John Doe
untuk penulisnya.
keywords: iot,wifi author: John Doe
Ini meminta Anda untuk lisensi. Tekan ENTER
untuk menerima nilai default ISC
.
license: (ISC)
Pada titik ini, npm
akan meminta Anda dengan ringkasan informasi sejauh ini. Output Anda harus mirip dengan berikut ini.
{ "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" }
Tekan ENTER
untuk menerima. npm
kemudian menghasilkan package.json
. Daftar semua file untuk diperiksa ulang.
ls
Ini akan menampilkan satu-satunya file di direktori ini, bersama dengan folder lingkungan virtual.
package.json riot
Instal dependensi NodeJS untuk proyek kami.
npm install electron --global # makes electron binary accessible globally npm install node-wifi --save
Mulailah dengan main.js
dari Electron Quick Start, dengan mengunduh file, menggunakan yang di bawah ini. Argumen -O
berikut mengganti nama main.js
menjadi app.js
.
wget https://raw.githubusercontent.com/electron/electron-quick-start/master/main.js -O app.js
Buka app.js
di nano
atau editor teks favorit Anda.
nano app.js
Pada baris 12, ubah index.html menjadi static/index.html , karena kita akan membuat direktori static
untuk memuat semua template 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.
Simpan perubahan Anda dan keluar dari editor. File Anda harus cocok dengan kode sumber file app.js
Sekarang buat direktori baru untuk menampung template HTML kita.
mkdir static
Unduh lembar gaya yang dibuat untuk proyek ini.
wget https://raw.githubusercontent.com/alvinwan/riot/master/static/style.css?token=AB-ObfDtD46ANlqrObDanckTQJ2Q1Pyuks5bf79PwA%3D%3D -O static/style.css
Buka static/index.html
di nano
atau editor teks favorit Anda. Mulailah dengan struktur HTML standar.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Room Detector</title> </head> <body> <main> </main> </body> </html>
Tepat setelah judul, tautkan font Montserrat yang ditautkan oleh Google Font dan stylesheet.
<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>
Di antara tag main
, tambahkan slot untuk nama kamar yang diprediksi.
<main> <!-- start new code --> <p class="text">I believe you're in the</p> <h1 class="title">(I dunno)</h1> <!-- end new code --> </main>
Skrip Anda sekarang harus sama persis dengan yang berikut ini. Keluar dari editor.
<!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>
Sekarang, ubah file paket untuk memuat perintah start.
nano package.json
Tepat setelah baris 7, tambahkan perintah start
alias electron .
. Pastikan untuk menambahkan koma di akhir baris sebelumnya.
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "electron ." },
Simpan dan keluar. Anda sekarang siap untuk meluncurkan aplikasi desktop Anda di Electron JS. Gunakan npm
untuk meluncurkan aplikasi Anda.
npm start
Aplikasi desktop Anda harus cocok dengan yang berikut ini.
Ini melengkapi aplikasi desktop awal Anda. Untuk keluar, navigasikan kembali ke terminal Anda dan CTRL+C. Pada langkah selanjutnya, kami akan merekam jaringan wifi, dan membuat utilitas perekaman dapat diakses melalui UI aplikasi desktop.
Langkah 2: Rekam Jaringan WiFi
Pada langkah ini, Anda akan menulis skrip NodeJS yang mencatat kekuatan dan frekuensi semua jaringan wifi dalam jangkauan. Buat direktori untuk skrip Anda.
mkdir scripts
Buka scripts/observe.js
di nano
atau editor teks favorit Anda.
nano scripts/observe.js
Impor utilitas wifi NodeJS dan objek sistem file.
var wifi = require('node-wifi'); var fs = require('fs');
Tentukan fungsi record
yang menerima penangan penyelesaian.
/** * Uses a recursive function for repeated scans, since scans are asynchronous. */ function record(n, completion, hook) { }
Di dalam fungsi baru, inisialisasi utilitas wifi. Setel iface
ke null untuk menginisialisasi ke antarmuka wifi acak, karena nilai ini saat ini tidak relevan.
function record(n, completion, hook) { wifi.init({ iface : null }); }
Tentukan array untuk menampung sampel Anda. Sampel adalah data pelatihan yang akan kami gunakan untuk model kami. Contoh dalam tutorial khusus ini adalah daftar jaringan wifi dalam jangkauan dan kekuatan, frekuensi, nama, dll. yang terkait.
function record(n, completion, hook) { ... samples = [] }
Tentukan fungsi rekursif startScan
, yang akan memulai pemindaian wifi secara asinkron. Setelah selesai, pemindaian wifi asinkron kemudian akan memanggil startScan
secara rekursif.
function record(n, completion, hook) { ... function startScan(i) { wifi.scan(function(err, networks) { }); } startScan(n); }
Dalam panggilan balik wifi.scan
, periksa kesalahan atau daftar kosong jaringan dan mulai ulang pemindaian jika demikian.
wifi.scan(function(err, networks) { if (err || networks.length == 0) { startScan(i); return } });
Tambahkan kasus dasar fungsi rekursif, yang memanggil penangan penyelesaian.
wifi.scan(function(err, networks) { ... if (i <= 0) { return completion({samples: samples}); } });
Keluarkan pembaruan kemajuan, tambahkan ke daftar sampel, dan lakukan panggilan rekursif.
wifi.scan(function(err, networks) { ... hook(n-i+1, networks); samples.push(networks); startScan(i-1); });
Di akhir file Anda, aktifkan fungsi record
dengan callback yang menyimpan sampel ke file di disk.
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();
Periksa kembali apakah file Anda cocok dengan yang berikut ini:
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();
Simpan dan keluar. Jalankan skrip.
node scripts/observe.js
Output Anda akan cocok dengan berikut ini, dengan jumlah variabel jaringan.
* [INFO] Collected sample 1 with 39 networks
Periksa sampel yang baru saja dikumpulkan. Pipa ke json_pp
untuk mencetak JSON dengan cantik dan pipa ke kepala untuk melihat 16 baris pertama.
cat samples.json | json_pp | head -16
Di bawah ini adalah contoh output untuk jaringan 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)" ] },
Ini menyimpulkan skrip pemindaian wifi NodeJS Anda. Ini memungkinkan kita untuk melihat semua jaringan WiFi dalam jangkauan. Pada langkah selanjutnya, Anda akan membuat skrip ini dapat diakses dari aplikasi desktop.
Langkah 3: Hubungkan Scan Script Ke Aplikasi Desktop
Pada langkah ini, pertama-tama Anda akan menambahkan tombol ke aplikasi desktop untuk memicu skrip. Kemudian, Anda akan memperbarui UI aplikasi desktop dengan kemajuan skrip.
Buka static/index.html
.
nano static/index.html
Masukkan tombol "Tambah", seperti yang ditunjukkan di bawah ini.
<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>
Simpan dan keluar. Buka static/add.html
.
nano static/add.html
Tempel konten berikut.
<!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>
Simpan dan keluar. Buka scripts/observe.js
.
nano scripts/observe.js
Di bawah fungsi cli
, tentukan fungsi ui
baru.
function cli() { ... } // start new code function ui() { } // end new code cli();
Perbarui status aplikasi desktop untuk menunjukkan bahwa fungsi telah mulai berjalan.
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..." }
Partisi data menjadi set data pelatihan dan validasi.
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); } }
Masih dalam panggilan balik completion
, tulis kedua set data ke disk.
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." } }
Panggil record
dengan panggilan balik yang sesuai untuk merekam 20 sampel dan menyimpan sampel ke disk.
function ui() { ... function completion(data) { ... } record(20, completion, function(i, networks) { number.innerHTML = i console.log(" * [INFO] Collected sample " + i + " with " + networks.length + " networks") }) }
Terakhir, aktifkan fungsi cli
dan ui
jika sesuai. Mulailah dengan menghapus cli();
panggilan di bagian bawah file.
function ui() { ... } cli(); // remove me
Periksa apakah objek dokumen dapat diakses secara global. Jika tidak, skrip sedang dijalankan dari baris perintah. Dalam hal ini, aktifkan fungsi cli
. Jika ya, skrip dimuat dari dalam aplikasi desktop. Dalam hal ini, ikat pendengar klik ke fungsi ui
.
if (typeof document == 'undefined') { cli(); } else { document.querySelector('#start-recording').addEventListener('click', ui) }
Simpan dan keluar. Buat direktori untuk menyimpan data kita.
mkdir data
Luncurkan aplikasi desktop.
npm start
Anda akan melihat beranda berikut. Klik "Tambah kamar".
Anda akan melihat formulir berikut. Ketikkan nama untuk ruangan tersebut. Ingat nama ini, karena kita akan menggunakannya nanti. Contoh kita adalah bedroom
.
Klik "Mulai merekam," dan Anda akan melihat status berikut "Mendengarkan wifi ...".
Setelah semua 20 sampel dicatat, aplikasi Anda akan cocok dengan yang berikut ini. Statusnya akan terbaca “Selesai.”
Klik "Batal" yang salah nama untuk kembali ke beranda, yang cocok dengan yang berikut ini.
Kami sekarang dapat memindai jaringan wifi dari UI desktop, yang akan menyimpan semua sampel yang direkam ke file di disk. Selanjutnya, kami akan melatih kotak terkecil algoritme pembelajaran mesin out-of-box pada data yang telah Anda kumpulkan.
Langkah 4: Tulis Script Pelatihan Python
Pada langkah ini, kita akan menulis skrip pelatihan dengan Python. Buat direktori untuk utilitas pelatihan Anda.
mkdir model
Buka model/train.py
nano model/train.py
Di bagian atas file Anda, impor pustaka komputasi numpy
dan scipy
untuk model kuadrat terkecilnya.
import numpy as np from scipy.linalg import lstsq import json import sys
Tiga utilitas berikutnya akan menangani pemuatan dan pengaturan data dari file di disk. Mulailah dengan menambahkan fungsi utilitas yang meratakan daftar bersarang. Anda akan menggunakan ini untuk meratakan daftar daftar sampel.
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, [])
Tambahkan utilitas kedua yang memuat sampel dari file yang ditentukan. Metode ini mengabstraksi fakta bahwa sampel tersebar di banyak file, hanya mengembalikan satu generator untuk semua sampel. Untuk setiap sampel, label adalah indeks file. misalnya, Jika Anda memanggil get_all_samples('a.json', 'b.json')
, semua sampel di a.json
akan memiliki label 0 dan semua sampel di b.json
akan memiliki label 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
Selanjutnya, tambahkan utilitas yang mengkodekan sampel menggunakan model bag-of-words-esque. Berikut ini contohnya: Asumsikan kita mengumpulkan dua sampel.
- jaringan wifi A pada kekuatan 10 dan jaringan wifi B pada kekuatan 15
- jaringan wifi B pada kekuatan 20 dan jaringan wifi C pada kekuatan 25.
Fungsi ini akan menghasilkan daftar tiga angka untuk masing-masing sampel: nilai pertama adalah kekuatan jaringan wifi A, yang kedua untuk jaringan B, dan yang ketiga untuk C. Akibatnya, formatnya adalah [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]
Menggunakan ketiga utilitas di atas, kami mensintesis kumpulan sampel dan labelnya. Kumpulkan semua sampel dan label menggunakan get_all_samples
. Tetapkan format ordering
yang konsisten untuk mengkodekan satu-panas semua sampel, lalu menerapkan pengkodean one_hot
ke sampel. Terakhir, buat data dan label matriks X
dan Y
masing-masing.
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
Fungsi-fungsi ini melengkapi pipa data. Selanjutnya, kami mengabstraksikan prediksi dan evaluasi model away. Mulailah dengan mendefinisikan metode prediksi. Fungsi pertama menormalkan keluaran model kita, sehingga jumlah semua nilai berjumlah 1 dan semua nilai tidak negatif; ini memastikan bahwa output adalah distribusi probabilitas yang valid. Yang kedua mengevaluasi model.
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)
Selanjutnya, mengevaluasi akurasi model. Baris pertama menjalankan prediksi menggunakan model. Yang kedua menghitung berapa kali nilai prediksi dan nilai benar cocok, kemudian dinormalisasi dengan jumlah total sampel.
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
Ini menyimpulkan utilitas prediksi dan evaluasi kami. Setelah utilitas ini, tentukan fungsi main
yang akan mengumpulkan kumpulan data, melatih, dan mengevaluasi. Mulailah dengan membaca daftar argumen dari baris perintah sys.argv
; ini adalah ruangan yang harus disertakan dalam pelatihan. Kemudian buat kumpulan data besar dari semua ruangan yang ditentukan.
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)
Terapkan enkode one-hot ke label. Encoding one-hot mirip dengan model bag-of-words di atas; kami menggunakan pengkodean ini untuk menangani variabel kategoris. Katakanlah kita memiliki 3 kemungkinan label. Alih-alih memberi label 1, 2, atau 3, kami memberi label pada data dengan [1, 0, 0], [0, 1, 0], atau [0, 0, 1]. Untuk tutorial ini, kami akan memberikan penjelasan mengapa pengkodean satu-panas itu penting. Latih model, dan evaluasi pada set kereta dan validasi.
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)
Cetak kedua akurasi, dan simpan model ke disk.
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()
Di akhir file, jalankan fungsi main
.
if __name__ == '__main__': main()
Simpan dan keluar. Periksa kembali apakah file Anda cocok dengan yang berikut ini:
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()
Simpan dan keluar. Ingat nama ruangan yang digunakan di atas saat merekam 20 sampel. Gunakan nama itu sebagai ganti bedroom
di bawah ini. Contoh kita adalah bedroom
. Kami menggunakan -W ignore
untuk mengabaikan peringatan dari bug LAPACK.
python -W ignore model/train.py bedroom
Karena kami hanya mengumpulkan sampel pelatihan untuk satu ruangan, Anda akan melihat akurasi pelatihan dan validasi 100%.
Train accuracy (100.0%), Validation accuracy (100.0%)
Selanjutnya, kami akan menautkan skrip pelatihan ini ke aplikasi desktop.
Langkah 5: Tautkan Skrip Kereta
Pada langkah ini, kami akan melatih ulang model secara otomatis setiap kali pengguna mengumpulkan kumpulan sampel baru. Buka scripts/observe.js
.
nano scripts/observe.js
Tepat setelah impor fs
, impor spawner dan utilitas proses anak.
var fs = require('fs'); // start new code const spawn = require("child_process").spawn; var utils = require('./utils.js');
Dalam fungsi ui
, tambahkan panggilan berikut untuk retrain
ulang di akhir handler penyelesaian.
function ui() { ... function completion() { ... retrain((data) => { var status = document.querySelector('#add-status'); accuracies = data.toString().split('\n')[0]; status.innerHTML = "Retraining succeeded: " + accuracies }); } ... }
Setelah fungsi ui
, tambahkan fungsi retrain
ulang berikut. Ini memunculkan proses anak yang akan menjalankan skrip python. Setelah selesai, proses memanggil penangan penyelesaian. Setelah gagal, itu akan mencatat pesan kesalahan.
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()) }) }
Simpan dan keluar. Buka scripts/utils.js
.
nano scripts/utils.js
Tambahkan utilitas berikut untuk mengambil semua kumpulan data di 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 }
Simpan dan keluar. Sebagai penutup dari langkah ini, pindahkan secara fisik ke lokasi baru. Idealnya harus ada dinding antara lokasi asli Anda dan lokasi baru Anda. Semakin banyak penghalang, semakin baik aplikasi desktop Anda akan bekerja.
Sekali lagi, jalankan aplikasi desktop Anda.
npm start
Sama seperti sebelumnya, jalankan skrip pelatihan. Klik "Tambah kamar".
Ketik nama kamar yang berbeda dari kamar pertama Anda. Kami akan menggunakan living room
.
Klik "Mulai merekam," dan Anda akan melihat status berikut "Mendengarkan wifi ...".
Setelah semua 20 sampel dicatat, aplikasi Anda akan cocok dengan yang berikut ini. Statusnya akan berbunyi “Selesai. Model pelatihan ulang…”
Pada langkah berikutnya, kita akan menggunakan model yang dilatih ulang ini untuk memprediksi ruangan tempat Anda berada, dengan cepat.
Langkah 6: Tulis Script Evaluasi Python
Pada langkah ini, kami akan memuat parameter model yang telah dilatih sebelumnya, memindai jaringan wifi, dan memprediksi ruangan berdasarkan pemindaian.
Buka model/eval.py
.
nano model/eval.py
Impor pustaka yang digunakan dan ditentukan dalam skrip terakhir kami.
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
Tentukan utilitas untuk mengekstrak nama semua kumpulan data. Fungsi ini mengasumsikan bahwa semua kumpulan data disimpan dalam data/
sebagai <dataset>_train.json
dan <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}))
Tentukan fungsi main
, dan mulai dengan memuat parameter yang disimpan dari skrip pelatihan.
def get_datasets(): ... def main(): w = np.load('w.npy') ordering = np.load('ordering.npy')
Buat kumpulan data dan prediksi.
def main(): ... classpaths = [sys.argv[1]] X, _, _ = create_dataset(classpaths, ordering) y = np.asscalar(predict(X, w))
Hitung skor kepercayaan berdasarkan perbedaan antara dua probabilitas teratas.
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)
Terakhir, ekstrak kategori dan cetak hasilnya. Untuk mengakhiri skrip, aktifkan fungsi main
.
def main() ... category = get_datasets()[y] print(json.dumps({"category": category, "confidence": confidence})) if __name__ == '__main__': main()
Simpan dan keluar. Periksa kembali kode Anda cocok dengan yang berikut (kode sumber):
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()
Selanjutnya, kami akan menghubungkan skrip evaluasi ini ke aplikasi desktop. Aplikasi desktop akan terus menjalankan pemindaian wifi dan memperbarui UI dengan ruang yang diprediksi.
Langkah 7: Hubungkan Evaluasi Ke Aplikasi Desktop
Pada langkah ini, kami akan memperbarui UI dengan tampilan "keyakinan". Kemudian, skrip NodeJS terkait akan terus menjalankan pemindaian dan prediksi, memperbarui UI yang sesuai.
Buka static/index.html
.
nano static/index.html
Tambahkan garis untuk percaya diri tepat setelah judul dan sebelum tombol.
<h1 class="title">(I dunno)</h1> <!-- start new code --> <p class="subtitle">with <span>0%</span> confidence</p> <!-- end new code --> <div class="buttons">
Tepat setelah main
tetapi sebelum akhir body
, tambahkan skrip baru predict.js
.
</main> <!-- start new code --> <script> require('../scripts/predict.js') </script> <!-- end new code --> </body>
Simpan dan keluar. Buka scripts/predict.js
.
nano scripts/predict.js
Impor utilitas NodeJS yang diperlukan untuk sistem file, utilitas, dan spawner proses anak.
var fs = require('fs'); var utils = require('./utils'); const spawn = require("child_process").spawn;
Tentukan fungsi predict
yang memanggil proses node terpisah untuk mendeteksi jaringan wifi dan proses Python terpisah untuk memprediksi ruangan.
function predict(completion) { const nodeProcess = spawn('node', ["scripts/observe.js"]); const pythonProcess = spawn('python', ["-W", "ignore", "./model/eval.py", "samples.json"]); }
Setelah kedua proses muncul, tambahkan panggilan balik ke proses Python untuk keberhasilan dan kesalahan. Callback yang berhasil mencatat informasi, memanggil callback penyelesaian, dan memperbarui UI dengan prediksi dan keyakinan. Panggilan balik kesalahan mencatat kesalahan.
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()); }) }
Tentukan fungsi utama untuk menjalankan fungsi predict
secara rekursif, selamanya.
function main() { f = function() { predict(f) } predict(f) } main();
Untuk terakhir kalinya, buka aplikasi desktop untuk melihat prediksi langsung.
npm start
Kira-kira setiap detik, pemindaian akan selesai dan antarmuka akan diperbarui dengan keyakinan terbaru dan ruang yang diprediksi. Selamat; Anda telah menyelesaikan pendeteksi ruangan sederhana berdasarkan semua jaringan WiFi dalam jangkauan.
Kesimpulan
Dalam tutorial ini, kami membuat solusi hanya menggunakan desktop Anda untuk mendeteksi lokasi Anda di dalam gedung. Kami membangun aplikasi desktop sederhana menggunakan Electron JS dan menerapkan metode pembelajaran mesin sederhana di semua jaringan WiFi dalam jangkauan. Ini membuka jalan bagi aplikasi Internet-of-things tanpa memerlukan susunan perangkat yang mahal untuk dirawat (biaya bukan dalam bentuk uang tetapi dalam hal waktu dan pengembangan).
Catatan : Anda dapat melihat kode sumber secara keseluruhan di Github.
Seiring waktu, Anda mungkin menemukan bahwa kuadrat terkecil ini sebenarnya tidak tampil spektakuler. Coba temukan dua lokasi dalam satu ruangan, atau berdiri di ambang pintu. Kuadrat terkecil akan menjadi besar tidak dapat membedakan antara kasus tepi. Bisakah kita melakukan yang lebih baik? Ternyata kami bisa, dan dalam pelajaran mendatang, kami akan memanfaatkan teknik lain dan dasar-dasar pembelajaran mesin untuk kinerja yang lebih baik. Tutorial ini berfungsi sebagai tempat uji cepat untuk eksperimen yang akan datang.