[PYTHON] Voir à travers la conversion de pétrissage de tarte avec Chainer

introduction

Après avoir essayé l'apprentissage automatique, je pense que le problème immédiat est de préparer un ensemble de données pour la formation. Vous allez d'abord tester avec MINIST ou quelque chose du genre, puis au mieux apprendre les opérations logiques, puis vous demander quoi faire ensuite. Même si vous souhaitez classer des images, ce n'est pas amusant de le faire avec un ensemble de données déposé, et si vous préparez vos propres images, les étiqueter est un problème mortel.

Ainsi, je peux préparer les données de manière appropriée par moi-même et essayer la conversion de pétrissage à tarte et la classification des nombres pseudo aléatoires en tant que classification non triviale.

La source est ici. https://github.com/kaityo256/chainer_bakermap

La version de Chainer est 2.0.1.

Remarque: cet article a été écrit par un amateur de machine learning.

Objectif

Les objectifs sont les suivants.

base de données

Les données sont données sous la forme d'une séquence unidimensionnelle de nombres $ \ {v_n } $. L'un est donné par le standard Python random.random (), l'autre n'est que la valeur initiale random.random (), et après cela

v = 3.0 * v - int(3.0*v)

Donner. Il s'agit d'une conversion dite de pétrissage à tarte (carte de boulanger), qui ressemble à un nombre aléatoire à première vue, mais vous pouvez voir la différence en traçant $ (v_n, v_ {n + 1}) $.

Premièrement, il n'y a pas de structure particulière lors de l'utilisation de nombres aléatoires standard.

rand.png

En revanche, dans le cas de la conversion de pétrissage de tarte, c'est immédiatement évident.

baker.png

Le réseau neuronal pourra-t-il voir la différence entre les deux en apprenant? Le problème. Avec cela, il est facile de créer des données d'enseignant et l'ajustement de la taille est également très facile. À proprement parler, les nombres aléatoires standard ont également des périodes et des structures, mais ils ne doivent pas du tout être visibles dans la plage de 200, ils doivent donc paraître aléatoires dans cette plage.

Réglage

Pour le moment, apprenons avec ce paramètre.

Tous les chiffres ont été décidés de manière appropriée.

Préparation des données

Je pense que le premier obstacle de Chainer (mais pas tellement) est la préparation des données. Pour plus de détails, veuillez vous reporter à Article séparé, mais en bref

Ensuite, définissez l'entrée et la sortie sur «x», «y», respectivement.

dataset = chainer.datasets.TupleDataset(x,y)

Si tel est le cas, ce sera un format de jeu de données que Chainer peut manger.

Ce n'est pas très long, donc je posterai un module qui crée des données.

data.py


import random
import numpy as np
import chainer

def make_baker(n):
    a = []
    x = random.random()
    for i in range(n):
        x = x * 3.0
        x = x - int(x) 
        a.append(x)
    return a

def make_random(n):
    a = []
    for i in range(n):
        a.append(random.random())
    return a

def make_data(ndata,units):
    data = []
    for i in range(ndata):
        a = make_baker(units)
        data.append([a,0])
    for i in range(ndata):
        a = make_random(units)
        data.append([a,1])
    return data

def make_dataset(ndata,units):
    data = make_data(ndata,units)
    random.shuffle(data)
    n = len(data)
    xn = len(data[0][0])
    x = np.empty((n,xn),dtype=np.float32)
    y = np.empty(n,dtype=np.int32)
    for i in range(n):
        x[i] = np.asarray(data[i][0])
        y[i] = data[i][1]
    return chainer.datasets.TupleDataset(x,y)

def main():
    dataset = make_dataset(2,3)
    print(dataset)

if __name__ == '__main__':
    random.seed(1)
    np.random.seed(1)
    main()

Je ne pense pas qu'il soit difficile de comprendre le contenu. Une fois, créez une data qui répertorie les paires (entrée, sortie), convertissez-la au format numpy et en faites un ensemble de données. plus tard

import data

units = 200
ndata = 10000
dataset = data.make_dataset(ndata,units)

Si tel est le cas, vous obtenez un ensemble de données dont Chainer peut se nourrir. Si vous réécrivez correctement uniquement la fonction make_data, vous devriez être capable de gérer toutes les données.

Paramètres du modèle

Tout d'abord, j'ai créé une classe qui a correctement enveloppé le modèle de Chainer. Comme ça.

model.py


import chainer
import chainer.functions as F
import chainer.links as L
import collections
import struct
from chainer import training
from chainer.training import extensions

class MLP(chainer.Chain):
    def __init__(self, n_units, n_out):
      super(MLP, self).__init__(
            l1 = L.Linear(None, n_units),
            l2 = L.Linear(None, n_out)
            )
    def __call__(self, x):
        return self.l2(F.relu(self.l1(x)))

