たかしくん (with TensorFlow) と考える積立年金の利率

最近機械学習抜きに TensorFlow を触ったので紹介します。
TensorFlow で方程式の近似解を求めたい人の参考になれば幸いです。

* (2018/05/07) 平均預け入れ年数を修正

# 最初に

* この記事内の人物・会社はフィクションです
* TensorFlow を使いますが、Deep Learning どころか Neural Network も出てきません
  * あくまで高次方程式の近似解を求めるために使います
* 定式化のため問題の一部を簡単化してます
* Google Colaboratory で動かしました(Chrome 以外だと上手く表示されないかも)
  * [作ったノートブック](https://colab.research.google.com/drive/1Y_Z0cEGIjFIXyHTVdmHyjIuAiJEP7lsX#scrollTo=vYY6URkW47e-)
* 本エントリ内の画像は [かわいいフリー素材集 いらすとや](https://www.irasutoya.com/) 様より拝借しております


# 導入

たかしくんは **現在 26歳** の Software Engineer です。

![kaisya_man_bad.png](https://qiita-image-store.s3.amazonaws.com/0/33573/b98de47e-a398-ca66-0afe-48ecea1ed615.png)


ある日、とある青い銀行に勤めるお友達（広義）からこんなお誘いがありました。

![job_ginkouin.png](https://qiita-image-store.s3.amazonaws.com/0/33573/e5dd2f38-adb4-7c75-0177-7478c4562fa8.png)


**「たかしくん！僕と契約して個人積立年金を始めてよ！（＝ノルマのための数字になってよ）」**

お友達からのお誘いだったので、たかしくんは詳しくお話を聞くことにしました。

聞いてみたところ以下のことが分かりました。

* **毎月 20,000 円** 支払う
  * 今は **1月とし、もし加入したら今月から支払うとする**
  * 1年間で 240,000 円支払う
* **65歳** になるまで支払いを続ける（65 - 26 = 39 年間支払いを続ける）
* 65歳になったらすぐもらえる（据え置き期間はなし）
* **9,930,000** 円をまとめてもらえる
  * 期間を挟みつつ分割払い、ではない

たかしくんはこれがお得（年利が高い）なら始めてみようと思いました。
ですがその場で年利を計算できなかったため、持ち帰り案件とすることにしました。

![shinsyakaijin_run_man2.png](https://qiita-image-store.s3.amazonaws.com/0/33573/28b49269-9f28-6ff7-936b-97b3aa7ee426.png)


# 問題の定式化

## 年利が高いとは（たかしくんが個人積立年金を始める基準）

年利が高いか否かを判断するために、たかしくんは定期預金の年利と比較してみることにしました。

[【定期預金の金利を徹底比較！】定期預金金利の高さで選ぶ！おすすめネット銀行ランキング！｜ネット銀行比較｜ザイ・オンライン](http://diamond.jp/articles/-/50932)


上記のサイトを確認したところ、一番高い銀行では **5年** 預けると **0.30%** の年利がつくことがわかりました（2018年5月3日時点）。

たかしくんが今から個人積立年金を始めた場合、**39年間**支払います（上記参照）。
払った年によって預け入れる年数が変わりますが、平均すると **(39 + 1) / 2 = 20** 年お金を預け入れることになります($ (\sum_{k=1}^N k) / N = (N + 1)/ 2$ より)。



ですので上記の **5年** の定期預金の年利と比較した結果、
たかしくんは **個人積立年金の年利** が **0.35% 以上** なら始めようと決意しました。

## 年利と総受け取り額の関係

ここで年利と総受け取り額の関係を表すため、下記のようなパラメータを定義します。

|パラメータ|内訳|
|-------|:---------|
|$R$ [%]|個人積立年金の年利|
|$P$ [円]|毎年たかしくんが支払う金額|
|$N$ [年]|たかしくんが支払いを続ける期間|
|$S(N)$ [円]|たかしくんが $N$ 年間支払った後にもらえる金額|

ここで、下記のような $r$ を定義します。

$$
r = 1 + \frac{R}{100}
$$

### 仮定

ここで少し面倒なのが、年利は **1年間** お金を預けた場合に発生するのに対し、たかしくんは **毎月** 積立金を支払っているということです。

例えば、たかしくんが **2018年5月** に支払った $p$ 円は、**2019年5月** になって初めて $rp$ 円になるということです。

ここでは計算を簡単にするため、以下の仮定を置きます。
（一ヶ月単位の利率を仮定することでより正確に計算できますが、それは各自でやってみてください）

* 仮定：たかしくんはその年の最後に $P$ 円をまとめて払ったとして、その 1年後に $r$ 倍になる

### 1年支払った場合の総受取金額

その年の最後に $P$ 円払ったとして、そのお金に年利がかかる前に返金されるので、下記のようになります。

$$
S(1) = P
$$



### 2年支払った場合の総受取金額

最初の年に支払った金額には年利がついて、2年目に支払った金額には年利がついてないです。

$$
S(2) = P\cdot r + P
$$

### N 年支払った場合の総受取金額

```math
\begin{align}
S(N) &= P \cdot r^{N-1} + P \cdot r^{N-2} + ... + P \cdot r + P\\
&= P \cdot \frac{r^N - 1}{r - 1}
\end{align}
```

## 年利を求めるには

ここで実際に求めたい（わかっていない）パラメータは $r$ のみです。
他のパラメータはわかっているので実際に代入してみます。

```math
9930000 = 240000 \cdot \frac{r^{39} - 1}{r - 1}
```

分かりやすいよう両辺を $240000$ で割ります。

```math
\frac{993}{24} = \frac{r^{39} - 1}{r - 1}
```

あとは上記の式を満たす $r$ を求めるだけです。

ですがたかしくんは精々2次方程式の解法しか覚えていません。

また、勾配法などでこの方程式の近似解を求めようにも、
たかしくんは微分が大の苦手なので上記の式を $r$ について微分することができません。

困ったことにこのままでは $r$ を求めることができません。

![komatta_man2.png](https://qiita-image-store.s3.amazonaws.com/0/33573/dc57782b-044c-fb17-d09a-84b4cd529875.png)



# TensorFlow による近似解の計算

しかしたかしくんはただのたかしくんではありません。
闇のパワー（TensorFlow）を持った "たかしくん with TensorFlow" なのです。

![kids_chuunibyou_boy.png](https://qiita-image-store.s3.amazonaws.com/0/33573/015d1c1f-d438-a70d-fae0-5569f83386dd.png)


そこでたかしくんは TensorFlow を使って利率の近似解を求めることにしました。
（近似解はダメ？厳密解じゃないと許さない？そんなやつは[理想を抱いて溺死しろ](https://typemoon.wiki.cre.jp/wiki/%E3%82%A8%E3%83%9F%E3%83%A4)）

## コード（TensorFlow）

※ `LEARNING_RATE`（学習率）の値によっては収束しなかったり、収束が遅いです。

```python:積立年金.py
import numpy as np
import tensorflow as tf

AGE_START = 26              # 支払い開始年齢
AGE_END = 65                # 支払い終了年齢
YEAR = AGE_END - AGE_START  # 支払い期間
COST_MONTH = 20000          # 月に支払う額
COST_YEAR = COST_MONTH * 12 # 年間支払い額
PENSION_TOTAL = 9930000     # 最終的にもらえる額(発表されてる額)
R_INIT = 2.                 # 年利の初期値(%)
EPOCH = 50                  # エポック数(パラメータの更新回数)
LEARNING_RATE = 0.000001    # 学習率、0.01 とかだとすぐ nan になって収束しないので注意

# Model parameters
r =  tf.Variable([1. + R_INIT/ 100], dtype=tf.float32)

# Model
model = (tf.pow(r, YEAR) - 1) / (r - 1)

# Label
label = tf.constant([PENSION_TOTAL / COST_YEAR], dtype=tf.float32)

# loss
loss = tf.square(model - label)

# optimizer
optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE)
train = optimizer.minimize(loss)

# 初期化用
init = tf.global_variables_initializer()

with tf.Session() as sess:
    init.run()
    print("           init r[%]: ", R_INIT)
    # EPOCH 回パラメータ更新
    for epoch in range(EPOCH):
        sess.run(train)
        # print(sess.run(r))
    r_estimated = (sess.run(r)[0] - 1.) * 100
    pension = sess.run(model)[0] * COST_YEAR
    pension = int(pension)
    print("      estimated r[%]: ", r_estimated)
    print("estimated pension[¥]: ", pension)
    print("             diff[¥]: ", PENSION_TOTAL - pension)
```

```text:実行結果
           init r[%]:  2.0
      estimated r[%]:  0.3084421157836914
estimated pension[¥]:  9929992
             diff[¥]:  8
```

上記の結果から、年利は約 $0.308$ % であり、この場合の誤差は $8$ 円であることがわかりました。
993 万円中の 8 円ですので、許しても良いのではないでしょうか。
（許せない人は、コード内の `dtype=tf.float32` を `dtype=tf.float64` にしてみましょう）

よって年利が $0.308$ %、すなわち $0.35$ % 未満であることが判明したので、
たかしくんは今回は個人積立年金を **始めない** ことにしました。



# 結論

* 個人積立年金するぐらいなら定期預金（5年）で十分

# 最後に

今回の例では色々仮定をおいたり、メリット・デメリットを無視したので、実際はそんなに簡単には決められないかもですが、このような考え方もあると知ってもらえれば幸いです。

ちなみに私は今年の昇給額がしょっぱかったのでまだ始めません。

そしてこれからは Qiita ポエマーとして稼いでいこうと思います。