TensorFlowでFizzBuzz書いてみた

# 御礼
[前回](https://qiita.com/shoji9x9/items/775f0d63589cfb4bd1f5)、[前々回](https://qiita.com/shoji9x9/items/b6f4db9e612b5af3a3f4)の記事にたくさんのいいねを頂きありがとうございました。ブログ初心者なので「いきなりこんなにいいねもらえるなんて、何かやらかしたか！？」と若干不安になっていますが・・・。また、フォローして下さった方もありがとうございます。みなさんの書かれた記事も読んでいきます。

# 本題
「TensorFlowできます！」と言うための第一歩と言えばFizzBuzzですよね（強引）。ということで、TensorFlowでFizzBuzzを書いてみました。TensorFlowでFizzBuzzを作ると聞くと「ディープラーニングにより、いい感じにFizzとかBuzzと言う分類器（もしくは生成器）を作るんだな？」と思われる方もいるかもしれません。しかし、今回は「TensorFlowでFizzBuzzを書く」です。ゴリゴリ自分でFizzBuzzロジックを書きました。

# 想定読者
* TensorFlowの低レベルAPIの挙動を知りたい方
* FizzBuzzを愛する方

# きっかけ
私が受講している[Deep Learning基礎講座](https://deeplearning.jp/lectures/dlb2018/)では理論を学ぶため、tf.layers.conv2dやtf.keras.layers.LSTMなんかは使えないようになっています。そして、LSTMを実現するために見慣れないtf.scanやtf.while_loopが登場してきました。課題は何となく乗り切ったのですが「もう少しちゃんと理解しておきたいなー、そのためには何か作ってみるか」ということでFizzBuzzを作ってみることにしました。

# FizzBuzzとは
人に説明するときは**「世界のナベアツみたいな感じです」**と言うことにしています。詳しく知りたい方は[Fizz Buzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)を参照下さい。

# まずはPythonでFizzBuzz書いてみた
ウォーミングアップということで、まずはPythonでFizzBuzzを書いてみました。所要時間5分ほどです。

``` Python
def fizz_buzz(start, end):
    list = []
    for i in range(start, end + 1):
        l = 'FizzBuzz' if i % (3 * 5) == 0 else 'Fizz' if i % 3 == 0 else 'Buzz' if i % 5 == 0 else str(i)
        list.append(l)
    return list
```

Jupyter Notebookでの実行結果はこちらです。

``` Python
fizz_buzz(1, 15)
```
``` Python
['1',
 '2',
 'Fizz',
 '4',
 'Buzz',
 'Fizz',
 '7',
 '8',
 'Fizz',
 'Buzz',
 '11',
 'Fizz',
 '13',
 '14',
 'FizzBuzz']
```

何か縦に表示されてて気持ち悪いですが、正しく動いているようです。

# そしてTensorFlowでFizzBuzz書いてみた
ついにメインテーマのTensorFlowでFizzBuzzを書いてみました。まずはコードをご覧下さい。

``` Python
import tensorflow as tf
def fizz_buzz(start, end): 
    # ループ継続条件
    def cond(index, end, result):
        return index < end
    
    # ループの中身
    def body(index, end, result):
        # FizzBuzzロジック
        _result = tf.as_string(index)
        _result = tf.cond(tf.equal(tf.mod(index, 3), 0), lambda: 'Fizz', lambda: _result)
        _result = tf.cond(tf.equal(tf.mod(index, 5), 0), lambda: 'Buzz', lambda: _result)
        _result = tf.cond(tf.equal(tf.mod(index, 3 * 5), 0), lambda: 'FizzBuzz', lambda: _result)
        
        # forじゃなくwhileなので自分でインクリメント
        index += 1
        
        # 今回の結果を以前の結果に結合
        result = tf.concat([result, [_result]], axis = 0)
        
        return [index, end, result]
    
    # 変数宣言
    index = tf.Variable(start, name = 'index')
    end = tf.constant(end + 1, name = 'end')
    result = tf.Variable([], dtype = tf.string, name = 'result')
    
    # ループ
    result_op = tf.while_loop(cond = cond, 
                           body = body, 
                           loop_vars = [index, end, result],
                           shape_invariants = [index.get_shape(), end.get_shape(), tf.TensorShape(None)])
    
    return result_op[2]
    
```

・・・疲れました。80分くらいかかりました（調査とトライ＆エラーのため）。入社面接だったら間違いなく落ちますね。でも、そのおかげか何となく挙動を理解できた気がします。

このコードには以下のAPIが登場しています。`tf.as_string`は初めて使った気がするなぁ。

* [tf.while_loop](https://www.tensorflow.org/api_docs/python/tf/while_loop)
* [tf.as_string](https://www.tensorflow.org/api_docs/python/tf/dtypes/as_string)
* [tf.cond](https://www.tensorflow.org/api_docs/python/tf/cond)
* [tf.equal](https://www.tensorflow.org/api_docs/python/tf/math/equal)
* [tf.mod](https://www.tensorflow.org/api_docs/python/tf/floormod)
* [tf.concat](https://www.tensorflow.org/api_docs/python/tf/concat)

そしてJupyter Notebookでの実行結果はこちらです。

``` Python
result = fizz_buzz(1, 15)
sess = tf.Session()
sess.run(tf.global_variables_initializer())
sess.run(result)
```
``` Python
array([b'1', b'2', b'Fizz', b'4', b'Buzz', b'Fizz', b'7', b'8', b'Fizz',
       b'Buzz', b'11', b'Fizz', b'13', b'14', b'FizzBuzz'], dtype=object)
```
一応正しく動いていそうです。でも、`b`って何なんでしょう・・・。本当は取り除きたかったのですが、そこまでの根性はありませんでした。どなたかわかる方教えて下さると嬉しいです。

# 他の方の実装
そして答え合わせのつもりで他の方の実装を見てみました。
[「FizzBuzzを書いてください」「まずTensorFlowをインポートして・・・」を自分も書いてみた](https://qiita.com/YusukeSuzuki@github/items/13268a780963d9446688)はエレガントですね！`tf.case`や`tf.control_dependencies`なんて初めて知りました。

FizzBuzzの実装ではないですが、[tf.while_loop分かったかも](https://qiita.com/n_kats_/items/9ce3c2fbcefe73c070df)は実装に当たり参考にさせて頂きました。すごい偶然なのですが、知り合いの方のブログでした。助かりました。ありがとうございました。

# 最後に
こういったTensorFlowのAPIを使う機会は多くないかもしれませんが、使うことになった場合はいきなり本当に作るものに適用するのではなく、簡単なロジックに適用し挙動を確認するというのが有効そうです。今回、簡単なロジックでもデバッグに苦労しました。本物にいきなり適用しようとすると、何がまずいのか特定が大変になるんでしょうね。

TensorFlow2.0はDefine by Runになるそうですが、そうするとデバッグも楽になるのかなぁ。そうだと嬉しいです。