Hatena::ブログ(Diary)

お前の血は何色だ!! 4 このページをアンテナに追加 RSSフィード Twitter

2017-02-18

フューチャーホームコントローラー(FHC)の複数台構成の実装の裏側

f:id:rti7743:20170218184003j:image

フューチャーホームコントローラー(FHC)の複数台構成について、裏側の実装をあまりに語っていないのでちょっと解説したいと思います。

https://rti-giken.jp/fhc/help/howto/multiroom.html


目指したのは、それぞれの部屋に置かれた端末がP2Pな関係で寄り添い通信して家全体のホームネットワークを自動で構築するというものです。

FHCには、webapiという機能があり、いわゆる共有キーのフレーズを使ってAPIを呼び出すことができます。

webapiは、 HTTP GETで要求を送り、返信は jsonで返ってくるシンプルなものです。

これを使えば自由に好きなアプリと連携できます。

https://rti-giken.jp/fhc/help/ref/setting_webapi.html

また、弊社サーバとhttps通信することで、リモートからでもWebAPIを呼び出せるようになっており、

外部webサービスと家の連携も可能になっています。

FHCの複数台連携は、このwebapiを利用する形で作られています。

具体的には、最初に相手を追加する時(信頼関係を結ばせる時)に、端末間でwebapiキーを交換します。

以後は、相手のwebapiキーを使ってそれぞれがそれぞれの機能を呼び出すというものです。

sshのキー認証みたいなものですね。

ただ、これは共有鍵なのでRSAとかに比べては弱いですが、ホームネットワークLAN内限定なのでまあ信頼しています。

もし、何か理由でキーを記録しているファイルが流出した場合、

webapiキーをリセットすれば問題ありません。

流失キーは即時無効になります。

そしてキーの変更はホームネットワークで自動適応されます。

ユーザは何も考えずにキーをリセットしてOKです。

機能としても、いろいろ頑張っていて、

1:Nの複数台とリンクできるし、

複数の部屋に置かれたFHC群へのアクセスもとても容易です。

信頼端末の信頼端末(友達友達)とも一度にコネクションできます。

自分<-->信頼端末(友達)<--->信頼端末の信頼端末(友達の友達)
自分<-->信頼端末(友達)で信頼関係を結ぶと、
自分<-->信頼端末の信頼端末(友達の友達)
友達の友達とも信頼関係を自動で結べます。

つまり、新しく部屋にFHCを設置したとしても、どこかの部屋においたFHCと信頼関係を結べば、新しく部屋に設置したFHCは、すべての部屋にアクセスできるというものです。

いちいち、すべての部屋へ信頼関係を手動で結ばせるのは手間ですからね。

信頼関係を結ぶには、自端末にログインでき、信頼関係を結びたい相手端末のIDとパスワードが必要です。

当然ながら、これらがわからないと信頼関係は結べません。

いたずらで侵入しようとしても無駄です。

正規に使うのは簡単でもイタズラは難しいを目指しています。

さらには、

DHCPで相手のIPが変わったとしても自動追従してネットワークを維持します。

webapiキーが変わっても同様です。

同時にすべての端末の電源が同時に切れて、相手のIPが変動しない限り追跡でき、

FHCのホームネットワークはそう簡単には破綻しません。

相手のIPが一度に全部変動しても追跡する機能も理論上は可能です。

ただ少し面倒なのでそこまでは作っていません。

以上が、フューチャーホームコントローラーの複数台対応です。

それぞれの部屋に置かれた端末がP2Pな関係で寄り添い通信して家全体のホームネットワークを自動で構築するというのを実現しました。

また、侵入者によるイタヅラもはねのける防御機能と、

ユーザの利便性を上げる裏方の機能があります。

あなたも、複数の部屋にFHCを置いて、家全体のホームネットワークを作ってみましょう。

https://rti-giken.jp/fhc/help/howto/multiroom.html

これが21世紀の新しい家の姿です。

2017-02-13

AOK年貢の復旧

f:id:rti7743:20140725170131j:image

昔作った AOKシナリオMODの年貢を復旧させることが出来た。

ついでに、機械翻訳で英語版も作ってみた。

ここからdownloadできるよ。

http://rtilabs.rti-giken.jp/files/2017_02_13/aok_nengu.7z


しかし、aokの新しいシナリオファイルって容量が10倍以上にでかくなるんたけど、開発陣はどういうプログラムかいているんだ?

