Fermer

août 29, 2018

Construire un détecteur de pièce pour les appareils IdO sur Mac OS


À propos de l'auteur

Doctorat en intelligence artificielle à l'Université de Californie à Berkeley, axé sur les petits réseaux neuronaux dans la perception, pour les véhicules autonomes. Grand fan de cheesecake, de corgis et de …
Pour en savoir plus sur Alvin

Dans ce didacticiel, vous apprendrez à créer une application de bureau qui prédit la pièce dans laquelle vous vous trouvez en utilisant un algorithme d'apprentissage automatique simple: les moindres carrés. Le code peut s'appliquer à n'importe quelle plate-forme – nous ne fournirons que des instructions d'installation de dépendance pour Mac OSX.

Connaître la pièce dans laquelle vous vous trouvez permet différentes applications IoT – de l'allumage à la modification des chaînes de télévision. Alors, comment pouvons-nous détecter le moment où vous et votre téléphone êtes dans la cuisine, la chambre ou le salon? Avec le matériel de base d'aujourd'hui, il existe une multitude de possibilités:

Une solution consiste à équiper chaque pièce d'un périphérique Bluetooth . Une fois que votre téléphone est à portée d'un périphérique Bluetooth, votre téléphone saura dans quelle pièce il se trouve, en fonction du périphérique Bluetooth. Cependant, la maintenance d’un ensemble de périphériques Bluetooth représente une charge considérable – du remplacement des batteries au remplacement de périphériques défectueux. De plus, la proximité de l’appareil Bluetooth n’est pas toujours la solution: si vous êtes dans le salon, près du mur partagé avec la cuisine, vos appareils de cuisine ne doivent pas commencer à produire de la nourriture.

Une autre solution à utiliser le GPS . Cependant, gardez à l'esprit que le GPS fonctionne mal à l'intérieur, dans lequel la multitude de murs, autres signaux et autres obstacles font des ravages sur la précision du GPS.

Notre approche est de tirer parti de tous les réseaux WiFi – même ceux auxquels votre téléphone n'est pas connecté. Voici comment: considérer la force du WiFi A dans la cuisine; disons que c’est 5. Comme il ya un mur entre la cuisine et la chambre, on peut raisonnablement s’attendre à ce que la force du WiFi A dans la chambre soit différente; disons que c’est 2. Nous pouvons exploiter cette différence pour prédire dans quelle pièce nous nous trouvons. De plus, le réseau WiFi B de notre voisin ne peut être détecté que depuis le salon, mais il est effectivement invisible de la cuisine. Cela rend la prédiction encore plus facile. En résumé, la liste de tous les réseaux WiFi en gamme nous fournit une information abondante.

Cette méthode présente les avantages suivants:

  1. ne nécessitant pas plus de matériel;
  2. fonctionnant avec des signaux plus stables comme le WiFi;
  3. fonctionnant bien où les autres techniques telles que le GPS sont faibles.

Plus il y a de murs, mieux c'est, car plus les forces du réseau WiFi sont disparates, plus les salles sont faciles à classer. Vous allez créer une application de bureau simple qui collecte des données, apprend à partir des données et prédit la pièce dans laquelle vous vous trouvez à un moment donné.

Pour en savoir plus sur SmashingMag:

Prérequis

, vous aurez besoin d'un Mac OSX. Alors que le code peut s’appliquer à n’importe quelle plate-forme, nous ne fournirons que des instructions d’installation de dépendance pour Mac.

Étape 0: Configuration de l'environnement de travail

Votre application de bureau sera écrite en NodeJS. Cependant, pour tirer parti de bibliothèques de calcul plus efficaces comme numpy le code de formation et de prédiction sera écrit en Python. Pour commencer, nous allons configurer vos environnements et installer des dépendances. Créez un nouveau répertoire pour héberger votre projet.

 mkdir ~ / riot

Accédez au répertoire.

 cd ~ / riot

Utilisez pip pour installer le gestionnaire d'environnement virtuel par défaut de Python.

 sudo pip install virtualenv

Créer un environnement virtuel Python3.6 nommé riot .

 virtualenv riot --python = python3.6

Activer l'environnement virtuel.

 source Riot / bin / activate

