「世界初、来店不要のフルオーダーメイド靴」のプレスリリースを行いました。

8-9. vcoptで台風の進路予想を3次関数で近似する

やること

台風の進路予想はなぜ直線なのでしょうか?今日は、vcoptを用いて台風の進路予想を3次関数で近似してみます。

実行環境

WinPython3.6をおすすめしています。

WinPython - Browse /WinPython_3.6/3.6.7.0 at SourceForge.net
Portable Scientific Python 2/3 32/64bit Distribution for Windows

vcoptの使い方についてはチュートリアルをご参照ください。

vcoptの仕様については最新の仕様書をご参照ください。本記事執筆時とは仕様が異なる場合があります。

直線じゃないよね…?

Yahoo!天気・災害によれば、2019年10月10日6時時点の台風19号の進路予想はこちらです。

うーん、「こいつらに曲線見せてもしゃーないやろ」と思われているのでしょうか。

でもほら、こんな感じで近似できそうな気がしてきました。

進路、予報円と暴風域のサイズを決める

こちらの画像(2019_typhoon19.jpg)を用いるので、右クリック→保存してください。

プログラム冒頭のパラメータで、台風の進路、予報円と暴風域のサイズを再現します。

import numpy as np
from PIL import Image
from vcopt import vcopt
import matplotlib.pyplot as plt

#============================
# パラメータ
#============================
#地図上の座標
x = np.array([545, 495, 475, 555, 800])
y = np.array([690, 590, 500, 350, 120])

#予報円と暴風域のサイズ
yohouen = np.array([0, 25, 35, 50, 65])
bofuiki = np.array([75, 95, 80, 75, 95])

#画像ファイル
f = '2019_typhoon19.jpg'


#============================
# 間を100分割して線形に補間する
#============================
x_add = np.zeros((len(x)-1)*100)
y_add = np.zeros((len(x)-1)*100)
yohouen_add = np.zeros((len(x)-1)*100)
bofuiki_add = np.zeros((len(x)-1)*100)
for i in range(len(x) - 1):
    for j in range(100):
        x_tmp = (x[i] * (100 - j) + x[i+1] * j) / 100
        y_tmp = (y[i] * (100 - j) + y[i+1] * j) / 100
        yohouen_tmp = (yohouen[i] * (100 - j) + yohouen[i+1] * j) / 100
        bofuiki_tmp = (bofuiki[i] * (100 - j) + bofuiki[i+1] * j) / 100

        x_add[i*100 + j] = x_tmp
        y_add[i*100 + j] = y_tmp
        yohouen_add[i*100 + j] = yohouen_tmp
        bofuiki_add[i*100 + j] = bofuiki_tmp


#============================
# 地図の表示
#============================
def show():
    plt.figure(figsize=(15, 15)) #固定する
    #地図の表示
    img = Image.open(f).convert('RGB'); img.close
    plt.imshow(img)
    #暴風域(補完)のプロット
    for i in range(len(x_add)):
        plt.plot(x_add[i], y_add[i], '-ok', markersize=bofuiki_add[i], markeredgecolor='none', markerfacecolor='red', alpha=1.00)
    #予報円(補完)のプロット
    for i in range(len(x_add)):
        plt.plot(x_add[i], y_add[i], '-ok', markersize=yohouen_add[i], markeredgecolor='none', markerfacecolor='lightblue', alpha=1.0)
    #座標(補完)のプロット
    plt.plot(x_add, y_add, '-k', linewidth=1)
    #暴風域のプロット
    for i in range(len(x)):
        plt.plot(x[i], y[i], 'ok', markersize=bofuiki[i], markerfacecolor='none')
    #予報円のプロット
    for i in range(len(x)):
        plt.plot(x[i], y[i], 'ok', markersize=yohouen[i], markerfacecolor='none')
    #座標のプロット
    plt.plot(x, y, 'ok', markersize=5)
    plt.show()

#表示
show()

たいだいこんなものでしょうか。

3次関数でフィッティング

vcopt().rcGA() を用いて、5つの座標を3次関数で近似します。このとき、xがyの3次関数であることに注意してください。学校で習ったのは y=ax^3+bx^2+cx+d ですが、ここでは x=ay^3+by^2+cy+d です。評価関数の返り値は、目的のx座標と算出したx座標の平均二乗誤差です。

#3次関数(ただしxがyの3次関数)
def sanji_kansu(para):
    #配列のまま3次関数に通す
    x_predict = para[0]*(y**3) + para[1]*(y**2) + para[2]*y + para[3]
    #誤差の二乗の平均を返す
    return np.mean((x - x_predict)**2)


#各パラメータの探索範囲
para_range = [[-0.1, 0.1],
              [-1, 1],
              [-10, 10],
              [-10000, 10000]]