年貢の解説記事はこっち。

http://d.hatena.ne.jp/rti7743/20140725/1406275415


年貢(nengu)
nengu is mean tax in medieval japan.

シングルプレイ用のシナリオです。
It is a scenario for single play.

ひたすら内政をやりたい人に向いています。
It is suitable for those who just want to do internal affairs.

プレイヤー(青)は、幕府のお代官様(赤)から出されるムチャな年貢(税金)をこなしていくゲームです。
The player (blue) is a game that carries out an unpleasant nengu(tax) from the odaikan-sama(red).
odaikan-sama is mean Governmental Executive officer(shogunate's officer). 

プレイヤー(青)は必死に働き、告知から10分以内に年貢を収めないといけません。
The Player(BLUE) must a work and must store a nengu by the term for 10 minutes. 

要求される年貢は毎回ひどくなります。
The nengu(tax) required is bad every time.

年貢は、食料500の要求から始まり、終盤には、金、徴兵になります。
It begins from the taxation of the food 500 at first, and there are a gold, and the conscriptionenlistment. 

もし、年貢を収めなければ、反逆者として、処刑されます。
If nengu is not stored, executed as traitor.

ゲームクリアには、合計12回の課税を収めきるか、または、年貢を払わずに反逆者となり、革命を起こします。
Game is clearable ,If all the 12 times taxation is paid, or taxation is disregarded and revolution is start.

年貢を納付するには、お代官様(赤)に資源を送信してください。
To pay the nengu, please send the resource to odaikan-sama(red).

日本語版(jpn)と英語版(eng)があります。
There are Japanese version (jpn) and English version (eng).

オリジナルが日本語版で、機械翻訳したものが英語版です。
Original is Japanese version, machine translated is English version.

created by @super_rti
http://d.hatena.ne.jp/rti7743/

2014年に作ったけど、utf対応パッチで壊れてしまったので、ツールを使って復旧させました。
Trigger Studio ( Trigger Studio 1.2.x for HD, UserPatch, AoFE, AoF + SWGB )を使ってな。
I made it in 2014, but it was destroyed by utf correspondence of patch update, I made a tool and restored it.

AOK FEの復旧

昔作った AOKシナリオMODFEを復旧させることが出来た。

ここからdownloadできるよ。

http://rtilabs.rti-giken.jp/files/2017_02_13/aok_fe.7z

ファイアーエムブレムをAOEで強引に再現するMOD
MOD which forcibly reproduces Fire Emblem at AOE

ファイアーエムブレム 暗黒龍と光の剣を AOEで強引に再現しました。
Fire Emblem The dark dragon and the sword of light were forcibly reproduced with AOE.

このMODは、二次創作作品です。
This MOD is a Fan Fiction.

公式とは一切関係ありません。
It has nothing to do with official.

現在は日本語のみです。
Currently it is only Japanese.

全12ステージあります。
There are 12 stages in all.

ファイアーエムブレムのサントラを流しながらやると楽しいですよ。
It's fun to do it while sending Fire Emblem's soundtrack.

I made it in 2014, but it was destroyed by utf correspondence of patch update, I made a tool and restored it...
http://d.hatena.ne.jp/rti7743/20140812/1407829818

2017-01-15

ナノ秒単位の高性能な時刻がほしい時

beagleboneやraspberry piで、ナノ秒単位の正確な時間がほしい時について。

通常のtime関数とかでは遅すぎて困るときは、以下のようにすると、ナノ秒単位で時刻が取れます。

値は、CPUとかの動作クロックの影響をうけるので注意してください。

ボードメモリ位置バイト長
beaglebone0x4804003C4バイト
raspberry pi 2-30x3F0030044バイト

このメモリ位置は常にカウントアップされていきます。

値はunsigned int値で、オーバーフローすると0に戻ります。

ここを読みだすことでとても正確な時間を取得することができます。

今何時?という用途にはまったく使えませんが、処理時間の間隔を正確に測るとかの用途に向いています。


カーネルランドで読みだす場合は、 ioremapが便利だと思う。

//ナノ秒単位の高性能な時刻がほしい時。
//kernel land
void *g_timer = ioremap(0x4804003C,4); //beaglebone
void *g_timer = ioremap(0x3F003004,4); //raspberry pi 2-3

//一番オーバーヘッドを生まないであろうマクロ展開で読みだす.
#define dmtimer_get()  ((unsigned int)ioread32(g_timer))

printk(KERN_ALERT "timer: %u\n", dmtimer_get() );

iounmap(g_timer);

ユーザランドで読みだす場合は、 mmapするよろし。

//ナノ秒単位の高性能な時刻がほしい時。
//userland
int g_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (g_fd < 0)
{
	printf("Could not open /dev/mem fd\n");
	return -1;
}

//mmap確保
//0x3F003004 とかを直接mmapすると落ちるので、ちょい手前から 0x100ぐらい適当に確保する.
//void* g_mmap = ( unsigned int*) mmap(NULL, 0x100, PROT_READ,MAP_SHARED,g_fd, 0x48040000); //beaglebone
unsigned int* g_mmap = ( unsigned int*) mmap(NULL, 0x100, PROT_READ,MAP_SHARED,g_fd, 0x3F003000); //raspberry pi 2-3
if (g_mmap == MAP_FAILED) {
	printf("Could not mmap %d\n",g_mmap);
	close(g_fd);
	g_fd=0;
	return -2;
}
//unsigned int* g_timer = &g_mmap[0x3C/sizeof(int)]; //0x4804003C
unsigned int* g_timer = &g_mmap[0x4/sizeof(int)]; //0x3F003004

//一番オーバーヘッドを生まないであろうマクロ展開で読みだす.
#define dmtimer_get()  (*g_timer)

for(int i = 0 ; i < 10 ;i++)
{
	printf("timer: %u\n", dmtimer_get() );
	sleep(1);
}

//unmap(g_mmap)を呼び出さなくてもプロセス終了時には自動解放される 
close(g_fd);

この高性能タイマー機能の名前がよくわからないのだが、dmtimer とか、high level timer とか、いろいろ名前があるらしい。

検索しにくいから統一させてほしいものである。

2017-01-13

最初にコンピュータに仕事を奪われた人たち

AIの発達で、コンピュータが仕事を奪うという人達がいる。

確かに、近年のAIの進化は素晴らしく、幾つかの分野で人間を超える成績を出すものがある。

だが、忘れてはいけない。はるか昔にコンピュータに仕事を奪われた人がいたことを。

はるか昔に、コンピュータに仕事を奪われた人たち、それはコンピュータだ。

何言っているんだお前と思うかもしれないが、weikipediaにもちゃんと載っている。

https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF

英語の 「computer(コンピューター)」は算術演算(数値計算)を行う人を指す言葉だった。

computer というのは、もともとは計算をする人をさす言葉だった。

それが機械の方のコンピュータが発達して、驚異的な演算能力があったためだろうか、いつしかコンピュータは機械の方のコンピュータをさす言葉になってしまった。

そして、もともとコンピュータというのが職業だったというのも忘れ去られようとしている。


コンピュータに最初に仕事を奪われた人たちは、コンピュータだった。

私たちにできることと言えば、コンピュータが仕事を奪うと言うが、最初にコンピュータに仕事を奪われた人たちって、コンピュータだよねみたいな、このことを知らないと意味不明な文章を言って遊ぶことぐらいだろうか。

2017-01-06

keras_iris_test

python初心者が kerasを使って、定番のiris分類をやった。

前回chainer使って作ってみたので、次はkerasでやってみようということで。

#
#とりあえず動くところまで作った iris分類.
#
import argparse
import numpy
import keras
from keras.layers.core import Dense, Dropout, Activation, Flatten
#from keras.layers.convolutional import Convolution2D, MaxPooling2D
numpy.random.seed(1192)  # for reproducibility

#分類した結果が文字列なので [0,1,2] という数字に変換する
def iris_name_to_index(s):
    if s == b'setosa':
        return 0   #正解データは0からスタートしないとダメ
    elif s == b'virginica':
        return 1
    elif s == b'versicolor':
        return 2
    else:
        print("Unknown Data:{}".format(s))
        raise()

#逆に、数字から、setosa/virginica/versicolor に変換する.
def iris_index_to_name(i):
    if i == 0:
        return 'setosa'
    elif i == 1:
        return 'virginica'
    elif i == 2:
        return 'versicolor'
    else:
        print("Unknown Label:{}".format(i))
        raise()


# 引数の処理
parser = argparse.ArgumentParser()
parser.add_argument('--mode',      type=str,   default='all') #train:学習だけ pred:分類だけ all:両方
parser.add_argument('--epoch',     type=int,   default=500) #何回学習を繰り返すか
parser.add_argument('--trainstart',type=int,   default=0)   #全データ150件のうち、何件から学習に使うか
parser.add_argument('--trainsize', type=int,   default=50)  #全データ150件のうち、何件を学習に使うか ディフォルト(150件中 0件から50件までを学習に使う)
parser.add_argument('--trainbatch',type=int,   default=50)  #学習するミニバッチに一度にかけるデータの個数
args = parser.parse_args()

#csv読込
#最初の4つが学習データ 最後の5番目が正解データ
csv = numpy.loadtxt("iris.csv",
    delimiter=",",                    #csvなので  , で、データは区切られている
    skiprows=1,                       #ヘッダーを読み飛ばす
    converters={4:iris_name_to_index} #4カラム目は分類がテキストでかかれているので 0から始まる数字ラベルに置き換える
    )

#学習データの定義
#学習データは float32の2次元配列
#例: [ [1,2,3,4],[5,6,7,8],[1,2,3,4],[5,6,7,8],[1,2,3,4] ].astype(numpy.float32)
data = csv[:,0:4].astype(numpy.float32)

#正解データの定義
#正解データは 0から始まる int32の1次元配列
#例: [ 0,1,2,1,0 ].astype(numpy.int32)
label= csv[:,4].astype(numpy.int32)

#学習
if args.mode in ['all','train']:

    model = keras.models.Sequential()
    model.add(Dense(100,input_dim=4))
    model.add(Activation('relu'))
    model.add(Dense(100))
    model.add(Activation('relu'))
    model.add(Dense(3))
    model.add(Activation('softmax'))

    model.compile(loss='sparse_categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])

    x = data[args.trainstart:args.trainstart+args.trainsize]
    y = label[args.trainstart:args.trainstart+args.trainsize]

    model.fit(x, y ,batch_size=args.trainbatch,nb_epoch=args.epoch,verbose=1)

    #学習結果を保存.
    model.save("model.dat")

#分類
if args.mode in ['all','pred']:

    #学習結果を読み込む
    model = keras.models.load_model("model.dat")

    #すべてのデータに対して、1件ずつ、分類を答えさせて、正しいか採点する.

    #keras組み込みの evaluateメソッドで一発なんだけど、 表示をこりたいので、forを回すよ.
    #score = model.evaluate(data, label, verbose=1)
    #print(score)

    ok_count = 0
    for i in range(len(data)):
        x = data[i:i+1]        #このデータについて調べたい.

        y = model.predict(x, batch_size=1, verbose=0) #分類の結果を取得

        pred = numpy.argmax(y)    #3つの分類のうち、どれの確率が一番高いのかを返す
        if label[i] == pred:
           ok_count = ok_count + 1
           #print("OK i:{} pred:{}({}) data:{}".format(i,iris_index_to_name(pred),iris_index_to_name(label[i]),data[i:i+1] ))
        else:
           print("NG i:{} pred:{}({}) data:{}".format(i,iris_index_to_name(pred),iris_index_to_name(label[i]),data[i:i+1] ))

    print("total:{} OK:{} NG:{} rate:{}".format(len(data),ok_count,len(data)-ok_count,ok_count/len(data)) )

ソースコードgithubにある https://github.com/rti7743/keras_iris_test

chainer_iris_testを改変してkeras版を作ったのでデータに対してはそれほど苦労としていない。

また、kerasは Tensorflowとかのラッパーであり、Tensorflowwindowsインストール方法の解説が公式サイトに書いてあり、それが chainerのGPU対応ビルドと方法がほとんど同じなので、これも苦労しなかった。


Using TensorFlow backend.
Traceback (most recent call last):
  File "test.py", line 85, in <module>
    model.fit(x, y ,batch_size=args.trainbatch,nb_epoch=args.epoch,verbose=1)
  File "keras\models.py", line 664, in
fit
    sample_weight=sample_weight)
  File "keras\engine\training.py", line
 1068, in fit
    batch_size=batch_size)
  File "keras\engine\training.py", line
 985, in _standardize_user_data
    exception_prefix='model target')
  File "keras\engine\training.py", line
 113, in standardize_input_data
    str(array.shape))
