Edited at

デスクトップのアイコンをすべて削除してくれるAIを強化学習(DQN)でつくってみた


はじめに

ふと気が付くと、デスクトップがアイコンだらけになっちゃうんだよなぁ。

なんとからならないかなぁ

という、いつも気づかぬうちにデスクトップがアイコンまみれになってしまうあなた!

そんなあなたに朗報です。

なな、なんと、デスクトップを自動的にきれいにしてくれるAIが開発されました。


動きを見てみましょう

8倍速で再生しています。

学習過程なので、最初のうちは時間がかかってますが、だんだんゴミ箱に捨てるスピードが速くなっているのがわかると思います。


環境

以下がなんやかんやの環境です。バージョンがない奴は、その時の最新を利用しています。

- python3.6.6

- tensorflow

- keras

- keras-rl

- Windows 10 pro

- GeForce GTX1080


DQN

このAIはDQNを使用しています。

Deep Q Learningの略で、Q学習にDeep Learningを適用した、すごい強化学習です。一時期話題になったAIが語の世界チャンピオンに勝ったというののAIに使われていたのが深層学習を組み込んだ強化学習です。DQNはその中のひとつです。

DQNに関してはQiita内にもかなりの数の記事があると思いますので、詳細はそちらを参照してください。ここでは数式を3つ並べて、説明したことにしてみます。


Q学習とDQN

Q学習は下記の学習過程に基づいて、TD誤差をもとに学習を進めていきます。

Q(s,a) \leftarrow Q(s,a) + \alpha \bigl(R(s,a) + \gamma \max_{a' \in A(s')} Q(s', a') -Q(s,a) \bigr)

DQNでは、この差を最小にするためにDeep Learningを利用します。

誤差関数は、

L_{\theta_i}=E\Bigl[\frac{1}{2}\bigl(R(s,a) + \gamma \max_{a' \in A(s')} Q(s', a'; \theta_{i-1}) -Q(s,a; \theta_i)\bigr)^2 \Bigr]

と定義され、勾配は


\nabla L(\theta_i)=E\Bigl[\bigl(R(s,a) + \gamma \max_{a' \in A(s')} Q(s', a'; \theta_{i-1}) -Q(s,a; \theta_i)\bigr)\nabla Q(s,a; \theta_i) \Bigr]

となります。

簡単に言うと、現在の状態で得られる報酬と、次の行動の中で得られる報酬の期待値の中で最大のもの(時間割引される)の和と、現在の状態と行動で定義されたQ関数の値の差分を最小化すれば、最適なQ関数になりますよ的な考え方です。


説明


状態(status)

まず初めに、状態の定義をします。

本当はAlpha-GOみたいに、CNNを利用して画面のイメージをそのまま学習させるということをしたかったのですが、我が家のGPUであるGTX1080では、かなり画像サイズを小さくしないといけなくて、さらに学習にものすごい時間がかかるようだったので、アイコンやごみ箱やマウスポインタの位置を状態としました。


状態

    observation_list = list()

# マウスの座標
observation_list.append(mx/1366)
observation_list.append(my/768)
# ゴミ箱の座標
observation_list.append(self.trashrect[0]/1366)
observation_list.append(self.trashrect[1]/1366)
observation_list.append(self.trashrect[2]/768)
observation_list.append(self.trashrect[3]/768)
# 最初のアイコンの座標
observation_list.append(self.icons[0, 0]/1366)
observation_list.append(self.icons[0, 1]/1366)
observation_list.append(self.icons[0, 2]/768)
observation_list.append(self.icons[0, 3]/768)
# アイコンの数
observation_list.append(self.iconcnt / 100)
# マウスの左ボタンが押されているかどうか
observation_list.append(self.btndown / 3)
# マウスとアイコンの距離
cenx, ceny = getcentercoord(self.icons[0, 0], self.icons[0, 1], self.icons[0, 2], self.icons[0, 3])
dist = getdistance(cenx / 1360, mx / 1360, ceny / 768, my/ 768)
observation_list.append(dist)
# マウスとゴミ箱の距離
tcx, tcy = getcentercoordbyrect(self.trashrect)
tdist = getdistance(tcx / 1360, mx / 1360, tcy / 768, my / 768)
observation_list.append(tdist)

値はすべて正規化してあります。


行動(action)

マウスの動きを行動として定義します。

上下左右各斜めの8種類とマウスのスピード2種類で合計16種類に、

