勉強会否定派が社内で機械学習の勉強会（ハンズオン）を開催してみた

# はじめに

- 私は、そもそも勉強会というのもがあまり好きではありません。なので、参加はほとんどしたことがありません。
- また、人前で発表したりするのも苦手です。
- 拒否反応をいままで示していましたが、色々なきっかけで開催まで踏み切ってみたお話です。
- 発表内容や資料などは稚拙なものや誤りがあるかもしれません。ご了承ください。

# きっかけ

1. 元々は、ビギナーながらも独学でAI、機械学習、ディープラーニングなどを勉強してきました。（[最初はここからスタートしました](https://qiita.com/neriai/items/bd7bc36ec42c8ef65b2e)）
2. 数学的概念やより実践的なものに近づくほど行き詰まりを感じるようになってきました。
3. 一度アウトプットしてセーブしたほうがいいかもしれないと思い始めるようになってきました。
4. AI技術を取り入れた施策でウハウハじゃん、と周囲がざわつき始めました。
5. そんな簡単なものじゃないよと言いたくて開催に踏み切りました。
6. ただ、目的は１つではなく複数設けました。

# AI、機械学習、ディープラーニングの正しい理解をしてもらう

## 目的

- 周囲に正しい理解を深めてもらう。
- 機械学習について興味を持ってもらって、一緒に勉強したい人を募集。
- 自身の理解を深めたり、発表力の向上。

## 勉強会の形式

- スライドを作成したプレゼン方式にしました。
- プレゼン後に、質疑応答や討論、可能性について話せる時間を設けました。
- アジェンダや時間配分などを設定しました。

## プレゼン資料の作成

- PowerPointを使って作成しました。
- どこからでも誰からでも見れるようにSlideShareにアップロードしました。
- 内容は色々調べなおして自分なりに纏め直しました。

## プレゼン資料

https://www.slideshare.net/KeitaNeriai/ss-83967210

# ハンズオン形式で、より機械学習について実践的に基礎知識を得てもらう

## 背景

- 上記の勉強会後、より理解を深めたいメンバーが絞られました。
- なので、より実践的に知識をつけてもらうべく開催に踏み切りました。
- ハンズオン形式は初めて行いました。
- チャットボットを作りたいという声があったので、自然言語系のテーマにしてみました。
- 最初からたくさんの時間を費やして行うことに私が抵抗としてあったため、1時間で行えるものにしました。
- プログラムを書く際は、対面式で写経してもらうようにしました。

## 目的

- メンバーに対して機械学習の概念的な仕組みを深めてもらう。
- 環境構築やPythonの基礎知識も深めてもらう。

## ハンズオン資料

- 内容としては、テキスト文章の単語を特徴量に変換するプログラムにしました。

```py:corpus2dense.py
#!/usr/local/bin/python3
#!-*- coding: utf-8 -*-

import argparse
import json
import MeCab

from gensim import corpora, matutils

def extract_words(filepath):

    input_file = open(filepath, "r")
    lines = json.load(input_file)

    words_list = []

    for line in lines.values():

        for text in line:
            words = extract_noun(text)

        words_list.append(words)

    lines.close()

    return words_list

def extract_noun(text):

    tagger = MeCab.Tagger("-Owakati")
    tagger.parse("")

    node = tagger.parseToNode(text)

    noun = []

    while node:
        if node.feature.split(',')[0] == "名詞":
            print(node.feature)
            noun.append(node.surface)

        node = node.next

    return noun

def create_dataset(words_list):

    dictionary = corpora.Dictionary(words_list)

    ids = auto_increment(dictionary)

    print(ids)

    features_list = []

    for words in words_list:
        features = convert_feature(dictionary, words)
        features_list.append(features)

    print(features_list)

    vectors_list = []

    for features in features_list:
        vectors = convert_vector(dictionary, features)
        vectors_list.append(vectors)

    print(vectors_list)

def auto_increment(dictionary):

    ids = dictionary.token2id

    return ids

def convert_feature(dictionary, words):

    features = dictionary.doc2bow(words)

    return features

def convert_vector(dictionary, features):

    dense = list(matutils.corpus2dense([features], num_terms=len(dictionary)).T[0])

    return dense

def main():

    parser = argparse.ArgumentParser()
    parser.add_argument('--filepath', default='input.json')
    args = parser.parse_args()

    words_list = extract_words(args.filepath)

    create_dataset(words_list)

if __name__ == '__main__':

    main()
```

```json:input.json
{
    "和風モダン": [
        "日本初の総合結婚式場。それが目黒雅叙園。伝統を保ちつつ常に新しいスタイルを追及続けております。多くの芸術家たちによって創られた壁画や天井画、彫刻などがちりばめられた園内は、えにしの糸を結ぶ舞台にふさわしい絢爛豪華な空間として多くの方々に愛されております。永年を経て洗練された目黒雅叙園ならではの風格のあるウエディングステージをご覧下さい。他にはない芸術を感じられる神前式。四季山水の壁画に囲まれた風雅な神殿と絢爛豪華な美術品に彩られた本社（ほんやしろ）造りの典雅な神殿。ご希望のスタイルをお選びいただけます。また、人気の秘密は前室にも秘密が。日本画が描かれた空間は、まさに芸術作品。また式の中では目黒雅叙園だけのオリジナル演出もご用意しております。一度は絶対に見てもらいたい。神殿。まずはお気軽に模擬挙式へ参加してみてください！！四季折々の料理、折衷料理といろいろなゲストの方にご満足いただけるお料理をご提案しております。舌で楽しみ、目で楽しむ。味にこだわるのはもちろんですが、目でお料理を楽しんでいただけるよう器にも細部までこだわっております。まずは一度試食会へ参加して目黒雅叙園の魅力を体感してみてください。"
    ],
    "ナチュラル": [
        "フランス各地のミシュラン星付きレストランにて約7年間修行を積んだ総料理長井上が作るお料理はフレンチをベースにしながらも、従来の考えにとらわれない料理で、見た目も味も楽しいフレンチを創作。また、1903年創業のパリの老舗サロン「サロン・ド・テ アンジェリーナ」などで経験を積んだデセールを務める松下が創りだす華やかなデザートがより一層パーティを盛り上げます。おふたりの夢の舞台は、ご自宅のガーデンをイメージした開放感のあるグリーンテラス、白を基調とした明るいフロアやお洒落なアンティーク雑貨が並ぶレストランフロアの３フロア♪３つのフロアからお好みやご人数様に合わせて会場を選べるのが最大の魅力！写真は1番人気の5階、ガーデンテラス付フロアです！Ｅｌｅｇａｎｔｅ　Ｖｉｔａではレストランウエディングも手掛けるプランナーが、海外や国内挙式後の1.5次会パーティも担当するのでサポート力も魅力のひとつ！お打ち合わせからパーティ当日までおふたりとおふたりにとって大切なゲストが最高の笑顔で迎えられるようお手伝いします！"
    ]
}
```

# 定期的に情報発信や意見交換をする場を作って、より理解を深めてもらう

- Slack上にチャンネルを作成して、メンバーを招待しました。
- その頃、NHKの番組なども見始めたので、おすすめの情報を流すようにしました。

# ハンズオン形式で、よりディープラーニングについて実践的に基礎知識を得てもらう

## 背景

- もう少し継続的に行ってみたいと思ったため、週に1回、1時間のハンズオンを7週に渡って行いました。
- テーマは、CNNで分類プログラムを作ってもらいました。
- 第1回のみ、スライドを作成したプレゼン方式にしました。
- プログラムを書く際は、対面式で写経してもらうようにしました。
- また、専門用語やアルゴリズムの基礎理解が必要になってきたため、翌週に1人5分程度の発表に纏められる宿題を与えました。

## 目的

- メンバーに対してニューラルネットワークやディープラーニングの概念的な仕組みを深めてもらう。
- 環境構築やPython、Tensorflowなどの基礎知識も深めてもらう。

## スライド資料

https://www.slideshare.net/KeitaNeriai/deep-learning-83968306

## ハンズオン資料

- 分類したいラベルや画像は各々で準備してもらうにしました。
- 実際には下記のソースコードを5分割くらいしたものをそれぞれ用意して、写経してもらうようにしました。
- データ作成の簡略化のために、これらを配布して使ってもらいました。

https://qiita.com/neriai/items/5e9ee66e7bd676057ed3
https://qiita.com/neriai/items/98e4f6b70c87e51014c6
https://qiita.com/neriai/items/a50c175ff346f34e4401

```py:learning.py
#!/usr/local/bin/python3
#!-*- coding: utf-8 -*-

import argparse
import sys
import tensorflow as tf
import random
import cv2
import numpy as np
import model
import time
import os

FLAGS = None

parser = argparse.ArgumentParser()


# 宿題：learning_rate,epochってなんだろう
parser.add_argument(
    '--learning_rate',
    type=float,
    default=1e-4,
    help='Initial learning rate.'
)

# データの一塊を示す
parser.add_argument(
    '--batch_size',
    type=int,
    default=1,
    help='Batch size.  Must divide evenly into the dataset sizes.'
)
parser.add_argument(
    '--input_train_data',
    type=str,
    default='./dataset/train.txt',
    help='File list data to put the input train data.'
)
parser.add_argument(
    '--input_test_data',
    type=str,
    default='./dataset/test.txt',
    help='File list data to put the input test data.'
)
parser.add_argument(
    '--log_dir',
    type=str,
    default='./logs',
    help='Directory to put the log data.'
)
parser.add_argument(
    '--image_size',
    type=int,
    default=81,
    help='Input image size'
)

# 宿題：batch_size,pool_sizeってなんだろう
parser.add_argument(
    '--pool_size',
    type=int,
    default=3,
    help='MAX pooling size'
)

# 定数の宣言
FLAGS, unparsed = parser.parse_known_args()

# 全データに対して答え合わせ
def do_eval(
    sess,
    eval_correct,
    images_placeholder,
    labels_placeholder,
    images_data,
    labels_data,
    keep_prob
):

    true_count = 0  # 正答数

    # 総数を算出
    steps_per_epoch = len(images_data) // FLAGS.batch_size  # 切り捨て除算
    num_examples = steps_per_epoch * FLAGS.batch_size       # num_examplesは結果的に切り捨て分のみを減算

    # 全件評価
    for step in range(steps_per_epoch):

        # 正答数を受け取り、加算
        true_count += sess.run(
            eval_correct,
            feed_dict={
                images_placeholder: images_data[step * FLAGS.batch_size: step * FLAGS.batch_size + FLAGS.batch_size],
                labels_placeholder: labels_data[step * FLAGS.batch_size: step * FLAGS.batch_size + FLAGS.batch_size],
                keep_prob: 1.0
            }
        )

    # 正答率計算と表示
    print('  Num examples: %d  Num correct: %d  Precision @ 1: %0.04f' % (num_examples, true_count, (float(true_count) / num_examples)))

def training():

    # データフローをTensorBoardで可視化する
    # with: pythonの文法。with以下の命令を、文脈外のコマンドに持ち越さないという命令。
    with tf.Graph().as_default():

        # 画像データのプレースホルダー
        # プレースホルダー：フィードの対象を示す箱のようなもの
        images_placeholder = tf.placeholder(
            tf.float32,
            name='images',
            shape=(FLAGS.batch_size, FLAGS.image_size, FLAGS.image_size, 3)
        )

        # ラベルデータのプレースホルダー
        labels_placeholder = tf.placeholder(
            tf.int32,
            name='labels',
            shape=(FLAGS.batch_size)
        )

        # ドロップアウト率
        # ドロップアウト：訓練データに対して学習されているが、未知のデータに対して適合できていない（汎化できていない）状態
        # 宿題：過学習、ドロップアウト率ってなんだろう
        keep_prob = tf.placeholder(tf.float32, name='keep_probability')

        # モデルを作成する
        logits = model.inference(
            images_placeholder,
            keep_prob,
            FLAGS.image_size,
            FLAGS.pool_size
        )

        # loss()を呼び出して損失を計算
        loss = model.loss(logits, labels_placeholder)

        # training()を呼び出して訓練して学習モデルのパラメーターを調整する
        train = model.training(loss, FLAGS.learning_rate)

        # 精度の計算
        eval_correct, accuracy = model.evaluation(logits, labels_placeholder, FLAGS.batch_size)

        # TensorBoardにここまでの内容を出力
        summary = tf.summary.merge_all()

        # 保存の準備
        saver = tf.train.Saver()

        # Sessionの作成
        with tf.Session() as sess:

            # TensorBoradへの書込準備
            summary_writer = tf.summary.FileWriter(FLAGS.log_dir, sess.graph)

            # 変数初期化
            sess.run(tf.global_variables_initializer())

            # 画像データループ
            for step in range(len(FLAGS.train_image) // FLAGS.batch_size):

                # 開始時間退避
                start_time = time.time()

                # batch_size分の画像に対して訓練の実行
                train_batch = FLAGS.batch_size * step

                # 訓練実行
                feed_dict = {
                    images_placeholder: FLAGS.train_image[train_batch:train_batch+FLAGS.batch_size],
                    labels_placeholder: FLAGS.train_label[train_batch:train_batch+FLAGS.batch_size],
                    keep_prob: 0.5
                }

                # trainは捨てているが、指定しておかないと賢くならない
                # 宿題：loss,accuracyってなんだろう
                _, loss_val, accuracy_val = sess.run(
                    [train, loss, accuracy],
                    feed_dict=feed_dict
                )

                # (1 epochあたり)処理時間算出
                duration = time.time() - start_time

                # 1回ごとにsummary(tensor boardのインスタンス)を取得し、writerに追加
                if step % 1 == 0:
                    # 10回ごとの結果出力
                    print('Step %d: loss = %.2f, accuracy = %.3f (%.4f sec)' % (step, loss_val, accuracy_val, duration))

                    # セッション実行しTensorBoardのサマリ取得
                    summary_str = sess.run(summary, feed_dict=feed_dict)

                    # TensorBoardにサマリを追加
                    summary_writer.add_summary(summary_str, step)
                    summary_writer.flush()

                # 最終ループ時に評価
                if (step + 1) == len(FLAGS.train_image)//FLAGS.batch_size:
                    saver.save(sess, os.path.join(FLAGS.log_dir, 'model.ckpt'), global_step=step)

                    print('Training Data Eval:')

                    # 訓練データ評価
                    do_eval(sess, eval_correct, images_placeholder, labels_placeholder, FLAGS.train_image, FLAGS.train_label, keep_prob)

                    # テストデータ評価
                    print('Test Data Eval:')

                    do_eval(sess, eval_correct, images_placeholder, labels_placeholder, FLAGS.test_image, FLAGS.test_label, keep_prob)

            # TensorBoardの書き込みクローズ
            summary_writer.close()

def read_images(file_image_list):

    image_list = []
    label_list = []

    with open(file_image_list) as file:
        file_data = file.readlines()

    random.shuffle(file_data)

    for line in file_data:

        line      = line.rstrip()
        file_name = line.split('\t')

        img = cv2.imread(file_name[0])
        img = cv2.resize(img, (FLAGS.image_size, FLAGS.image_size))

        # 画像のピクセル値を0.0~1.0に正規化する
        image_list.append(img.astype(np.float32)/255.0)

        label_list.append(int(file_name[1]))

    return np.asarray(image_list), np.asarray(label_list)

def main(_):

    print('start reading images')

    FLAGS.train_image, FLAGS.train_label = read_images(FLAGS.input_train_data)
    FLAGS.test_image,  FLAGS.test_label  = read_images(FLAGS.input_test_data)

    print('start training')

    training()

if __name__ == '__main__':

    # パース実行
    # コマンドラインをエスケープした後に実行するargvを引数とする関数function
    tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
```

```py:model.py
#!/usr/local/bin/python3
#!-*- coding: utf-8 -*-

import tensorflow as tf

# TensorBoardに出力するImageのタグ
IMAGE_SOURCE = 'source'
IMAGE_FILTER = 'filter'
IMAGE_CONV   = 'conv'
IMAGE_POOL   = 'pool'

# 識別ラベルの数(餃子:0,ピザ：1,寿司：2)
NUM_CLASSES       = 3

NUM_OUTPUT_IMAGES = 64
NUM_FILTER1       = 32
NUM_FILTER2       = 4
SIZE_FILTER1      = 5
SIZE_FILTER2      = 8
NUM_FC            = 1024

def inference(images, keep_prob, image_size, pool_size):

    # tf.name_scope： TFの名前管理関数。この関数下の入れ子の命令に、ひとつの文脈・グループを形成する。
    # 名前の管理をするメリットは、描画である。TensorFlowはTensorBoardにて構築した学習器のモデルを図示することができる。名前を管理しておくと、描画のアウトプットがわかりやすくなる。
    # ここでは、以下のweight や biasへの代入行為がname_scope関数下にネストされている。
    # すわなち、これらの命令が'hidden1'という隠れ層のパラメータ計算に関する文脈に入っていることを定義づけている
    with tf.name_scope('inference'):

        # 重みを標準偏差0.1の正規分布乱数で定義
        # 重み: 特定の個体を重要視するような意味で個体ごとに値を設定する
        def weight_variable(shape):

            # tf.Variable: 変数を作成する
            # tf.truncated_normal: Tensorを正規分布かつ標準偏差の2倍までのランダムな値で初期化する
            return tf.Variable(tf.truncated_normal(shape, stddev=0.1))

        # バイアスを初期値0.1定数で定義
        # バイアス：パーセプトロンというニューロンが発火する傾向の高さ（閾値みたいなもの）を表すといえます。
        # 宿題：バイアス、パーセプトロンとはなんだろう
        def bias_variable(shape):

            # tf.constant: 配列型定数の初期化
            return tf.Variable(tf.constant(0.1, shape=shape))

        # 畳み込み層定義
        def conv2d(x, W):

            # x: インプットデータを、4次元([batch, in_height, in_width, in_channels])のテンソルを渡す
            # W: 畳込みでinputテンソルとの積和に使用するweightにあたる
            # ストライド（=１画素ずつではなく、数画素ずつフィルタの適用範囲を計算するための値)を指定
            # SAME: ゼロパディングを利用する場合
            return tf.nn.conv2d(x, W, [1, 1, 1, 1], 'SAME')

        # プーリング層定義
        def max_pool(x):

            # x: inputデータ 畳込み層からの出力データ
            # ksize: プーリングサイズ
            # strides: ストライドサイズ
            # padding: SAME
            # 宿題：フィルター、ストライド、パディングとはなんだろう
            return tf.nn.max_pool(
                x,
                ksize=[1, pool_size, pool_size, 1],
                strides=[1, pool_size, pool_size, 1],
                padding='SAME'
            )

        # 入力情報
        with tf.name_scope('input'):
            tf.summary.image(IMAGE_SOURCE, images, NUM_OUTPUT_IMAGES, family=IMAGE_SOURCE)

        # 第1畳み込み層
        # 画像の濃淡パターンを検出する（エッジ抽出等の特徴抽出）
        with tf.name_scope('conv1') as scope:

            # 5x5フィルタで32チャネルを出力（入力はRGB画像なので3チャンネル）
            W_conv1 = weight_variable([SIZE_FILTER1, SIZE_FILTER1, 3, NUM_FILTER1])

            # 畳み込み層のバイアス
            b_conv1 = bias_variable([NUM_FILTER1])

            # 活性化関数ReLUでの畳み込み層を構築
            # ReLUは、入力した値が0以下のとき0になり、1より大きいとき入力をそのまま出力
            h_conv1 = tf.nn.relu(conv2d(images, W_conv1) + b_conv1)

            # Tensorを[縦,横,3,フィルタ数]から[フィルタ数,縦,横,3]と順列変換してimage出力
            tf.summary.image(
                IMAGE_FILTER,
                tf.transpose(W_conv1, perm=[3,0,1,2]),
                4,
                family=IMAGE_FILTER
            )

            # Tensorを[-1,縦,横,フィルタ数]から[-1,フィルタ数,縦,横]と順列変換し、頭2次元をマージしてimage出力
            tf.summary.image(
                IMAGE_CONV,
                tf.reshape(tf.transpose(h_conv1, perm=[0,3,1,2]), [-1, image_size,image_size, 1]),
                4,
                family=IMAGE_CONV
            )

        # 第1プーリング層
        # 物体の位置が変動しても同一の物体であるとみなす（位置ズレを許容する）
        with tf.name_scope('pool1') as scope:

            # プーリング処理後の画像サイズ計算
            image_size1 = int(image_size / pool_size)

            h_pool1 = max_pool(h_conv1)

            # Tensorを[-1,縦,横,フィルタ数]から[-1,フィルタ数,縦,横]と順列変換し、頭2次元をマージしてimage出力
            tf.summary.image(
                IMAGE_POOL,
                tf.reshape(tf.transpose(h_pool1, perm=[0,3,1,2]), [-1,image_size1,image_size1,1]),
                NUM_OUTPUT_IMAGES,
                family=IMAGE_POOL
            )

        # 第2畳み込み層
        with tf.name_scope('conv2') as scope:

            W_conv2 = weight_variable([SIZE_FILTER2, SIZE_FILTER2, NUM_FILTER1, NUM_FILTER2])
            b_conv2 = bias_variable([NUM_FILTER2])
            h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)

            # Tensorを[縦,横,フィルタ数1,フィルタ数2]から[フィルタ数1*フィルタ数2,縦,横,1]と順列変換してTensorBoardにiimage出力
            tf.summary.image(
                IMAGE_FILTER,
                tf.reshape(tf.transpose(W_conv2, perm=[2,3,0,1]), [-1,SIZE_FILTER2,SIZE_FILTER2,1]),
                4,
                family=IMAGE_FILTER
            )

            # Tensorを[-1,縦,横,64]から[-1,64,縦,横]と順列変換し、[-1]と[64]をマージしてTensorBoardにiimage出力
            tf.summary.image(
                IMAGE_CONV,
                tf.reshape(tf.transpose(h_conv2, perm=[0,3,1,2]), [-1,image_size1,image_size1,1]),
                4,
                family=IMAGE_CONV
            )

        # 第2プーリング層
        with tf.name_scope('pool2') as scope:

            # プーリング処理後の画像サイズ計算
            image_size2 = int(image_size1 / pool_size)

            h_pool2 = max_pool(h_conv2)

            # Tensorを[-1,縦,横,フィルタ数2]から[-1,フィルタ数2,縦,横]と順列変換し、頭2次元をマージしてTensorBoardにimage出力
            tf.summary.image(
                IMAGE_POOL,
                tf.reshape(tf.transpose(h_pool2, perm=[0,3,1,2]), [-1,image_size2,image_size2,1]),
                NUM_OUTPUT_IMAGES,
                family=IMAGE_POOL
            )

        # 全結合層1の作成
        # 第1第2を通して特徴部分が取り出された画像データを一つのノードに結合し、
        # 活性化関数によって変換された値（特徴変数）を出力する
        with tf.name_scope('fc1') as scope:

            W_fc1 = weight_variable([image_size2 ** 2 * NUM_FILTER2, NUM_FC])
            b_fc1 = bias_variable([NUM_FC])
            h_pool2_flat = tf.reshape(h_pool2, [-1,image_size2 ** 2 * NUM_FILTER2])

            h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
            h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

        # 全結合層2の作成(読み出しレイヤー)
        # 全結合層からの出力（特徴変数）を元に確率に変換し、
        # それぞれの領域に正しく分類される確率を最大化する（最尤推定法）ことによって分類する
        with tf.name_scope('fc2') as scope:

            W_fc2 = weight_variable([NUM_FC, NUM_CLASSES])
            b_fc2 = bias_variable([NUM_CLASSES])

            logits = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

        return logits

def loss(logits, labels):

    with tf.name_scope('loss'):

        # 交差エントロピーの計算
        # 宿題：交差エントロピーとソフトマック関数とはなんだろう
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            labels=labels,
            logits=logits,
            name='xentropy'
        )

        # 誤差の率の値(cross_entropy)を返す
        return tf.reduce_mean(cross_entropy)

# 誤差(loss)を元に誤差逆伝播を用いて設計した学習モデルを訓練する
def training(loss, learning_rate):

    with tf.name_scope('training'):

        # TensorBoardに誤差のScalar出力
        tf.summary.scalar('loss', loss)

        # Adamを使って最適化
        # 宿題：Adam、reduce_sum、reduce_meanとはなんだろう
        train = tf.train.AdamOptimizer(learning_rate).minimize(loss)

        return train

# 学習モデルが出した予測結果の正解数を算出する
def evaluation(logits, labels, batch_size):

    with tf.name_scope('evaluation'):

        # 正解数の計算
        correct = tf.reduce_sum(tf.cast(tf.nn.in_top_k(logits, labels, 1), tf.int32))

        # 正答率の計算とTensorBoardへのScalar出力
        accuracy = correct / batch_size
        tf.summary.scalar('accuracy', accuracy)

        return correct, accuracy
```

```py:validation.py
#!/usr/local/bin/python3
#!-*- coding: utf-8 -*-

import argparse
import cv2
import numpy as np
import tensorflow as tf
import model

# 学習済み画像識別モデルの場所
CKPT_PATH = './logs/'

# 基本的なモデルパラメータ
FLAGS = None

# 識別ラベルと各ラベル番号に対応する名前
FOOD_NAMES = { 0: u'gyoza', 1: u'pizza', 2: u'sushi',}

# 顔判定
def run_judge(image):

    # 入力画像に対して、各ラベルの確率を出力して返す
    logits = model.inference(image, 1.0, FLAGS.image_size, FLAGS.pool_size)

    # Softmax計算
    loss   = tf.nn.softmax(logits)

    # TensorBoardにここまでの内容を出力
    summary = tf.summary.merge_all()

    # 学習済モデル呼出準備
    saver = tf.train.Saver()

    # Sessionの作成(TensorFlowの計算は絶対Sessionの中でやらなきゃだめ)
    with tf.Session() as sess:

        # TensorBoradへの書込準備
        summary_writer = tf.summary.FileWriter(FLAGS.log_dir, sess.graph)

        # 変数初期化(学習済モデル読込前に実行)
        sess.run(tf.global_variables_initializer())

        # 学習済モデルの取得
        ckpt = tf.train.get_checkpoint_state(CKPT_PATH)

        # 学習済モデルがあった場合には読込
        if ckpt:
            saver.restore(sess, ckpt.model_checkpoint_path)

        # セッション実行し、TensorBoardのサマリと誤差の取得
        summary_str, loss_value = sess.run([summary, loss])

        # TensorBoardにサマリを追加してクローズ
        summary_writer.add_summary(summary_str)
        summary_writer.close()

        # 判定結果
        result = loss_value[0]

        foods = []

        # 名前、確率のHashを作成
        for index, rate in enumerate(result):
            foods.append({
                'name': FOOD_NAMES[index],
                'rate': rate * 100
            })

        # 確率で降順ソートして最も確からしい答えを出力
        rank = sorted(foods, key=lambda x: x['rate'], reverse=True)

        print(rank[0])
        print(rank[1])
        print(rank[2])

        print('Probalibity %d %% :This image is %s' % (rank[0]['rate'], rank[0]['name']))

# OpenCVを使って顔検出して判断
def read_and_edit_image(file_name):

    # 画像ファイル読込
    img = cv2.imread(FLAGS.input_dir + file_name)

    # 大量に画像があると稀に失敗するファイルがあるのでログ出力してスキップ(原因不明)
    if img is None:
        print(file_name + ':Cannot read image file')
        return 0

    # 検出できなかった場合はログ出力して終了
    if len(img) <= 0:
        print(file_name + ':No Face')
    else:

        image = []

        img = cv2.resize(img, (FLAGS.image_size, FLAGS.image_size))

        # 画像のピクセル値を0.0~1.0に正規化する
        image.append(img.astype(np.float32)/255.0)

        # 配列にしてReturn
        return np.asarray(image)

# 直接実行されている場合に通る(importされて実行時は通らない)
if __name__ == '__main__':

    parser = argparse.ArgumentParser()

    parser.add_argument(
        '--input_dir',
        type=str,
        default='./test/',
        help='The path of input directory.'
    )
    parser.add_argument(
        '--input_file',
        type=str,
        default='sample3.jpg',
        help='The path of input directory.'
    )
    parser.add_argument(
        '--image_size',
        type=int,
        default=81,
        help='Input image size'
    )
    parser.add_argument(
        '--pool_size',
        type=int,
        default=3,
        help='Pooling size'
    )
    parser.add_argument(
        '--log_dir',
        type=str,
        default='./test/logs/',
        help='Directory to put the log data.'
    )

    # パラメータ取得と実行
    FLAGS, unparsed = parser.parse_known_args()

    # TensorBoard保存ディレクトリが存在したら削除し、再作成
    if tf.gfile.Exists(FLAGS.log_dir):
        tf.gfile.DeleteRecursively(FLAGS.log_dir)

    tf.gfile.MakeDirs(FLAGS.log_dir)

    image = read_and_edit_image(FLAGS.input_file)

    run_judge(image)
```

# まとめ

- 勉強会すると自分が正しい理解や説明ができないといけないため、丁寧に勉強するきっかけとなり、より理解が深まりました。
- ハンズオン形式では宿題の発表後にメンバーに質疑や討論するこでより理解を深めようとする傾向が見れました。また、分散しているため私が理解が不足している部分もメンバーが補ってくれました。
- 64bitマシンじゃないとやはり厳しいことを目の当たりにしました。（Windows32bitマシンの方がいて、それにはTensorflowが入らなかった...。）
- 上記踏まえて、リモートでハンズオンとか容易にできる環境に進んでくれると良いかもしれません。
- 大人数だと抵抗でてくるとはおもいますが、身内の少人数の勉強会には抵抗が薄くなりました。