ValueError: Error when checking model target: expected activation_3 to have shap
e (None, 3) but got array with shape (50, 1)

しかし、罠がなかったわけではない。

model.compile時に loss='categorical_crossentropy'を使うと上記エラーが発生して動かなかった。

いろいろ調べてみると、loss='categorical_crossentropy' なのがだめらしい。

sparse_categorical_crossentropyにするとうまく動作する。

#NG
#model.compile(loss='categorical_crossentropy',
#              optimizer='adam',
#              metrics=['accuracy'])

#OK
model.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

たぶん、正解データの形式の問題かな・・・?

keras.np_utils.to_categoricalとかで用意すれば いいのかも。

まあ、どちらにせよ、loss='sparse_categorical_crossentropy'で動いたので気にしない方向で。


GPU利用については、バックエンドのTensorflowがなんかいい感じにしてくれるので、コードには何も書いていない。

何もしなくていいのは楽でいいと思った。

chainer_iris_test

python初心者が chainerを使って、定番のiris分類をやった。

人の作ったモデルを改変してばかりでは理解がすすまないので、iris分類を最初から作ってみた。

知りたかったのは、次の4つ。

1.モデルの定義方法と利用方法

2.csvの読込と、そのデータの活用方法

3.学習した分類データの保存

4.保存したデータの読み込みと、それを使って分類する方法

