بناء كاشف غرفة لأجهزة إنترنت الأشياء على نظام التشغيل Mac OS

نشرت: 2022-03-10
ملخص سريع ↬ في هذا البرنامج التعليمي ، تقوم بإنشاء تطبيق سطح مكتب يتنبأ بالغرفة التي تستخدمها باستخدام خوارزمية تعلم آلي بسيطة: المربعات الصغرى. ينطبق الرمز على أي نظام أساسي ، لكننا نقدم فقط إرشادات تثبيت التبعية لنظام التشغيل Mac OSX.

تتيح معرفة الغرفة التي تتواجد بها العديد من تطبيقات إنترنت الأشياء - من تشغيل الضوء إلى تغيير القنوات التلفزيونية. إذن ، كيف يمكننا اكتشاف اللحظة التي تكون فيها أنت وهاتفك في المطبخ أو غرفة النوم أو غرفة المعيشة؟ مع الأجهزة السلعية اليوم ، هناك عدد لا يحصى من الاحتمالات:

يتمثل أحد الحلول في تجهيز كل غرفة بجهاز بلوتوث . بمجرد أن يكون هاتفك داخل نطاق جهاز بلوتوث ، سيعرف هاتفك الغرفة الموجودة فيه ، بناءً على جهاز البلوتوث. ومع ذلك ، فإن الحفاظ على مجموعة من أجهزة Bluetooth يمثل عبئًا كبيرًا - من استبدال البطاريات إلى استبدال الأجهزة المعطلة. بالإضافة إلى ذلك ، فإن القرب من جهاز Bluetooth ليس هو الحل دائمًا: إذا كنت في غرفة المعيشة ، بجوار الحائط المشترك مع المطبخ ، فلا يجب أن تبدأ أدوات المطبخ في إخراج الطعام.

الحل الآخر ، وإن كان غير عملي ، هو استخدام GPS . ومع ذلك ، ضع في اعتبارك أن نظام تحديد المواقع العالمي (GPS) يعمل بشكل سيء في الأماكن المغلقة حيث يتسبب العديد من الجدران والإشارات الأخرى والعقبات الأخرى في إحداث فوضى في دقة نظام تحديد المواقع العالمي (GPS).

نهجنا بدلاً من ذلك هو الاستفادة من جميع شبكات WiFi داخل النطاق - حتى تلك التي لم يتصل بها هاتفك. وإليك الطريقة: ضع في اعتبارك قوة WiFi A في المطبخ ؛ قل إنها 5. نظرًا لوجود جدار بين المطبخ وغرفة النوم ، يمكننا أن نتوقع بشكل معقول أن تختلف قوة WiFi A في غرفة النوم ؛ قل إنها 2. يمكننا استغلال هذا الاختلاف للتنبؤ بالغرفة التي نحن فيها. والأكثر من ذلك: لا يمكن اكتشاف شبكة WiFi B من جيراننا إلا من غرفة المعيشة ولكنها غير مرئية فعليًا من المطبخ. هذا يجعل التنبؤ أسهل. باختصار ، توفر لنا قائمة جميع شبكات WiFi الموجودة في النطاق معلومات وفيرة.

تتميز هذه الطريقة بمزايا مميزة تتمثل في:

  1. لا تتطلب المزيد من الأجهزة ؛
  2. الاعتماد على إشارات أكثر استقرارًا مثل WiFi ؛
  3. تعمل بشكل جيد حيث تكون التقنيات الأخرى مثل 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

بعد ذلك ، أضف أداة مساعدة تقوم بتشفير العينات باستخدام نموذج كيس من الكلمات. هذا مثال: افترض أننا نجمع عينتين.

  1. شبكة wifi A بقوة 10 وشبكة wifi B بقوة 15
  2. شبكة wifi B بقوة 20 وشبكة wifi C بقوة 25.

ستنتج هذه الوظيفة قائمة من ثلاثة أرقام لكل عينة: القيمة الأولى هي قوة شبكة wifi A ، والثانية للشبكة B ، والثالثة لـ C. في الواقع ، التنسيق هو [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]

باستخدام الأدوات الثلاثة المذكورة أعلاه ، نقوم بتجميع مجموعة من العينات وتسمياتها. اجمع كل العينات والتسميات باستخدام 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 ...".

بدء تسجيل "إضافة غرفة جديدة" للغرفة الثانية (معاينة كبيرة)

بمجرد تسجيل جميع العينات العشرين ، سيتطابق تطبيقك مع ما يلي. ستظهر الحالة "تم. نموذج إعادة التدريب ... "

انتهى التسجيل 2
صفحة "إضافة غرفة جديدة" بعد اكتمال التسجيل للغرفة الثانية (معاينة كبيرة)

في الخطوة التالية ، سنستخدم هذا النموذج المعاد تدريبه للتنبؤ بالغرفة التي تتواجد فيها أثناء التنقل.

الخطوة 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 الموجودة في النطاق.

تجريبي
تسجيل 20 عينة داخل الغرفة و 20 عينة أخرى في الردهة. عند العودة إلى الداخل ، يتنبأ النص بشكل صحيح بـ "المدخل" ثم "غرفة النوم". (معاينة كبيرة)

خاتمة

في هذا البرنامج التعليمي ، أنشأنا حلاً باستخدام سطح المكتب فقط لاكتشاف موقعك داخل المبنى. لقد أنشأنا تطبيقًا بسيطًا لسطح المكتب باستخدام Electron JS وطبقنا طريقة بسيطة للتعلم الآلي على جميع شبكات WiFi داخل النطاق. هذا يمهد الطريق لتطبيقات إنترنت الأشياء دون الحاجة إلى مصفوفات من الأجهزة التي تكون صيانتها باهظة (التكلفة ليس من حيث المال ولكن من حيث الوقت والتطوير).

ملاحظة : يمكنك رؤية الكود المصدري بالكامل على جيثب.

مع مرور الوقت ، قد تجد أن هذه المربعات الصغرى لا تعمل بشكل مذهل في الواقع. حاول العثور على موقعين داخل غرفة واحدة ، أو الوقوف في المداخل. ستكون المربعات الصغرى كبيرة غير قادرة على التمييز بين حالات الحافة. هل يمكننا أن نفعل ما هو أفضل؟ اتضح أننا نستطيع ، وفي الدروس المستقبلية ، سنستفيد من التقنيات الأخرى وأساسيات التعلم الآلي من أجل أداء أفضل. هذا البرنامج التعليمي بمثابة سرير اختبار سريع للتجارب القادمة.