独学でプログラミングとかやってみる 〜ITとかの勉強レポート〜

ボクが勉強したプログラミングやIT関連の情報を記事にしていきます。機械学習や深層学習なども取り扱います。

MacでC++をコンパイルする

調べて忘れるのももったいないので、 メモとして記載します。

g++を使ってコンパイルを行います。 ターミナルに

$gcc --help

と入力して一覧が出てくれば準備は整っています。 例えば、「main.cpp」というファイルをコンパイルするには、

#a.outというファイルが出力される
$g++ main.cpp

#helloというファイルが出力される
$g++ -o hello main.cpp

出力されたファイルを実行するときは

#a.outを実行する場合
$./a.out

#helloを実行する場合
$./hello

以上のように入力すればOKです。

PythonでOpenCVを使ってみた

画像を開く、保存する

import cv2
img = cv2.imread("画像ファイルPath")
cv2.imwrite("保存ファイルPath", img)

動画を開く、保存する

import cv2
video = cv2.VideoCapture("動画ファイルPath")
if cap.isOpened() == False:
    sys.exit()
ret, frame = cap.read()
h, w = frame.shape[:2]
fourcc = cv2.VideoWriter_fourcc(*"XVID")
dst = cv2.VideoWriter("保存ファイルPath", fourcc, 30.0, (w,h))

リサイズ

import cv2
img = cv2.imread("画像ファイルPath")
size = (300, 200) #変更後のサイズを指定する
img_resize = cv2.resize(img, size)

以上の簡単な操作を行いました。

フルスクラッチで単回帰分析をやってみる

機械学習の理解を深める

Pythonにはsklearnという便利な機械学習ライブラリがあります。 ただ、内部の計算に触れることは大事だと思うのです。 というわけで今回は単回帰分析をsklearnを使わずに実装します。

計算の準備

計算式の確認をします。 単回帰なのでパラメータは一つです。 パラメータをaとすると、


a = \dfrac{\displaystyle{\sum_{n=1}^{N}}x_{n}y_{n}}
{\displaystyle{\sum_{n=1}^{N}}x_{n}^{2}}

上記のようになります。
簡単な計算式ですので、簡単に実装できます。例えば、pandasのデータフレームを使えば、 次のように実装できると思います。

xx = x * x
xy = x * y
a = xy.sum() / xx.sum()

簡単にパラメータが求められます。 データからの予測値は


result = ax

上記の式で求めることができます。 実際にデータを扱うときはすこしデータに手を加えて、 値から平均を引いた値を使って計算しやすくするなどの工夫をしたほうがいいと思います。

TensorFlowでMNISTを使ってみる

今回やったこと

TensorFlowのチュートリアルを今回やってみました。 MNISTというかなり有名な数字画像の識別問題をやってみます。

どうやったか?

チュートリアルに倣ってsoftmaxと勾配降下法を使いました。 ミニバッチでランダムにサンプルを選んで学習させてマシンへの負担を減らしています。

PythonでのコードはTensorFlowのチュートリアルにあるので省略します。

結果は92%の識別精度が出ました!
特別なことを特にしていないのにすごい精度ですね。 99%以上の精度を出す方法もあることが知られていますが、 それはDeepLearningを使っているそうです。
MNISTは何回かコード書いて遊んでますが、TensorFlowを使うとコーディング楽ですね。

DeepQ-NetworkでOpenAI Gymに挑戦!

DeepQ-Networkとは

一言で言うと多層のQネットワークです。 Qネットワークに隠れ層を追加して多層化したもので、 今回はExperience Replayというアルゴリズムを使って実装していきます。 メモリにステート、アクション、次のステート、次のアクションを記録していきます。 メモリの上限を超えたら端から消していくDouble Ended Queueと呼ばれる構造を使いたいと思います。

今回はカートポールというOpenAI Gymの有名なゲームに挑戦します。

Pythonでサクサクと実装していきます。 コードを全部載せると長いので省略して書いていきます。