家に帰るまでが遠足ですので、分類と学習したモデルの保存、保存したモデルの活用法までわからないと意味ないじゃないかということで、一通りのことをやりたかった。

それと、trainerを使った書き方よりも、自分でforループを回す方が、より理解しやすいと思う。

これを作るにはいろいろと罠にはまって絶望しまくった。

その記録はこちらにある。 http://d.hatena.ne.jp/rti7743/20170105/1483602327

#
#とりあえず動くところまで作った iris分類.
#
import argparse
import numpy
import chainer
import chainer.functions as F
import chainer.links as L
import chainer.cuda as cuda

#irisを分類するモデル.
class IrisModel(chainer.Chain):
    def __init__(self):
        super(IrisModel, self).__init__(
            l1=L.Linear(4, 100),     #4つの素性を受け取り
            l2=L.Linear(100, 100),
            l3=L.Linear(100, 3),     #3つの分類のどれに近いかを返す.
        )

    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        y = self.l3(h2)

        return y

#分類した結果が文字列なので [0,1,2] という数字に変換する
def iris_name_to_index(s):
    if s == b'setosa':
        return 0   #正解データは0からスタートしないとダメ
    elif s == b'virginica':
        return 1
    elif s == b'versicolor':
        return 2
    else:
        print("Unknown Data:{}".format(s))
        raise()

