Membangun Detektor Ruangan Untuk Perangkat IoT Di Mac OS

Diterbitkan: 2022-03-10
Ringkasan cepat Dalam tutorial ini, Anda membuat aplikasi desktop yang memprediksi ruangan mana yang Anda gunakan menggunakan algoritme pembelajaran mesin sederhana: kuadrat terkecil. Kode ini berlaku untuk platform apa pun, tetapi kami hanya menyediakan petunjuk penginstalan ketergantungan untuk Mac OSX.

Mengetahui 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:

  1. tidak membutuhkan lebih banyak perangkat keras;
  2. mengandalkan sinyal yang lebih stabil seperti WiFi;
  3. 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”
Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

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.

halaman rumah dengan tombol
Halaman beranda dengan tombol “Tambahkan Kamar Baru” tersedia (Pratinjau besar)

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".

(Pratinjau besar)

Anda akan melihat formulir berikut. Ketikkan nama untuk ruangan tersebut. Ingat nama ini, karena kita akan menggunakannya nanti. Contoh kita adalah bedroom .

Tambahkan halaman Kamar Baru
Halaman “Tambahkan Kamar Baru” saat dimuat (Pratinjau besar)

Klik "Mulai merekam," dan Anda akan melihat status berikut "Mendengarkan wifi ...".

mulai merekam
"Tambahkan Ruang Baru" mulai merekam (Pratinjau Besar)

Setelah semua 20 sampel dicatat, aplikasi Anda akan cocok dengan yang berikut ini. Statusnya akan terbaca “Selesai.”

Halaman “Tambah Ruangan Baru” setelah perekaman selesai (Pratinjau besar)

Klik "Batal" yang salah nama untuk kembali ke beranda, yang cocok dengan yang berikut ini.

selesai merekam
Halaman “Tambah Ruangan Baru” setelah perekaman selesai (Pratinjau besar)

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.

  1. jaringan wifi A pada kekuatan 10 dan jaringan wifi B pada kekuatan 15
  2. 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 ].

  1. [10, 15, 0]
  2. [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".

halaman rumah dengan tombol
Halaman beranda dengan tombol “Tambahkan Kamar Baru” tersedia (Pratinjau besar)

Ketik nama kamar yang berbeda dari kamar pertama Anda. Kami akan menggunakan living room .

Tambahkan halaman Kamar Baru
Halaman “Tambahkan Kamar Baru” saat dimuat (Pratinjau besar)

Klik "Mulai merekam," dan Anda akan melihat status berikut "Mendengarkan wifi ...".

"Tambahkan Ruang Baru" mulai merekam untuk ruang kedua (Pratinjau besar)

Setelah semua 20 sampel dicatat, aplikasi Anda akan cocok dengan yang berikut ini. Statusnya akan berbunyi “Selesai. Model pelatihan ulang…”

selesai merekam 2
Halaman “Tambah Kamar Baru” setelah perekaman untuk kamar kedua selesai (Pratinjau besar)

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.

demo
Merekam 20 sampel di dalam ruangan dan 20 lainnya di lorong. Saat berjalan kembali ke dalam, skrip dengan benar memprediksi "lorong" lalu "kamar tidur." (Pratinjau besar)

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.