Votre invite est maintenant précédée de (émeute) . Cela indique que nous sommes entrés avec succès dans l'environnement virtuel. Installez les paquetages suivants à l'aide de pip :

  • numpy : Une bibliothèque d'algèbre linéaire efficace
  • scipy : bibliothèque de calcul scientifique implémentant des modèles d'apprentissage automatique populaires [19659033] pip install numpy == 1.14.3 scipy
    == 1.1.0

    Avec la configuration du répertoire de travail, nous allons commencer avec une application de bureau qui enregistre tous les réseaux WiFi à portée. Ces enregistrements constitueront des données d'entraînement pour votre modèle d'apprentissage automatique. Une fois les données disponibles, vous écrirez un classificateur des moindres carrés, formé sur les signaux WiFi collectés précédemment. Enfin, nous utiliserons le modèle des moindres carrés pour prédire la pièce dans laquelle vous vous trouvez, sur la base des réseaux WiFi à portée.

    Étape 1: Application de bureau initiale

    Dans cette étape, nous allons créer une nouvelle application de bureau en utilisant Electron JS. Pour commencer, nous allons plutôt utiliser le gestionnaire de paquets Node npm et un utilitaire de téléchargement wget .

     brew install npm wget
    

    Pour commencer, nous allons créer un nouveau projet Node.

     npm init
    

    Vous êtes invité à indiquer le nom du package, puis le numéro de version. Appuyez sur ENTER pour accepter le nom par défaut de anti-émeute et la version par défaut de 1.0.0 .

     nom du package: (émeute)
    version: (1.0.0)
    

    Ceci vous demande une description du projet. Ajoutez une description non vide que vous souhaitez. Ci-dessous, la description est Détecteur de pièce

     description: Détecteur de pièce
    

    Vous êtes invité à indiquer le point d'entrée ou le fichier principal à partir duquel exécuter le projet. Entrez app.js .

     Point d'entrée: (index.js) app.js
    

    Ceci vous invite à la commande de test et git repository . Cliquez sur ENTER pour ignorer ces champs pour le moment.

     commande de test:
    dépôt git:
    

    Ceci vous invite à mots clés et auteur . Remplissez les valeurs que vous souhaitez. Ci-dessous, nous utilisons iot wifi pour les mots-clés et utiliser John Doe pour l'auteur.

     mots-clés: wifi
    auteur: John Doe
    

    Vous êtes invité à entrer la licence. Hit ENTER pour accepter la valeur par défaut de ISC .

     licence: (ISC)
    

    A ce stade, npm vous demandera un résumé des informations à ce jour. Votre sortie doit être similaire à la suivante.

     {
      "nom": "émeute",
      "version": "1.0.0",
      "description": "détecteur de pièce",
      "main": "app.js",
      "scripts": {
        "test": "echo " Erreur: aucun test spécifié  "&& exit 1"
      },
      "mots-clés": [
        "iot",
        "wifi"
      ],
      "auteur": "John Doe",
      "licence": "ISC"
    }
    

    Hit ENTER pour accepter. npm produit alors un package.json . Lister tous les fichiers à vérifier.

     ls
    

    Cela affichera le seul fichier dans ce répertoire, ainsi que le dossier de l'environnement virtuel.

     package.json
    émeute
    

    Installez les dépendances NodeJS pour notre projet.

     npm install electron --global # rend les binaires électroniques accessibles globalement
    npm installer node-wifi --save
    

    Commencez avec main.js à partir d'Electron Quick Start en téléchargeant le fichier en utilisant ce qui suit. L'argument -O suivant renomme main.js en app.js .

     wget https://raw.githubusercontent.com/electron/electron- démarrage rapide / master / main.js -O app.js
    

    Ouvrez app.js dans nano ou votre éditeur de texte préféré.

     nano app.js
    

    Sur la ligne 12, remplacez index.html par static / index.html car nous allons créer un répertoire static pour contenir tous les modèles HTML. 19659021] function createWindow () {
      // Crée la fenêtre du navigateur.
      win = new BrowserWindow ({width: 1200, height: 800})

      // et charge le index.html de l'application.
      win.loadFile ('static / index.html')

      // Ouvre les DevTools.

    Enregistrez vos modifications et quittez l'éditeur. Votre fichier doit correspondre au code source du fichier app.js . Maintenant, créez un nouveau répertoire pour héberger nos modèles HTML.

     mkdir static
    

    Téléchargez une feuille de style créée pour ce projet.

     wget https://raw.githubusercontent.com/alvinwan/riot/master/static/style.css?token=AB-ObfDtD46ANlqrObDanckTQJ2Q1Pyuks5bf79PwA%3D%3D -O static / style.css
    

    Ouvrir static / index.html dans nano ou votre éditeur de texte préféré. Commencez avec la structure HTML standard.

    
      
        
          
           Riot | Détecteur de pièce 
        
        
          
          
           

    Juste après le titre, liez la police Montserrat liée par Google Fonts et la feuille de style.

     Riot | Détecteur de pièce 
      
      
      
      
    
    

    Entre les balises principale ajoutez un emplacement pour le nom de pièce prédit.

    Je crois que vous êtes dans le

    (I dunno)

      

    Votre script devrait maintenant correspondre exactement à ce qui suit. Quittez l'éditeur.

    
      
        
          
           Riot | Détecteur de pièce 
          
          
        
        
          

    Je crois que vous êtes dans le

    (I dunno)

          
           

    Modifiez maintenant le fichier de package pour qu'il contienne une commande de démarrage.

     nano package.json
    

    Juste après la ligne 7, ajouter une commande de start qui est aliasée à electron. . Veillez à ajouter une virgule à la fin de la ligne précédente.

     "scripts": {
      "test": "echo " Erreur: aucun test spécifié  "&& exit 1",
      "commencer": "électron"
    },
    

    Enregistrez et quittez. Vous êtes maintenant prêt à lancer votre application de bureau dans Electron JS. Utilisez npm pour lancer votre application.

     npm start
    

    Votre application de bureau doit correspondre à la suivante:


     page d'accueil avec le bouton
    Page d'accueil avec le bouton "Ajouter une nouvelle pièce" disponible ( Grand aperçu )

    Ceci complète votre application de bureau de démarrage. Pour quitter, retournez à votre terminal et CTRL + C. À l'étape suivante, nous enregistrerons les réseaux wifi et rendrons l'utilitaire d'enregistrement accessible via l'interface utilisateur de l'application de bureau.

    Étape 2: Enregistrement de réseaux WiFi

    Dans cette étape, vous allez écrire un script NodeJS fréquence de tous les réseaux wifi dans la gamme. Créez un répertoire pour vos scripts.

     scripts mkdir
    

    Ouvrez scripts / observe.js dans nano ou votre éditeur de texte préféré.

     nano scripts / observe.js
    

    Importer un utilitaire wifi NodeJS et l'objet système de fichiers.

     var wifi = require ('node-wifi');
    var fs = require ('fs');
    

    Définir une fonction d'enregistrement qui accepte un gestionnaire d'achèvement.

     / **
     * Utilise une fonction récursive pour les analyses répétées, car les analyses sont asynchrones.
     * /
    enregistrement de fonction (n, complétion, hook) {
    }
    

    Dans la nouvelle fonction, initialisez l'utilitaire wifi. Définissez iface sur null pour l'initialiser sur une interface wifi aléatoire, car cette valeur n'est pas pertinente pour le moment.

     enregistrement de fonction (n, completion, hook) {
        wifi.init ({
            iface: null
        })
    }
    

    Définissez un tableau pour contenir vos échantillons. Les échantillons sont des données d'entraînement que nous utiliserons pour notre modèle. Les exemples de ce tutoriel sont des listes de réseaux Wi-Fi de gamme et leurs forces, fréquences, noms, etc. associés.

     enregistrement de fonction (n, complétion, hook) {
        ...
        échantillons = []
    }
    

    Définissez une fonction récursive startScan qui lancera de manière asynchrone des analyses wifi. Une fois terminé, le scan asynchrone wifi invoquera récursivement startScan .

     enregistrement de fonction (n, achèvement, hook) {
      ...
      fonction startScan (i) {
        wifi.scan (fonction (err, réseaux) {
        })
      }
      startScan (n);
    }
    

    Dans le rappel wifi.scan vérifiez les erreurs ou les listes de réseaux vides et redémarrez l'analyse si c'est le cas.

     wifi.scan (fonction (err, networks) {
      if (err || networks.length == 0) {
        startScan (i);
        revenir
      }
    })
    

    Ajoutez le cas de base de la fonction récursive, qui appelle le gestionnaire d'achèvement.

     wifi.scan (function (err, networks) {
      ...
      if (i 

    Affiche une mise à jour de la progression, ajoute à la liste des échantillons et effectue l'appel récursif.

     wifi.scan (function (err, networks) {
      ...
      crochet (n-i + 1, réseaux);
      samples.push (réseaux);
      startScan (i-1);
    })
    

    À la fin de votre fichier, appelez la fonction record avec un rappel qui enregistre les échantillons dans un fichier sur le disque.

     enregistrement de fonction (achèvement) {
      ...
    }
    
    fonction cli () {
      enregistrement (1, fonction (données) {
        fs.writeFile ('samples.json', JSON.stringify (data), 'utf8', function () {});
      }, fonction (i, réseaux) {
        console.log ("* [INFO] Échantillon collecté" + (21-i) + "avec" + networks.length + "networks");
      })
    }
    
    cli ();
    

    Vérifiez que votre fichier correspond à ce qui suit:

     var wifi = require ('node-wifi');
    var fs = require ('fs');
    
    / **
     * Utilise une fonction récursive pour les analyses répétées, car les analyses sont asynchrones.
     * /
    enregistrement de fonction (n, complétion, hook) {
      wifi.init ({
          iface: null // interface réseau, choisissez une interface wifi aléatoire si elle est définie sur null
      })
    
      échantillons = []
      fonction startScan (i) {
        wifi.scan (fonction (err, réseaux) {
            if (err || networks.length == 0) {
              startScan (i);
              revenir
            }
            if (i 

    Enregistrer et quitter. Exécuter le script.

     node scripts / observe.js
    

    Votre sortie correspondra aux éléments suivants, avec un nombre variable de réseaux.

     * [INFO] Échantillon recueilli 1 avec 39 réseaux
    

    Examinez les échantillons qui viennent d'être recueillis. Pipe to json_pp pour imprimer assez le JSON et le pipe to head pour voir les 16 premières lignes.

     cat samples.json | json_pp | tête -16
    

    L'exemple ci-dessous est un exemple de sortie pour un réseau 2,4 GHz.

     {
      "samples": [
        [
          {
            "mac": "64:0f:28:79:9a:29",
            "bssid": "64:0f:28:79:9a:29",
            "ssid": "SMASHINGMAGAZINEROCKS",
             "channel": 4,
             "frequency": 2427,
              "signal_level": "-91",
              "security": "WPA WPA2",
              "security_flags": [
               "(PSK/AES,TKIP/TKIP)",
              "(PSK/AES,TKIP/TKIP)"
            ]
          },
    

    Ceci conclut votre script de numérisation wifi NodeJS. Cela nous permet de voir tous les réseaux WiFi dans la gamme. Dans l'étape suivante, vous rendrez ce script accessible depuis l'application de bureau.

    Étape 3: Connectez le script de numérisation à l'application de bureau

    Dans cette étape, vous ajouterez d'abord un bouton à l'application de bureau pour déclencher le script avec . Ensuite, vous mettrez à jour l'interface utilisateur du bureau avec la progression du script.

    Ouvrir static / index.html .

     nano static / index.html
    

    Insérer le bouton "Ajouter", comme illustré ci-dessous.

    (I dunno)

                 

    Enregistrez et quittez. Ouvrez static / add.html .

     nano static / add.html
    

    Collez le contenu suivant.

    
      
        
          
           Riot | Ajouter une nouvelle pièce 
          
          
        
        
          

    0

    de 20 échantillons nécessaires. N'hésitez pas à vous déplacer dans la pièce.

           

    Enregistrez et quittez. Rouvrez scripts / observe.js .

     nano scripts / observe.js
    

    Sous la fonction cli définissez une nouvelle fonction ui .

     function cli () {
        ...
    }
    
    // commence le nouveau code
    fonction ui () {
    }
    // met fin au nouveau code
    
    cli ();
    

    Mettez à jour l'état de l'application de bureau pour indiquer que la fonction a commencé à s'exécuter.

     function ui () {
      var room_name = document.querySelector ('# add-room-name'). valeur;
      var status = document.querySelector ('# add-status');
      var number = document.querySelector ('# add-title');
      status.style.display = "bloquer"
      status.innerHTML = "Ecouter le wifi ..."
    }
    

    Partitionnez les données en jeux de données d'entraînement et de validation.

     function ui () {
      ...
      achèvement de la fonction (données) {
        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);
      }
    }
    

    Toujours dans le rappel écrivez les deux jeux de données sur le disque.

     function ui () {
      ...
      achèvement de la fonction (données) {
        ...
        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] Terminé")
        status.innerHTML = "Fait"
      }
    }
    

    Appelez l'enregistrement avec les rappels appropriés pour enregistrer 20 échantillons et enregistrer les échantillons sur le disque.

     function ui () {
      ...
      achèvement de la fonction (données) {
        ...
      }
      enregistrement (20, achèvement, fonction (i, réseaux) {
        number.innerHTML = i
        console.log ("* [INFO] Exemple de collecte" + i + "avec" + networks.length + "networks")
      })
    }
    

    Enfin, invoquer les fonctions cli et ui le cas échéant. Commencez par supprimer l'appel cli (); au bas du fichier.

     function ui () {
        ...
    }
    
    cli (); // me retirer
    

    Vérifiez si l'objet du document est accessible globalement. Sinon, le script est exécuté à partir de la ligne de commande. Dans ce cas, appelez la fonction cli . Si c'est le cas, le script est chargé depuis l'application de bureau. Dans ce cas, associez l'écouteur de clic à la fonction ui .

     if (typeof document == 'undefined') {
        cli ();
    } autre {
        document.querySelector ('# start-recording'). addEventListener ('click', ui)
    }
    

    Enregistrez et quittez. Créez un répertoire contenant nos données.

     mkdir data
    

    Lancez l'application de bureau.

     npm start
    

    Vous verrez la page d'accueil suivante. Cliquez sur "Ajouter une pièce".


    ( Grand aperçu )

    Vous verrez le formulaire suivant. Tapez un nom pour la salle. Rappelez-vous ce nom, comme nous l'utiliserons plus tard. Notre exemple sera chambre à coucher .


     Page de la nouvelle salle
    Page "Ajouter une nouvelle salle" à charger ( Grand aperçu )

    Cliquez sur "Démarrer l'enregistrement" et vous verrez le statut suivant "Ecoute pour le wifi ...".


     Début de l'enregistrement
    "Ajouter une nouvelle salle" pour lancer l'enregistrement ( Grand aperçu )

    Une fois que tous les 20 échantillons sont enregistrés, votre application correspondra à ce qui suit. L'état indiquera "Terminé".


    La page "Ajouter une nouvelle pièce" après l'enregistrement est terminée ( Grand aperçu )

    Cliquez sur le lien "Annuler" pour revenir à la page d'accueil, qui correspond à la suivante.


     a terminé l'enregistrement
    La page "Ajouter une pièce" après l'enregistrement est terminée ( Grand aperçu )

    Nous pouvons maintenant analyser les réseaux wifi à partir de l'interface utilisateur du bureau, ce qui permettra d'enregistrer tous les échantillons enregistrés dans des fichiers sur le disque. Ensuite, nous allons former un algorithme d'apprentissage automatique prêt à l'emploi - les moindres carrés sur les données que vous avez collectées.

    Étape 4: Ecrire un script de formation Python

    Créez un répertoire pour vos utilitaires de formation.

     Modèle mkdir
    

    Open model / train.py

     nano model / train.py
    

    En haut de votre fichier, importez la bibliothèque de calcul numpy et scipy pour son modèle des moindres carrés.

     import numpy as np
    à partir de scipy.linalg import lstsq
    importer json
    import sys
    

    Les trois utilitaires suivants gèrent le chargement et la configuration des données à partir des fichiers sur le disque. Commencez par ajouter une fonction utilitaire qui aplatit les listes imbriquées. Vous allez l'utiliser pour aplatir une liste de liste d'échantillons.

     import sys
    
    def flatten (list_of_lists):
        "" "Aplatir une liste de listes pour en faire une liste.
        >>> aplatir ([[1][2][3, 4]])
        [1, 2, 3, 4]
        "" "
        somme de retour (list_of_lists, [])
    

    Ajoutez un deuxième utilitaire qui charge des exemples à partir des fichiers spécifiés. Cette méthode fait abstraction du fait que les échantillons sont répartis sur plusieurs fichiers, ne renvoyant qu’un seul générateur pour tous les échantillons. Pour chacun des échantillons, l'étiquette est l'index du fichier. Par exemple, si vous appelez get_all_samples ('a.json', 'b.json') tous les échantillons de a.json porteront l'étiquette 0 et tous les échantillons de b .json aura le label 1.

     def get_all_samples (chemins):
      "" "Charger tous les échantillons des fichiers JSON." ""
      pour label, chemin en enumerate (chemins):
      avec open (path) comme f:
        pour un exemple dans json.load (f) ['samples']:
          signal_levels = [
            network['signal_level'] .replace ('RSSI', '') ou 0
            pour le réseau dans l'échantillon]
          rendement [network['mac'] pour le réseau dans l'échantillon]signal_levels, label
    

    Ensuite, ajoutez un utilitaire qui code les échantillons à l'aide d'un modèle de type bag-of-words. Voici un exemple: Supposons que nous collections deux échantillons.

    1. Réseau wifi A à la puissance 10 et réseau wifi B à la puissance 15
    2. Réseau wifi B à la puissance 20 et réseau wifi C à la puissance 25.

    produire une liste de trois nombres pour chacun des échantillons: la première valeur est la force du réseau wifi A, la deuxième pour le réseau B et la troisième pour C. En fait, le format est [A, B, C].

    1. [10, 15, 0]
    2. [0, 20, 25]
      def bag_of_words (all_networks, all_strengths, ordering):
      Msgstr "" "Appliquer le codage des mots de passe aux variables catégorielles.
    
      >>> samples = bag_of_words (
      ... [['a', 'b']['b', 'c']['a', 'c']],
      ... [[1, 2][2, 3][1, 3]],
      ... ['a', 'b', 'c'])
      >>> suivant (échantillons)
      [1, 2, 0]
      >>> suivant (échantillons)
      [0, 2, 3]
      "" "
      pour les réseaux, les points forts de zip (all_networks, all_strengths):
        rendement [strengths[networks.index(network)]
          si le réseau dans les réseaux sinon 0
          pour le réseau dans la commande]
    

    En utilisant les trois utilitaires ci-dessus, nous synthétisons une collection d'échantillons et leurs étiquettes. Rassemblez tous les échantillons et étiquettes en utilisant get_all_samples . Définissez un format cohérent ordonnant à coder tous les échantillons à chaud, puis appliquez le codage one_hot aux échantillons. Enfin, construisez respectivement les matrices de données et d'étiquettes X et Y .

     def create_dataset (classpaths, ordering = None):
      "" "Créer un ensemble de données à partir d'une liste de chemins d'accès aux fichiers JSON." ""
      réseaux, forces, labels = zip (* get_all_samples (classpaths))
      si la commande est Aucune:
        ordering = list (trié (set (aplatir (réseaux))))
      X = np.array (liste (bag_of_words (réseaux, forces, classement))). Astype (np.float64)
      Y = np.array (liste (labels)). Astype (np.int)
      retourner X, Y, ordonner
    

    Ces fonctions complètent le pipeline de données. Ensuite, nous décrivons la prédiction et l'évaluation des modèles éloignés. Commencez par définir la méthode de prédiction. La première fonction normalise les sorties de notre modèle, de sorte que la somme de toutes les valeurs s’élève à 1 et que toutes les valeurs sont non négatives; Cela garantit que la sortie est une distribution de probabilité valide. La seconde évalue le modèle.

     def softmax (x):
      "" Convertir les sorties à un point en distribution de probabilité "" "
      x = np.exp (x)
      return x / np.sum (x)
    
    
    def prédire (X, w):
      "" "Prédire en utilisant les paramètres du modèle" ""
      return np.argmax (softmax (X.dot (w)), axis = 1)
    

    Ensuite, évaluez la précision du modèle. La première ligne exécute la prédiction en utilisant le modèle. La seconde compte les nombres de fois où les valeurs prédites et vraies sont en accord, puis se normalise par le nombre total d'échantillons.

     def evaluation (X, Y, w):
      "" "Évaluez le modèle W sur les échantillons X et les étiquettes Y." ""
      Y_pred = prédire (X, w)
      précision = (Y == Y_pred) .sum () / X.shape [0]
      précision de retour
    

    Ceci conclut nos utilitaires de prévision et d'évaluation. Après ces utilitaires, définissez une fonction principale qui collectera le jeu de données, formera et évaluera. Commencez par lire la liste des arguments de la ligne de commande sys.argv ; Ce sont les salles à inclure dans la formation. Créez ensuite un ensemble de données volumineux à partir de toutes les pièces spécifiées.

     def main ():
      classes = sys.argv [1:]
    
      train_paths = trié (['data/{}_train.json'.format(name) for name in classes])
      chemins_test = classés (['data/{}_test.json'.format(name) for name in classes])
      X_train, Y_train, ordering = create_dataset (train_paths)
      X_test, Y_test, _ = create_dataset (chemins_test, commande = commande)
    

    Appliquez un encodage à chaud aux étiquettes. Un codage à chaud unique est similaire au modèle ci-dessus contenant des mots-clés; nous utilisons cet encodage pour gérer les variables catégorielles. Disons que nous avons 3 étiquettes possibles. Au lieu d'étiqueter 1, 2 ou 3, nous étiquetons les données avec [1, 0, 0][0, 1, 0] ou [0, 0, 1]. Pour ce tutoriel, nous expliquerons pourquoi l’encodage à un seul point est important. Entraînez le modèle et évaluez à la fois le train et les ensembles de validation.

     def main ():
      ...
      X_test, Y_test, _ = create_dataset (chemins_test, commande = commande)
      
      Y_train_oh = np.eye (len (classes)) [Y_train]
      w, _, _, _ = lstsq (X_train, Y_train_oh)
      train_accuracy = évaluer (X_train, Y_train, w)
      test_accuracy = évaluer (X_test, Y_test, w)
    

    Imprimez les deux précisions et enregistrez le modèle sur le disque.

     def main ():
      ...
      print ('Précision du train ({}%), Précision de la validation ({}%)'. format (train_accuracy * 100, test_accuracy * 100))
      np.save ('w.npy', w)
      np.save ('ordering.npy', np.array (commande))
      sys.stdout.flush ()
    

    A la fin du fichier, exécutez la fonction main .

     si __name__ == '__main__':
      principale()
    

    Enregistrez et quittez. Vérifiez que votre fichier correspond aux éléments suivants:

     import numpy as np
    à partir de scipy.linalg import lstsq
    importer json
    import sys
    
    
    def flatten (list_of_lists):
        "" "Aplatir une liste de listes pour en faire une liste.
        >>> aplatir ([[1][2][3, 4]])
        [1, 2, 3, 4]
        "" "
        somme de retour (list_of_lists, [])
    
    
    def get_all_samples (chemins):
        "" "Charger tous les échantillons des fichiers JSON." ""
        pour label, chemin en enumerate (chemins):
            avec open (path) comme f:
                pour un exemple dans json.load (f) ['samples']:
                    signal_levels = [
                        network['signal_level'] .replace ('RSSI', '') ou 0
                        pour le réseau dans l'échantillon]
                    rendement [network['mac'] pour le réseau dans l'échantillon]signal_levels, label
    
    
    def bag_of_words (all_networks, all_strengths, ordering):
        Msgstr "" "Appliquer le codage des mots de passe aux variables catégorielles.
        >>> samples = bag_of_words (
        ... [['a', 'b']['b', 'c']['a', 'c']],
        ... [[1, 2][2, 3][1, 3]],
        ... ['a', 'b', 'c'])
        >>> suivant (échantillons)
        [1, 2, 0]
        >>> suivant (échantillons)
        [0, 2, 3]
        "" "
        pour les réseaux, les points forts de zip (all_networks, all_strengths):
            rendement [int(strengths[networks.index(network)])
                si le réseau dans les réseaux sinon 0
                pour le réseau dans la commande]
    
    
    def create_dataset (classpaths, ordering = None):
        "" "Créer un ensemble de données à partir d'une liste de chemins d'accès aux fichiers JSON." ""
        réseaux, forces, labels = zip (* get_all_samples (classpaths))
        si la commande est Aucune:
            ordering = list (trié (set (aplatir (réseaux))))
        X = np.array (liste (bag_of_words (réseaux, forces, classement))). Astype (np.float64)
        Y = np.array (liste (labels)). Astype (np.int)
        retourner X, Y, ordonner
    
    
    def softmax (x):
        "" Convertir les sorties à un point en distribution de probabilité "" "
        x = np.exp (x)
        return x / np.sum (x)
    
    
    def prédire (X, w):
        "" "Prédire en utilisant les paramètres du modèle" ""
        return np.argmax (softmax (X.dot (w)), axis = 1)
    
    
    def évaluer (X, Y, w):
        "" "Évaluez le modèle W sur les échantillons X et les étiquettes Y." ""
        Y_pred = prédire (X, w)
        précision = (Y == Y_pred) .sum () / X.shape [0]
        précision de retour
    
    
    def main ():
        classes = sys.argv [1:]
    
        train_paths = trié (['data/{}_train.json'.format(name) for name in classes])
        chemins_test = classés (['data/{}_test.json'.format(name) for name in classes])
        X_train, Y_train, ordering = create_dataset (train_paths)
        X_test, Y_test, _ = create_dataset (chemins_test, commande = commande)
    
        Y_train_oh = np.eye (len (classes)) [Y_train]
        w, _, _, _ = lstsq (X_train, Y_train_oh)
        train_accuracy = évaluer (X_train, Y_train, w)
        validation_accurat = évaluer (X_test, Y_test, w)
    
        print ('Précision du train ({}%), Précision de la validation ({}%)'. format (train_accuracy * 100, validation_accure * 100))
        np.save ('w.npy', w)
        np.save ('ordering.npy', np.array (commande))
        sys.stdout.flush ()
    
    
    si __name__ == '__main__':
        principale()
    

    Enregistrez et quittez. Rappelez-vous le nom de la pièce utilisé ci-dessus lors de l'enregistrement des 20 échantillons. Utilisez ce nom au lieu de bedroom ci-dessous. Notre exemple est chambre à coucher . Nous utilisons -W ignore pour ignorer les avertissements d'un bogue LAPACK.

     python -W ignore le modèle / train.py
    

    Comme nous n'avons collecté que des échantillons de formation pour une salle, vous devriez voir une précision de 100% pour l'entraînement et la validation.

     Précision du train (100,0%), précision de la validation (100,0%)
    

    Ensuite, nous lierons ce script de formation à l'application de bureau.

    Dans cette étape, nous reconditionnerons automatiquement le modèle chaque fois que l'utilisateur collecte un nouveau lot d'échantillons. Ouvrez scripts / observe.js .

     nano scripts / observe.js
    

    Juste après l'importation de fs importez le générateur de processus enfant et les utilitaires.

     var fs = require ('fs');
    // commence le nouveau code
    const spawn = require ("child_process").
    var utils = require ('./ utils.js');
    

    Dans la fonction ui ajoutez l'appel suivant à retrain à la fin du gestionnaire d'achèvement.

     function ui () {
      ...
      achèvement de la fonction () {
        ...
        recycler ((données) => {
          var status = document.querySelector ('# add-status');
          Précision = data.toString (). split (' n') [0];
          status.innerHTML = "Réorganisation réussie:" + précision
        })
      }
        ...
    }
    

    Après la fonction ui ajouter la fonction de reconditionnement suivante . Cela génère un processus enfant qui exécutera le script python. Une fois terminé, le processus appelle un gestionnaire d'achèvement. En cas d'échec, il enregistrera le message d'erreur.

     function ui () {
      ..
    }
    
    reconditionnement de la fonction (achèvement) {
      var noms de fichiers = utils.get_filenames ()
      const pythonProcess = spawn ('python', ["./model/train.py"] .concat (noms de fichiers));
      pythonProcess.stdout.on ('data', accomplissement);
      pythonProcess.stderr.on ('data', (data) => {
        console.log ("* [ERROR]" + data.toString ())
      })
    }
    

    Enregistrez et quittez. Ouvrez scripts / utils.js .

     nano scripts / utils.js
    

    Ajoutez l'utilitaire suivant pour récupérer tous les jeux de données dans data / .

     var fs = require ('fs');
    
    module.exports = {
      get_filenames: get_filenames
    }
    
    function get_filenames () {
      noms de fichiers = nouvel ensemble ([]);
      fs.readdirSync ("data /"). forEach (function (filename) {
          nomsfichiers.add (filename.replace ('_ train', '') .replace ('_ test', '') .replace ('. json', ''))
      })
      noms de fichiers = Array.from (nomsfichiers.values ​​())
      noms de fichier.sort ();
      nomsfichiers.splice (nomsfichiers.indexOf ('. DS_Store'), 1)
      renvoyer les noms de fichiers
    }
    

    Enregistrez et quittez. Pour terminer cette étape, déplacez-vous physiquement vers un nouvel emplacement. Il devrait idéalement être un mur entre votre emplacement d'origine et votre nouvel emplacement. Plus les obstacles sont nombreux, plus votre application de bureau fonctionnera.

    Exécutez à nouveau votre application de bureau.

     npm start
    

    Comme précédemment, lancez le script de formation. Cliquez sur "Ajouter une pièce".


     Page d'accueil avec le bouton
    Page d'accueil avec le bouton "Ajouter une nouvelle pièce" disponible ( Grand aperçu )

    Saisissez un nom de chambre différent de celui de votre première pièce. Nous utiliserons salon .


     Page de la nouvelle salle
    Page "Ajouter une nouvelle pièce" ( Grand aperçu )

    Click “Start recording,” and you will see the following status “Listening for wifi…”.


    “Add New Room” starting recording for second room (Large preview)

    Once all 20 samples are recorded, your app will match the following. The status will read “Done. Retraining model…”


    finished recording 2
    “Add New Room” page after recording for second room complete (Large preview)

    In the next step, we will use this retrained model to predict the room you’re in, on the fly.

    Step 6: Write Python Evaluation Script

    In this step, we will load the pretrained model parameters, scan for wifi networks, and predict the room based on the scan.

    Open model/eval.py.

    nano model/eval.py
    

    Import libraries used and defined in our last script.

    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
    

    Define a utility to extract the names of all datasets. This function assumes that all datasets are stored in data/ as _train.json and _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}))
    

    Define the main function, and start by loading parameters saved from the training script.

    def get_datasets():
      ...
    
    def main():
      w = np.load('w.npy')
      ordering = np.load('ordering.npy')
    

    Create the dataset and predict.

    def main():
      ...
      classpaths = [sys.argv[1]]
      X, _, _ = create_dataset(classpaths, ordering)
      y = np.asscalar(predict(X, w))
    

    Compute a confidence score based on the difference between the top two probabilities.

    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)
    

    Finally, extract the category and print the result. To conclude the script, invoke the main function.

    def main()
      ...
      category = get_datasets()[y]
      print(json.dumps({"category": category, "confidence": confidence}))
    
    if __name__ == '__main__':
      main()
    

    Save and exit. Double check your code matches the following (source code):

    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()
    

    Next, we will connect this evaluation script to the desktop app. The desktop app will continuously run wifi scans and update the UI with the predicted room.

    Step 7: Connect Evaluation To Desktop App

    In this step, we will update the UI with a “confidence” display. Then, the associated NodeJS script will continuously run scans and predictions, updating the UI accordingly.

    Open static/index.html.

    nano static/index.html
    

    Add a line for confidence right after the title and before the buttons.

    (I dunno)

    with 0% confidence

    Right after main but before the end of the bodyadd a new script predict.js.

    
      
         
    
    

    Save and exit. Open scripts/predict.js.

    nano scripts/predict.js
    

    Import the needed NodeJS utilities for the filesystem, utilities, and child process spawner.

    var fs = require('fs');
    var utils = require('./utils');
    const spawn = require("child_process").spawn;
    

    Define a predict function which invokes a separate node process to detect wifi networks and a separate Python process to predict the room.

    function predict(completion) {
      const nodeProcess = spawn('node', ["scripts/observe.js"]);
      const pythonProcess = spawn('python', ["-W", "ignore", "./model/eval.py", "samples.json"]);
    }
    

    After both processes have spawned, add callbacks to the Python process for both successes and errors. The success callback logs information, invokes the completion callback, and updates the UI with the prediction and confidence. The error callback logs the error.

    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());
      })
    }
    

    Define a main function to invoke the predict function recursively, forever.

    function main() {
      f = function() { predict(f) }
      predict(f)
    }
    
    main();
    

    One last time, open the desktop app to see the live prediction.

    npm start
    

    Approximately every second, a scan will be completed and the interface will be updated with the latest confidence and predicted room. Congratulations; you have completed a simple room detector based on all in-range WiFi networks.

    demo
    Recording 20 samples inside the room and another 20 out in the hallway. Upon walking back inside, the script correctly predicts “hallway” then “bedroom.” (Large preview)

    Conclusion

    In this tutorial, we created a solution using only your desktop to detect your location within a building. We built a simple desktop app using Electron JS and applied a simple machine learning method on all in-range WiFi networks. This paves the way for Internet-of-things applications without the need for arrays of devices that are costly to maintain (cost not in terms of money but in terms of time and development).

    Note: You can see the source code in its entirety on Github.

    With time, you may find that this least squares does not perform spectacularly in fact. Try finding two locations within a single room, or stand in doorways. Least squares will be large unable to distinguish between edge cases. Can we do better? It turns out that we can, and in future lessons, we will leverage other techniques and the fundamentals of machine learning to better performance. This tutorial serves as a quick test bed for experiments to come.

    Smashing Editorial(ra, il)




Source link