祭囃子は遠く、

理系大学生のハッピーエヴリディを書いていきます。

getch・getcheに関するめも(Mac/Unix用) 追記4/12(キーボード入力まわりについて)

とあるプログラムをCで書いてたら

#include <conio.h>
getch()

の部分でconio.hがないよと怒られる。

調べるとmac(というかunixには実装されてない模様)

他の手立てもあるらしいが、あんまり難しいことを考えたくないため、どうにかできないか検索

nonberanai.blog.fc2.com

僕「なーんだ、もうあるじゃんw簡単簡単w」

と言っていたのも束の間、includeフォルダにgconio.hを移動できない

su使って移動させればええやろ!と思ったが、それでもPermission deniedされる。

(ちなみにsuを有効化するのも1手順必要だった
Mac でルートユーザを有効にする方法やルートパスワードを変更する方法 - Apple サポート

おかしいなと思いつつ調べるとusr以下は最近のアップデートでルートユーザーでもアクセスできなくなっている模様

rootlessとかいうやつで。

やばくないですか?(やばくはない)

というわけで解除しました

qiita.com

これの通りにやればOKです。

ただ、rootlessはマルウェアからの防衛的な意味もあるようなので

作業が終わった同じ手順でcsrutil disableの部分を

csrutil enable

というコマンドにして有効化しておきましょう。

終わり。


追記4/12
・kbhitについて

#include <stdio.h>
#include <conio.h>

int main(){
	int key;
	int flag = 1;

	while(flag){
		if(_kbhit()){
			処理1
			flag = 0;
		}
	}
}

キーボード入力がヒットする→キーボード入力があると処理をするような関数

これもMacにはデフォルトでないみたいです、上のヘッダファイルにもありませんでした。

そこでここ参照

tricky-code.net

解決

・矢印キーの入力について
特殊入力はなんか色々特殊らしい、安直に調べたやつではうまく認識できなかった

Console Operation for RT-Series

結局ここで解決した。以下例文(unix用)インデントおかしいけど許して

#include <stdio.h>
#include <gconio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>


int kbhit(void)
{
    ~略~
    }

    return 0;
}

int main(){

	switch(getche())
   {
    case 0x1b:
    switch(getche())
      {
			case 0x5b:
			switch(getche()){
			        case 0x41: printf("↑\n"); break;
		                case 0x42: printf("↓\n"); break;
		                case 0x44: printf("←\n"); break;
		                case 0x43: printf("→\n"); break;
			}
      }
    break;
   }

return 0;
}

ボルツマンマシン

この前のこれ

f:id:ta_ichi:20180223193050g:plain

の記事を書いていこうと思います。

はじめに

動画のこいつは何者なのか、とかいう質問が出てくると思いますが最初に答えます。

人類に限りなく近いゴリラです。

本題に入りましょう、今回のモデルは情報分野だと「ボルツマンマシン」とか「ホップフィールドネットワーク」とか言われてるやつです。

物理科の民は「え、ただのイジングモデルじゃん・・・。」ってなるやつです。

この辺の説明は後でやります。

流れとしては、説明書いてコードはって結果はって終わり!ってなると思います。

各モデルの説明

上で出てきたモデルの説明をしていきます。

物理の話から情報分野に下る流れでいきます。


イジングモデル(Ising-model)

統計物理とかやった人はわかると思いますが、磁性体のモデルの中で一番(?)簡単なやつです。

まず、棒磁石を思い浮かべてください。おそらく大勢の人は赤と青に塗られ、それぞれにN、Sと書いてあるやつを思い浮かべると思います。

こいつらを半分に切ったらどうなるでしょう?

答えは「長さが半分になった棒磁石が二つできる」です。知ってる人も知らない人もいると思いますが・・・。

では、それぞれもう半分にしたらどうなるでしょう?

もう簡単だと思いますが「さらに半分になった棒磁石が四つできる」ということになります。

これを繰り返していくと、これ以上分割できない「磁石の最小単位」にたどり着くことがわかると思います。

これが「スピン」です。

このスピンは矢印に例えられ、矢印がみんなで同じ方向を向いていると全体としてN極だとかS極だとかが生まれるわけです。

イジングモデルというやつもこれをモデル化したもので、矢印は上か下かのどちらかしか向きません。

全部上に向いていればその方向がN極で、逆はS極ということになります。

f:id:ta_ichi:20180223195528p:plain

大体こんなイメージです。全部上向いてますね

この時矢印(スピン)がどこを向きたいかと言うものを指定しなければいけません。

それがハミルトニアン(エネルギー関数)です。今回の場合

f:id:ta_ichi:20180223202644p:plain

こう書けます。本来は量子力学波動関数の重なりがどうとか議論して積分したりして得られるものですが、直感的にも良さそうなことがすぐにわかります。

またSはベクトルで書いていますがイジングモデルにおいては±1しかとりません(上か下か)、あとsummationはあらゆるijの組で取ることにしています。

例えばJijが全部+1の場合を考えましょう。しかし、どういう状態が「答え」でしょうか?

