プログラミング学習


##はじめに
この記事ではプログラミング初学者の私が勉強していく中での感想などを書いていけたらと思います。

##学習内容
今回私はオンラインプログラミングスクールのAidemyを受講しました。
・環境構築
・pyhon入門
・Numpy基礎（数値計算）
・Pandas基礎（表計算）
・Matplotlib基礎（可視化）
・機械学習概論
・教師あり学習（分類）
・スクレイピング入門
・ディープラーニング基礎
・CNNを用いた画像認識
・html＆css入門
・MNISTを用いた手書き文字認識アプリ作成
・データクレンジング
・男女識別（深層学習発展）
・コマンドライン入門
・Git入門
・Herokuへのデプロイ方法
・アプリ制作
の流れで学習してきました。

しっかりと機械学習の基礎を学ぶことができました。
学習していく中でチャットやビデオ通話を通してメンターさんに気軽に質問ができる感じが良かったです。独学でやっていくことを考えるとこういうカリキュラムがあるのは非常にありがたかったです。

今後はpythonの知識を深めるためにアルゴリズムや数学を学んでいきたいと思います。

##アプリ制作
アプリ制作の手順として、まずデータを取得するところから始めました。今回スクレイピングを使わず手動で集めたデータを色んな加工を施して水増しする方法を選びました。だいたい1000枚くらい作りました。
次に水増ししたデータを構築したモデルに渡して学習させました。
今回はデータ量が少なかったので60％ぐらいの精度しか出せませんでした。
次にFlaskを使ったアプリの構成をvscode上でやりました。html&cssの知識も多少触れることができたので良かったと思います。
最後にherokuにデプロイして終了なのですが、ここでかなり苦労してしまったのでそれはまた別の記事で取り上げたいと思います。

##コード
```python:画像の水増し
import keras
import numpy as np
from keras.utils import np_utils
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator
from keras.preprocessing.image import load_img, save_img, img_to_array, array_to_img
import os
import cv2
import sys
import random

DATA_DIR = '加工元画像パス' #加工する画像が入ってるディレクトリを指定
character_name = ['', '', ''] 

SAVE_DIR = os.path.join('セーブするディレクトリ','ファイル名')
img_size =  #画像のサイズに応じて指定
train_data = []

if not os.path.exists(SAVE_DIR): #SAVE_DIRというディレクトリがなければ作成する
    os.makedirs(SAVE_DIR)

datagen = ImageDataGenerator(
            rotation_range = 90,#-９０°~９０°の間でランダムに回転する
            width_shift_range = 0.3,#[-0.3 * Width, 0.3 * Width] の範囲でランダムに左右平行移動する
            height_shift_range = 0.3,#[-0.3 * Height, 0.3 * Height] の範囲でランダムに上下平行移動する
            zoom_range = [0.7, 1.3],# [0.7, 1.3]の範囲でランダムに拡大縮小する
            horizontal_flip = True,#ランダムに左右反転する
            vertical_flip = True,#ランダムに上下反転する
        )

copy_num =  #水増しする枚数(元データ１枚に対しての水増し枚数)

def create_data():
    
    for index, category in enumerate(character_name):#character_nameからindexにindex番号, categoryに要素を取り出す
        path = os.path.join(DATA_DIR, category) #os.path.joinでDATA_DIRとcategoryを結合
        
        save_path = os.path.join(SAVE_DIR, category) #保存先のpathをそれぞれのcategoryに指定する

        if not os.path.exists(save_path): #save_pathというディレクトリがなければ作成する
            os.makedirs(save_path)

        
        for image_name in os.listdir(path): #ファイルディレクトリの一覧をimage_nameとして取り出す
            try: #例外を処理
                img = cv2.imread(os.path.join(path, image_name))#pathとディレクトリの一覧を結合して読み込ませる
                img_resize = cv2.resize(img, (img_size, img_size))#imgを50×50でresizeしimg_resizeに代入
                img_array = image.img_to_array(img_resize)#resizeしたimgをPIL形式からndarray型に
#                 print(img_array.shape)
                img_array = np.expand_dims(img_array, axis=0)#(1, 50, 50, 3)の配列にする
#                 print(img_array.shape)

                j = 0
                for i in datagen.flow(img_array, batch_size=1,
                                     save_to_dir=save_path, #保存するディレクトリ
                                     save_prefix=category, #ファイル名
                                     save_format='jpeg'): #ファイル形式
                 
                    j += 1 #画像を加工したらプラスする
                    i = np.resize(i,(i.shape[1], i.shape[2], i.shape[3])) #3次元に戻す
                    train_data.append([i, index])
                    if j == copy_num: #jがcopy_numになったら終了
                        break
            except Exception as e : #tryの例外をpassする
                    pass

create_data()

random.shuffle(train_data)

X = []
y = []

for feature, label in train_data:
    X.append(feature)
    y.append(label)

X = np.array(X)
y = np.array(y)

np.savez('セーブする場所のパス', X, y)
   
```