class Model:
    def __init__(self,n_unit):
        self.unit = n_unit
        self.model = L.Classifier(MLP(n_unit, 2))
    def load(self,filename):
        chainer.serializers.load_npz(filename, self.model)
    def save(self,filename):
        chainer.serializers.save_npz(filename, self.model)
    def predictor(self, x):
        return self.model.predictor(x)
    def get_model(self):
        return self.model
    def export(self,filename):
        p = self.model.predictor
        l1W = p.l1.W.data
        l1b = p.l1.b.data
        l2W = p.l2.W.data
        l2b = p.l2.b.data
        d = bytearray()
        for v in l1W.reshape(l1W.size):
            d += struct.pack('f',v)
        for v in l1b:
            d += struct.pack('f',v)
        for v in l2W.reshape(l2W.size):
            d += struct.pack('f',v)
        for v in l2b:
            d += struct.pack('f',v)
        open(filename,'w').write(d)

C'est écrit dans le désordre, mais comme usage,

python


m = Model(units)      #Créer une classe wrapper pour le modèle
model = m.get_model() #Obtenir un objet modèle(Utilisé pour la formation)
m.save("baker.model") #Enregistrer le modèle(Sérialiser)
m.load("baker.model") #Modèle de charge(Désérialiser)
m.export("baker.dat") # C++Exporter pour

Utilisé comme.

Apprentissage

Quant à l'apprentissage, si vous recevez le modèle de la classe Model, le reste est l'échantillon Chainer tel quel, donc je pense qu'il n'y a pas de problème particulier. Pour le moment, si vous regardez train.py, vous pouvez voir qu'il est tel quel. Cependant, le modèle est sérialisé après apprentissage.

Test post-apprentissage

Le test.py, qui teste le modèle après l'entraînement, ressemble à ceci.

test.py


from model import Model
import numpy as np
import random
import data
import math

def main():
    ndata = 1000
    unit = 200
    model = Model(unit)
    model.load("baker.model")
    d = data.make_data(ndata,unit)
    x = np.array([v[0] for v in d], dtype=np.float32)
    y = model.predictor(x).data
    r = [np.argmax(v) for v in y]
    bs = sum(r[:ndata])
    rs = sum(r[ndata:])
    print("Check Baker")
    print "Success/Fail",ndata-bs,"/",bs
    print("Check Random")
    print "Success/Fail",rs,"/",ndata-rs

def test():
    unit = 200
    model = Model(unit)
    model.load("baker.model")
    a = []
    for i in range(unit):
        a.append(0.5)
    x = np.array([a], dtype=np.float32)
    y = model.predictor(x).data
    print(y)

if __name__ == '__main__':
    random.seed(2)
    np.random.seed(2)
    test()
    main()

Je viens de créer une instance de la classe Model, de la désérialiser et de la tester [^ 1]. Le résultat de l'exécution ressemble à ceci.

[^ 1]: En regardant en arrière maintenant, les noms de fonction tels que main et test ne sont pas bons, et j'aurais dû passer une instance de la classe Model à chacun ...

$ python test.py
[[-0.84465003  0.10021734]]
Check Baker
Success/Fail 929 / 71
Check Random
Success/Fail 913 / 87

la première

[[-0.84465003  0.10021734]]

Émet le poids lorsque les données "200 pièces sont toutes 0,5" sont alimentées. Cela signifie que le poids reconnu comme 0, c'est-à-dire que la conversion de pétrissage de tarte est «-0,84465003», et le poids reconnu comme un nombre aléatoire est «0,10021734». En d'autres termes, lorsqu'une constante est alimentée, elle est reconnue comme aléatoire [^ 2]. Cela sera utilisé plus tard pour vérifier si le modèle chargé en C ++ fonctionne correctement.

[^ 2]: Probablement, lorsque le rapport des nombres adjacents triplé est élevé, il est reconnu comme une conversion de pétrissage de tarte, donc je pense qu'il est normal de reconnaître que la constante n'est pas la conversion de pétrissage de tarte.

Après ça

Check Baker
Success/Fail 929 / 71

Le résultat est que lorsque 1000 ensembles de conversions de pétrissage de tarte ont été consommés, 929 ensembles ont été reconnus comme des conversions de pétrissage de tarte et 71 ensembles ont été reconnus par erreur comme aléatoires.

Après ça

Check Random
Success/Fail 913 / 87

Signifie que 1000 ensembles de nombres aléatoires ont été mangés et 913 ensembles ont été correctement reconnus comme des nombres aléatoires.

Exporter + Importer vers C ++

Voir Article séparé pour l'exportation et l'importation vers C ++. L'exportation est laissée à la classe wrapper, donc c'est facile.

export.py


from model import Model

def main():
    unit = 200
    model = Model(unit)
    model.load("baker.model")
    model.export("baker.dat")
    
if __name__ == '__main__':
    main()

Il lit «baker.model» et crache «baker.dat».

C'est facile à importer, mais classons-le pour plus de commodité plus tard. Comme ça.

model.hpp