現実世界というのは基本的に万物が全体としてエネルギー安定な方向へ向かっています。高いところから物を落とすと下に落ちるのもそうです。

何が言いたいかというと、このエネルギー関数は現実では常に最小化される状態(向き)になるということです。(厳密には自由エネルギーが最小化)

全てがJij=+1となる時は簡単で全てが上、または下に揃えばエネルギーが最小になることがわかると思います。

これはスピンが何個ある時でも同じです。(N>2) 磁石は全体として同じ方向を向いているので、こういったJijによって振る舞いが決まっていそう、ということも納得できると思います。

例ではJijが全て等しい時を考えましたが、Jijが色々な値を取るとそれぞれの矢印がもはやどういった向きをとれば良いのかわからないと思います。実際この場合は解析的に解くことはできません。


・ホップフィールドネットワーク(Hopfield-network)/ボルツマンマシン(Boltzmann-machine)

まずはホップフィールド ネットワークについてです。これはもうイジングモデルそのものです「スピンをJijと言う結合定数で結んでいる」と考えるとなんとなくネットワーク感も出てくるのではないでしょうか。具体的には下図のようになっています。

f:id:ta_ichi:20180324005006p:plain

Jijの値が有限=繋がっている(繋がってないところは0)と考えるとイジングモデルはこのようになり、情報分野ではホップフィールド ネットワークと呼ばれます。

また、CNNなどは順方向に情報が伝わっていき、ニューロンが次々と発火していくような向きを持った構造になっています。しかしこのモデルは向きを持たない無向グラフの一種です。

次にボルツマンマシンについてですが、ホップフィールド とほとんど等価です。何が違うのかと言うと、「温度」の概念が取り込まれていることです。

温度を取り込む利点として、記憶を想起させる時に便利とかはあると思いますが、他にはあるんでしょうかねえ〜〜勉強してないのでよくわかりません。


・結局何が違うのか

結局一緒なんじゃん、となると思います。はっきり言うと

結局一緒

です。本質的には何も変わりません。

じゃあ情報と物理で何が違うかと言うと「問題設定」です。

物理では、物質構造や現象から「Jijを予め決めてスピンの向きを決める」のに対して

情報では基本的に「スピンの向きからJijの組みを推定する」と言う問題を解きます。

逆問題の関係になっているわけですね。

例えば今回の話だと「2値画像(1ピクセル=1スピン)からJijを推定(学習)」し、それを今度は物理の問題として「与えられたJijの組みに対してのスピンの向きを決める(学習画像を想起する)」と言う二段階になっています。

学習・アルゴリズム

ここまででイメージがついたと思うので(思うので)実装に入ります。

正直実装にあたって触れていない重要なことがあるんですが、1つの記事に書ききれないのでいくつか天下り的に紹介します。気になったら調べてください。


学習方法

ホップフィールド における二値画像の学習方法は古くから知られていて「Hebb則」ってやつを使います。

f:id:ta_ichi:20180324012032p:plain

Jij:i番目とj番目のピクセルの結合定数
N:総ピクセル
p:覚えさせたいp番目の画像
S:2値をとる学習させたい画像のピクセル

こんな感じです。今回の設定だと与えた画像のセットから決定論的に決まるので推定とかではないですが、立派な学習です。

また、Jiiなど添え字が同じやつは0にします。(自分自身への結合は0ということ)


想起方法

メトロポリスヘイスティングス法」を使います。

これは簡単に言うと「乱数で1つのピクセルの向きを決めて、それがエネルギーを下げる方向ならば採用する」というのを繰り返すモンテカルロ法の1種です。ボルツマンマシンの文脈では少し間違ったことを言っていますが、だいたいあってるのであとは調べてください。

もしかしたらこれ単体の記事を書くかもしれません。

実装

ではこれらの概念を使って実際に実装します。

import numpy as np
import cv2

def local_E(spin,i,j,img,img2,img_size):#ローカルな1スピンまわりのエネルギー計算

    E = 0
    for k in range(img_size):
        for l in range(img_size):

            N = img_size**2
            
            #2値(0 or 255)の画像からいちいち直接Jijを計算する。この際0は-1となり、255は1となるように調節
            #予めJijの配列を作成しても良いが、添え字を考えるのがめちゃくちゃ面倒
            #画像が二枚なので手打ちですが、画像自体をリスト化して、for回すのが賢いと思います
            tmp = 2*img[i][j]/255-1 #1枚目画像
            tmp2 = 2*img[k][l]/255-1
            tmp3 = 2*img2[i][j]/255-1 #2枚目画像
            tmp4 = 2*img2[k][l]/255-1
            jij = (tmp*tmp2+tmp3*tmp4)/N #Hebb則からJijを算出
            if i==k and j==l:
                jij = 0.0 #自分自身への結合は0

            E = -1*jij*spin[i][j]*spin[k][l] + E #p番目の画像の全ピクセルに渡ってsummationをとる

    return E