#逆に、数字から、setosa/virginica/versicolor に変換する.
def iris_index_to_name(i):
    if i == 0:
        return 'setosa'
    elif i == 1:
        return 'virginica'
    elif i == 2:
        return 'versicolor'
    else:
        print("Unknown Label:{}".format(i))
        raise()


# 引数の処理
parser = argparse.ArgumentParser()
parser.add_argument('--mode',      type=str,   default='all') #train:学習だけ pred:分類だけ all:両方
parser.add_argument('--gpu',       type=int,   default=0)   #GPUを使うかどうか
parser.add_argument('--epoch',     type=int,   default=500) #何回学習を繰り返すか
parser.add_argument('--trainstart',type=int,   default=0)   #全データ150件のうち、何件から学習に使うか
parser.add_argument('--trainsize', type=int,   default=50)  #全データ150件のうち、何件を学習に使うか ディフォルト(150件中 0件から50件までを学習に使う)
parser.add_argument('--trainbatch',type=int,   default=50)  #学習するミニバッチに一度にかけるデータの個数
args = parser.parse_args()

#cudaを使うなら初期化する.
if args.gpu > 0:
    cuda.init()

#csv読込
#最初の4つが学習データ 最後の5番目が正解データ
csv = numpy.loadtxt("iris.csv",
    delimiter=",",                    #csvなので  , で、データは区切られている
    skiprows=1,                       #ヘッダーを読み飛ばす
    converters={4:iris_name_to_index} #4カラム目は分類がテキストでかかれているので 0から始まる数字ラベルに置き換える
    )

#学習データの定義
#学習データは float32の2次元配列
#例: [ [1,2,3,4],[5,6,7,8],[1,2,3,4],[5,6,7,8],[1,2,3,4] ].astype(numpy.float32)
data = csv[:,0:4].astype(numpy.float32)

