[PYTHON] Durchketten von Kuchen mit Chainer durchschauen

Einführung

Nach dem Versuch des maschinellen Lernens besteht das unmittelbare Problem darin, einen Datensatz für das Training vorzubereiten. Sie werden zuerst mit MINIST oder etwas anderem testen, dann bestenfalls logische Operationen lernen und sich dann fragen, was als nächstes zu tun ist. Selbst wenn Sie Bilder klassifizieren möchten, macht es keinen Spaß, dies mit einem abgelegten Datensatz zu tun. Wenn Sie Ihre eigenen Bilder vorbereiten, ist das Beschriften dieser Bilder tödlich mühsam.

So kann ich die Daten selbst angemessen aufbereiten und die Kuchen-Knetumwandlung und die Klassifizierung von Pseudozufallszahlen als nicht triviale Klassifizierung versuchen.

Die Quelle ist hier. https://github.com/kaityo256/chainer_bakermap

Die Version von Chainer ist 2.0.1.

Hinweis: Dieser Artikel wurde von einem Amateur für maschinelles Lernen verfasst.

Zweck

Die Ziele sind wie folgt.

Datensatz

Die Daten werden als eindimensionale Folge von Zahlen $ \ {v_n } $ angegeben. Einer wird durch den Python-Standard "random.random ()" angegeben, der andere ist nur der Anfangswert "random.random ()" und danach

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

Nachgeben. Dies ist eine sogenannte Kuchen-Knetumwandlung (Bäcker-Karte), die auf den ersten Blick wie eine Zufallszahl aussieht, aber Sie können den Unterschied erkennen, indem Sie $ (v_n, v_ {n + 1}) $ zeichnen.

Erstens gibt es keine bestimmte Struktur bei der Verwendung von Standard-Zufallszahlen.

rand.png

Auf der anderen Seite ist es bei der Umwandlung von Kuchenkneten sofort offensichtlich.

baker.png

Wird das neuronale Netz in der Lage sein, den Unterschied zwischen den beiden durch Lernen zu erkennen? Das Problem. Damit ist es einfach, Lehrerdaten zu erstellen, und das Anpassen der Größe ist auch sehr einfach. Genau genommen haben Standard-Zufallszahlen auch Perioden und Strukturen, sollten jedoch im Bereich von 200 überhaupt nicht sichtbar sein, sodass sie in diesem Bereich zufällig aussehen sollten.

Aufbau

Lassen Sie uns vorerst mit dieser Einstellung lernen.

Alle Zahlen wurden entsprechend entschieden.

Datenaufbereitung

Ich denke, die erste Barriere (wenn auch nicht so sehr) von Chainer ist die Aufbereitung von Daten. Einzelheiten finden Sie unter Separater Artikel, jedoch kurz

Stellen Sie dann den Eingang und den Ausgang auf "x" bzw. "y" ein.

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

In diesem Fall handelt es sich um ein Datensatzformat, das Chainer essen kann.

Es ist nicht sehr lang, also werde ich ein Modul veröffentlichen, das Daten erstellt.

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

Ich denke nicht, dass es schwierig ist, den Inhalt zu verstehen. Erstellen Sie einmal ein "Daten", das die (Eingabe-, Ausgabe-) Paare auflistet, konvertieren Sie es in das Numpy-Format und machen Sie es zu einem "Datensatz". später

import data

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

In diesem Fall erhalten Sie einen Datensatz, von dem Chainer profitieren kann. Wenn Sie nur die Funktion make_data richtig umschreiben, sollten Sie in der Lage sein, alle Daten zu verarbeiten.

Modelleinstellungen

Zuerst habe ich eine Klasse gemacht, die Chainers Modell richtig verpackt hat. So was.

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)

Es ist in einem Durcheinander geschrieben, aber als Verwendung,

python


m = Model(units)      #Erstellen Sie eine Wrapper-Klasse für das Modell
model = m.get_model() #Modellobjekt abrufen(Wird für das Training verwendet)
m.save("baker.model") #Modell speichern(Serialisieren)
m.load("baker.model") #Modell laden(Deserialisieren)
m.export("baker.dat") # C++Exportieren für

Benutzen als.

Lernen