#hyper parameter
img_size = 82 #画像の大きさ(正方形を想定)
border = 105 #2値にする際のボーダーライン、お好みで調整
N = img_size**2 #総ピクセル数
seeds = 11 #乱数シード
sweep = 10 #画像の更新回数(sweep*N回更新)
temp = 0.06 #温度、小さな値だと綺麗に元画像が思い出せるが、計算コストがかかる

#1枚目画像読み込み
img = cv2.imread("pipo.png")
img = cv2.resize(img, (img_size, img_size)) #一応指定したサイズと形にリサイズ
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) #グレースケール
ret, img = cv2.threshold(img, border, 255, cv2.THRESH_BINARY_INV) #2値化

#二枚目読み込み、上と同様
img2 = cv2.imread("ash.jpg")
img2 = cv2.resize(img2, (img_size, img_size))
img2 = cv2.cvtColor(img2, cv2.COLOR_RGB2GRAY)
ret, img2 = cv2.threshold(img2, border, 255, cv2.THRESH_BINARY_INV)

#2値画像をgazoフォルダに吐き出し(フォルダなどは適当に変えてください)
cv2.imwrite('./gazo/pipo.png', img)
cv2.imwrite('./gazo/ash.png', img2)

#スピン配列(画像を思い出させる用)を作成
spin = np.zeros((img_size,img_size))
tmp_image = np.zeros((img_size,img_size),dtype = int)

#+1-1を適当に配置
for i in range(img_size):
    spin[i] = np.random.choice([-1,1],img_size)

#どのスピン(ピクセル)を更新するかを決めるための配列
xy = np.zeros((img_size),dtype = int)
for i in range(img_size):
    xy[i] = i

np.random.seed(seeds)

for M_i in range(sweep):

    ran = np.random.rand(img_size**2)
    
    k = 0 #画像の吐き出し用

    for i in range(img_size):
        for j in range(img_size):

            #x座標とy座標をランダムにチョイス(普通にやる場合は順番に全部更新する)
            x = np.random.choice(xy,1)[0]
            y = np.random.choice(xy,1)[0]

            E_old = local_E(spin,x,y,img,img2,img_size) #1つのスピンが感じるエネルギーを計算
            spin[x][y] = - spin[x][y] #向きを反転させてみる
            E_new = local_E(spin,x,y,img,img2,img_size) #更新後の1スピン周りのエネルギーを計算
            dE = E_new - E_old #更新前後のエネルギー差を計算
            #本来更新することで系全体のエネルギーが下がるか上がるかを計算しなくてはならない
            #しかし、差分をとるため、注目しているスピンまわりだけ計算すれば十分

            if ran[k] > np.exp(-dE/temp): #乱数が更新採択確率を上回ればreject
                spin[x][y] = - spin[x][y] #元に戻す

            if k%50 == 0: #50回更新するごとに画像を吐き出し(gif用)
                for i in range(img_size):
                    for j in range(img_size):
                        tmp_image[i][j] = int((spin[i][j] * 255 + 255)/2) #スピンを0,255の2値画像に変換

                new_image_path = './gazo/'+str(M_i) +'_'+str(k)+ '.png';
               	cv2.imwrite(new_image_path, tmp_image) #書き込み

            k += 1

ざっとこんなもんです

色々書き足りていないところはありますが、上のプログラムをいい感じに書き直せばもっとたくさんの画像を記憶できるでしょう。

ただ、画像同士がほぼ直交していないとたくさんの画像は記憶できないようです。

結果

上のプログラムだと二枚記憶させているので、実は初期配置によっては

f:id:ta_ichi:20180324113347g:plain

違う人が出てきます。

まとめ

一番簡単な画像記憶をやりましたが、どうでしょうか。

多分説明が足らないところがあると思います。モチベがあればここに付け足すか別の記事で補完したいですね(やらない)

画像記憶以外でもボルツマンマシンですぐできそうなこと見つけたら実装してみようと思います。

藤江杯いってきた話

金沢レジャーランドで開催された第五回藤江杯に行ってまいりました。

詳細はこんな感じ

地方で開かれていますが、毎回参加人数が50人を超える人気のある大会です。

参加者のほとんどが遠征者という稀有な大会なので、色々な地方のぷよらーをつまみ食いできるいいチャンスです。

僕は今回(も)ぷよらーの方に車を出していただいたので、ワイワイガヤガヤしながら大阪から金沢へーーー



まず、到着後もりもり寿司という「質の高い寿司がたまたま回っているだけ」のお寿司屋さんにいきました。

ついつい3000円使ってしまうものの僕は旅先での予算は常に無限大、財布の紐が緩いどころか底が抜けている状態です。

爽やかな顔で会計を済まし、ACぷよ通のある施設へ出陣します。



最初に初心者大会が行われるわけですが、僕の友人であり犯罪者のぽかりすくんが予選で無双していました。決勝トナメですぐ負けた

僕は出ないことになっていたため一般大会の方に全てをかけていましたが、すんなり負けてしまい真顔で音ゲーコーナーに

f:id:ta_ichi:20180226012538p:plain

