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.
Les objectifs sont les suivants.
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.
En revanche, dans le cas de la conversion de pétrissage de tarte, c'est immédiatement évident.
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.
Pour le moment, apprenons avec ce paramètre.
Tous les chiffres ont été décidés de manière appropriée.
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
numpy.float32
) (qui sera un tableau d'objets Numpy).numpy.int32
) (donnez un objet Numpy)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.
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.
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.
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.
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.
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.
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.
Je suis désolé pour l'article que j'ai écrit.