最適化ライブラリで最大化, 最小化メソッドしかない場合の目的関数の設定方法

# ’20/01/19更新 例(4)を追記
 本記事ではPythonに限らない話である。例のためPythonの多目的最適化のフレームワーク「Platypus」を挙げると、最大化問題を解くProblem.MAXIMIZEと、最小化問題を解くProblem.MINIMIZEの二つがある。
最大化問題とは、例えばy=f(x)において、yが最大となるxを探索すること。
最小化問題は、その逆でyが最小となるxを探索することである。
その他の最適化のライブラリにおいても、自分のやりたいことの例題が少ない(見つけられない)かったりするが、最大化or最小化できれば下記のように考えて目的関数を設定することで、最適化問題を解くことができる。

例(1) f(x)の目的値を0にしたい場合
→目的関数をy=1/f(x)として、yを最大化問題として解けばf(x)は0に近づく。それは式変形してf(x)=1/yにより分かる。

例(2) f(x)の目的値を負側から0にしたい場合
→目的関数をy=-1/f(x)として、yを最大化問題として解けばf(x)は負側から0に近づく。それは式変形してf(x)=-1/yより分かる。

例(3) f(x)の目的値を3にしたい場合
→目的関数をy=3-f(x)として、yを最小化問題として解けばf(x)は3に近づく。それは式変形してf(x)=3-yよりわかる。ここで、もしy(≒f(x))がマイナス(-)を探索する可能性がある場合は二乗を取ったりしても良い。しかし、パレート解(得られる複数の最適解)から負の解は異常だと判別出来て振るいに掛けられることは可能なため、特に意識しすぎなくともよいと思う。

例(4) 最大化したいが指定方法が最小化問題しかない場合
→例えば、アルゴリズムNSGAⅢはProblem.MINIMIZEのみ指定できる。このような場合は目的関数をy=-1 × f(x)として、マイナスを掛けることでyを最小化問題として解けばf(x)は負側無限へ近づく。そして、得られた結果に対して、再びー1を掛け戻せば、所望の最大値の結果を得られることになる。

 

■本記事の例(3)のコード例を下記に示す

目的関数2について腹囲の回帰式をそのまま用いるのではなくて、「目的関数=75ー回帰式」、objective_function2 = 75 - myfunc2.interaction_ver_func(Chins, Situps, Jumps)として、腹囲の目的値を75に設定している。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# https://platypus.readthedocs.io/en/latest/getting-started.html#defining-unconstrained-problems
import sys
sys.path.append(".")
import Weight_LinearRegression_interaction_ver_module as myfunc1 # 体重の回帰式
import Waist_LinearRegression_interaction_ver_module as myfunc2 # 腹囲の回帰式
import Pulse_LinearRegression_interaction_ver_module as myfunc3 # 心拍数の回帰式

from platypus import NSGAII, Problem, Real, Integer
import pandas as pd

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns

# 目的関数を作成して返す関数:自作モジュールを読み出す場合
def my_function_from_module(vars):
    Chins = vars[0] # x1 懸垂の回数
    Situps = vars[1] # x2 上体反らしの回数
    Jumps = vars[2] # x3 ジャンプ
    
    objective_function1 = myfunc1.interaction_ver_func(Chins, Situps, Jumps)
    objective_function2 = 75 - myfunc2.interaction_ver_func(Chins, Situps, Jumps)
    objective_function3 = myfunc3.interaction_ver_func(Chins, Situps, Jumps)
    
    return [objective_function1,
            objective_function2,
            objective_function3]

# パレート解を図示する関数
def plot_scatter(algorithm, out_file_name, my_color):
    my_file_name = out_file_name + '_NSGAII'

    x_list = [s.objectives[0] for s in algorithm.result]
    y_list = [75 - s.objectives[1] for s in algorithm.result]
    
    fig = plt.figure(figsize=(8,8)) #dpi=120)
    ax = fig.add_subplot(1,1,1)
    ax.set_title('Multi-objective optimization', fontsize=14)
    #plt.xlabel("$f_1(x)$")
    #plt.ylabel("$f_2(x)$")
    ax.set_xlabel(my_xlabel, fontsize=16)
    ax.set_ylabel(my_ylabel, fontsize=16)
    if my_xrange:
        ax.set_xlim([my_xrange[0], my_xrange[1]])
    if my_yrange:
        ax.set_ylim([my_yrange[0], my_yrange[1]])
    ax.scatter(x_list, y_list, facecolors='none', edgecolors=my_color,
               label="NSGAII" + '\n  Pareto frontier')
    ax.legend(loc='best', fontsize=14)
    ax.grid(which='both')
    ax.tick_params(labelsize=14)
    plt.tight_layout()
    plt.savefig(my_file_name + '_Pareto_frontier.png')
    plt.close()