#Q-Networkのクラス
class QNetwork:
    def __init__(self,learning_rate=0.01,state_size=4,action_size=2,hidden_size=10,name="QNetwork"):
        with tf.variable_scope(name):
            self.inputs_ = tf.placeholder(tf.float32,[None,state_size],name="inputs")
            self.actions_ = tf.placeholder(tf.int32,[None],name="actions")
            one_hot_actions = tf.one_hot(self.actions_,action_size)
            self.targetQs_ = tf.placeholder(tf.float32,[None],name="target")
            self.fc1 = tf.contrib.layers.fully_connected(self.inputs_,hidden_size)
            self.fc2 = tf.contrib.layers.fully_connected(self.fc1,hidden_size)
            self.output = tf.contrib.layers.fully_connected(self.fc2,action_size,activation_fn=None)
            self.Q = tf.reduce_sum(tf.multiply(self.output,one_hot_actions),axis=1)
            self.loss = tf.reduce_mean(tf.square(self.targetQs_-self.Q))
            self.opt = tf.train.AdamOptimizer(learning_rate).minimize(self.loss)
#MemoryObjectのクラス
from collections import deque
class Memory:
    def __init__(self,max_size=2000):
        self.buffer = deque(maxlen=max_size)
        
    def add(self,experience):
        self.buffer.append(experience)
        
    def sample(self,batch_size):
        idx = np.random.choice(np.arange(len(self.buffer)),size=batch_size,replace=False)
        return [self.buffer[i] for i in idx]
#Experience Memory
env.reset()
state,reward,done,_ = env.step(env.action_space.sample())
memory = Memory(max_size=memory_size)

for i in range(pretrain_length):
    action = env.action_space.sample()
    next_state,reward,done,_ = env.step(action)
    
    if done:
        next_state = np.zeros(state.shape)
        memory.add((state,action,reward,next_state))
        
        env.reset()
        state,reward,done,_ = env.step(env.action_space.sample())
    else:
        memory.add((state,action,reward,next_state))
        state = next_state

隠れ層は64個、トレーニング回数は2000回とします。
トレーニングが完了したらいつものように、ゲームを実行してみます。

ゲームのスコアは199.0でした!
最大スコアが200なのでうまく学習してくれたことがわかります。 強化学習は面白いですね!

Q-NetworkでOpenAI Gymに挑戦!

Q-Networkに挑戦してみる

強化学習のQ-NetworkでOpenAI Gymのフローズンレイクに挑戦します。 目標は前回までのQラーニングよりさらにゲームが上手いAIを作ることです。

Q-Networkについて簡単に説明しておきます。

ステートと重みをかけ合わせてQ値を求めます。 Qテーブルの代わりに重みを用いることで、 メモリの節約や計算の高速化ができます。

Q値の目標値と推定値の二乗和誤差を最小にする重みを計算する手法です。

損失関数は下の通りです。

Loss = Σ(Q_target - Q)2

ひとまずコーディングしてみます。 Pythonでサクサク書いていきましょう。

import gym
import numpy as np
import random
import tensorflow as tf

env = gym.make("FrozenLake-v0")

#tensorflowの初期化
tf.reset_default_graph

#変数を設定する
inputs1 = tf.placeholder(shape=[1,16], dtype=tf.float32)

#重みを正規分布で用意する。分散は0.01で設定
W = tf.Variable(tf.random_uniform([16,4],0,0.01))

#かけ算を行う
Qout = tf.matmul(inputs1, W)

#Q値の推定値を最大にする値を一つ取り出して予測値に代入する
predict = tf.argmax(Qout, 1)

#Q値の推定値を入れる変数
nextQ = tf.placeholder(shape=[1,4], dtype=tf.float32)

#損失関数
loss = tf.reduce_sum(tf.square(nextQ - Qout))

#勾配降下法。パラメータを更新する頻度は0.1に設定する
trainer = tf.train.GradientDescentOptimizer(learning_rate=0.1)

#2乗誤差を最小にするように計算する
updateModel = trainer.minimize(loss)

ここまでをまず説明します。 input1は1×16のベクトルです。 15個の0と1個の1が格納されています。 ゲームのフィールドのどこにいるかの情報を入れておく変数です。

Wは16×4のベクトルでinputs1と掛け算できる形に作ります。 こいつが重みです。

inputs1とWをかけ合わせるとQ値が1×4のベクトルで得られます。 このベクトルの中身で最大の値をとるindexを1の値にして取り出します。

