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

#はじめに
ふと気が付くと、デスクトップがアイコンだらけになっちゃうんだよなぁ。
なんとからならないかなぁ
という、いつも気づかぬうちにデスクトップがアイコンまみれになってしまうあなた！
そんなあなたに朗報です。
なな、なんと、デスクトップを自動的にきれいにしてくれるＡＩが開発されました。

#動きを見てみましょう
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">強化学習でデスクトップをきれいにしてくれるＡＩをつくってみた <a href="https://t.co/vD2HDlG1QG">https://t.co/vD2HDlG1QG</a></p>&mdash; teamm (@teamm89201677) <a href="https://twitter.com/teamm89201677/status/1183377362865405952?ref_src=twsrc%5Etfw">October 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

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

#環境
以下がなんやかんやの環境です。バージョンがない奴は、その時の最新を利用しています。
- python3.6.6
- tensorflow
- keras
- keras-rl
- Windows 10 pro
- GeForce GTX1080

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

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

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

```math
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を利用します。
誤差関数は、

```math
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]

```
と定義され、勾配は

```math

\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では、かなり画像サイズを小さくしないといけなくて、さらに学習にものすごい時間がかかるようだったので、アイコンやごみ箱やマウスポインタの位置を状態としました。

```python:状態
    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）
マウスの動きを行動として定義します。
上下左右各斜めの８種類とマウスのスピード２種類で合計１６種類に、
マウスの左ボタンダウンとアップの２種類と
あと、何もしないの１種類の合計１９種類の行動を定義しました。

```python:行動
        self.action_space = gym.spaces.Discrete(19)
```

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

1. アイコンのところに行く
2. マウスの左ボタンダウン
3. マウスをゴミ箱までドラッグ
4. マウスの左ボタンアップ

割と複雑なんですよね。なので、報酬を２パターンにわけました。
マウスの左ボタンを押していない場合と押している場合の２パターンです。
押していない場合は、アイコンに近づくことを報酬としました。
押している場合は、ドラッグの場合は報酬を与え、目的のアイコンではない場所で押してドラッグしている場合は負の報酬としました。

```python: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にクリッピングして返却しています。
こんな長い報酬が本当に正解なのかどうか、少し疑問を持っています。本来であれば、アイコンを削除した時にだけ報酬を与えるべきなのではないかとも思っています。

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

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

#最後に
この強化学習自体実験中なので、詳細なコードは載せていません。
もし、ご興味をお持ちの方がいらっしゃいましたら、コメントください！
この研究自体はまだまだ続けます。野望があるので。

####関連するサイト
こっちもよかったら見てみてください。

ＡＩでデスクトップアイコンを自動削除してくれるツールを作ったった（YOLOv3+Windows 10）
https://qiita.com/yasunari_matsuo/items/2ac2140e7ac250304d4a

Yolo v3+Windows 10でデスクトップのアイコンを識別してみる その１
https://qiita.com/yasunari_matsuo/items/78695e9f53bc6b589daa

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

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