昨日はバイト先から「一問解答を作成するごとに二千円」という案件が来ていて4問ほど解いてみたのですが全然割に合いませんでした。まあ時給にするとそれなりだけど、QOL換算で。多分僕は仕事と私事を分けるのが下手なタイプなのだと思います。自宅で作業するとそれにのめり込んでしまって自分の時間が消滅してしまう。ある意味でそれは長所なのだけれど、それほど優先度の高くない課題に対してもそうなってしまうということは、単に優先順位をつけられていないってことなのでしょう。
今日はQ学習を実装してみました。ひとまず最初はマルバツゲームをやらせてみることに。Q関数をどう設定してやれば良いのかわからなかったので適当にニューラルネットで表現してみることにしました。盤面の現状を入力にとり、次の一手となりうる9手それぞれのQ値を出力とするようなパーセプトロンを用意して、バックプロパゲーションで学習。ランダムに打ってくる敵と一万回くらい対戦させたところまず負けなくなりました。良い感じです。ただマルバツゲームはちょっと分り易すぎた感があるので、今度はもうちょっともやもやした課題をやらせてみよう。
一応コードも貼っておく。ニューラルネットの部分はこちら*1を借用してちょっと改変しただけ。
import numpy as np def tanh(x): return np.tanh(x) def tanh_deriv(x): return 1.0 - x**2 def logistic(x): return 1/(1 + np.exp(-x)) def logistic_derivative(x): return logistic(x)*(1-logistic(x)) class NeuralNetwork: def __init__(self, layers, activation='tanh'): """ :param layers: A list containing the number of units in each layer. Should be at least two values :param activation: The activation function to be used. Can be "logistic" or "tanh" """ if activation == 'logistic': self.activation = logistic self.activation_deriv = logistic_derivative elif activation == 'tanh': self.activation = tanh self.activation_deriv = tanh_deriv self.weights = [] for i in range(0, len(layers) - 1): # self.weights.append((2*np.random.random((layers[i - 1] , layers[i] # ))-1)*0.25) self.weights.append((2*np.random.random((layers[i], layers[i + 1]))-1)*0.25) def fit(self, X, y, learning_rate=0.1, epochs=10000): X = np.atleast_2d(X) temp = np.ones([X.shape[0], X.shape[1]+1]) temp[:, 0:-1] = X # adding the bias unit to the input layer X = temp y = np.array(y) for k in range(epochs): i = np.random.randint(X.shape[0]) a = [X[i]] for l in range(0,len(self.weights)): a.append(self.activation(np.dot(a[l], self.weights[l]))) error = y[i] - a[-1] deltas = [error * self.activation_deriv(a[-1])] for l in range(len(a) - 2, 0, -1): # we need to begin at the second to last layer deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l])) deltas.reverse() for i in range(len(self.weights)): layer = np.atleast_2d(a[i]) delta = np.atleast_2d(deltas[i]) self.weights[i] += learning_rate * layer.T.dot(delta) def fit_once(self, x, y, learning_rate=0.2): x = np.array(x).flatten() x = np.insert(x, len(x), 1) y = np.array(y) a = [x] for l in range(len(self.weights)): a.append(self.activation(np.dot(a[l],self.weights[l]))) error = y - a[-1] deltas = [error * self.activation_deriv(a[-1])] for l in range(len(a)-2, 0, -1): deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l])) deltas.reverse() for i in range(len(self.weights)): layer = np.atleast_2d(a[i]) delta = np.atleast_2d(deltas[i]) self.weights[i] += learning_rate * layer.T.dot(delta) def predict(self, x): x = np.array(x) temp = np.ones(x.shape[0]+1) temp[0:-1] = x a = temp for l in range(0, len(self.weights)): a = self.activation(np.dot(a, self.weights[l])) return a def judge(board): winner = 0 pos = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]] for p in pos: if board[p[0]] == board[p[1]] and board[p[1]] == board[p[2]] and board[p[0]]!=0: winner = board[p[0]] return winner def Q_learn(net): eta = 0.1 gamma = 0.95 board = [0,0,0,0,0,0,0,0,0] while judge(board)==0 and reduce(lambda x,y:x*y,board)==0: Q_list = net.predict(board) target=[] next_move_Q=-1 next_move = 0 for i in range(9): if board[i] != 0: target.append(-1) continue board_n = list(board) board_n[i] = 1 R = judge(board_n) maxQ_n = max(net.predict(board_n)) if R!=1 else 1 Q_i = Q_list[i] if Q_i > next_move_Q: next_move_Q = Q_i next_move = i target.append( R + gamma*maxQ_n - Q_i ) y = np.array(Q_list) + np.array(target) * eta net.fit_once(board,y) board[next_move] = 1 switch = 0 while switch==0 and judge(board)==0 and reduce(lambda x,y:x*y,board)==0: enemy = np.random.randint(9) if board[enemy]==0: board[enemy]=-1 switch = 1 #print board return judge(board)
実験。
net = NeuralNetwork([10,20,9]) rate = [] for i in range(100): win = 0 for j in range(100): if Q_learn(net)==1: win += 1 rate.append(win/(100.0))
結果。
100回勝負を一セットとして100セットやった勝率の推移。
たぶん僕より強い。