#正解データの定義
#正解データは 0から始まる int32の1次元配列
#例: [ 0,1,2,1,0 ].astype(numpy.int32)
label= csv[:,4].astype(numpy.int32)

#学習
if args.mode in ['all','train']:
    #分類機の用意
    model = L.Classifier(IrisModel())
    if args.gpu > 0:
        model.to_gpu()

    #最適化アルゴリズムの設定
    optimizer = chainer.optimizers.Adam()
    optimizer.setup(model)

    for epoch in range(args.epoch):
        print('epoch', epoch)

        #perm = numpy.random.permutation(args.trainsize)
        #サンプルとかを見ると perm[i:i+args.trainbatch] しているが、これを使う意味がよくわからんので外している.

        #学習を開始する.
        #学習に使うデータの範囲を args.trainbatchサイズに分けて学習器に投げ込む
        for i in range(args.trainstart, args.trainsize, args.trainbatch):
            x = data[i:i + args.trainbatch]
            y = label[i:i + args.trainbatch]
            if args.gpu > 0:
                x = cuda.to_gpu(x)
                y = cuda.to_gpu(y)

            x = chainer.Variable(x)
            y = chainer.Variable(y)

            optimizer.update(model, x, y)

    #学習結果を保存.
    chainer.serializers.save_npz("model.dat", model)

#分類
if args.mode in ['all','pred']:

    #学習結果を読み込む
    model = L.Classifier(IrisModel())
    chainer.serializers.load_npz("model.dat",model)
    if args.gpu > 0:
        model.to_gpu()
    else:
        model.to_cpu()

    #すべてのデータに対して、1件ずつ、分類を答えさせて、正しいか採点する.
    ok_count = 0
    for i in range(len(data)):
        x = data[i:i+1]        #このデータについて調べたい.
        if args.gpu > 0:
            x = cuda.to_gpu(x)

        x = chainer.Variable(x)

        y = model.predictor(x) #分類の結果を取得

        y = y.data             #chainer.Variableからデータの取出し
        if args.gpu > 0:
            y = cuda.to_cpu(y)

        pred = numpy.argmax(y)    #3つの分類のうち、どれの確率が一番高いのかを返す
        if label[i] == pred:
           ok_count = ok_count + 1
           #print("OK i:{} pred:{}({}) data:{}".format(i,iris_index_to_name(pred),iris_index_to_name(label[i]),data[i:i+1] ))
        else:
           print("NG i:{} pred:{}({}) data:{}".format(i,iris_index_to_name(pred),iris_index_to_name(label[i]),data[i:i+1] ))

    print("total:{} OK:{} NG:{} rate:{}".format(len(data),ok_count,len(data)-ok_count,ok_count/len(data)) )

コードとデータ一式はgithubにある。

https://github.com/rti7743/chainer_iris_test

git cloneしたあとで、python test.py とかやると、iris.csvを読み込んで先頭50件を使って学習し、学習した結果をもとに全150件の分類を答えます。

サンプルとかを見ると perm[i:i+args.trainbatch] しているが、これを使う意味がよくわからんので外している.

なんで、これを使わないといけないの知っている人は教えてほしい。

#perm = numpy.random.permutation(args.trainsize)

先にも書いたが、ディフォルトだと、とりあえず、 iris.csvを読み込んで、 先頭50件を使って学習します。

その後、学習したモデルをファイルに書きだします。

で、そのあとで、その保存したモデルを使って、 iris.csvの全データ150件のテスト分類をします。

だいたい正解率 96%-99%ぐらいです。

よくわからないが、実行する度に結果が変わる。numpy.random.permutationを排除しているから、乱数はないはずなんだけどライブラリの中で乱数使っているのかな・・・?初心者なのでよくわからない。

オプションpython test.py --gpu 1 とかすると、 GPUを使って計算します。

総数150件程度なので、この程度の規模だと、GPUはあってもなくてもあんまり変わらないみたいですが、とりあえず。

オプションpython test.py --mode train と、すると、学習だけやります。

オプションpython test.py --mode pred と、すると、分類だけやります。

ディフォルトは、 all となっており、両方やります。学習したあとで分類を実行して結果を返します。

python初心者が書いたコードなので間違っているかもしれないが、間違っていたら、クスリと笑った後で、なぜそれが間違っていてどうすればいいと思うかを教えてください。