#pragma once
#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
#include <algorithm>
//------------------------------------------------------------------------
typedef std::vector<float> vf;
//------------------------------------------------------------------------
class Link {
private:
  vf W;
  vf b;
  float relu(float x) {
    return (x > 0) ? x : 0;
  }
  const int n_in, n_out;
public:
  Link(int in, int out) : n_in(in), n_out(out) {
    W.resize(n_in * n_out);
    b.resize(n_out);
  }
  void read(std::ifstream &ifs) {
    ifs.read((char*)W.data(), sizeof(float)*n_in * n_out);
    ifs.read((char*)b.data(), sizeof(float)*n_out);
  }

  vf get(vf x) {
    vf y(n_out);
    for (int i = 0; i < n_out; i++) {
      y[i] = 0.0;
      for (int j = 0; j < n_in; j++) {
        y[i] += W[i * n_in + j] * x[j];
      }
      y[i] += b[i];
    }
    return y;
  }

  vf get_relu(vf x) {
    vf y = get(x);
    for (int i = 0; i < n_out; i++) {
      y[i] = relu(y[i]);
    }
    return y;
  }
};
//------------------------------------------------------------------------
class Model {
private:
  Link l1, l2;
public:
  const int n_in, n_out;
  Model(int in, int n_units, int out):
    n_in(in), n_out(out),
    l1(in, n_units), l2(n_units, out) {
  }
  void load(const char* filename) {
    std::ifstream ifs(filename);
    l1.read(ifs);
    l2.read(ifs);
  }
  vf predict(vf &x) {
    return l2.get(l1.get_relu(x));
  }
  int argmax(vf &x) {
    vf y = predict(x);
    auto it = std::max_element(y.begin(), y.end());
    auto index = std::distance(y.begin(), it);
    return index;
  }
};
//------------------------------------------------------------------------

avec ça,

#include "model.hpp"
int
main(void){
  const int n_in = 200;
  const int n_units = 200;
  const int n_out = 2;
  Model model(n_in, n_units, n_out);
  model.load("baker.dat");
}

Le modèle peut être lu comme.

Test d'importation

Tout d'abord, essayez de nourrir la même chose et de cracher exactement le même poids.

Écrivons ce code.

void
test(Model &model) {
  vf x;
  for (int i = 0; i < model.n_in; i++) {
    x.push_back(0.5);
  }
  vf y = model.predict(x);
  printf("%f %f\n", y[0], y[1]);
}

cependant,

typedef std::vector<float> vf;

Est. Le résultat de l'exécution est

-0.844650 0.100217

Il s'avère que cela correspond correctement au résultat de Python.

En outre, le taux de réponse correct lorsque la conversion de pétrissage de tarte et les nombres aléatoires sont alimentés est également étudié.

int
test_baker(Model &model) {
  static std::mt19937 mt;
  std::uniform_real_distribution<float> ud(0.0, 1.0);
  vf x;
  float v = ud(mt);
  for (int i = 0; i < model.n_in; i++) {
    x.push_back(v);
    v = v * 3.0;
    v = v - int(v);
  }
  return model.argmax(x);
}
//------------------------------------------------------------------------
int
test_random(Model &model) {
  static std::mt19937 mt;
  std::uniform_real_distribution<float> ud(0.0, 1.0);
  vf x;
  for (int i = 0; i < model.n_in; i++) {
    x.push_back(ud(mt));
  }
  return model.argmax(x);
}
//------------------------------------------------------------------------
int
main(void) {
  const int n_in = 200;
  const int n_units = 200;
  const int n_out = 2;
  Model model(n_in, n_units, n_out);
  model.load("baker.dat");
  test(model);
  const int TOTAL = 1000;
  int bn = 0;
  for (int i = 0; i < TOTAL; i++) {
    bn += test_baker(model);
  }
  std::cout << "Check Baker" << std::endl;
  std::cout << "Success/Fail:" << (TOTAL - bn) << "/" << bn << std::endl;
  int rn = 0;
  for (int i = 0; i < TOTAL; i++) {
    rn += test_random(model);
  }
  std::cout << "Check Random" << std::endl;
  std::cout << "Success/Fail:" << rn << "/" << (TOTAL - rn) << std::endl;
}

Le résultat de chaque exécution est comme ça.

Check Baker
Success/Fail:940/60
Check Random
Success/Fail:923/77

Il semble que le taux de réponse correct soit presque le même.

Résumé

En utilisant Chainer, j'ai essayé un test pour distinguer la séquence de nombres obtenue par la conversion de pétrissage de tarte et le nombre aléatoire standard. Je pensais que ce serait plus facile à distinguer, mais avec 3 couches et 200 unités / couche, est-ce quelque chose comme ça? Pour le moment, j'ai pu créer un flux d'apprentissage avec Python → en l'utilisant avec C ++, j'aimerais donc l'appliquer de différentes manières.

référence

Je suis désolé pour l'article que j'ai écrit.

Recommended Posts

Voir à travers la conversion de pétrissage de tarte avec Chainer
Seq2Seq (1) avec chainer
Utiliser tensorboard avec Chainer
Déplaçons word2vec avec Chainer et voyons la progression de l'apprentissage