マウスの左ボタンダウンとアップの2種類と

あと、何もしないの1種類の合計19種類の行動を定義しました。


行動

        self.action_space = gym.spaces.Discrete(19)



報酬

デスクトップのアイコンをすべて削除するためには、下記のプロセスを踏みます。


  1. アイコンのところに行く

  2. マウスの左ボタンダウン

  3. マウスをゴミ箱までドラッグ

  4. マウスの左ボタンアップ

割と複雑なんですよね。なので、報酬を2パターンにわけました。

マウスの左ボタンを押していない場合と押している場合の2パターンです。

押していない場合は、アイコンに近づくことを報酬としました。

押している場合は、ドラッグの場合は報酬を与え、目的のアイコンではない場所で押してドラッグしている場合は負の報酬としました。


reward

    def _get_reward(self, moved):

# get mouse coordinate
mx = self.rpaienvcpp.mouseX()
my = self.rpaienvcpp.mouseY()

ret = 0
tdist = 0
dist = 0

if self.btndown == 0:
bhittest = False
cenx, ceny = getcentercoord(self.icons[0, 0], self.icons[0, 1], self.icons[0, 2], self.icons[0, 3])
dist = getdistance(cenx, mx, ceny, my)
if dist < self.prevdistance:
ret = ret + 1
else:
ret = ret - 1
if self.icons[0, 1] != 0 and mx > self.icons[0, 0] and mx < self.icons[0, 1] and my > self.icons[0, 2] and my < self.icons[0, 3]:
bhittest = True
break
if bhittest:
ret = ret - 2
else:
ret = ret - 1
if self.dragging:
ret = ret - 1
print('drag end')
self.dragging = False
else:
if self.prevbtndown == 0:
if self.icons[0, 1] != 0 and mx > self.icons[0, 0] and mx < self.icons[0, 1] and my > self.icons[0, 2] and my < self.icons[0, 3]:
print('drag start [%d]' % (i))
self.dragging = True
self.diconidx = i
ret = ret + 1
tcx, tcy = getcentercoordbyrect(self.trashrect)
tdist = getdistance(tcx, mx, tcy, my)
break
if self.dragging == False:
ret = ret - 1
elif self.dragging == True:
print('dragging')
tcx, tcy = getcentercoordbyrect(self.trashrect)
tdist = getdistance(tcx, mx, tcy, my)
if tdist < self.prevtdistance:
ret = ret + 1
else:
ret = ret - 1
print('ret [%f]' % (ret))
else:
ret = ret - 2

if self.previconcnt != self.iconcnt:
ret = ret + 100

self.prevdistance = dist
self.prevtdistance = tdist
self.previconcnt = self.iconcnt
self.prevbtndown = self.btndown

ret = np.sign(ret)
return ret


最終的に、-1 0 1にクリッピングして返却しています。

こんな長い報酬が本当に正解なのかどうか、少し疑問を持っています。本来であれば、アイコンを削除した時にだけ報酬を与えるべきなのではないかとも思っています。

ただ、普通のプログラムのように、マウスをクリックしてドラッグしてゴミ箱に行ってマウスのボタンを離すといったプログラムを全くせずに、報酬だけでマウスの動きを学習させられているので、これは強化学習になっていると思っています。


おもしろかったこと

強化学習は、報酬によって行動が決まるため、報酬が適切ではなかった場合、例えば、こちらが期待しない動きを繰り返して報酬を稼ごうとしたり、動きを完全に止めて、報酬が下がるのを極力減らしたりといったかんじになりました。まるで要領のいい人間のような行動をとりはじめました。この動きに強化学習の可能性を感じました。


最後に

この強化学習自体実験中なので、詳細なコードは載せていません。

もし、ご興味をお持ちの方がいらっしゃいましたら、コメントください!

この研究自体はまだまだ続けます。野望があるので。


関連するサイト

こっちもよかったら見てみてください。

AIでデスクトップアイコンを自動削除してくれるツールを作ったった(YOLOv3+Windows 10)

https://qiita.com/yasunari_matsuo/items/2ac2140e7ac250304d4a

Yolo v3+Windows 10でデスクトップのアイコンを識別してみる その1

https://qiita.com/yasunari_matsuo/items/78695e9f53bc6b589daa


参考にしたサイト

この記事を書くにあたって、下記サイトを参考にさせていただきました。ありがとうございます。

https://qiita.com/ishizakiiii/items/5eff79b59bce74fdca0d