```python:モデルの構築、学習、モデルのセーブ
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator
import keras.callbacks

input_tensor = Input(shape=(50, 50, 3))#水増しの段階で調整済み
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) #include_topをFalseにしてVGGの特徴抽出部分のみ使用

def main():
    top_model = Sequential()

    top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) #16層以降の層を指定
    top_model.add(Dense(256, activation='relu')) 
    top_model.add(Dropout(0.5)) #Dropoutでオーバーフィッティングを防ぐ
    top_model.add(Dense(7, activation='softmax')) #7キャラクターの識別なのでDenceは7、出力を確率にしたいためsoftmaxを指定

    model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) #vgg16とtop_modelを結合

    for layer in model.layers[:15]: #vgg16の重みを固定
        layer.trainable=False

    model.compile(loss='categorical_crossentropy', #多クラスのためcategorical_crossentropyを指定
                  optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), #
                  metrics=['accuracy']) #評価関数にaccuracyを指定

       #model.summary()

    train_datagen = ImageDataGenerator(rescale=1./255) #正規化　０−１の間で表示したいため調整
    test_datagen = ImageDataGenerator(rescale=1./255) #色の明度は0~255で表されるため２５５で割る

    train_generator = train_datagen.flow_from_directory(
                      'パス', #読み込みたいデータのurlを指定
                       target_size=(50, 50), #読み込んだデータを５０, ５０にresize
                       batch_size=10) #一括で処理するデータの個数

    validation_generator = test_datagen.flow_from_directory(
                       'パス', #読み込みたいデータのurlを指定
                       target_size=(50, 50), #読み込んだデータを５０, ５０にresize
                       batch_size=1) #一括で処理するデータの個数

    model.fit_generator(
                                     train_generator, # 訓練用のデータ
                                     epochs=30, #繰り返し回数
                                     steps_per_epoch=30, #1epochsの中で行うフィッティングの回数
                                     validation_data=validation_generator, #テスト用データ
                                     validation_steps=30) #何回に１回テストをするか指定

    model.summary()
    model.save('model.h5') #model.h5として保存

if __name__ == '__main__':   
    main()
```

```python:main.py
import os
from flask import Flask, request, redirect, render_template, flash
from werkzeug.utils import secure_filename
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.preprocessing import image
from bottle import route, run
import numpy as np

classes = []

image_size = 50

UPLOAD_FOLDER = "uploads"
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

model = load_model('model.h5')

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':

        if 'file' not in request.files:
            flash('ファイルがありません')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('ファイルがありません')
            return redirect(request.url)
        
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(UPLOAD_FOLDER, filename))
            filepath = os.path.join(UPLOAD_FOLDER, filename)

            img = image.load_img(filepath, target_size=(image_size,image_size))
            img = image.img_to_array(img)
            data = np.array([img])
            #変換したデータをモデルに渡して予測する
            result = model.predict(data)[0]
            predicted = result.argmax()

            pred_answer = "これは" + classes[predicted] + "です"

            return render_template('main.html', answer=pred_answer, images=filepath)

    return render_template('main.html', answer='')

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 8080))
    app.run(host ='0.0.0.0', port = port)
```

```
Procfile
web: python main.py


requirements.txt
absl-py==0.11.0
astroid==2.4.2
astunparse==1.6.3
bottle==0.12.19
cachetools==4.2.1
certifi==2020.12.5
chardet==4.0.0
click==7.1.2
Flask==1.1.2
flatbuffers==1.12
gast==0.3.3
h5py==2.10.0
idna==2.10
isort==5.7.0
itsdangerous==1.1.0
Jinja2==2.11.3
Keras==2.4.3
Keras-Preprocessing==1.1.2
Markdown==3.3.3
MarkupSafe==1.1.1
numpy==1.18.1
oauthlib==3.1.0
opt-einsum==3.3.0
Pillow==8.1.0
protobuf==3.14.0
PyYAML==5.4.1
requests==2.25.1
requests-oauthlib==1.3.0
rsa==4.7
scipy==1.4.1
six==1.15.0
tensorboard==2.3.0
tensorflow-cpu==2.3.0
termcolor==1.1.0
urllib3==1.26.3
Werkzeug==1.0.1
wheel==0.36.2
wrapt==1.12.1


runtime.txt
python-3.6.12
```

##最後に
データの量が少なく精度は低いですが鬼滅の刃の炭治郎、禰豆子、善逸、伊之助、胡蝶さん、富岡さん、煉獄さんの７キャラを識別するアプリを作ってみました。
https://kimetunoapuli.herokuapp.com/
今後の課題としてはデータ量をもっと増やして精度の高いモデルを作ること。
を目標に頑張りたいと思います。