بناء كاشف غرفة لأجهزة إنترنت الأشياء على نظام التشغيل Mac OS
نشرت: 2022-03-10تتيح معرفة الغرفة التي تتواجد بها العديد من تطبيقات إنترنت الأشياء - من تشغيل الضوء إلى تغيير القنوات التلفزيونية. إذن ، كيف يمكننا اكتشاف اللحظة التي تكون فيها أنت وهاتفك في المطبخ أو غرفة النوم أو غرفة المعيشة؟ مع الأجهزة السلعية اليوم ، هناك عدد لا يحصى من الاحتمالات:
يتمثل أحد الحلول في تجهيز كل غرفة بجهاز بلوتوث . بمجرد أن يكون هاتفك داخل نطاق جهاز بلوتوث ، سيعرف هاتفك الغرفة الموجودة فيه ، بناءً على جهاز البلوتوث. ومع ذلك ، فإن الحفاظ على مجموعة من أجهزة Bluetooth يمثل عبئًا كبيرًا - من استبدال البطاريات إلى استبدال الأجهزة المعطلة. بالإضافة إلى ذلك ، فإن القرب من جهاز Bluetooth ليس هو الحل دائمًا: إذا كنت في غرفة المعيشة ، بجوار الحائط المشترك مع المطبخ ، فلا يجب أن تبدأ أدوات المطبخ في إخراج الطعام.
الحل الآخر ، وإن كان غير عملي ، هو استخدام GPS . ومع ذلك ، ضع في اعتبارك أن نظام تحديد المواقع العالمي (GPS) يعمل بشكل سيء في الأماكن المغلقة حيث يتسبب العديد من الجدران والإشارات الأخرى والعقبات الأخرى في إحداث فوضى في دقة نظام تحديد المواقع العالمي (GPS).
نهجنا بدلاً من ذلك هو الاستفادة من جميع شبكات WiFi داخل النطاق - حتى تلك التي لم يتصل بها هاتفك. وإليك الطريقة: ضع في اعتبارك قوة WiFi A في المطبخ ؛ قل إنها 5. نظرًا لوجود جدار بين المطبخ وغرفة النوم ، يمكننا أن نتوقع بشكل معقول أن تختلف قوة WiFi A في غرفة النوم ؛ قل إنها 2. يمكننا استغلال هذا الاختلاف للتنبؤ بالغرفة التي نحن فيها. والأكثر من ذلك: لا يمكن اكتشاف شبكة WiFi B من جيراننا إلا من غرفة المعيشة ولكنها غير مرئية فعليًا من المطبخ. هذا يجعل التنبؤ أسهل. باختصار ، توفر لنا قائمة جميع شبكات WiFi الموجودة في النطاق معلومات وفيرة.
تتميز هذه الطريقة بمزايا مميزة تتمثل في:
- لا تتطلب المزيد من الأجهزة ؛
- الاعتماد على إشارات أكثر استقرارًا مثل WiFi ؛
- تعمل بشكل جيد حيث تكون التقنيات الأخرى مثل GPS ضعيفة.
كلما زاد عدد الجدران ، كان ذلك أفضل ، فكلما كانت نقاط قوة شبكة WiFi أكثر تباينًا ، كان تصنيف الغرف أسهل. ستنشئ تطبيق سطح مكتب بسيطًا يجمع البيانات ويتعلم من البيانات ويتوقع الغرفة التي تتواجد فيها في أي وقت.
مزيد من القراءة على SmashingMag:
- ظهور واجهة مستخدم محادثة ذكية
- تطبيقات التعلم الآلي للمصممين
- كيفية وضع نماذج أولية لتجارب إنترنت الأشياء: بناء الأجهزة
- تصميم لإنترنت الأشياء العاطفية
المتطلبات الأساسية
في هذا البرنامج التعليمي ، ستحتاج إلى نظام تشغيل Mac OSX. في حين أن الكود يمكن أن ينطبق على أي نظام أساسي ، فإننا سنقدم فقط تعليمات تثبيت التبعية لنظام التشغيل Mac.
- نظام التشغيل Mac OSX
- Homebrew ، مدير الحزم لنظام التشغيل Mac OSX. للتثبيت ، انسخ والصق الأمر في brew.sh
- تركيب NodeJS 10.8.0+ و npm
- تثبيت Python 3.6+ و pip. راجع الأقسام الثلاثة الأولى من "كيفية تثبيت virtualenv والتثبيت باستخدام pip وإدارة الحزم"
الخطوة 0: إعداد بيئة العمل
سيتم كتابة تطبيق سطح المكتب الخاص بك في NodeJS. ومع ذلك ، للاستفادة من مكتبات حسابية أكثر كفاءة مثل numpy
، سيتم كتابة رمز التدريب والتنبؤ بلغة Python. للبدء ، سنقوم بإعداد بيئاتك وتثبيت التبعيات. قم بإنشاء دليل جديد لإيواء مشروعك.
mkdir ~/riot
انتقل إلى الدليل.
cd ~/riot
استخدم النقطة لتثبيت مدير البيئة الافتراضية الافتراضي في Python.
sudo pip install virtualenv
قم بإنشاء بيئة افتراضية Python3.6 تسمى riot
.
virtualenv riot --python=python3.6
تفعيل البيئة الافتراضية.
source riot/bin/activate
موجهك يسبقه الآن (riot)
. يشير هذا إلى أننا دخلنا البيئة الافتراضية بنجاح. قم بتثبيت الحزم التالية باستخدام pip
:
-
numpy
: مكتبة جبر خطية فعالة -
scipy
: مكتبة حوسبة علمية تنفذ نماذج التعلم الآلي الشائعة
pip install numpy==1.14.3 scipy ==1.1.0
مع إعداد دليل العمل ، سنبدأ بتطبيق سطح المكتب الذي يسجل جميع شبكات WiFi في النطاق. ستشكل هذه التسجيلات بيانات تدريب لنموذج التعلم الآلي الخاص بك. بمجرد حصولنا على البيانات ، ستكتب مصنفًا للمربعات الصغرى ، مدربًا على إشارات WiFi التي تم جمعها مسبقًا. أخيرًا ، سنستخدم نموذج المربعات الصغرى للتنبؤ بالغرفة التي تتواجد فيها ، بناءً على شبكات WiFi في النطاق.
الخطوة 1: التطبيق الأولي لسطح المكتب
في هذه الخطوة ، سننشئ تطبيق سطح مكتب جديدًا باستخدام Electron JS. للبدء ، سنقوم بدلاً من ذلك بمدير حزمة Node npm
تنزيل أداة wget
.
brew install npm wget
للبدء ، سننشئ مشروع Node جديد.
npm init
يطالبك هذا باسم الحزمة ثم رقم الإصدار. اضغط على ENTER
لقبول الاسم الافتراضي لـ riot
والإصدار الافتراضي 1.0.0
.
package name: (riot) version: (1.0.0)
يطالبك هذا بوصف المشروع. أضف أي وصف غير فارغ تريده. أدناه ، الوصف هو room detector
description: room detector
يطالبك هذا بنقطة الإدخال أو الملف الرئيسي لتشغيل المشروع منه. أدخل app.js
entry point: (index.js) app.js
يطالبك هذا test command
git repository
. اضغط على ENTER
لتخطي هذه الحقول في الوقت الحالي.
test command: git repository:
هذا يطالبك keywords
author
. املأ أي قيم تريدها. أدناه ، نستخدم iot
و wifi
للكلمات الرئيسية ونستخدم John Doe
للمؤلف.
keywords: iot,wifi author: John Doe
يطالبك هذا للحصول على الترخيص. اضغط على ENTER
لقبول القيمة الافتراضية لـ ISC
.
license: (ISC)
في هذه المرحلة ، npm
بملخص للمعلومات حتى الآن. يجب أن يكون الإخراج الخاص بك مشابهًا لما يلي.
{ "name": "riot", "version": "1.0.0", "description": "room detector", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "iot", "wifi" ], "author": "John Doe", "license": "ISC" }
اضغط على ENTER
لقبول. ثم ينتج npm
package.json
. قائمة بجميع الملفات للتحقق مرة أخرى.
ls
سيؤدي هذا إلى إخراج الملف الوحيد في هذا الدليل ، إلى جانب مجلد البيئة الافتراضية.
package.json riot
قم بتثبيت تبعيات NodeJS لمشروعنا.
npm install electron --global # makes electron binary accessible globally npm install node-wifi --save
ابدأ بـ main.js
من بداية سريعة للإلكترون ، عن طريق تنزيل الملف باستخدام ما يلي. تعيد الوسيطة -O
التالية تسمية main.js
إلى app.js
wget https://raw.githubusercontent.com/electron/electron-quick-start/master/main.js -O app.js
افتح app.js
في nano
أو محرر النصوص المفضل لديك.
nano app.js
في السطر 12 ، قم بتغيير index.html إلى static / index.html ، حيث سنقوم بإنشاء دليل static
يحتوي على جميع قوالب HTML.
function createWindow () { // Create the browser window. win = new BrowserWindow({width: 1200, height: 800}) // and load the index.html of the app. win.loadFile('static/index.html') // Open the DevTools.
احفظ التغييرات واخرج من المحرر. يجب أن يتطابق ملفك مع الكود المصدري لملف app.js
الآن قم بإنشاء دليل جديد لإيواء قوالب HTML الخاصة بنا.
mkdir static
قم بتنزيل ورقة أنماط تم إنشاؤها لهذا المشروع.
wget https://raw.githubusercontent.com/alvinwan/riot/master/static/style.css?token=AB-ObfDtD46ANlqrObDanckTQJ2Q1Pyuks5bf79PwA%3D%3D -O static/style.css
افتح static/index.html
في nano
أو محرر النصوص المفضل لديك. ابدأ ببنية HTML القياسية.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Room Detector</title> </head> <body> <main> </main> </body> </html>
مباشرة بعد العنوان ، اربط خط مونتسيرات المرتبط بخطوط Google وورقة الأنماط.
<title>Riot | Room Detector</title> <!-- start new code --> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet"> <link href="style.css" rel="stylesheet"> <!-- end new code --> </head>
بين العلامات main
، أضف فتحة لاسم الغرفة المتوقع.
<main> <!-- start new code --> <p class="text">I believe you're in the</p> <h1 class="title">(I dunno)</h1> <!-- end new code --> </main>
يجب أن يتطابق البرنامج النصي الآن مع ما يلي تمامًا. اخرج من المحرر.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Room Detector</title> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet"> <link href="style.css" rel="stylesheet"> </head> <body> <main> <p class="text">I believe you're in the</p> <h1 class="title">(I dunno)</h1> </main> </body> </html>
الآن ، قم بتعديل ملف الحزمة ليحتوي على أمر بدء.
nano package.json
بعد السطر 7 مباشرةً ، أضف أمر start
يحمل اسمًا مستعارًا electron .
. تأكد من إضافة فاصلة في نهاية السطر السابق.
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "electron ." },
حفظ وخروج. أنت الآن جاهز لبدء تشغيل تطبيق سطح المكتب في Electron JS. استخدم npm
لبدء تشغيل التطبيق الخاص بك.
npm start
يجب أن يتطابق تطبيق سطح المكتب مع ما يلي.
هذا يكمل بدء تطبيق سطح المكتب الخاص بك. للخروج ، انتقل مرة أخرى إلى الجهاز الطرفي و CTRL + C. في الخطوة التالية ، سوف نسجل شبكات wifi ، ونجعل أداة التسجيل قابلة للوصول من خلال واجهة مستخدم تطبيق سطح المكتب.
الخطوة 2: تسجيل شبكات WiFi
في هذه الخطوة ، ستكتب برنامجًا نصيًا NodeJS يسجل قوة وتكرار جميع شبكات wifi داخل النطاق. قم بإنشاء دليل للنصوص الخاصة بك.
mkdir scripts
افتح scripts/observe.js
في nano
أو محرر النصوص المفضل لديك.
nano scripts/observe.js
استيراد أداة NodeJS wifi وكائن نظام الملفات.
var wifi = require('node-wifi'); var fs = require('fs');
حدد دالة record
تقبل معالج الإكمال.
/** * Uses a recursive function for repeated scans, since scans are asynchronous. */ function record(n, completion, hook) { }
داخل الوظيفة الجديدة ، قم بتهيئة أداة wifi. اضبط iface
على null للتهيئة إلى واجهة wifi عشوائية ، لأن هذه القيمة ليست ذات صلة حاليًا.
function record(n, completion, hook) { wifi.init({ iface : null }); }
حدد مصفوفة تحتوي على عيناتك. العينات هي بيانات تدريب سوف نستخدمها لنموذجنا. العينات في هذا البرنامج التعليمي المحدد عبارة عن قوائم بشبكات wifi داخل النطاق وما يرتبط بها من نقاط القوة والترددات والأسماء وما إلى ذلك.
function record(n, completion, hook) { ... samples = [] }
حدد startScan
للدالة العودية ، والتي ستبدأ عمليات مسح wifi بشكل غير متزامن. عند الانتهاء ، سيقوم فحص wifi غير المتزامن باستدعاء startScan
بشكل متكرر.
function record(n, completion, hook) { ... function startScan(i) { wifi.scan(function(err, networks) { }); } startScan(n); }
في رد wifi.scan
، تحقق من الأخطاء أو قوائم الشبكات الفارغة وأعد الفحص إذا كان الأمر كذلك.
wifi.scan(function(err, networks) { if (err || networks.length == 0) { startScan(i); return } });
أضف الحالة الأساسية للدالة العودية ، والتي تستدعي معالج الإكمال.
wifi.scan(function(err, networks) { ... if (i <= 0) { return completion({samples: samples}); } });
قم بإخراج تحديث للتقدم ، وإلحاق بقائمة العينات ، وإجراء مكالمة متكررة.
wifi.scan(function(err, networks) { ... hook(n-i+1, networks); samples.push(networks); startScan(i-1); });
في نهاية الملف الخاص بك ، قم باستدعاء وظيفة record
مع رد اتصال يحفظ عينات إلى ملف على القرص.
function record(completion) { ... } function cli() { record(1, function(data) { fs.writeFile('samples.json', JSON.stringify(data), 'utf8', function() {}); }, function(i, networks) { console.log(" * [INFO] Collected sample " + (21-i) + " with " + networks.length + " networks"); }) } cli();
تحقق جيدًا من تطابق ملفك مع ما يلي:
var wifi = require('node-wifi'); var fs = require('fs'); /** * Uses a recursive function for repeated scans, since scans are asynchronous. */ function record(n, completion, hook) { wifi.init({ iface : null // network interface, choose a random wifi interface if set to null }); samples = [] function startScan(i) { wifi.scan(function(err, networks) { if (err || networks.length == 0) { startScan(i); return } if (i <= 0) { return completion({samples: samples}); } hook(n-i+1, networks); samples.push(networks); startScan(i-1); }); } startScan(n); } function cli() { record(1, function(data) { fs.writeFile('samples.json', JSON.stringify(data), 'utf8', function() {}); }, function(i, networks) { console.log(" * [INFO] Collected sample " + i + " with " + networks.length + " networks"); }) } cli();
حفظ وخروج. قم بتشغيل البرنامج النصي.
node scripts/observe.js
سيتطابق الإخراج الخاص بك مع ما يلي ، مع عدد متغير من الشبكات.
* [INFO] Collected sample 1 with 39 networks
افحص العينات التي تم جمعها للتو. استخدم الأنابيب إلى json_pp
لطباعة JSON وأنبوب الرأس لعرض أول 16 سطرًا.
cat samples.json | json_pp | head -16
فيما يلي مثال لإخراج لشبكة 2.4 جيجا هرتز.
{ "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)" ] },
هذا يختتم البرنامج النصي الخاص بمسح wifi NodeJS. يتيح لنا ذلك عرض جميع شبكات WiFi الموجودة في النطاق. في الخطوة التالية ، ستجعل هذا البرنامج النصي يمكن الوصول إليه من تطبيق سطح المكتب.
الخطوة 3: قم بتوصيل Scan Script إلى تطبيق سطح المكتب
في هذه الخطوة ، ستقوم أولاً بإضافة زر إلى تطبيق سطح المكتب لتشغيل البرنامج النصي باستخدام. بعد ذلك ، ستقوم بتحديث واجهة مستخدم تطبيق سطح المكتب بتقدم البرنامج النصي.
افتح static/index.html
.
nano static/index.html
أدخل الزر "إضافة" ، كما هو موضح أدناه.
<h1 class="title">(I dunno)</h1> <!-- start new code --> <div class="buttons"> <a href="add.html" class="button">Add new room</a> </div> <!-- end new code --> </main>
حفظ وخروج. افتح static/add.html
.
nano static/add.html
الصق المحتوى التالي.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Riot | Add New Room</title> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet"> <link href="style.css" rel="stylesheet"> </head> <body> <main> <h1 class="title">0</h1> <p class="subtitle">of <span>20</span> samples needed. Feel free to move around the room.</p> <input type="text" class="text-field" placeholder="(room name)"> <div class="buttons"> <a href="#" class="button">Start recording</a> <a href="index.html" class="button light">Cancel</a> </div> <p class="text"></p> </main> <script> require('../scripts/observe.js') </script> </body> </html>
حفظ وخروج. أعد فتح scripts/observe.js
.
nano scripts/observe.js
أسفل وظيفة cli
، حدد دالة ui
جديدة.
function cli() { ... } // start new code function ui() { } // end new code cli();
قم بتحديث حالة تطبيق سطح المكتب للإشارة إلى بدء تشغيل الوظيفة.
function ui() { var room_name = document.querySelector('#add-room-name').value; var status = document.querySelector('#add-status'); var number = document.querySelector('#add-title'); status.style.display = "block" status.innerHTML = "Listening for wifi..." }
قسّم البيانات إلى مجموعات بيانات التدريب والتحقق من الصحة.
function ui() { ... function completion(data) { train_data = {samples: data['samples'].slice(0, 15)} test_data = {samples: data['samples'].slice(15)} var train_json = JSON.stringify(train_data); var test_json = JSON.stringify(test_data); } }
لا يزال ضمن رد استدعاء completion
، اكتب مجموعتي البيانات على القرص.
function ui() { ... function completion(data) { ... fs.writeFile('data/' + room_name + '_train.json', train_json, 'utf8', function() {}); fs.writeFile('data/' + room_name + '_test.json', test_json, 'utf8', function() {}); console.log(" * [INFO] Done") status.innerHTML = "Done." } }
استدعاء record
مع عمليات الاسترجاعات المناسبة لتسجيل 20 عينة وحفظ العينات على القرص.
function ui() { ... function completion(data) { ... } record(20, completion, function(i, networks) { number.innerHTML = i console.log(" * [INFO] Collected sample " + i + " with " + networks.length + " networks") }) }
أخيرًا ، قم باستدعاء دالتي cli
و ui
عند الاقتضاء. ابدأ بحذف cli();
استدعاء في الجزء السفلي من الملف.
function ui() { ... } cli(); // remove me
تحقق مما إذا كان كائن المستند يمكن الوصول إليه بشكل عام. إذا لم يكن الأمر كذلك ، يتم تشغيل البرنامج النصي من سطر الأوامر. في هذه الحالة ، قم باستدعاء وظيفة cli
. إذا كان الأمر كذلك ، فسيتم تحميل البرنامج النصي من داخل تطبيق سطح المكتب. في هذه الحالة ، قم بربط مستمع النقر بوظيفة ui
.
if (typeof document == 'undefined') { cli(); } else { document.querySelector('#start-recording').addEventListener('click', ui) }
حفظ وخروج. قم بإنشاء دليل يحتوي على بياناتنا.
mkdir data
قم بتشغيل تطبيق سطح المكتب.
npm start
سترى الصفحة الرئيسية التالية. انقر فوق "إضافة غرفة".
سترى النموذج التالي. اكتب اسمًا للغرفة. تذكر هذا الاسم ، حيث سنستخدمه لاحقًا. مثالنا سيكون bedroom
.
انقر فوق "بدء التسجيل" ، وسترى الحالة التالية "الاستماع لشبكة wifi ...".
بمجرد تسجيل جميع العينات العشرين ، سيتطابق تطبيقك مع ما يلي. ستظهر الحالة "تم".
انقر فوق "إلغاء" المسمى بشكل خاطئ للعودة إلى الصفحة الرئيسية ، والتي تطابق ما يلي.
يمكننا الآن فحص شبكات wifi من واجهة مستخدم سطح المكتب ، والتي ستحفظ جميع العينات المسجلة في الملفات الموجودة على القرص. بعد ذلك ، سنقوم بتدريب خوارزمية التعلم الآلي الجاهزة - المربعات الصغرى على البيانات التي جمعتها.
الخطوة 4: اكتب سيناريو تدريب بايثون
في هذه الخطوة ، سنكتب نصًا تدريبيًا بلغة بايثون. قم بإنشاء دليل لأدوات التدريب الخاصة بك.
mkdir model
فتح model/train.py
nano model/train.py
في الجزء العلوي من الملف الخاص بك ، قم باستيراد المكتبة الحسابية numpy
و scipy
لنموذج المربعات الصغرى.
import numpy as np from scipy.linalg import lstsq import json import sys
ستتعامل الأدوات المساعدة الثلاثة التالية مع تحميل البيانات وإعدادها من الملفات الموجودة على القرص. ابدأ بإضافة وظيفة مساعدة تعمل على تسطيح القوائم المتداخلة. سوف تستخدم هذا لتسطيح قائمة من العينات.
import sys def flatten(list_of_lists): """Flatten a list of lists to make a list. >>> flatten([[1], [2], [3, 4]]) [1, 2, 3, 4] """ return sum(list_of_lists, [])
أضف أداة مساعدة ثانية تقوم بتحميل عينات من الملفات المحددة. هذه الطريقة تلخص حقيقة أن العينات منتشرة عبر ملفات متعددة ، مما يعيد مولد واحد فقط لجميع العينات. لكل عينة ، التسمية هي فهرس الملف. على سبيل المثال ، إذا اتصلت get_all_samples('a.json', 'b.json')
، فستحتوي جميع العينات في a.json
على التصنيف 0 وستحتوي جميع العينات في b.json
على التصنيف 1.
def get_all_samples(paths): """Load all samples from JSON files.""" for label, path in enumerate(paths): with open(path) as f: for sample in json.load(f)['samples']: signal_levels = [ network['signal_level'].replace('RSSI', '') or 0 for network in sample] yield [network['mac'] for network in sample], signal_levels, label
بعد ذلك ، أضف أداة مساعدة تقوم بتشفير العينات باستخدام نموذج كيس من الكلمات. هذا مثال: افترض أننا نجمع عينتين.
- شبكة wifi A بقوة 10 وشبكة wifi B بقوة 15
- شبكة wifi B بقوة 20 وشبكة wifi C بقوة 25.
ستنتج هذه الوظيفة قائمة من ثلاثة أرقام لكل عينة: القيمة الأولى هي قوة شبكة wifi A ، والثانية للشبكة B ، والثالثة لـ C. في الواقع ، التنسيق هو [A ، B ، C ].
- [10 ، 15 ، 0]
- [0 ، 20 ، 25]
def bag_of_words(all_networks, all_strengths, ordering): """Apply bag-of-words encoding to categorical variables. >>> samples = bag_of_words( ... [['a', 'b'], ['b', 'c'], ['a', 'c']], ... [[1, 2], [2, 3], [1, 3]], ... ['a', 'b', 'c']) >>> next(samples) [1, 2, 0] >>> next(samples) [0, 2, 3] """ for networks, strengths in zip(all_networks, all_strengths): yield [strengths[networks.index(network)] if network in networks else 0 for network in ordering]
باستخدام الأدوات الثلاثة المذكورة أعلاه ، نقوم بتجميع مجموعة من العينات وتسمياتها. اجمع كل العينات والتسميات باستخدام get_all_samples
. حدد تنسيقًا متسقًا لترميز جميع العينات ، ثم one_hot
ordering
العينات. أخيرًا ، قم بإنشاء مصفوفات البيانات والتسمية X
و Y
على التوالي.
def create_dataset(classpaths, ordering=None): """Create dataset from a list of paths to JSON files.""" networks, strengths, labels = zip(*get_all_samples(classpaths)) if ordering is None: ordering = list(sorted(set(flatten(networks)))) X = np.array(list(bag_of_words(networks, strengths, ordering))).astype(np.float64) Y = np.array(list(labels)).astype(np.int) return X, Y, ordering
تكمل هذه الوظائف خط أنابيب البيانات. بعد ذلك ، نقوم بتجريد تنبؤ وتقييم النموذج. ابدأ بتحديد طريقة التنبؤ. تعمل الوظيفة الأولى على تسوية مخرجات نموذجنا ، بحيث يكون مجموع جميع القيم الإجمالية إلى 1 وأن جميع القيم غير سالبة ؛ هذا يضمن أن الناتج هو توزيع احتمالي صالح. الثاني يقيم النموذج.
def softmax(x): """Convert one-hotted outputs into probability distribution""" x = np.exp(x) return x / np.sum(x) def predict(X, w): """Predict using model parameters""" return np.argmax(softmax(X.dot(w)), axis=1)
بعد ذلك ، قم بتقييم دقة النموذج. يعمل السطر الأول على التنبؤ باستخدام النموذج. يحسب الثاني عدد المرات التي تتفق فيها القيم المتوقعة والحقيقية ، ثم يتم تطبيعها من خلال العدد الإجمالي للعينات.
def evaluate(X, Y, w): """Evaluate model w on samples X and labels Y.""" Y_pred = predict(X, w) accuracy = (Y == Y_pred).sum() / X.shape[0] return accuracy
هذا يخلصنا التنبؤ وتقييم المرافق. بعد هذه الأدوات المساعدة ، حدد وظيفة main
ستجمع مجموعة البيانات وتدريبها وتقييمها. ابدأ بقراءة قائمة الوسائط من سطر الأوامر sys.argv
؛ هذه هي الغرف التي يجب تضمينها في التدريب. ثم أنشئ مجموعة بيانات كبيرة من جميع الغرف المحددة.
def main(): classes = sys.argv[1:] train_paths = sorted(['data/{}_train.json'.format(name) for name in classes]) test_paths = sorted(['data/{}_test.json'.format(name) for name in classes]) X_train, Y_train, ordering = create_dataset(train_paths) X_test, Y_test, _ = create_dataset(test_paths, ordering=ordering)
تطبيق ترميز واحد ساخن على الملصقات. الترميز الواحد الساخن مشابه لنموذج كيس الكلمات أعلاه ؛ نستخدم هذا الترميز للتعامل مع المتغيرات الفئوية. لنفترض أن لدينا 3 تسميات محتملة. بدلاً من تصنيف 1 أو 2 أو 3 ، نقوم بتسمية البيانات بـ [1 ، 0 ، 0] ، [0 ، 1 ، 0] ، أو [0 ، 0 ، 1]. في هذا البرنامج التعليمي ، سنوفر شرحًا عن سبب أهمية التشفير الواحد الساخن. تدريب النموذج ، وتقييمه في كل من مجموعات التدريب والتحقق من الصحة.
def main(): ... X_test, Y_test, _ = create_dataset(test_paths, ordering=ordering) Y_train_oh = np.eye(len(classes))[Y_train] w, _, _, _ = lstsq(X_train, Y_train_oh) train_accuracy = evaluate(X_train, Y_train, w) test_accuracy = evaluate(X_test, Y_test, w)
اطبع كلاهما بدقة واحفظ النموذج على القرص.
def main(): ... print('Train accuracy ({}%), Validation accuracy ({}%)'.format(train_accuracy*100, test_accuracy*100)) np.save('w.npy', w) np.save('ordering.npy', np.array(ordering)) sys.stdout.flush()
في نهاية الملف ، قم بتشغيل الوظيفة main
.
if __name__ == '__main__': main()
حفظ وخروج. تحقق جيدًا من تطابق ملفك مع ما يلي:
import numpy as np from scipy.linalg import lstsq import json import sys def flatten(list_of_lists): """Flatten a list of lists to make a list. >>> flatten([[1], [2], [3, 4]]) [1, 2, 3, 4] """ return sum(list_of_lists, []) def get_all_samples(paths): """Load all samples from JSON files.""" for label, path in enumerate(paths): with open(path) as f: for sample in json.load(f)['samples']: signal_levels = [ network['signal_level'].replace('RSSI', '') or 0 for network in sample] yield [network['mac'] for network in sample], signal_levels, label def bag_of_words(all_networks, all_strengths, ordering): """Apply bag-of-words encoding to categorical variables. >>> samples = bag_of_words( ... [['a', 'b'], ['b', 'c'], ['a', 'c']], ... [[1, 2], [2, 3], [1, 3]], ... ['a', 'b', 'c']) >>> next(samples) [1, 2, 0] >>> next(samples) [0, 2, 3] """ for networks, strengths in zip(all_networks, all_strengths): yield [int(strengths[networks.index(network)]) if network in networks else 0 for network in ordering] def create_dataset(classpaths, ordering=None): """Create dataset from a list of paths to JSON files.""" networks, strengths, labels = zip(*get_all_samples(classpaths)) if ordering is None: ordering = list(sorted(set(flatten(networks)))) X = np.array(list(bag_of_words(networks, strengths, ordering))).astype(np.float64) Y = np.array(list(labels)).astype(np.int) return X, Y, ordering def softmax(x): """Convert one-hotted outputs into probability distribution""" x = np.exp(x) return x / np.sum(x) def predict(X, w): """Predict using model parameters""" return np.argmax(softmax(X.dot(w)), axis=1) def evaluate(X, Y, w): """Evaluate model w on samples X and labels Y.""" Y_pred = predict(X, w) accuracy = (Y == Y_pred).sum() / X.shape[0] return accuracy def main(): classes = sys.argv[1:] train_paths = sorted(['data/{}_train.json'.format(name) for name in classes]) test_paths = sorted(['data/{}_test.json'.format(name) for name in classes]) X_train, Y_train, ordering = create_dataset(train_paths) X_test, Y_test, _ = create_dataset(test_paths, ordering=ordering) Y_train_oh = np.eye(len(classes))[Y_train] w, _, _, _ = lstsq(X_train, Y_train_oh) train_accuracy = evaluate(X_train, Y_train, w) validation_accuracy = evaluate(X_test, Y_test, w) print('Train accuracy ({}%), Validation accuracy ({}%)'.format(train_accuracy*100, validation_accuracy*100)) np.save('w.npy', w) np.save('ordering.npy', np.array(ordering)) sys.stdout.flush() if __name__ == '__main__': main()
حفظ وخروج. تذكر اسم الغرفة المستخدم أعلاه عند تسجيل 20 عينة. استخدم هذا الاسم بدلاً من bedroom
أدناه. مثالنا هو bedroom
. نستخدم -W ignore
لتجاهل التحذيرات من خطأ LAPACK.
python -W ignore model/train.py bedroom
نظرًا لأننا قمنا بجمع عينات تدريب لغرفة واحدة فقط ، فمن المفترض أن ترى دقة تدريب وتحقق 100٪.
Train accuracy (100.0%), Validation accuracy (100.0%)
بعد ذلك ، سنقوم بربط نص التدريب هذا بتطبيق سطح المكتب.
الخطوة 5: ربط البرنامج النصي للقطار
في هذه الخطوة ، سنعيد تدريب النموذج تلقائيًا عندما يجمع المستخدم دفعة جديدة من العينات. افتح scripts/observe.js
.
nano scripts/observe.js
مباشرة بعد استيراد fs
، قم باستيراد أداة توليد العمليات الفرعية والأدوات المساعدة.
var fs = require('fs'); // start new code const spawn = require("child_process").spawn; var utils = require('./utils.js');
في وظيفة ui
، أضف الاستدعاء التالي retrain
في نهاية معالج الإكمال.
function ui() { ... function completion() { ... retrain((data) => { var status = document.querySelector('#add-status'); accuracies = data.toString().split('\n')[0]; status.innerHTML = "Retraining succeeded: " + accuracies }); } ... }
بعد وظيفة ui
، أضف وظيفة retrain
التالية. ينتج عن هذا عملية فرعية تقوم بتشغيل برنامج Python النصي. عند الانتهاء ، تستدعي العملية معالج الإكمال. عند الفشل ، سيتم تسجيل رسالة الخطأ.
function ui() { .. } function retrain(completion) { var filenames = utils.get_filenames() const pythonProcess = spawn('python', ["./model/train.py"].concat(filenames)); pythonProcess.stdout.on('data', completion); pythonProcess.stderr.on('data', (data) => { console.log(" * [ERROR] " + data.toString()) }) }
حفظ وخروج. افتح scripts/utils.js
.
nano scripts/utils.js
أضف الأداة المساعدة التالية لجلب جميع مجموعات البيانات في data/
.
var fs = require('fs'); module.exports = { get_filenames: get_filenames } function get_filenames() { filenames = new Set([]); fs.readdirSync("data/").forEach(function(filename) { filenames.add(filename.replace('_train', '').replace('_test', '').replace('.json', '' )) }); filenames = Array.from(filenames.values()) filenames.sort(); filenames.splice(filenames.indexOf('.DS_Store'), 1) return filenames }
حفظ وخروج. لإنهاء هذه الخطوة ، انتقل فعليًا إلى موقع جديد. من الناحية المثالية ، يجب أن يكون هناك جدار بين موقعك الأصلي وموقعك الجديد. كلما زادت الحواجز ، كان تطبيق سطح المكتب الخاص بك أفضل.
مرة أخرى ، قم بتشغيل تطبيق سطح المكتب الخاص بك.
npm start
تمامًا كما كان من قبل ، قم بتشغيل البرنامج النصي للتدريب. انقر فوق "إضافة غرفة".
اكتب اسم غرفة يختلف عن اسم غرفتك الأولى. سوف نستخدم living room
.
انقر فوق "بدء التسجيل" ، وسترى الحالة التالية "الاستماع لشبكة wifi ...".
بمجرد تسجيل جميع العينات العشرين ، سيتطابق تطبيقك مع ما يلي. ستظهر الحالة "تم. نموذج إعادة التدريب ... "
في الخطوة التالية ، سنستخدم هذا النموذج المعاد تدريبه للتنبؤ بالغرفة التي تتواجد فيها أثناء التنقل.
الخطوة 6: اكتب البرنامج النصي لتقييم Python
في هذه الخطوة ، سنقوم بتحميل معلمات النموذج المحددة مسبقًا ، والبحث عن شبكات wifi ، والتنبؤ بالغرفة بناءً على الفحص.
افتح model/eval.py
nano model/eval.py
مكتبات الاستيراد المستخدمة والمحددة في نصنا الأخير.
import numpy as np import sys import json import os import json from train import predict from train import softmax from train import create_dataset from train import evaluate
حدد أداة مساعدة لاستخراج أسماء جميع مجموعات البيانات. تفترض هذه الوظيفة أن جميع مجموعات البيانات مخزنة في data/
كـ <dataset>_train.json
و <dataset>_test.json
.
from train import evaluate def get_datasets(): """Extract dataset names.""" return sorted(list({path.split('_')[0] for path in os.listdir('./data') if '.DS' not in path}))
حدد الوظيفة main
، وابدأ بتحميل المعلمات المحفوظة من البرنامج النصي للتدريب.
def get_datasets(): ... def main(): w = np.load('w.npy') ordering = np.load('ordering.npy')
قم بإنشاء مجموعة البيانات وتوقع.
def main(): ... classpaths = [sys.argv[1]] X, _, _ = create_dataset(classpaths, ordering) y = np.asscalar(predict(X, w))
احسب درجة الثقة على أساس الفرق بين أعلى احتمالين.
def main(): ... sorted_y = sorted(softmax(X.dot(w)).flatten()) confidence = 1 if len(sorted_y) > 1: confidence = round(sorted_y[-1] - sorted_y[-2], 2)
أخيرًا ، قم باستخراج الفئة وطباعة النتيجة. لاختتام النص ، قم باستدعاء الوظيفة main
.
def main() ... category = get_datasets()[y] print(json.dumps({"category": category, "confidence": confidence})) if __name__ == '__main__': main()
حفظ وخروج. تحقق مرة أخرى من تطابق الكود الخاص بك مع ما يلي (رمز المصدر):
import numpy as np import sys import json import os import json from train import predict from train import softmax from train import create_dataset from train import evaluate def get_datasets(): """Extract dataset names.""" return sorted(list({path.split('_')[0] for path in os.listdir('./data') if '.DS' not in path})) def main(): w = np.load('w.npy') ordering = np.load('ordering.npy') classpaths = [sys.argv[1]] X, _, _ = create_dataset(classpaths, ordering) y = np.asscalar(predict(X, w)) sorted_y = sorted(softmax(X.dot(w)).flatten()) confidence = 1 if len(sorted_y) > 1: confidence = round(sorted_y[-1] - sorted_y[-2], 2) category = get_datasets()[y] print(json.dumps({"category": category, "confidence": confidence})) if __name__ == '__main__': main()
بعد ذلك ، سنقوم بتوصيل نص التقييم هذا بتطبيق سطح المكتب. سيقوم تطبيق سطح المكتب بتشغيل عمليات مسح wifi باستمرار وتحديث واجهة المستخدم بالغرفة المتوقعة.
الخطوة 7: ربط التقييم بتطبيق سطح المكتب
في هذه الخطوة ، سنقوم بتحديث واجهة المستخدم بعرض "موثوق". بعد ذلك ، سيقوم البرنامج النصي NodeJS المرتبط بإجراء عمليات المسح والتنبؤات باستمرار ، وتحديث واجهة المستخدم وفقًا لذلك.
افتح static/index.html
.
nano static/index.html
أضف سطرًا للثقة بعد العنوان مباشرةً وقبل الأزرار.
<h1 class="title">(I dunno)</h1> <!-- start new code --> <p class="subtitle">with <span>0%</span> confidence</p> <!-- end new code --> <div class="buttons">
مباشرة بعد main
ولكن قبل نهاية body
، أضف نصًا جديدًا predict.js
.
</main> <!-- start new code --> <script> require('../scripts/predict.js') </script> <!-- end new code --> </body>
حفظ وخروج. افتح scripts/predict.js
.
nano scripts/predict.js
قم باستيراد الأدوات المساعدة NodeJS المطلوبة لنظام الملفات ، والأدوات المساعدة ، ومولد العملية الفرعية.
var fs = require('fs'); var utils = require('./utils'); const spawn = require("child_process").spawn;
حدد وظيفة predict
التي تستدعي عملية عقدة منفصلة لاكتشاف شبكات wifi وعملية Python منفصلة للتنبؤ بالغرفة.
function predict(completion) { const nodeProcess = spawn('node', ["scripts/observe.js"]); const pythonProcess = spawn('python', ["-W", "ignore", "./model/eval.py", "samples.json"]); }
بعد ظهور كلتا العمليتين ، أضف عمليات رد نداء إلى عملية Python لكل من النجاحات والأخطاء. يسجل رد الاتصال النجاح المعلومات ، ويستدعي رد اتصال الإكمال ، ويحدّث واجهة المستخدم بالتنبؤ والثقة. يسجل رد الاتصال الخطأ الخطأ.
function predict(completion) { ... pythonProcess.stdout.on('data', (data) => { information = JSON.parse(data.toString()); console.log(" * [INFO] Room '" + information.category + "' with confidence '" + information.confidence + "'") completion() if (typeof document != "undefined") { document.querySelector('#predicted-room-name').innerHTML = information.category document.querySelector('#predicted-confidence').innerHTML = information.confidence } }); pythonProcess.stderr.on('data', (data) => { console.log(data.toString()); }) }
حدد وظيفة رئيسية لاستدعاء وظيفة predict
بشكل متكرر إلى الأبد.
function main() { f = function() { predict(f) } predict(f) } main();
للمرة الأخيرة ، افتح تطبيق سطح المكتب لمشاهدة التنبؤ المباشر.
npm start
تقريبًا كل ثانية ، سيتم الانتهاء من الفحص وسيتم تحديث الواجهة بأحدث غرفة ثقة وتوقع. تهانينا؛ لقد أكملت كاشف غرفة بسيطًا يعتمد على جميع شبكات WiFi الموجودة في النطاق.
خاتمة
في هذا البرنامج التعليمي ، أنشأنا حلاً باستخدام سطح المكتب فقط لاكتشاف موقعك داخل المبنى. لقد أنشأنا تطبيقًا بسيطًا لسطح المكتب باستخدام Electron JS وطبقنا طريقة بسيطة للتعلم الآلي على جميع شبكات WiFi داخل النطاق. هذا يمهد الطريق لتطبيقات إنترنت الأشياء دون الحاجة إلى مصفوفات من الأجهزة التي تكون صيانتها باهظة (التكلفة ليس من حيث المال ولكن من حيث الوقت والتطوير).
ملاحظة : يمكنك رؤية الكود المصدري بالكامل على جيثب.
مع مرور الوقت ، قد تجد أن هذه المربعات الصغرى لا تعمل بشكل مذهل في الواقع. حاول العثور على موقعين داخل غرفة واحدة ، أو الوقوف في المداخل. ستكون المربعات الصغرى كبيرة غير قادرة على التمييز بين حالات الحافة. هل يمكننا أن نفعل ما هو أفضل؟ اتضح أننا نستطيع ، وفي الدروس المستقبلية ، سنستفيد من التقنيات الأخرى وأساسيات التعلم الآلي من أجل أداء أفضل. هذا البرنامج التعليمي بمثابة سرير اختبار سريع للتجارب القادمة.