しかし、先週普通に落ちて凹んでいた中伝に合格することができ、一気にウキウキモードへ

大会は結局僕の友人であるキウくんが優勝しました。おめでとう



その後は食事会があり、新潟勢を迎え予約していたゆめのゆの大部屋宿泊会場に向かいました。

部屋はこんな感じ

f:id:ta_ichi:20180226010931p:plain

一人一人のスペースも確保されている上に何と言っても「安い

入館料や部屋代込みで一人当たり3700円程でした。遠征者のみなさん、おすすめです。

「仮眠スペースなら2000円で済むじゃん!」という方々もいらっしゃると思います。

確かに少人数での利用ならその方がいいかも知れませんが、10人集まるならばこのプランがいいと思います。

それはやはり「修学旅行感」に集約されるでしょう。

こんな大部屋に男だけで泊まったらやることは一つ

恋バナ

ですよね??



はい、嘘をつきました。僕はそういったものとは無縁の世界の住人なのでぷよぷよ対戦をしながらツイッターのオタクたちについて話し合っていました。

しかし、お話ししながらゲームしながら広々とスペースを使えるのはアドバンテージですし、テレビもあるので交渉次第ではうまく使えるかも知れません。

ゲーマーの皆さんは是非



翌日は早めに起床し、ジムで大胸筋を鍛えた後、港の食堂で食事をとりました。

これについても色々書きたいですが割愛

最後は忍者寺という全く忍者に関係ない寺に行き、施設内案内ツアーに参加したのですが、キウくんが係員に私語を注意されたところがピークでした。


帰りは車の中でドヒャドヒャ笑いながら運転していたらいつの間にか大阪にいました。

今回の藤江も楽しかったですし、早く就活を終わらせて次の大会にも笑顔で出場したいですね。

では

LSTMっていうオモチャを見付けました

はじめに

これはぼくのインターンメモです。

LSTM

標語的にはリカレントニューラルネットワーク(RNN)の一種でLong short-term memoryの略だとか言われてますね。

この手法の何が他と違うかというと、ある決められた(人の手で入れる)長さのデータを保持しつつ(たまに忘却しつつ)

学習していくというところみたいですね、すごそう。下のリンクの記事は読んだ事ないですが貼ります

qiita.com


何に強いかというと時系列データだとか、文章(前後の繋がりがあるため)でその辺メインで使われてるらしい。

qiita.com
deepage.net


画像認識もいけるみたいだけど、わざわざやる意味はいまいち分かりません。

動かしてみた

株価予想とか腐る程出てくるので他のでやりたい、文章どうこうはあんまり興味がない

んで、今回は


素数予想」


やります。

CNNで素数判定!みたいな話は結構出てくるんですが、時系列データとして生成するってのはあんまり出てこなかったんで。

まぁ多分やった結果面白くなかったから誰も言ってないないとか、数学的に不可能と証明されているとかそんな感じでしょう。

とりあえず素数をDLしましょう

素数2357: 素数一覧ダウンロード

10000個のやつでやりました、計算重いからね。

毎回思うんですが、こういうcsvファイルってプログラムに読み込ませる以外の用途思いつかないのに、なんで余計なヘッダー付いてるんでしょう。

エクセルで編集はあまりいい思い出が無いので、エディタで消しましょう。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import math
from keras.models import Sequential
from keras.models import load_model
from keras.layers import Dense
from keras.layers import LSTM
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error


#データ読み込み
dataframe = pd.read_table('primelist10000.txt')

#変数の抽出ためのデータフレーム作成
df = pd.DataFrame()

#目的変数
df['prime'] = dataframe['Number']#csvのカラム名をぼくが勝手に変えました
max_prime = max(df.prime)#データ範囲での最大値を取得
df.prime = df.prime/max_prime#絶対値0~1でスケール、LSTMはこれが推奨されてるらしい

#説明変数+上と同じ操作
df['index'] = dataframe.index
max_index = max(df.index)
df.index = df.index/max_index

#ここでnumpy-arrayにする。型は適当に設定
df = df.values
df = df.astype('float32')

#データ分割
train_size = int(len(df) * 0.9)#データの9割で学習、1割でテスト
train, test = df[0:train_size,:], df[train_size:len(df),:]

#LSTMが受け取れる形にしましょう、[データ行数][変数の個数][ルックバックの個数]みたいな形でしか受け取れない
def make_dataset(dataset, look_back=1):
    dataX, dataY = [], []
    for i in range(len(dataset)-look_back-1):
        xset = []
        for j in range(dataset.shape[1]):
            a = dataset[i:(i+look_back), j]
            xset.append(a)
        dataY.append(dataset[i + look_back, 0])
        dataX.append(xset)
    return np.array(dataX), np.array(dataY)


look_back = 10#上で出てきましたが、保持しておくデータ数で今回は適当に10とおいた
trainX, trainY = make_dataset(train, look_back)#訓練データリシェープ
testX, testY = make_dataset(test, look_back)#テストデータリシェープ