選んだQ値のアクション(上下左右の入力)を行い、 ステータス、報酬、穴に落ちてないか、次の報酬を予測して、 アクションした後のQ値と比較します。

今選んだQ値とその結果得られるQ値の差が最小になる重みWを求めていくことでAIが賢くなっていきます。

では、学習させるコードを一気に書いていきましょう。

#トレーニングをする。まずは初期化する。
init = tf.global_variables_initializer()

#学習用のパラメータ
#割引率
y = 0.99
#選択するアクションを決めるパラメータ
e = 0.01
#ゲームをプレイする回数
num_episodes = 20000

#トータルの報酬を格納する
rList = []

#ネットワークの学習を進めるためのコード
with tf.Session() as sess:
    sess.run(init)
    #学習させる回数
    for i in range(num_episodes):
        #変数をリセットする
        s = env.reset()
        rAll = 0
        d = False
        #何回アクションするか
        j = 0
        while j < 100:
            j += 1
            #これまでのQ値から次のアクションを決める
            a, allQ = sess.run([predict, Qout], feed_dict = {inputs1:np.identity(16)[s:s+1]})
            # ε-グリーティー法、10%以下の確率でランダムにアクションをする
            if np.random.rand(1) < e:
                a[0] = env.action_space.sample()
            s1, r, d, _ = env.step(a[0])
            Q1 = sess.run(Qout, feed_dict = {inputs1:np.identity(16)[s1:s1+1]})
            maxQ1 = np.max(Q1)
            targetQ = allQ
            #ベルマン方程式
            targetQ[0, a[0]] = r + y * maxQ1
            
            _, W1 = sess.run([updateModel,W], feed_dict={inputs1:np.identity(16)[s:s+1], nextQ:targetQ})
            rAll += r
            s = s1
            #穴に落ちたら閾値を更新して次のエピソードに移る
            if d == True:
                e = 1/((i/50)+10)
                break
        rList.append(rAll)
print("全ゲーム中、どれくらいゴールできたか:" + str(sum(rList)/num_episodes*100) + "%")

全ゲーム中、どれくらいゴールできたか:67.50500000000001%
前回よりかなり賢くなってますね。 f:id:realJ:20180415231037p:plain

ゴールもちゃんとできています。 ゴールするまでにかかる手数も短くなっていて、 Qラーニングより今回挑戦したQ-Networkの方がゲーム上手なAIを作ることができました!

OpenAI Gymで強化学習!

ベルマン方程式

前回の続きです。 OpenAI GymのFrozenLake-v0を攻略して行きます。 Qテーブルを更新するのにベルマン方程式を使うので、 まずはベルマン方程式についてお話しします。

Q(s,a) = r + γ(max(Q(s',a')))

Q:行動価値関数 s:state a,:action

r:報酬(reward) γ:割引率

さて、数式は上記のように書きます。 どういうことかというと、 ゲームのある時点でQ値は次に行うアクションで得られる値の最大値を使います。 この時に得られる値には割引率をかけて、 古いゲームプレイの報酬の影響を減らしています。

ゲームのキー入力に相当するアクションをどう選ぶかがポイントになって来るのですが、 今回はε-グリーディー法という、何度かプレイしてうまくいったアクションを選択し、時々ランダムでアクションする手法を使います。

では早速、Qテーブルを更新していくプログラムを実装しましょう。

実装

#Qテーブルを更新するコード
import gym
import numpy as np
env = gym.make("FrozenLake-v0")

#Qテーブルの初期化
Q = np.zeros([env.observation_space.n, env.action_space.n])

ここまででQテープルの準備はできました。 今Qテーブルの中は全部0が入っているはずです。

array([[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]])

Qテーブルを初期化したのでベルマン方程式のパラメータを今度は設定しましょう。

#パラメータを更新するための値
lr = 0.8
#割引率
y = 0.95
#何回ゲームをプレイするか
num_episodes = 2000
#得られる報酬の一覧を格納するリスト
rList = []

ここまでできたらあとはゲームをプレイさせながらQテーブルを更新するだけです。 一気に行きます。