Wenn Sie das Modell der Modellklasse erhalten, ist der Rest das Chainer-Beispiel, so wie es ist. Ich denke, dass es kein besonderes Problem gibt. Wenn Sie sich vorerst train.py ansehen, können Sie sehen, dass es so ist, wie es ist. Das Modell wird jedoch nach dem Lernen serialisiert.

Test nach dem Lernen

So sieht die test.py aus, die das Modell nach dem Training testet.

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

Ich habe gerade eine Instanz der Model-Klasse erstellt, sie deserialisiert und getestet [^ 1]. Das Ausführungsergebnis sieht so aus.

[^ 1]: Rückblickend sind die Funktionsnamen wie "main" und "test" nicht gut, und ich hätte jedem eine Instanz der Model-Klasse übergeben sollen ...

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

Der Erste

[[-0.84465003  0.10021734]]

Gibt das Gewicht aus, wenn die Daten "200 Stück sind alle 0,5" eingegeben werden. Dies bedeutet, dass das als 0 erkannte Gewicht, dh die Kuchen-Knetumwandlung, "-0,84465003" beträgt und das als Zufallszahl erkannte Gewicht "0,10021734" beträgt. Mit anderen Worten, wenn eine Konstante gespeist wird, wird sie als zufällig erkannt [^ 2]. Dies wird später verwendet, um zu überprüfen, ob das in C ++ geladene Modell ordnungsgemäß funktioniert.

[^ 2]: Wenn das Verhältnis der verdreifachten benachbarten Zahlen hoch ist, wird es wahrscheinlich als Kuchenknetumwandlung erkannt, daher denke ich, dass es in Ordnung ist zu erkennen, dass die Konstante keine Kuchenknetumwandlung ist.

Nachdem

Check Baker
Success/Fail 929 / 71

Die Ausgabe ist, dass beim Verzehr von 1000 Sätzen von Kuchen-Knet-Konvertierungen 929 Sätze als Kuchen-Knet-Konvertierungen erkannt wurden und 71 Sätze fälschlicherweise als zufällig erkannt wurden.

Nachdem

Check Random
Success/Fail 913 / 87

Bedeutet, dass 1000 Sätze von Zufallszahlen gegessen wurden und 913 Sätze korrekt als Zufallszahlen erkannt wurden.

Exportieren + Importieren nach C ++

Informationen zum Exportieren und Importieren nach C ++ finden Sie unter Separater Artikel. Das Exportieren bleibt der Wrapper-Klasse überlassen, daher ist es einfach.

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

Es liest "baker.model" und spuckt "baker.dat" aus.

Es ist einfach zu importieren, aber lassen Sie es uns später der Einfachheit halber klassifizieren. So was.

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;
  }
};
//------------------------------------------------------------------------

mit diesem,

#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");
}

Das Modell kann als gelesen werden.

Test importieren

Versuchen Sie zunächst, dasselbe zu füttern und genau dasselbe Gewicht auszuspucken.

Schreiben wir diesen 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]);
}

Jedoch,

typedef std::vector<float> vf;

Ist. Das Ausführungsergebnis ist

-0.844650 0.100217

Es stellt sich heraus, dass es dem Ergebnis von Python richtig entspricht.

Darüber hinaus wird auch die korrekte Antwortrate untersucht, wenn die Kuchen-Knetumwandlung und Zufallszahlen eingegeben werden.

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

Das Ergebnis jeder Ausführung ist wie folgt.

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

Es scheint, dass die richtige Antwortrate fast gleich ist.

Zusammenfassung

Mit Chainer habe ich einen Test versucht, um zwischen der durch die Kuchenknetumwandlung erhaltenen Zahlenfolge und der Standardzufallszahl zu unterscheiden. Ich dachte, es wäre einfacher zu unterscheiden, aber mit 3 Schichten und 200 Einheiten / Schicht ist es so etwas? Vorläufig konnte ich mit Python → mithilfe von C ++ einen Lernfluss erstellen, daher möchte ich ihn auf verschiedene Arten anwenden.

Referenz

Es tut mir leid für den Artikel, den ich geschrieben habe.

Recommended Posts

Durchketten von Kuchen mit Chainer durchschauen
Seq2Seq (1) mit Chainer
Verwenden Sie Tensorboard mit Chainer
Lassen Sie uns word2vec mit Chainer verschieben und den Lernfortschritt sehen