Hidden_Layer = 300#隠れ層の数、多いほどコスト上がるけど多いほどいいとかなんとか
Epochs = 1000#学習回数
Batch_Size = 10#バッチサイズ(まとまった単位で最適化する感じかな?よくわかってなさそう)

#モデル作成
model = Sequential()
model.add(LSTM(Hidden_Layer, input_shape=(testX.shape[1], look_back)))#隠れ層の指定とインプットデータの指定、ルックバックの指定
model.add(Dense(1))#出力の数
model.compile(loss='mean_squared_error', optimizer='adam')#損失関数と最適化法の指定
model.fit(trainX, trainY, epochs=Epochs, batch_size=Batch_Size)#学習開始
model.save("prime_LSTM2.h5")#学習したモデル保存
#model = load_model("prime_LSTM.h5")#読み込む時はこっち

#予測値
trainPredict = model.predict(trainX)
testPredict = model.predict(testX)

#リスケール
trainPredict = trainPredict * max_prime
trainY = trainY * max_prime
testPredict = testPredict * max_prime
testY = testY * max_prime


# 誤差率評価
err_rate_train = (abs(trainPredict-trainY)/trainY)
err_rate_test = (abs(testPredict-testY)/testY)

#以下予測と実際の素数のプロット
test_len = len(testY)
x_pl = np.arange(test_len)
plt.scatter(x_pl,testY,label="actual")
plt.scatter(x_pl,testPredict,label="predict",color="r")
plt.legend()
plt.xlim([0,50])
plt.savefig("ac_pre.png")

#予測された数値を整数として吐き出し
np.savetxt('pre_train.csv',trainPredict.astype("int"),delimiter=',')
np.savetxt('pre_test.csv',(testPredict.astype("int")),delimiter=',')
np.savetxt('test.csv',testY.astype("int"),delimiter=',')

結果

f:id:ta_ichi:20180215231609p:plain

うーん、微妙そうだけど素数が書く軌跡?みたいなのは若干拾えてそう。わからん!

というか予測した値が実際素数かどうか判定しなきゃ意味がないですね!このプロットはなんなんだ!(一応平行移動して心眼で見る事は可能)

そのプログラムは面倒なのでまた今度回しておきます・・・。

まとめ

よくわからん。

というか、int型での推定(離散的な推定)ってそれ用に何かあるのかな

色々改善できそうだけどもうやらなさそうです。

おしまい

TensorFlow:チュートリアル②

はいどーも♪
前回は基礎の基礎を書いたので、今回は軽い学習の章について書いて行きたいと思います。
例によって以下の焼増しです。
Getting Started  |  TensorFlow
今回はGetting Started With TensorFlowの後半と、MNIST For ML Beginnersに触れていきます。

1.最初に

全然機械学習やったことないので、僕が進めて行くのとパラレルに書いています。
信ぴょう性はこの記事もありません。
前回全コメ+ソース全文を載せてたので、今回も同様の形式でやります。
実行結果の掲載は気分次第で載せますが、載ってなかったらそういうことです。

今回の流れとしては(出来たら)

  1. すごく簡単な機械学習
  2. 手書き文字の画像認識について(概要)
  3. モデル
  4. 原理、クロスエントロピーについてなど
  5. 結果と次回

でいきたいと思います。

2.すごく簡単な機械学習(前回の続き)

前回の基本的な操作を用いて、簡単な機械学習をしていきます。(元記事の後半部分)

変数定義とかループのやり方さえ分かれば、ゴリ押しで大抵のプログラムはかけると信じています。
つまり、前回の記事がわかったら理論上なんでも書けるっていう事ですよ(クソザコプログラマー感)

学習と呼べるレベルかアヤシイですが、学習のhelloworldを見て行きましょう。
以下にいきなりコード貼ります。スラスラ読めたらそのあとの解説は読まなくていいです。

一応学習の流れだけ書いておくと

  1. 学習させたいデータを入力して、設定したモデルで計算
  2. 正解と比べてどの程度ずれているのかを計算
  3. パラメータ修正(正解に近付ける)

って感じです。まあ人間でもとりあえずトライしてみて、ミスってたらその都度やり方を変えますよね、それと同じです。

import tensorflow as tf #TensorFlowをtfとして読み込み

# モデルのパラメータ(これを変更していって入力が正解を吐き出すようにする)
W = tf.Variable([.3], dtype=tf.float32)#入力に対する重み
b = tf.Variable([-.3], dtype=tf.float32)#入力に依らない定数(バイアス)
# モデルとその入力と出力
x = tf.placeholder(tf.float32)#ここに教師データを入力
linear_model = W*x + b #モデルの式(これを計算して出力)
y = tf.placeholder(tf.float32)#ここに正解データを入れる

#正解からのずれを計算
loss = tf.reduce_sum(tf.square(linear_model - y)) # 正解とのズレの2乗和
#最適化手法(正解への近付き方にも色々有る)
optimizer = tf.train.GradientDescentOptimizer(0.01)#一番ポピュラーな最急降下法で正解に近づける
train = optimizer.minimize(loss)#上の方法で正解からのズレを小さくして行きましょう

