001 /************************************************/ 002 /* 最急降下法による最小値の計算 */ 003 /* f(x, y) = 100(y - x**2)**2 + (1 - x)**2 */ 004 /* 最小値:(1,1) で 0.0 */ 005 /* coded by Y.Suganuma */ 006 /************************************************/ 007 #include <stdio.h> 008 #include <math.h> 009 010 /****************************************/ 011 /* xにおける微係数dxを計算する */ 012 /* (最小値のため,符号を逆にしてある) */ 013 /****************************************/ 014 void dsnx(double *x, double *dx) 015 { 016 dx[0] = 400.0 * x[0] * (x[1] - x[0] * x[0]) + 2.0 * (1.0 - x[0]); 017 dx[1] = -200.0 * (x[1] - x[0] * x[0]); 018 } 019 020 /*********************************************/ 021 /* 与えられた点xから,dx方向へk*dxだけ進んだ */ 022 /* 点における関数値を計算する */ 023 /*********************************************/ 024 double snx(double k, double *x, double *dx) 025 { 026 double w[2]; 027 028 w[0] = x[0] + k * dx[0]; 029 w[1] = x[1] + k * dx[1]; 030 double x1 = w[1] - w[0] * w[0]; 031 double y1 = 1.0 - w[0]; 032 double y = 100.0 * x1 * x1 + y1 * y1; 033 034 return y; 035 } 036 037 /****************************************************************/ 038 /* 黄金分割法(与えられた方向での最小値) */ 039 /* a,b : 初期区間 a < b */ 040 /* eps : 許容誤差 */ 041 /* val : 関数値 */ 042 /* ind : 計算状況 */ 043 /* >= 0 : 正常終了(収束回数) */ 044 /* = -1 : 収束せず */ 045 /* max : 最大試行回数 */ 046 /* w : 位置 */ 047 /* dw : 傾きの成分 */ 048 /* fun : 関数値を計算する関数の名前 */ 049 /* return : 結果(w+y*dwのy) */ 050 /****************************************************************/ 051 double gold(double a, double b, double eps, double *val, int *ind, int max, 052 double *w, double *dw, double (*fun)(double, double *, double *)) 053 { 054 // 初期設定 055 *ind = -1; 056 double x = 0.0; 057 double tau = (sqrt(5.0) - 1.0) / 2.0; 058 double x1 = b - tau * (b - a); 059 double x2 = a + tau * (b - a); 060 double f1 = fun(x1, w, dw); 061 double f2 = fun(x2, w, dw); 062 int count = 0; 063 // 計算 064 while (count < max && *ind < 0) { 065 count += 1; 066 if (f2 > f1) { 067 if (fabs(b-a) < eps) { 068 *ind = 0; 069 x = x1; 070 *val = f1; 071 } 072 else { 073 b = x2; 074 x2 = x1; 075 x1 = a + (1.0 - tau) * (b - a); 076 f2 = f1; 077 f1 = fun(x1, w, dw); 078 } 079 } 080 else { 081 if (fabs(b-a) < eps) { 082 *ind = 0; 083 x = x2; 084 *val = f2; 085 f1 = f2; 086 } 087 else { 088 a = x1; 089 x1 = x2; 090 x2 = b - (1.0 - tau) * (b - a); 091 f1 = f2; 092 f2 = fun(x2, w, dw); 093 } 094 } 095 } 096 // 収束した場合の処理 097 if (*ind == 0) { 098 *ind = count; 099 double fa = fun(a, w, dw); 100 double fb = fun(b, w, dw); 101 if (fb < fa) { 102 a = b; 103 fa = fb; 104 } 105 if (fa < f1) { 106 x = a; 107 *val = fa; 108 } 109 } 110 111 return x; 112 } 113 114 /********************************************************/ 115 /* 最急降下法(steepest descent method) */ 116 /* opt_1 : =0 : 1次元最適化を行わない */ 117 /* =1 : 1次元最適化を行う */ 118 /* max : 最大繰り返し回数 */ 119 /* n : 次元 */ 120 /* eps : 収束判定条件 */ 121 /* step : きざみ幅 */ 122 /* y : 最小値 */ 123 /* x : x(初期値と答え) */ 124 /* dx : 関数の微分値 */ 125 /* fun : 関数値を計算する関数名 */ 126 /* dfun : 関数の微分を計算する関数名(符号を変える) */ 127 /* return : >=0 : 正常終了(収束回数) */ 128 /* =-1 : 収束せず */ 129 /* =-2 : 1次元最適化の区間が求まらない */ 130 /* =-3 : 黄金分割法が失敗 */ 131 /********************************************************/ 132 int steep(int opt_1, int max, int n, double eps, double step, double *y, 133 double *x, double *dx, double (*fun)(double, double *, double *), 134 void (*dfun)(double *, double *)) 135 { 136 double f1, f2, k, sp, y1, y2; 137 int count = 0, sw = 0; 138 139 y1 = fun(0.0, x, dx); 140 while (count < max && sw == 0) { 141 // 傾きの計算 142 dfun(x, dx); 143 count++; 144 // 1次元最適化を行わない 145 if (opt_1 == 0) { 146 // 新しい点 147 for (int i1 = 0; i1 < n; i1++) 148 x[i1] += step * dx[i1]; 149 // 新しい関数値 150 y2 = fun(0.0, x, dx); 151 // 関数値の変化が大きい 152 if (fabs(y2-y1) > eps) 153 y1 = y2; 154 // 収束(関数値の変化<eps) 155 else { 156 sw = count; 157 *y = y2; 158 } 159 } 160 // 1次元最適化を行う 161 else { 162 // 区間を決める 163 int sw1 = 0; 164 f1 = y1; 165 sp = step; 166 f2 = fun(sp, x, dx); 167 if (f2 > f1) 168 sp = -step; 169 for (int i1 = 0; i1 < max && sw1 == 0; i1++) { 170 f2 = fun(sp, x, dx); 171 if (f2 > f1) 172 sw1 = 1; 173 else { 174 sp *= 2.0; 175 f1 = f2; 176 } 177 } 178 // 区間が求まらない 179 if (sw1 == 0) 180 sw = -2; 181 // 区間が求まった 182 else { 183 // 黄金分割法 184 k = gold(0.0, sp, eps, &y2, &sw1, max, x, dx, fun); 185 // 黄金分割法が失敗 186 if (sw1 < 0) 187 sw = -3; 188 // 黄金分割法が成功 189 else { 190 // 新しい点 191 for (int i1 = 0; i1 < n; i1++) 192 x[i1] += k * dx[i1]; 193 // 関数値の変化が大きい 194 if (fabs(y1-y2) > eps) 195 y1 = y2; 196 // 収束(関数値の変化<eps) 197 else { 198 sw = count; 199 *y = y2; 200 } 201 } 202 } 203 } 204 } 205 206 if (sw == 0) 207 sw = -1; 208 209 return sw; 210 } 211 212 /****************/ 213 /* main program */ 214 /****************/ 215 int main() 216 { 217 // 初期設定 218 int n = 2; // 変数の数 219 int max = 10000; // 最大試行回数 220 double eps = 1.0e-10; // 許容誤差 221 double step = 0.002; // 刻み幅 222 double *x = new double [n]; 223 double *dx = new double [n]; // 微係数 224 for (int i1 = 0; i1 < n; i1++) // 初期位置 225 x[i1] = 0.0; 226 // 実行と出力 227 double y; 228 int sw; 229 for (int opt_1 = 0; opt_1 < 2; opt_1++) { 230 // 実行 231 sw = steep(opt_1, max, n, eps, step, &y, x, dx, snx, dsnx); 232 // 結果の出力 233 if (opt_1 == 0) 234 printf("1 次元最適化を行わなかった場合(最急降下法)\n"); 235 else 236 printf("1 次元最適化を行った場合(最適勾配法)\n"); 237 if (sw < 0) { 238 printf(" 収束しませんでした!"); 239 switch (sw) { 240 case -1: 241 printf("(収束回数)\n"); 242 break; 243 case -2: 244 printf("(1次元最適化の区間)\n"); 245 break; 246 case -3: 247 printf("(黄金分割法)\n"); 248 break; 249 } 250 } 251 else { 252 printf(" 結果="); 253 for (int i1 = 0; i1 < n; i1++) 254 printf("%f ", x[i1]); 255 printf(" 最小値=%f 回数=%d\n", y, sw); 256 } 257 } 258 259 return 0; 260 }
1 次元最適化を行わなかった場合(最急降下法) 結果=0.999750 0.999500 最小値=0.000000 回数=8880 1 次元最適化を行った場合(最適勾配法) 結果=1.000000 1.000000 最小値=0.000000 回数=2
z = f(x, y)
xi = x0 + k * ei i = 1, ・・・, n
H : 関数値が最大になる点 fH = f( xH ) G : 2 番目に関数値が最大になる点 fG = f( xG ) L : 関数値が最小になる点 fL = f( xL )
xR = 2xC - xH
fR ≧ fH
xN = ( 1 - λ1)xH + λ1xR, fN = f( xN ) ただし,0 < λ1 < 1, λ1 ≠ 0.5
fR < ( fL + (λ2 - 1 )fH) / λ2 ただし,1 < λ2
xE = λ2xR - (λ2 - 1 )xH, fE = f( xE )
fE ≦ fR
fN ≧ fG
xi = 0.5 * (xi + xL) i = 1, ・・・, n+1
001 /************************************************/ 002 /* シンプレックス法による最小値の計算 */ 003 /* f(x, y) = 100(y - x**2)**2 + (1 - x)**2 */ 004 /* 最小値:(1,1) で 0.0 */ 005 /* coded by Y.Suganuma */ 006 /************************************************/ 007 #include <stdio.h> 008 009 /*******************/ 010 /* 関数値の計算 */ 011 /* y = f(x) */ 012 /* return : y */ 013 /*******************/ 014 double snx(double *x) 015 { 016 double x1 = x[1] - x[0] * x[0]; 017 double y1 = 1.0 - x[0]; 018 double y = 100.0 * x1 * x1 + y1 * y1; 019 return y; 020 } 021 022 /******************************************/ 023 /* シンプレックス法 */ 024 /* max : 最大繰り返し回数 */ 025 /* n : 次元(変数の数) */ 026 /* k : 初期値設定係数 */ 027 /* r1 : 縮小比率 */ 028 /* r2 : 拡大比率 */ 029 /* y : 最小値 */ 030 /* x : x(初期値と答え) */ 031 /* fun : 関数値を計算する関数名 */ 032 /* return : >=0 : 正常終了(収束回数) */ 033 /* =-1 : 収束せず */ 034 /******************************************/ 035 int simplex(int max, int n, double k, double eps, double r1, double r2, double *y, 036 double *x, double (*fun)(double *)) 037 { 038 // 初期値の設定 039 double *xg = new double [n]; // 最大点以外の重心 040 double *xr = new double [n]; 041 double *xn = new double [n]; 042 // シンプレックス 043 double *yy = new double [n+1]; 044 double **xx = new double * [n+1]; 045 for (int i1 = 0; i1 < n+1; i1++) 046 xx[i1] = new double [n]; 047 for (int i1 = 0; i1 < n+1; i1++) { 048 for (int i2 = 0; i2 < n; i2++) 049 xx[i1][i2] = x[i2]; 050 if (i1 > 0) 051 xx[i1][i1-1] += k; 052 yy[i1] = fun(xx[i1]); // シンプレックスの各点の関数値 053 } 054 // 最大値,最小値となる点 055 int fh = -1; // 最大値となる点 056 int fl = -1; // 最小値となる点 057 for (int i1 = 0; i1 < n+1; i1++) { 058 if (fh < 0 || yy[i1] > yy[fh]) 059 fh = i1; 060 if (fl < 0 || yy[i1] < yy[fl]) 061 fl = i1; 062 } 063 // 2 番目に関数値が最大になる点 064 int fg = -1; // 2 番目に関数値が最大になる点 065 for (int i1 = 0; i1 < n+1; i1++) { 066 if (i1 != fh && (fg < 0 || yy[i1] > yy[fg])) 067 fg = i1; 068 } 069 // 最小値の計算 070 int count = 0; // 試行回数 071 int sw = -1; 072 while (count < max && sw < 0) { 073 count++; 074 // 最大点以外の重心の計算 075 for (int i1 = 0; i1 < n; i1++) 076 xg[i1] = 0.0; 077 for (int i1 = 0; i1 < n+1; i1++) { 078 if (i1 != fh) { 079 for (int i2 = 0; i2 < n; i2++) 080 xg[i2] += xx[i1][i2]; 081 } 082 } 083 for (int i1 = 0; i1 < n; i1++) 084 xg[i1] /= n; 085 // 最大点の置き換え 086 for (int i1 = 0; i1 < n; i1++) 087 xr[i1] = 2.0 * xg[i1] - xx[fh][i1]; 088 double yr = fun(xr); 089 if (yr >= yy[fh]) { // 縮小 090 for (int i1 = 0; i1 < n; i1++) 091 xr[i1] = (1.0 - r1) * xx[fh][i1] + r1 * xr[i1]; 092 yr = fun(xr); 093 } 094 else if (yr < (yy[fl]+(r2-1.0)*yy[fh])/r2) { // 拡張 095 for (int i1 = 0; i1 < n; i1++) 096 xn[i1] = r2 * xr[i1] - (r2 - 1.0) * xx[fh][i1]; 097 double yn = fun(xn); 098 if (yn <= yr) { 099 for (int i1 = 0; i1 < n; i1++) 100 xr[i1] = xn[i1]; 101 yr = yn; 102 } 103 } 104 for (int i1 = 0; i1 < n; i1++) 105 xx[fh][i1] = xr[i1]; 106 yy[fh] = yr; 107 // シンプレックス全体の縮小 108 if (yy[fh] >= yy[fg]) { 109 for (int i1 = 0; i1 < n+1; i1++) { 110 if (i1 != fl) { 111 for (int i2 = 0; i2 < n; i2++) 112 xx[i1][i2] = 0.5 * (xx[i1][i2] + xx[fl][i2]); 113 yy[i1] = fun(xx[i1]); 114 } 115 } 116 } 117 // 最大値,最小値,2 番目に関数値が最大になる点の計算 118 fh = -1; 119 fg = -1; 120 fl = -1; 121 for (int i1 = 0; i1 < n+1; i1++) { 122 if (fh < 0 || yy[i1] > yy[fh]) 123 fh = i1; 124 if (fl < 0 || yy[i1] < yy[fl]) 125 fl = i1; 126 } 127 for (int i1 = 0; i1 < n+1; i1++) { 128 if (i1 != fh && (fg < 0 || yy[i1] > yy[fg])) 129 fg = i1; 130 } 131 // 収束判定 132 double e = 0.0; 133 for (int i1 = 0; i1 < n+1; i1++) { 134 if (i1 != fl) { 135 yr = yy[i1] - yy[fl]; 136 e += yr * yr; 137 } 138 } 139 if (e < eps) 140 sw = 0; 141 } 142 143 if (sw == 0) { 144 sw = count; 145 *y = yy[fl]; 146 for (int i1 = 0; i1 < n; i1++) 147 x[i1] = xx[fl][i1]; 148 } 149 150 delete [] yy; 151 delete [] xg; 152 delete [] xr; 153 delete [] xn; 154 for (int i1 = 0; i1 < n+1; i1++) 155 delete [] xx[i1]; 156 delete [] xx; 157 158 return sw; 159 } 160 161 /****************/ 162 /* main program */ 163 /****************/ 164 int main() 165 { 166 // 初期設定 167 int n = 2; // 変数の数 168 int max = 1000; // 最大試行回数 169 double k = 1.0; // 初期値設定係数 170 double eps = 1.0e-20; // 許容誤差 171 double r1 = 0.7; // 縮小比率 172 double r2 = 1.5; // 拡大比率 173 double *x = new double [n]; 174 for (int i1 = 0; i1 < n; i1++) // 初期値 175 x[i1] = 0.0; 176 // 実行 177 double y; 178 int sw = simplex(max, n, k, eps, r1, r2, &y, x, snx); 179 // 結果の出力 180 printf("シンプレックス法\n"); 181 if (sw < 0) 182 printf(" 収束しませんでした!\n"); 183 else { 184 printf(" 結果="); 185 for (int i1 = 0; i1 < n; i1++) 186 printf("%f ", x[i1]); 187 printf(" 最小値=%f 回数=%d\n", y, sw); 188 } 189 190 delete [] x; 191 192 return 0; 193 }
シンプレックス法 結果=1.000001 1.000001 最小値=0.000000 回数=82
講義内容目次 | 菅沼ホーム | 前ページ | 次ページ |