# 行列散布図を作成する関数
def plot_matrix_scatter(label, DF, my_sequential_colormap):
    print("plot_matrix_scatter ...")
    sns.set(style="ticks", font_scale=1.2, palette=my_sequential_colormap, color_codes=True)
    g = sns.pairplot(DF, diag_kind="hist")
    g.fig.suptitle(label)
    g.fig.subplots_adjust(top=0.9)
    plt.grid(True)
    plt.savefig(label + "_Matrix_scatter.png")
    plt.close()

# 最適化の過程をcsvファイルに出力する関数(この中で行列散布図の作成する関数も呼び出す)
def create_Summary_data(Chins,
                        Situps,
                        Jumps,
                        algorithm, my_sequential_colormap):
    my_file_name = out_file_name + '_NSGAII'    

    column_name_list = ['Chins', 'Situps', 'Jumps',
                        'Weight', 'Waist', 'Pulse']
    column_name_list = ','.join(column_name_list)
    row_list=[]
    with open(my_file_name + '.csv', 'w') as f:
        f.writelines(column_name_list)
        f.write('\n')
        for i in range(len(algorithm.result)):
            row_list.append(Chins.decode(algorithm.result[i].variables[0]))
            row_list.append(Situps.decode(algorithm.result[i].variables[1]))
            row_list.append(Jumps.decode(algorithm.result[i].variables[2]))
            row_list.extend(algorithm.result[i].objectives[:])
            
            row_list[-2] = 75 - row_list[-2]
            
            print('NSGAII epoch' +str(i+1) + ' -> ' + str(row_list))
            row_list_str = ','.join(map(str, row_list))
            f.writelines(row_list_str)
            f.write('\n')
            row_list=[]
    df = pd.read_csv(my_file_name + '.csv')
    plot_matrix_scatter(my_file_name, df, my_sequential_colormap)

# メイン関数:platypusフレームワークによる多目的最適化
def main(my_color, my_sequential_colormap):
    problem = Problem(3, 3) # define 3 inputs and 3 objective (and no constraints)
    problem.directions[:] = [Problem.MAXIMIZE,
                             Problem.MINIMIZE,
                             Problem.MINIMIZE] # Problem.MINIMIZE
    
    Chins = Integer(1, 17)		# x1 
    Situps = Integer(50, 250)	# x2 
    Jumps = Integer(25, 250)	# x3 
    
    problem.types[:] = [Chins,
                        Situps,
                        Jumps]

    problem.function = my_function_from_module
    print(problem.function)

    algorithm = NSGAII(problem, 200) # 200 is the population size
    algorithm.run(10000) # evaluation number
    print(algorithm)
    plot_scatter(algorithm, out_file_name, my_color)
    create_Summary_data(Chins,
                        Situps,
                        Jumps,
                        algorithm, my_sequential_colormap)

# パラメータの設定
if __name__ == '__main__':
    # parameter
    out_file_name = 'linnerud'
    my_xlabel = 'Weigth'
    my_xscale = 'linear'
    my_xrange = () # グラフのレンジを設定。空タプル()でAuto
    my_ylabel = 'Waist'
    my_yscale = 'linear'
    my_yrange = (0, 100) # グラフのレンジを設定。(最小値, 最大値)

    my_colors = ["r", "g", "b", "c", "m", "y", "k", "w"]
    my_sequential_colormaps = ["spring", "summer", "autumn", "winter", 'bone',
                            "copper", "plasma", "magma", "cividis", "hsv"]
        
    # call function
    main(my_colors[0], my_sequential_colormaps[0])
    
    print('finished')

下図は本コードを実行した結果である。縦軸「Waist」は目的関数2の「腹囲」である。図中の左端は「腹囲」の最適解で目的値に設定した75付近である。(ちなみに、それから右側へパレート解を描いている様子がわかる)

f:id:HK29:20200113022706p:plain

参考までに、本コードだけでは実行することは出来ないです。本記事の例題データで試したい場合は、下記リンク先のリンク先などで、多目的最適化をするための回帰モデル(回帰式)を各々作成しておく必要があります。

hk29.hatenablog.jp

以上

<広告>