#教え込みたいデータ
x_train = [1, 2, 3, 4]#入力データ(教師データ、今回は固定)
y_train = [0, -1, -2, -3]#正解データ(これも今回はハッキリと決まっている)
#学習
init = tf.global_variables_initializer()#変数の初期化をinitに
sess = tf.Session()#セッション打つのめんどいしsessにぶち込む
sess.run(init) #初期化(init)実行(sess.run)
for i in range(1000):#1000回学習
  sess.run(train, {x: x_train, y: y_train})#xに教師データ、yに正解データを入れて学習

#パラメータの値と正解からのズレの表示
curr_W, curr_b, curr_loss = sess.run([W, b, loss], {x: x_train, y: y_train})#左辺にそれぞれW,b,lossを代入
print("W: %s b: %s loss: %s"%(curr_W, curr_b, curr_loss))#それを表示

最初は目がびっくりして追いつかない事があっても、理解出来ないってレベルではないと思いますが解説します。

まず、今回の学習の目標は線型モデルW*x + bx_train = [1, 2, 3, 4]を入れた時にy_train = [0, -1, -2, -3]が帰ってくるようにするというものです。普通に解こうと思ったら手で解けます。
正解が分かっているものを最初にやるのはとても大事なので、わざわざ機械に解かせてるわけです。

順を追って説明すると、まず始めにモデルで使う変数を定義しています。Wとかbが主役です。(繰り返しますが、これを変えていって正解を吐き出させるので)
その後、入力データxを定義し(実用上入力は動的に代入されるので初期値を入れたりする必要はない)正解データy
を定義しています。(こちらも同様)
ここから「正解との近さ」と「正解への近付き方」を定めるわけです。
まず、「正解の近さ」というのはどの様に定義されるでしょう。コードでは

loss = tf.reduce_sum(tf.square(linear_model - y)) # 正解とのズレの2乗和

これで定義されています。正解データの値と入力データをモデルにぶち込んで得られた値の差の2乗です。
これは非常にシンプルかつ直感的で良い定義だと思います。単純に自分の家と駅の離れ具合(出力と正解の差に相当)を聞かれたら、普通は距離で表しますよね(時間というのもアリですが実質同じ事です)
いわばこの「正解との距離」が0になればそれは正解な訳です。これで一つ目の「正解との近さ」は定義し終えました。
二つ目の「正解への近付き方」ですが、これは無数にあります。現実でも飛行機とか徒歩とか色々モノや場所への近付き方ありますよね(実際はこれらはあまり良い例えじゃないですが・・・)その中から一つ選ぶわけです。
今回は最急降下法(GradientDescentoMethod)を用いているので、その説明をします。
名前にGradientと入っているくらいなので、勾配を使って関数の最小値(勾配0)を探す、というアルゴリズムです。
先にアルゴリズムの式を以下に載せます。
f:id:ta_ichi:20180111200633p:plain:w200
一般的には
f:id:ta_ichi:20180111200936p:plain:w250
こんな感じでしょうか。(ここ直すのめんどいからやりませんがあんまり一般的じゃないですね)

これだけパッと見てあーーーってなるかもしれませんが一応解説します。
例として一番簡単なのは二次関数です。

わりとと数式を使う解説

f:id:ta_ichi:20180111203037p:plain:w100
わざわざこの様に書いたのにも理由があります。高校物理を思い出すと、これは加速度aで時間tだけ進んだ時の距離の式でよく見ました。右辺が今まででいう正解との距離に相当します(つまりこれを0にする作業)これを上の式に当てはめてみましょう。
f:id:ta_ichi:20180111204030p:plain:w200
こう書くとこのアルゴリズムの正体がわかりやすくなってきます。
ある時刻t^kから、適当な係数×その時刻での速さで、原点(t=0)に向かって近づいていくという式」になっています。
また、t=0を代入するとそこで更新が止まるのも確認できます。(そんなに大変じゃないので、自分でa*α=0.01、t=0.5とかで計算してみて下さい)
この時、問題はある適当な係数を速度にかけるというところで、これは大きすぎるとt=0を通り越してしまうのがわかると思います。(例えば極端な話t=1から始めてa*αが100000だったらダメですよね)
なのでそこそこ小さな値に設定しなければならないわけです。でも小さすぎると計算回数が増えてしまう・・・。こういうのは誰か熱心な人が大体この値にしとけばオッケーって決めてて、コードでは0.01になっているのはそういう事情でしょう。
正直思ったよりわかりやすい解説にならなかったのを感じている

図でなんとなく解説

f:id:ta_ichi:20180111234044j:plain:w300
関数の上に球を置くと極小値に向かうのが想像できると思います。これが最急降下法といっても良いでしょう。
点線の球の間隔がα(速度vでどの程度の時間進む事にするか)に対応します。

というわけで

アルゴリズムがわかったので、今回のヤツを見ていきましょう。
f:id:ta_ichi:20180111194956p:plain:w200
今回は変数がW,bとあってこの両方を変えながら正解との距離を縮めていくわけです。この時は

