覚えたての転移学習を使って上白石萌歌さんと上白石萌音さんを分類してみた話

はじめまして！！

つい1ヶ月ほど前から深層学習とPyTorchの勉強をはじめたidachiです！！

これから勉強したものをアウトプットするために記事を書いていこうと思うのでよろしくお願いします！！(不定期)

今回は転移学習について勉強し、PyTorchのチュートリアルや他の人の記事を参考に実装してみました。

## 転移学習とは
以下の記事を参考に勉強しました。
[転移学習：機械学習の次のフロンティアへの招待](https://qiita.com/icoxfog417/items/48cbf087dd22f1f8c6f4)
[Kerasで学ぶ転移学習](https://elix-tech.github.io/ja/2016/06/22/transfer-learning-ja.html)
ざっくり僕なりの言葉でまとめると、低次の特徴量(月だと丸いとか？)はいろんなところで使い回せるのでそれは学習してある重みを使えばよく、高次の特徴量(クレーターがあるとか？)はその物体特有の特徴量なのでそこだけ学習し直すことで、少ないデータ量で速く学習を終わらすことができるものが転移学習...なんですかね？

## 今回やりたいこと
突然ですが、僕は上白石萌歌さんが大好きです！！本気で好きです！！
ということで、上白石萌歌さんと上白石萌音さんを分類できるようにしたい！となりました。(は？)

やるべきことは大まかに以下の2つとなります。
1. 上白石萌歌さん(以下moka)と上白石萌音さん(以下mone)のデータセットを作成する(スクレイピング)
2. 転移学習をPyTorchで実装する

これからこの2つについて書いていきます。

## データ収集
(以下のコードはjupyter notebook上で実行)
これが一番時間かかりました。かなり苦労しました。
とりあえず 上白石萌歌 画像 だったり 上白石萌音 画像　と調べたりして、使えそうな画像がたくさんあるサイトを探したりしたのですが、あまりいいサイトが見つからなかったので、結局Yahoo画像検索を使うことにしました。
スクレイピングのコードは以下。
(browser.get()のurlは適宜変えれば良い)

```Python
from selenium import webdriver
import io
from urllib import request
from PIL import Image

browser = webdriver.Safari()
browser.get("https://ord.yahoo.co.jp/o/image/RV=1/RU=aHR0cHM6Ly9zZWFyY2gueWFob28uY28uanAvd2ViL3NhdmVwcmVmP3ByZWZfZG9uZT1odHRwcyUzQSUyRiUyRnNlYXJjaC55YWhvby5jby5qcCUyRmltYWdlJTJGc2VhcmNoJTNGcCUzRCUyNUU0JTI1QjglMjU4QSUyNUU3JTI1OTklMjVCRCUyNUU3JTI1OUYlMjVCMyUyNUU4JTI1OTAlMjU4QyUyNUU5JTI1OUYlMjVCMyUyNnJrZiUzRDElMjZkaW0lM0QlMjZjdHlwZSUzRGZhY2UlMjZpbXclM0QwJTI2aW1oJTNEMCUyNmltYyUzRCUyNmVpJTNEVVRGLTgmaW1zcmNoX2RzPTE-;_ylt=A2RCL5i6xedc1B0AkAGU3uV7")

elems = []
tbs = []

#一ページ目の処理
#一ページ目は 次へ しか表示されない
tbs = browser.find_elements_by_class_name("tb")
for tb in tbs:
    tb = tb.find_element_by_tag_name("img").get_attribute("src")
    elems.append(tb)
ref = browser.find_element_by_id("Sp1")
ref = ref.find_element_by_class_name("m")
ref = ref.find_element_by_tag_name("a").get_attribute("href")
browser.get(ref)

#二ページ目のは 前へ と 次へ しか表示されない
for i in range(1, 15):
    tbs = []
    tbs = browser.find_elements_by_class_name("tb")
    for tb in tbs:
        tb = tb.find_element_by_tag_name("img").get_attribute("src")
        elems.append(tb)
    ref = browser.find_element_by_id("Sp1")
    ref = ref.find_elements_by_class_name("m")
    ref = ref[1].find_element_by_tag_name("a").get_attribute("href")
    browser.get(ref)

fs = []
for elem in elems:
    try:
        fs.append(io.BytesIO(request.urlopen(elem).read()))
    except:
        continue

for index, f in enumerate(fs):
    img = Image.open(f)
    try:
        img.save('Image/img{}.jpg'.format(index))
    except:
        continue
```

とりあえずここまでで画像の収集は完了。ここからは収集した画像から顔だけを切り抜いていく。
顔の切り抜きは、OpenCV2を使用した。
以下のサイトからコードをお借りしました。
[大量の画像から顔の部分のみトリミングして保存する方法](https://ai-coordinator.jp/opencv_face)
コードは以下。
(カスケードファイルがどこにあるかわからない場合は
https://github.com/opencv/opencv/tree/master/data/haarcascades
からカスケードファイルをダウンロードして自分の置きたい場所に置けば良い)

```python
import cv2
import matplotlib.pyplot as plt
import numpy as np
import sys, os
from PIL import Image
%matplotlib inline
 
#入力ファイルのパスを指定
in_jpg = "./mone/"
out_jpg = "./mone_out/"

#リストで結果を返す関数
def get_file(dir_path):
    filenames = os.listdir(dir_path)
    return filenames

pic = get_file(in_jpg)
 
for i in pic:
    # 画像の読み込み 
    image_gs = cv2.imread(in_jpg + i)

    # 顔認識用特徴量ファイルを読み込む --- （カスケードファイルのパスを指定）
    cascade = cv2.CascadeClassifier("./haarcascades/haarcascade_frontalface_alt.xml")
    
    # 顔認識の実行
    face_list = cascade.detectMultiScale(image_gs,scaleFactor=1.1,minNeighbors=1,minSize=(1,1))
    
    # 顔だけ切り出して保存
    no = 1;
    for rect in face_list:
        x = rect[0]
        y = rect[1]
        width = rect[2]
        height = rect[3]
        dst = image_gs[y:y + height, x:x + width]
        save_path = out_jpg + '/' + 'out_('  + str(i) +')' + str(no) + '.jpg'
        
        #認識結果の保存
        a = cv2.imwrite(save_path, dst)
        plt.show(plt.imshow(np.asarray(Image.open(save_path))))
        print(no)
        no += 1
```
ここまででやっと顔面データセットが完成です!!
顔を見すぎて混乱してきました・・・

## 転移学習させてみる
以下のサイトを参考にしました。
[TRANSFER LEARNING TUTORIAL](https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html)
[PyTorch (9) Transfer Learning (Dogs vs Cats)](http://aidiary.hatenablog.com/entry/20180221/1519223357)
Google Colaboratoryで実行しました。
(https://colab.research.google.com/drive/1hhgzPhzFxUeKC-jFsEXCc0BDo261Xcv8)

VGG16を使って転移学習をやってみた。

## 結果

```
epoch 0, loss: 0.7737 val_loss: 0.6472 val_acc: 0.5333
val_acc improved from 0.00000 to 0.53333!
epoch 1, loss: 0.7488 val_loss: 0.5552 val_acc: 0.7333
val_acc improved from 0.53333 to 0.73333!
epoch 2, loss: 0.6776 val_loss: 0.6033 val_acc: 0.6667
epoch 3, loss: 0.6643 val_loss: 0.4808 val_acc: 0.8000
val_acc improved from 0.73333 to 0.80000!
epoch 4, loss: 0.6296 val_loss: 0.6594 val_acc: 0.6000
epoch 5, loss: 0.7010 val_loss: 0.4576 val_acc: 0.7333
epoch 6, loss: 0.5446 val_loss: 0.4440 val_acc: 0.7667
epoch 7, loss: 0.5787 val_loss: 0.4283 val_acc: 0.8333
val_acc improved from 0.80000 to 0.83333!
epoch 8, loss: 0.5084 val_loss: 0.4287 val_acc: 0.9000
val_acc improved from 0.83333 to 0.90000!
epoch 9, loss: 0.5290 val_loss: 0.5065 val_acc: 0.7333
epoch 10, loss: 0.5405 val_loss: 0.4559 val_acc: 0.8000
epoch 11, loss: 0.5156 val_loss: 0.4661 val_acc: 0.8000
epoch 12, loss: 0.5797 val_loss: 0.4679 val_acc: 0.7667
epoch 13, loss: 0.5404 val_loss: 0.4361 val_acc: 0.8667
epoch 14, loss: 0.5209 val_loss: 0.4413 val_acc: 0.8333
epoch 15, loss: 0.5192 val_loss: 0.4349 val_acc: 0.7667
epoch 16, loss: 0.5288 val_loss: 0.5070 val_acc: 0.7000
epoch 17, loss: 0.5752 val_loss: 0.4322 val_acc: 0.8000
epoch 18, loss: 0.5562 val_loss: 0.3914 val_acc: 0.8667
epoch 19, loss: 0.5750 val_loss: 0.3962 val_acc: 0.8667
epoch 20, loss: 0.5043 val_loss: 0.5074 val_acc: 0.7333
epoch 21, loss: 0.5454 val_loss: 0.3975 val_acc: 0.9000
epoch 22, loss: 0.4642 val_loss: 0.4125 val_acc: 0.8333
epoch 23, loss: 0.4313 val_loss: 0.3846 val_acc: 0.8333
epoch 24, loss: 0.4801 val_loss: 0.3881 val_acc: 0.8000
epoch 25, loss: 0.4927 val_loss: 0.4130 val_acc: 0.8000
epoch 26, loss: 0.4634 val_loss: 0.4261 val_acc: 0.8667
epoch 27, loss: 0.4777 val_loss: 0.4104 val_acc: 0.8667
epoch 28, loss: 0.5163 val_loss: 0.4168 val_acc: 0.8333
epoch 29, loss: 0.4600 val_loss: 0.4135 val_acc: 0.8667
```
validationに対しての最高精度は90%となりました!!
データ数が少ない割には良い結果がでたのではないでしょうか。

## 実際にテストしてみる
テストした画像達は以下。
![test_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423403/eb7286fb-8151-9563-1752-1cd977096cd3.png)

実際にテストして、結果をmoneとmokaに分離して表示してみると、以下のようになった。
![mone_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423403/19f584c6-aed8-32a3-b1c7-514a6e1e152e.png)
moneの方は割と良い結果なのではないでしょうか！

![moka_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423403/b499f8fe-aa3f-a4e7-d65e-5210911a3843.png)
mokaの方は良い結果とはいえないですね...

## まとめ
今回は転移学習について勉強し、PyTorchでの実装までをやってみました。
結果に関してはまずまずといった感じなのでしょうか。
改善策としては、画像を増やす(前処理で左右反転とかしたら画像の水増しができそう)だったり、過学習気味かもしれないので他の重みを使うだったりが思いつきます。

今回はこれで終わりです。
未熟なのでいろんなところにトンチンカンなことが書いてあるかもしれません。
見つけたら教えていただけると嬉しいです。

次回やることはまだ未定ですが、どうにか続けていきたいです。それでは！
[次回](https://qiita.com/I_Love_idachi/items/fe93d5c2f18ac9451052)できました！！