Mac OS'de IoT Cihazları İçin Bir Oda Dedektörü Oluşturma
Yayınlanan: 2022-03-10Hangi odada olduğunuzu bilmek, ışığı açmaktan TV kanallarını değiştirmeye kadar çeşitli IoT uygulamalarına olanak tanır. Peki sizin ve telefonunuzun mutfakta, yatak odasında veya oturma odasında olduğunuz anı nasıl tespit edebiliriz? Günümüzün emtia donanımı ile sayısız olasılık var:
Bir çözüm, her odayı bir bluetooth cihazı ile donatmaktır . Telefonunuz bir bluetooth cihazının kapsama alanına girdiğinde, telefonunuz bluetooth cihazına bağlı olarak hangi oda olduğunu bilecektir. Bununla birlikte, bir dizi Bluetooth cihazının bakımı, pillerin değiştirilmesinden işlevsiz cihazların değiştirilmesine kadar önemli bir ek yüktür. Ek olarak, Bluetooth cihazına yakın olmak her zaman çözüm değildir: oturma odasında, mutfakla paylaşılan duvarın yanındaysanız, mutfak aletleriniz yiyecek yaymaya başlamamalıdır.
Pratik olmasa da başka bir çözüm de GPS kullanmaktır . Ancak, GPS'in çok sayıda duvarın, diğer sinyallerin ve diğer engellerin GPS'in hassasiyetine zarar verdiği iç mekanlarda yetersiz çalıştığını unutmayın.
Bunun yerine yaklaşımımız, telefonunuzun bağlı olmadığı ağlar bile, tüm menzil içi WiFi ağlarından yararlanmaktır. İşte nasıl: mutfakta WiFi A'nın gücünü düşünün; 5 olduğunu söyleyin. Mutfak ile yatak odası arasında bir duvar olduğundan, yatak odasında WiFi A'nın gücünün farklı olmasını makul bir şekilde bekleyebiliriz; 2 olduğunu söyle. Hangi odada olduğumuzu tahmin etmek için bu farkı kullanabiliriz. Dahası: Komşumuzdan gelen WiFi ağı B sadece oturma odasından algılanabilir, ancak mutfaktan fiilen görünmez. Bu, tahmini daha da kolaylaştırır. Özetle, tüm menzil içi WiFi listesi bize bol miktarda bilgi verir.
Bu yöntemin belirgin avantajları vardır:
- daha fazla donanım gerektirmeyen;
- WiFi gibi daha kararlı sinyallere güvenmek;
- GPS gibi diğer tekniklerin zayıf olduğu yerlerde iyi çalışıyor.
Duvarlar ne kadar çok olursa, WiFi ağının güçleri ne kadar farklı olursa, odaların sınıflandırılması o kadar kolay olur. Veri toplayan, verilerden öğrenen ve herhangi bir zamanda hangi odada olduğunuzu tahmin eden basit bir masaüstü uygulaması oluşturacaksınız.
SmashingMag'de Daha Fazla Okuma :
- Akıllı Konuşma Kullanıcı Arayüzü Yükselişi
- Tasarımcılar İçin Makine Öğrenimi Uygulamaları
- IoT Deneyimlerinin Prototipi Nasıl Yapılır: Donanımı İnşa Etme
- Duygusal Şeylerin İnterneti İçin Tasarım
Önkoşullar
Bu eğitim için bir Mac OSX'e ihtiyacınız olacak. Kod herhangi bir platforma uygulanabilirken, yalnızca Mac için bağımlılık kurulum talimatlarını sağlayacağız.
- Mac OS X
- Homebrew, Mac OSX için bir paket yöneticisi. Yüklemek için brew.sh adresindeki komutu kopyalayıp yapıştırın
- NodeJS 10.8.0+ ve npm kurulumu
- Python 3.6+ ve pip kurulumu. Virtualenv Nasıl Kurulur, pip ile Kurulum ve Paketleri Yönetme konusunun ilk 3 bölümüne bakın.
Adım 0: Çalışma Ortamını Kurun
Masaüstü uygulamanız NodeJS'de yazılacaktır. Ancak, numpy
gibi daha verimli hesaplama kitaplıklarından yararlanmak için eğitim ve tahmin kodu Python'da yazılacaktır. Başlamak için ortamlarınızı ayarlayacağız ve bağımlılıkları kuracağız. Projenizi barındırmak için yeni bir dizin oluşturun.
mkdir ~/riot
Dizine gidin.
cd ~/riot
Python'un varsayılan sanal ortam yöneticisini kurmak için pip kullanın.
sudo pip install virtualenv
riot
adlı bir Python3.6 sanal ortamı oluşturun.
virtualenv riot --python=python3.6
Sanal ortamı etkinleştirin.
source riot/bin/activate
İsteminizin önüne artık (riot)
. Bu, sanal ortama başarıyla girdiğimizi gösterir. pip
kullanarak aşağıdaki paketleri kurun:
-
numpy
: Verimli, doğrusal bir cebir kitaplığı -
scipy
: Popüler makine öğrenimi modellerini uygulayan bilimsel bir bilgi işlem kitaplığı
pip install numpy==1.14.3 scipy ==1.1.0
Çalışma dizini kurulumuyla, menzil içindeki tüm WiFi ağlarını kaydeden bir masaüstü uygulamasıyla başlayacağız. Bu kayıtlar, makine öğrenimi modeliniz için eğitim verilerini oluşturacaktır. Elimizde veri olduğunda, daha önce toplanan WiFi sinyallerine göre eğitilmiş bir en küçük kareler sınıflandırıcı yazacaksınız. Son olarak, menzil içindeki WiFi ağlarını temel alarak bulunduğunuz odayı tahmin etmek için en küçük kareler modelini kullanacağız.
Adım 1: İlk Masaüstü Uygulaması
Bu adımda Electron JS kullanarak yeni bir masaüstü uygulaması oluşturacağız. Başlamak için, bunun yerine Node paket yöneticisi npm
ve wget
indirme yardımcı programını kullanacağız.
brew install npm wget
Başlamak için yeni bir Node projesi oluşturacağız.
npm init
Bu, sizden paket adını ve ardından sürüm numarasını ister. riot
varsayılan adını ve 1.0.0
varsayılan sürümünü kabul etmek için ENTER
basın.
package name: (riot) version: (1.0.0)
Bu, sizden bir proje açıklaması ister. İstediğiniz boş olmayan açıklamayı ekleyin. Aşağıda, açıklama room detector
description: room detector
Bu, sizden giriş noktasını veya projeyi çalıştıracağınız ana dosyayı ister. app.js
girin.
entry point: (index.js) app.js
Bu, sizden test command
ve git repository
ister. Şimdilik bu alanları atlamak için ENTER
basın.
test command: git repository:
Bu, sizden keywords
ve author
ister. İstediğiniz değerleri girin. Aşağıda, anahtar kelimeler için iot
, wifi
ve yazar için John Doe
kullanıyoruz.
keywords: iot,wifi author: John Doe
Bu, sizden lisans ister. ISC
varsayılan değerini kabul etmek için ENTER
basın.
license: (ISC)
Bu noktada, npm
size şimdiye kadarki bilgilerin bir özetini soracaktır. Çıktınız aşağıdakine benzer olmalıdır.
{ "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" }
Kabul etmek için ENTER
basın. npm
daha sonra bir package.json
üretir. Tekrar kontrol edilecek tüm dosyaları listeleyin.
ls
Bu, sanal ortam klasörüyle birlikte bu dizindeki tek dosyayı çıkaracaktır.
package.json riot
Projemiz için NodeJS bağımlılıklarını kurun.
npm install electron --global # makes electron binary accessible globally npm install node-wifi --save
Aşağıdakileri kullanarak dosyayı indirerek Electron Quick main.js
ile başlayın. Aşağıdaki -O
bağımsız değişkeni, app.js
main.js
olarak yeniden adlandırır.
wget https://raw.githubusercontent.com/electron/electron-quick-start/master/main.js -O app.js
app.js
nano
veya favori metin düzenleyicinizde açın.
nano app.js
12. satırda index.html'yi static/index.html olarak değiştirin, çünkü tüm HTML şablonlarını içeren static
bir dizin oluşturacağız.
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.
Değişikliklerinizi kaydedin ve düzenleyiciden çıkın. Dosyanız, app.js
dosyasının kaynak koduyla eşleşmelidir. Şimdi HTML şablonlarımızı barındıracak yeni bir dizin oluşturun.
mkdir static
Bu proje için oluşturulmuş bir stil sayfasını indirin.
wget https://raw.githubusercontent.com/alvinwan/riot/master/static/style.css?token=AB-ObfDtD46ANlqrObDanckTQJ2Q1Pyuks5bf79PwA%3D%3D -O static/style.css
static/index.html
nano
veya favori metin düzenleyicinizde açın. Standart HTML yapısıyla başlayın.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Room Detector</title> </head> <body> <main> </main> </body> </html>
Başlığın hemen ardından, Google Fonts ve stil sayfası ile bağlantılı Montserrat yazı tipini bağlayın.
<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
etiketler arasına tahmini oda adı için bir yuva ekleyin.
<main> <!-- start new code --> <p class="text">I believe you're in the</p> <h1 class="title">(I dunno)</h1> <!-- end new code --> </main>
Komut dosyanız şimdi aşağıdakiyle tam olarak eşleşmelidir. Düzenleyiciden çıkın.
<!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>
Şimdi, paket dosyasını bir başlatma komutu içerecek şekilde değiştirin.
nano package.json
7. satırdan hemen sonra, electron .
olarak adlandırılan bir start
komutu ekleyin. . Önceki satırın sonuna virgül eklediğinizden emin olun.
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "electron ." },
Kaydet ve çık. Artık masaüstü uygulamanızı Electron JS'de başlatmaya hazırsınız. Uygulamanızı başlatmak için npm
kullanın.
npm start
Masaüstü uygulamanız aşağıdakilerle eşleşmelidir.
Bu, başlangıç masaüstü uygulamanızı tamamlar. Çıkmak için terminalinize ve CTRL+C'ye geri dönün. Bir sonraki adımda, wifi ağlarını kaydedeceğiz ve kayıt yardımcı programını masaüstü uygulaması kullanıcı arayüzü üzerinden erişilebilir hale getireceğiz.
2. Adım: WiFi Ağlarını Kaydedin
Bu adımda, tüm menzil içi wifi ağlarının gücünü ve sıklığını kaydeden bir NodeJS betiği yazacaksınız. Komut dosyalarınız için bir dizin oluşturun.
mkdir scripts
scripts/observe.js
nano
veya favori metin düzenleyicinizde açın.
nano scripts/observe.js
Bir NodeJS wifi yardımcı programını ve dosya sistemi nesnesini içe aktarın.
var wifi = require('node-wifi'); var fs = require('fs');
Bir tamamlama işleyicisini kabul eden bir record
işlevi tanımlayın.
/** * Uses a recursive function for repeated scans, since scans are asynchronous. */ function record(n, completion, hook) { }
Yeni işlevin içinde, wifi yardımcı programını başlatın. Bu değer şu anda alakasız olduğundan, rastgele bir wifi arabirimini başlatmak için iface
null olarak ayarlayın.
function record(n, completion, hook) { wifi.init({ iface : null }); }
Örneklerinizi içerecek bir dizi tanımlayın. Örnekler , modelimiz için kullanacağımız eğitim verileridir. Bu özel eğitimdeki örnekler, menzil içi wifi ağlarının ve bunların ilgili güçleri, frekansları, adları vb. listeleridir.
function record(n, completion, hook) { ... samples = [] }
Wi-Fi taramalarını eşzamansız olarak başlatacak bir özyinelemeli startScan
işlevi tanımlayın. Tamamlandıktan sonra, asenkron wifi taraması tekrar tekrar startScan
çağırır.
function record(n, completion, hook) { ... function startScan(i) { wifi.scan(function(err, networks) { }); } startScan(n); }
wifi.scan
geri aramasında, hataları veya boş ağ listelerini kontrol edin ve varsa taramayı yeniden başlatın.
wifi.scan(function(err, networks) { if (err || networks.length == 0) { startScan(i); return } });
Tamamlama işleyicisini çağıran özyinelemeli işlevin temel durumunu ekleyin.
wifi.scan(function(err, networks) { ... if (i <= 0) { return completion({samples: samples}); } });
Bir ilerleme güncellemesi çıktısı alın, örnekler listesine ekleyin ve özyinelemeli çağrıyı yapın.
wifi.scan(function(err, networks) { ... hook(n-i+1, networks); samples.push(networks); startScan(i-1); });
Dosyanızın sonunda, örnekleri diskteki bir dosyaya kaydeden bir geri arama ile record
işlevini çağırın.
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();
Dosyanızın aşağıdakilerle eşleştiğini iki kez kontrol edin:
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();
Kaydet ve çık. Komut dosyasını çalıştırın.
node scripts/observe.js
Çıktınız, değişken sayıda ağ ile aşağıdakilerle eşleşecektir.
* [INFO] Collected sample 1 with 39 networks
Yeni toplanan örnekleri inceleyin. json_pp
güzel bir şekilde yazdırmak için json_pp'ye ve ilk 16 satırı görüntülemek için başa boruya yönlendirin.
cat samples.json | json_pp | head -16
Aşağıda 2,4 GHz ağ için örnek çıktı verilmiştir.
{ "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)" ] },
Bu, NodeJS kablosuz tarama komut dosyanızı tamamlar. Bu, tüm menzil içi WiFi ağlarını görüntülememizi sağlar. Bir sonraki adımda, bu komut dosyasını masaüstü uygulamasından erişilebilir hale getireceksiniz.
3. Adım: Tarama Komut Dosyasını Masaüstü Uygulamasına Bağlayın
Bu adımda, komut dosyasını tetiklemek için önce masaüstü uygulamasına bir düğme ekleyeceksiniz. Ardından, komut dosyasının ilerleyişi ile masaüstü uygulaması kullanıcı arayüzünü güncelleyeceksiniz.
static/index.html
açın.
nano static/index.html
Aşağıda gösterildiği gibi “Ekle” düğmesini yerleştirin.
<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>
Kaydet ve çık. static/add.html
açın.
nano static/add.html
Aşağıdaki içeriği yapıştırın.
<!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>
Kaydet ve çık. scripts/observe.js
açın.
nano scripts/observe.js
cli
işlevinin altında yeni bir ui
işlevi tanımlayın.
function cli() { ... } // start new code function ui() { } // end new code cli();
İşlevin çalışmaya başladığını belirtmek için masaüstü uygulaması durumunu güncelleyin.
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..." }
Verileri eğitim ve doğrulama veri kümelerine ayırın.
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); } }
Yine de completion
geri araması dahilinde, her iki veri kümesini de diske yazın.
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." } }
20 örneği kaydetmek ve örnekleri diske record
için uygun geri aramalarla kaydı çağırın.
function ui() { ... function completion(data) { ... } record(20, completion, function(i, networks) { number.innerHTML = i console.log(" * [INFO] Collected sample " + i + " with " + networks.length + " networks") }) }
Son olarak, uygun olduğunda cli
ve ui
işlevlerini çağırın. cli();
dosyanın altındaki arayın.
function ui() { ... } cli(); // remove me
Belge nesnesinin küresel olarak erişilebilir olup olmadığını kontrol edin. Değilse, komut dosyası komut satırından çalıştırılıyor. Bu durumda, cli
işlevini çağırın. Öyleyse, komut dosyası masaüstü uygulamasından yüklenir. Bu durumda, tıklama dinleyicisini kullanıcı ui
işlevine bağlayın.
if (typeof document == 'undefined') { cli(); } else { document.querySelector('#start-recording').addEventListener('click', ui) }
Kaydet ve çık. Verilerimizi tutmak için bir dizin oluşturun.
mkdir data
Masaüstü uygulamasını başlatın.
npm start
Aşağıdaki ana sayfayı göreceksiniz. "Oda ekle"ye tıklayın.
Aşağıdaki formu göreceksiniz. Oda için bir ad yazın. Daha sonra kullanacağımız için bu ismi hatırlayın. Örneğimiz bedroom
olacak.
“Kaydı başlat” seçeneğine tıkladığınızda, “Wifi için dinleniyor…” durumunu göreceksiniz.
20 örneğin tümü kaydedildiğinde, uygulamanız aşağıdakilerle eşleşir. Durum "Bitti" yazacaktır.
Aşağıdakilerle eşleşen ana sayfaya dönmek için yanlış adlandırılan “İptal”e tıklayın.
Artık tüm kayıtlı örnekleri diskteki dosyalara kaydedecek olan masaüstü kullanıcı arayüzünden wifi ağlarını tarayabiliriz. Ardından, topladığınız veriler üzerinde kullanıma hazır bir makine öğrenme algoritması-en küçük kareler eğiteceğiz.
Adım 4: Python Eğitim Komut Dosyası Yazın
Bu adımda Python'da bir eğitim scripti yazacağız. Eğitim yardımcı programlarınız için bir dizin oluşturun.
mkdir model
model/train.py
açın
nano model/train.py
Dosyanızın en üstünde, en küçük kareler modeli için numpy
hesaplama kitaplığını ve scipy
içe aktarın.
import numpy as np from scipy.linalg import lstsq import json import sys
Sonraki üç yardımcı program, diskteki dosyalardan veri yükleme ve ayarlama işlemlerini gerçekleştirecektir. İç içe listeleri düzleştiren bir yardımcı program işlevi ekleyerek başlayın. Bunu, bir numune listesini düzleştirmek için kullanacaksınız.
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, [])
Belirtilen dosyalardan örnekleri yükleyen ikinci bir yardımcı program ekleyin. Bu yöntem, örneklerin birden çok dosyaya yayıldığı gerçeğini ortadan kaldırır ve tüm örnekler için yalnızca tek bir oluşturucu döndürür. Numunelerin her biri için etiket, dosyanın indeksidir. örneğin, get_all_samples('a.json', 'b.json')
a.json
tüm örneklerin etiketi 0 ve b.json
tüm örneklerin etiketi 1 olacaktır.
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
Ardından, bir kelime torbası benzeri model kullanarak örnekleri kodlayan bir yardımcı program ekleyin. İşte bir örnek: İki örnek topladığımızı varsayalım.
- Wi-Fi ağı A gücü 10'da ve Wi-Fi ağı B gücü 15'te
- Wi-Fi ağı B gücü 20'de ve Wi-Fi ağı C gücü 25'te.
Bu işlev, örneklerin her biri için üç sayıdan oluşan bir liste üretecektir: ilk değer, wifi ağı A'nın gücü, ikincisi ağ B için ve üçüncüsü C içindir. Gerçekte, biçim [A, B, C şeklindedir. ].
- [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]
Yukarıdaki üç yardımcı programın tümünü kullanarak, bir numune koleksiyonunu ve etiketlerini sentezliyoruz. get_all_samples
kullanarak tüm örnekleri ve etiketleri toplayın. Tüm örnekleri one-hot kodlamak için tutarlı bir format tanımlayın, ardından örneklere ordering
kodlamasını one_hot
. Son olarak, verileri oluşturun ve sırasıyla X
ve Y
matrislerini etiketleyin.
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
Bu işlevler veri hattını tamamlar. Ardından, model tahminini ve değerlendirmesini soyutlarız. Tahmin yöntemini tanımlayarak başlayın. İlk fonksiyon model çıktılarımızı normalleştirir, böylece tüm değerlerin toplamı 1 olur ve tüm değerler negatif olmaz; bu, çıktının geçerli bir olasılık dağılımı olmasını sağlar. İkincisi modeli değerlendirir.
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)
Ardından, modelin doğruluğunu değerlendirin. İlk satır, modeli kullanarak tahmini çalıştırır. İkincisi, hem tahmin edilen hem de gerçek değerlerin kaç kez uyuştuğunu sayar ve ardından toplam numune sayısına göre normalleştirir.
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
Bu, tahmin ve değerlendirme yardımcı programlarımızı tamamlar. Bu yardımcı programlardan sonra, veri kümesini toplayacak, eğitecek ve değerlendirecek bir main
işlev tanımlayın. sys.argv
komut satırındaki argümanların listesini okuyarak başlayın; bunlar eğitime dahil edilecek odalardır. Ardından, belirtilen tüm odalardan büyük bir veri kümesi oluşturun.
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)
Etiketlere tek sıcak kodlama uygulayın. Tek-sıcak kodlama , yukarıdaki kelime torbası modeline benzer; kategorik değişkenleri işlemek için bu kodlamayı kullanırız. Diyelim ki 3 olası etiketimiz var. 1, 2 veya 3 olarak etiketlemek yerine verileri [1, 0, 0], [0, 1, 0] veya [0, 0, 1] ile etiketliyoruz. Bu öğretici için, tek-sıcak kodlamanın neden önemli olduğuna ilişkin açıklamayı ayıracağız. Modeli eğitin ve hem tren hem de doğrulama kümelerinde değerlendirin.
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)
Her iki doğruluğu da yazdırın ve modeli diske kaydedin.
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()
Dosyanın sonunda main
işlevi çalıştırın.
if __name__ == '__main__': main()
Kaydet ve çık. Dosyanızın aşağıdakilerle eşleştiğini iki kez kontrol edin:
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()
Kaydet ve çık. 20 örneği kaydederken yukarıda kullanılan oda adını hatırlayın. Aşağıdaki bedroom
yerine bu adı kullanın. Örneğimiz bedroom
. Bir LAPACK hatasından gelen uyarıları yok saymak için -W ignore
geliriz.
python -W ignore model/train.py bedroom
Yalnızca bir oda için eğitim örnekleri topladığımızdan, %100 eğitim ve doğrulama doğruluğu görmeniz gerekir.
Train accuracy (100.0%), Validation accuracy (100.0%)
Ardından, bu eğitim komut dosyasını masaüstü uygulamasına bağlayacağız.
Adım 5: Tren Komut Dosyasını Bağlayın
Bu adımda, kullanıcı yeni bir grup numune topladığı zaman modeli otomatik olarak yeniden eğiteceğiz. scripts/observe.js
açın.
nano scripts/observe.js
fs
içe aktarma işleminden hemen sonra, alt süreç oluşturucuyu ve yardımcı programları içe aktarın.
var fs = require('fs'); // start new code const spawn = require("child_process").spawn; var utils = require('./utils.js');
ui
işlevinde, tamamlama işleyicisinin sonuna retrain
için aşağıdaki çağrıyı ekleyin.
function ui() { ... function completion() { ... retrain((data) => { var status = document.querySelector('#add-status'); accuracies = data.toString().split('\n')[0]; status.innerHTML = "Retraining succeeded: " + accuracies }); } ... }
ui
işlevinden sonra aşağıdaki retrain
işlevini ekleyin. Bu, python betiğini çalıştıracak bir alt süreç oluşturur. Tamamlandığında, süreç bir tamamlama işleyicisini çağırır. Başarısızlık durumunda, hata mesajını günlüğe kaydeder.
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()) }) }
Kaydet ve çık. scripts/utils.js
açın.
nano scripts/utils.js
data/
içindeki tüm veri kümelerini almak için aşağıdaki yardımcı programı ekleyin.
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 }
Kaydet ve çık. Bu adımın sonuçlanması için fiziksel olarak yeni bir yere gidin. İdeal olarak, orijinal konumunuz ile yeni konumunuz arasında bir duvar olmalıdır. Engeller ne kadar fazlaysa, masaüstü uygulamanız o kadar iyi çalışır.
Bir kez daha, masaüstü uygulamanızı çalıştırın.
npm start
Daha önce olduğu gibi, eğitim komut dosyasını çalıştırın. "Oda ekle"ye tıklayın.
İlk odanızdan farklı bir oda adı yazın. living room
kullanacağız.
“Kaydı başlat” seçeneğine tıkladığınızda, “Wifi için dinleniyor…” durumunu göreceksiniz.
20 örneğin tümü kaydedildiğinde, uygulamanız aşağıdakilerle eşleşir. Durum "Bitti. Yeniden eğitim modeli…”
Bir sonraki adımda, içinde bulunduğunuz odayı anında tahmin etmek için bu yeniden eğitilmiş modeli kullanacağız.
Adım 6: Python Değerlendirme Komut Dosyası Yazın
Bu adımda, önceden eğitilmiş model parametrelerini yükleyeceğiz, wifi ağlarını tarayacağız ve taramaya göre odayı tahmin edeceğiz.
model/eval.py
açın.
nano model/eval.py
Son betiğimizde kullanılan ve tanımlanan kitaplıkları içe aktarın.
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
Tüm veri kümelerinin adlarını çıkarmak için bir yardımcı program tanımlayın. Bu işlev, tüm veri kümelerinin data/
içinde <dataset>_train.json
ve <dataset>_test.json
olarak depolandığını varsayar.
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
işlevi tanımlayın ve eğitim komut dosyasından kaydedilen parametreleri yükleyerek başlayın.
def get_datasets(): ... def main(): w = np.load('w.npy') ordering = np.load('ordering.npy')
Veri kümesini oluşturun ve tahmin edin.
def main(): ... classpaths = [sys.argv[1]] X, _, _ = create_dataset(classpaths, ordering) y = np.asscalar(predict(X, w))
İlk iki olasılık arasındaki farka dayalı olarak bir güven puanı hesaplayın.
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)
Son olarak, kategoriyi çıkarın ve sonucu yazdırın. Komut dosyasını sonlandırmak için main
işlevi çağırın.
def main() ... category = get_datasets()[y] print(json.dumps({"category": category, "confidence": confidence})) if __name__ == '__main__': main()
Kaydet ve çık. Kodunuzun aşağıdakiyle eşleştiğini iki kez kontrol edin (kaynak kodu):
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()
Ardından, bu değerlendirme komut dosyasını masaüstü uygulamasına bağlayacağız. Masaüstü uygulaması, sürekli olarak wifi taramaları çalıştıracak ve kullanıcı arayüzünü tahmini oda ile güncelleyecektir.
7. Adım: Değerlendirmeyi Masaüstü Uygulamasına Bağlayın
Bu adımda, kullanıcı arayüzünü bir “güven” ekranı ile güncelleyeceğiz. Ardından, ilişkili NodeJS betiği sürekli olarak taramaları ve tahminleri çalıştıracak ve kullanıcı arayüzünü buna göre güncelleyecektir.
static/index.html
açın.
nano static/index.html
Başlıktan hemen sonra ve düğmelerden önce güven için bir satır ekleyin.
<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
hemen sonra, ancak body
sonundan önce, yeni bir komut dosyası predict.js
ekleyin.
</main> <!-- start new code --> <script> require('../scripts/predict.js') </script> <!-- end new code --> </body>
Kaydet ve çık. scripts/predict.js
açın.
nano scripts/predict.js
Dosya sistemi, yardımcı programlar ve alt süreç oluşturucu için gerekli NodeJS yardımcı programlarını içe aktarın.
var fs = require('fs'); var utils = require('./utils'); const spawn = require("child_process").spawn;
Wifi ağlarını algılamak için ayrı bir düğüm sürecini ve odayı tahmin etmek için ayrı bir Python sürecini çağıran bir predict
işlevi tanımlayın.
function predict(completion) { const nodeProcess = spawn('node', ["scripts/observe.js"]); const pythonProcess = spawn('python', ["-W", "ignore", "./model/eval.py", "samples.json"]); }
Her iki işlem de oluşturulduktan sonra, hem başarımlar hem de hatalar için Python işlemine geri aramalar ekleyin. Başarılı geri arama, bilgileri günlüğe kaydeder, tamamlama geri aramasını başlatır ve kullanıcı arabirimini tahmin ve güvenle günceller. Hata geri araması hatayı günlüğe kaydeder.
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
işlevini sonsuza kadar yinelemeli olarak çağırmak için bir ana işlev tanımlayın.
function main() { f = function() { predict(f) } predict(f) } main();
Son bir kez, canlı tahmini görmek için masaüstü uygulamasını açın.
npm start
Yaklaşık her saniye, bir tarama tamamlanacak ve arayüz, en son güven ve tahmini oda ile güncellenecektir. Tebrikler; tüm menzil içi WiFi ağlarına dayalı basit bir oda dedektörü tamamladınız.
Çözüm
Bu öğreticide, bir bina içindeki konumunuzu algılamak için yalnızca masaüstünüzü kullanarak bir çözüm oluşturduk. Electron JS kullanarak basit bir masaüstü uygulaması oluşturduk ve tüm menzil içi WiFi ağlarında basit bir makine öğrenimi yöntemi uyguladık. Bu, bakımı maliyetli (para açısından değil, zaman ve geliştirme açısından maliyet) cihaz dizilerine ihtiyaç duymadan Nesnelerin İnterneti uygulamalarının önünü açar.
Not : Kaynak kodunun tamamını Github üzerinde görebilirsiniz.
Zamanla, bu en küçük karelerin aslında olağanüstü bir performans göstermediğini görebilirsiniz. Tek bir oda içinde iki yer bulmayı deneyin veya kapı eşiğinde durun. En küçük kareler, kenar durumları arasında ayrım yapamayacak kadar büyük olacaktır. Daha iyisini yapabilir miyiz? Yapabileceğimiz ortaya çıktı ve gelecekteki derslerde daha iyi performans için diğer tekniklerden ve makine öğreniminin temellerinden yararlanacağız. Bu eğitim, gelecek deneyler için hızlı bir test yatağı görevi görür.