f:id:ta_ichi:20180111235606p:plain:w200
f:id:ta_ichi:20180111235629p:plain:w200
それぞれについて偏微分して二つについてどんどん傾きが小さくなる方(速度小の方)に行けば、いつか「正解までの距離」が0になる地点が見つかると思います。

注意

f:id:ta_ichi:20180112000558j:plain:w300
この場合最急降下法は使えるでしょうか?関数の上に載せられた級は明らかに手前の極小値(not最小値)にハマってしまいそうですね。
これが最急降下法の弱点で、局所解(極小値)にハマりやすいです。図のレベルならαの値をチューニングしたり、スタートの位置を変えれば最小値が見つかりますが、機械学習ではパラメータが数百とか数千とか越えることがあるみたいです。数千次元の空間で球をいろんなところから転がしていたらキリがありません。なので、他の方法を使ったりします。(他の方法でも同様の問題は抱えていますが・・・。)でも結構高級なライブラリなので、こんな単純なことだけをやる最急降下法じゃないのかもしれません。これはこれで強力な方法なので、当面はこれを使っていきます。

やっと続き

話がそれたというか本筋にやっと戻りますが、こうして正解との距離を縮めて学習していくわけです。
上を実行すると

W: [-0.9999969] b: [ 0.99999082] loss: 5.69997e-11

と出てくると思います。W=-1とb=1を突っ込むとxを入力したときに正解のyが帰ってくるのがすぐに確かめられるので、上手に学習できていることが確認できました。めでたしめでたし

予告

簡単な学習なので、簡単に書くつもりが長くなってしまいました。
残りはチュートリアル③にまわしていきたいと思います。
今週中には書きたい・・・。
あと最近やっとDeep MNIST for Expertsのコンテンツへの理解がやっと追いついてきたので、もうちょっと高級な記事もかけるかもしれない。(初心者の域を脱さない)

TensorFlow:チュートリアル①

正月暇過ぎて(やる事はあるけど研究室の計算機に入れない)TensorFlowの勉強をしました。
適当にまとめておこうと思います、基本的には個人用というか僕の理解で適当に書く予定です。
あってるか間違ってるか知らないよ、という事

1.最初に

出展元は基本的にはこれ
Getting Started  |  TensorFlow
ここGetting Started With TensorFlowからボチボチまとめて行きます。
もっと言うと検索すると掃いて捨てるほど翻訳記事とかあるから、そっち見た方がいいかも…
一応差別化的な意味合いで物理っぽい視点から書けたらいいなとかは思ってます。(今回は無理)

想定される読者は

  1. pythonのプログラミングよく分かってない人(僕)
  2. 簡単に機械学習を体験してみたい人(僕)
  3. なんか今はなんも知らんけどゆくゆくはDeep-Learning触りたい人(僕)

俺得でしかなさそう

2.TensorFlowの基本

よく分かってないのに基本を書いていきます、ええ。
pythonやってる人は読めばすんなり入っていくタイプの奴だと思う(クラスとかオブジェクトとか無縁なプログラミングばっかしてるから慣れない)
導入の方法は調べて下さい・・・。
qiita.com
僕はこれ参考にした流れで入れました。

・TensorFlowって何

僕が聞きたい、凄いpythonのライブラリ・・・?
テンソルの計算が上手いらしい。(テンソルが得意でイメージつかなければ、行列とかの線型代数得意マンを想像すればよさそう)
Deep-Learningにも使われてそう。

・TensorFlowをとりあえず動かす
import tensorflow as tf #TensorFlowライブラリをtfとして読み込みますよ
node1 = tf.constant(3.0, dtype=tf.float32) #node1に定数3を代入、型は浮動小数点単精度です
node2 = tf.constant(4.0) #node2に定数4を代入します
print(node1, node2) #node1,node2を表示します

これが所謂HelloWorld的なヤツ?になってそう。単純に変数に定数を入れただけ。
ここで思ったのは(pythonド初心者並感)

  • 一々tf.を付ける(オブジェクトなんちゃらのアレ?本当に詳しくないから知らない)
  • 変数名nodeってわざわざ書くの意味あるんか?重過ぎん?最初ならx、yでええやん
  • constantでわざわざ用意されてるのなんでや
  • printで変なのでてくるけど?

ってな感じ。
解答としては

  • そうっぽい?知らん
  • そもそも変数のイメージがグラフ理論的になってそう、独立した箱というより変数同志の「繋がり」を重視したイメージ?(かっちょいい図を手描きしました)そもそもグラフ理論の言葉でこの丸いヤツをノードっていうからそれはそうなのかも。

f:id:ta_ichi:20180104023219j:plain:w300

  • 調べるとどうやらあとから値を変えたりする事が出来ないように用意されているらしい。Fortranでいうparameter指定(誰にもつたわらない)
  • これは以下に詳しく書く

なんか値としてはき出させるにはSessionしなきゃダメらしい、僕の中のイメージでは実行ボタンとか、コンパイルに近い。