for i in range(num_episodes):
    #ステートの初期化
    s = env.reset()
    
    #報酬の累積和の初期化
    rAll = 0
    
    #穴に落ちたか
    d = False
    
    #スタートからゴールか穴に落ちるまで何回アクションするか
    j = 0
    while j < 100:
        j += 1
        #ε-グリーディー法(結果が良かった方法を取り出す)
        a = np.argmax(Q[s,:] + np.random.randn(1, env.action_space.n)*(1/(i+1)))
        #アクションに対して新しいステータスと報酬と穴に落ちてるか更新する
        s1, r, d, _ = env.step(a)
        #ベルマン方程式を使って更新する
        Q[s,a] = Q[s,a] + lr * (r + y * np.max(Q[s1,:]) - Q[s,a])
        #新しい報酬の総和を更新する
        rAll += r
        #新しいステータスに更新する
        s = s1
        #まだ穴に落ちていなければ次の計算を始める
        if d == True:
            break
    #最終的に得られた報酬を報酬のリストに入れる
    rList.append(rAll)

rListの中には1と0が格納されています。 学習する過程で1の時はスタートからゴールまでたどり着いた時を表していて、 0の時は途中で穴に落ちたかゴールにたどり着けなくてゲームオーバーになったことを表しています。

とりあえず、QテーブルをアップデートしながらゲームをAIにプレイさせたわけですが、 2000回今回はプレイさせましたが、どの程度ゲームクリアできたか見て見ます。

print("回数ごとの結果:" + str(sum(rList)/num_episodes))

回数ごとの結果:0.4325 だいたい43%くらいクリアできてますね。 へたっぴ状態から練習させたからこんなものでしょう。

せっかくなので完成したQテーブルを見て見ます。

#最終的なQテーブルの値
print(Q)

[[3.30403805e-03 1.50916310e-01 4.67828897e-03 4.22277711e-03] [2.87994417e-04 2.36482442e-04 1.14266941e-04 5.24967471e-02] [7.66652742e-04 1.22894047e-03 9.88034171e-04 1.06186835e-02] [1.92752453e-04 6.67114284e-04 3.10096833e-04 4.74819685e-03] [1.70613675e-01 4.20748780e-03 9.26752005e-04 1.33450131e-04] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [4.08413509e-03 4.59097357e-05 5.87518554e-05 3.83861167e-05] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [3.02812052e-03 0.00000000e+00 3.16699706e-03 4.82855578e-01] [4.83901672e-03 7.84723753e-01 0.00000000e+00 0.00000000e+00] [6.29977810e-01 1.39727419e-03 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 1.41025980e-03 9.22835605e-01 3.71121000e-04] [0.00000000e+00 9.98525704e-01 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]]

各列は上下左右のキー入力だったのは覚えていますよね。 各行は今いるゲームのフィールド位置ですよ。 値が大きいほどそのコマンドを入力した時に効果的だったことを表しています。

じゃあ、実際にこのQテーブルを使ってゲームさせてみましょう。

#学習したQテーブルを使ってゲームをプレイする
s = env.reset()
d = False
k = 0
while k < 100:
    k += 1
    a = np.argmax(Q[s,:])
    s1, r, d, _ = env.step(a)
    s = s1
    env.render()
    if d == True:
        break

f:id:realJ:20180415032307p:plain

ゴールにたどり着けてますね。 AIはちゃんと玄人プレイヤーになってくれたようです。

今のQテーブルで100回プレイしたら何回クリアできるかみてみましょう。

#100回プレイして何回ゲームクリアできるか
clear_num = 0
for _ in range(100):
    s = env.reset()
    d = False
    k = 0
    while k < 100:
        k += 1
        a = np.argmax(Q[s,:])
        s1, r, d, _ = env.step(a)
        s = s1
        if s == 15:
            clear_num += 1
        if d == True:
            break
            
print("100回ゲームをプレイして{}回ゴールまでたどり着けた!".format(clear_num))

100回ゲームをプレイして52回ゴールまでたどり着けた!

この結果は実行するたびに変化しますが50%はゲームがクリアできる子にAIは育ってくれたようです。 Qラーニングについてはここで一つの区切りとします。

次回はQ Networkについて書いてみたいと思います。