#GAでフィッティング
para, score = vcopt().rcGA(para_range,              # パラメータ範囲
                           sanji_kansu,             # 評価関数
                           0.0,                     # 評価関数の目標値
                           pool_num=2000,           # 個体数を増やす
                           show_pool_func='print')  # 表示オプション

実行します。

___________________ info ___________________
para_range : n=4
score_func : <class 'function'>
aim : 0.0
show_pool_func : 'print'
seed : None
pool_num : 2000
max_gen : None
core_num : 1 (*vcopt, vc-grendel)
___________________ start __________________
Scoring first gen 2000/2000        
gen=0, best_score=3217431586686.621, mean_score=172331798018087.7, mean_gap=172331798018087.7, time=0.4
gen=2000, best_score=362753300.3123, mean_score=69500428850076.35, mean_gap=69500428850076.35, time=1.1
gen=4000, best_score=203541318.074, mean_score=20225021141230.203, mean_gap=20225021141230.203, time=1.5
gen=6000, best_score=72794447.7365, mean_score=4613401547041.52, mean_gap=4613401547041.52, time=1.8
.
.
.
gen=750000, best_score=20.9867, mean_score=81.5369, mean_gap=81.5369, time=119.1
gen=752000, best_score=20.9867, mean_score=81.4673, mean_gap=81.4673, time=119.4
gen=754000, best_score=20.9867, mean_score=81.3175, mean_gap=81.3175, time=119.6
gen=756000, best_score=20.9867, mean_score=81.1152, mean_gap=81.1152, time=119.9
__________________ results _________________
para : [  0.00000144   0.00017607  -1.41334481 964.6314771 ]
score : 20.98665164883034
____________________ end ___________________

平均二乗誤差が21くらいまで下がりました。良さそうです。

カーブを見てみる

「x」とそれを補完した「x_add」を、フィットした3次関数を用いて更新します。

#============================
# 間を100分割して3次関数で補間する
#============================
x = para[0]*(y**3) + para[1]*(y**2) + para[2]*y + para[3]
for i in range(len(x) - 1):
    for j in range(100):
        y_tmp = (y[i] * (100 - j) + y[i+1] * j) / 100
        x_tmp = para[0]*(y_tmp**3) + para[1]*(y_tmp**2) + para[2]*y_tmp + para[3] # ここが変わった
        yohouen_tmp = (yohouen[i] * (100 - j) + yohouen[i+1] * j) / 100
        bofuiki_tmp = (bofuiki[i] * (100 - j) + bofuiki[i+1] * j) / 100

        x_add[i*100 + j] = x_tmp
        y_add[i*100 + j] = y_tmp
        yohouen_add[i*100 + j] = yohouen_tmp
        bofuiki_add[i*100 + j] = bofuiki_tmp

#表示
show()

カーブしています!

他の台風でもやってみる

斬新な進路で世間を賑わせた、2016年の台風10号でも試してみます。

こちらをダウンロードして用いてください。

#============================
# パラメータ
#============================
#地図上の座標
x = np.array([395, 425, 455, 450, 345])
y = np.array([350, 325, 290, 200, 85])

#予報円と暴風域のサイズ
yohouen = np.array([0, 25, 60, 80, 90])
bofuiki = np.array([55, 75, 110, 130, 135])

#画像ファイル
f = '2016_typhoon10.jpg'
___________________ info ___________________
para_range : n=4
score_func : <class 'function'>
aim : 0.0
show_pool_func : 'print'
seed : None
pool_num : 2000
max_gen : None
core_num : 1 (*vcopt, vc-grendel)
___________________ start __________________
Scoring first gen 2000/2000        
gen=0, best_score=51970610330.8003, mean_score=3736413919611.128, mean_gap=3736413919611.128, time=0.4
gen=2000, best_score=12544382.9878, mean_score=1496703640652.8335, mean_gap=1496703640652.8335, time=1.1
gen=4000, best_score=12544382.9878, mean_score=382630739017.8471, mean_gap=382630739017.8471, time=1.4
gen=6000, best_score=3065087.0677, mean_score=94961798439.965, mean_gap=94961798439.965, time=1.8
.
.
.
gen=670000, best_score=0.6626, mean_score=12.6764, mean_gap=12.6764, time=113.4
gen=672000, best_score=0.6626, mean_score=12.6691, mean_gap=12.6691, time=113.8
gen=674000, best_score=0.6626, mean_score=12.6536, mean_gap=12.6536, time=114.1
gen=676000, best_score=0.6626, mean_score=11.6674, mean_gap=11.6674, time=114.4
__________________ results _________________
para : [-9.84367032e-06  1.38390252e-03  1.15505210e+00  2.42695946e+02]
score : 0.6626203048034978
____________________ end ___________________

ほとんど誤差なく、美しくカーブしました。

タイトルとURLをコピーしました