import tensorflow as tf #TensorFlowライブラリをtfとして読み込みますよ
node1 = tf.constant(3.0, dtype=tf.float32) #node1に定数3を代入、型は浮動小数点単精度です
node2 = tf.constant(4.0) #node2に定数4を代入します
#これを新たに書く
sess = tf.Session() #セッションを打ち込むの面倒だからsessにします
print(sess.run([node1, node2]))#node1,node2を実行(3.0,4.0と出てくるはず)

これで値としてやっとはき出されますね!ハローワールドにしてはめんどい!!!!!
あと地味にsess = tf.Session()ってFortran使ってると目がびっくりする、関数をぶち込んでるようなものなので

・もうちょっと動かす

定数をはき出させただけでしたが、足し算したりなんたりしてみましょう。

import tensorflow as tf #TensorFlowライブラリをtfとして読み込みますよ
from __future__ import print_function #なんかからprintの関数持ってきますよ(これ分かってない)

node1 = tf.constant(3.0, dtype=tf.float32) #node1に定数3を代入、型は浮動小数点単精度です
node2 = tf.constant(4.0) #node2に定数4を代入します
node3 = tf.add(node1, node2) #node1とnode2を足し算しましょう!

sess = tf.Session() #セッションを打ち込むの面倒だからsessにします
print("node3:", node3) #7じゃないよ
print("sess.run(node3):", sess.run(node3)) #ちゃんとセッションして実行すると7になるよ

これが足し算、tf.addってところでやってるのでコレはわかりやすいと思います。
上のかっこいい図の右端が足し算のイメージ

import tensorflow as tf #TensorFlowライブラリをtfとして読み込みますよ
from __future__ import print_function #なんかからprintの関数持ってきますよ(これ分かってない)

a = tf.placeholder(tf.float32) #aに浮動小数点単精度の値を後で入れるよ
b = tf.placeholder(tf.float32) #bに浮動小数点単精度の値を後で入れるよ
adder_node = a + b  #まだ値は決まってないけど足し算したものを左辺に入れるよ

sess = tf.Session() #セッションを打ち込むの面倒だからsessにします
print(sess.run(adder_node, {a: 3, b: 4.5})) 
#aに定数3bに定数4.5を入れてその足した奴を表示+実行
print(sess.run(adder_node, {a: [1, 3], b: [2, 4]})) 
#aとbに定ベクトル入れて足し算した奴を表示+実行

コレが一番pythonプログラミングというか普通な感じしますね。
変数宣言して入れて、計算して・・・といった素朴なコードです。

でも変数〜〜って感じのヤツは次の方で(もしかしたら大きな仕様の違いがあるかも、分からんけど)

import tensorflow as tf #TensorFlowライブラリをtfとして読み込みますよ
from __future__ import print_function #なんかからprintの関数持ってきますよ(これ分かってない)

W = tf.Variable([.3], dtype=tf.float32) #Wに0.3を入れる、これは後から変更可能
b = tf.Variable([-.3], dtype=tf.float32) #bに−0.3を入れる、これは後から変更可能
x = tf.placeholder(tf.float32) #浮動小数点単精度の値を後で入れるよ
linear_model = W*x + b #この計算をするよ、そして左辺ににぶち込む

sess = tf.Session() #セッションを打ち込むの面倒だからsessにします
init = tf.global_variables_initializer() #グローバル変数とか初期化
sess.run(init) #初期化実行

print(sess.run(linear_model, {x: [1]})) #x=1で上の式を計算するよ

まぁ上との違いは先に値を入れてるとこですかね?それ以外はよくわかりません、あとはテンソルのランク(ベクトルか、定数かとか)を先に決めてるのかな?
あとは tf.global_variables_initializer()ですが、これは無くても動くけどやった方が大概いい系のヤツだと思います。
どんどん足し算していく時とかに、最初初期化しないとヤバめな事起こったりするアレ防止とか・・・?

まあ今回はこのくらいにしときますか。

終わりに

今回はプログラム全コメ+毎回の全文貼りとかにしたけど、どうでしょうか?
僕がこうだったらいいかなぁと思って勝手にやったので、読む人にはどうなんでしょう。

次は冒頭のサイトで、これらを使って軽い学習をしてるコンテンツの説明と、その次のMNIST For ML Beginnersに載ってる画像認識のところまで行けたらいいかなぁ。
画像認識の所はもうちょい理解してからじゃないと書くのが怖いかも。

教授「ワイーーーーー!相図を書けーーー!!」ドン!!! ワイ「き、教授ァ・・・」ポロポロ

ワイ「で、でもワイ、、、先週原理上書けないって説明したゾ、、、」

教授「うるせェ!!!!カけ!!!!!!」ドン!!!

教授「俺は俺の言うことを聞く奴が好きだ!!!」ニィ

俺「う、う゛う゛、、、き゛ょう゛じゅ、、、、」ポロポロ

 

その後色々あって炎上しました。(途中で改変めんどくなったので気が向いたら足します)