この1コ前の更新では、


授業で、遺伝的アルゴリズム(GA)と進化的アルゴリズム(EA)を


C++で勉強していったことを述べましたが、



今日の更新では、その完成させた進化的アルゴリズム(EA)の


プログラムを添付したいと思います。



プログラムの知識はそこまで無いので完成させるのに苦労しました。


同じ授業を受けていた同期の方にいろいろ教えてもらいながら完成させました。



以下のプログラムを見てもらえればわかると思いますが、


このように、-1.28から1.27に乱数をばらまいて、


突然変異させています。


突然変異を排他的論理和により、すべてのビットにおいて行い、


100個体で確かめました。


そしてそれを100世代ループさせました。








#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <time.h>


#define TERM_NUM (2) // 目的関数の項数
#define GEN_NUM (100) // 世代数
#define IND_NUM (100) // 個体数
#define PROB_C (0.5) // 交叉確率
#define PROB_M (0.01) // 突然変異確率
#define AMPLITUDE (5.0) // J2の振幅A


// 個体の構造体
typedef struct {
//int x[TERM_NUM]; // 項(4バイト)(-1~1→-2147483648~2147483647に対応?)
//long gene; // 遺伝子(8バイト)
char x[TERM_NUM]; // 項(1バイト)(-1.28~1.27)
unsigned short gene; // 遺伝子(2バイト)
double J; // 目的関数の値
double f; // 適合度
} SIndividual;


// 世代の構造体
typedef struct {
SIndividual ind[IND_NUM];
} SGeneration;



// 目的関数 J1 = x1^2 + x2^2
//double J1(int x[TERM_NUM])
double J1(char x[TERM_NUM])
{
double x_d[TERM_NUM];
double J = 0.0f;

for (int i=0; i<TERM_NUM; i++) {
//x_d[i] = (double)x[i]/(double)INT_MAX; // -1~1に正規化
x_d[i] = (double)x[i]/100.0; // -1.28~1.27

J += pow(x_d[i]-0.1, 2); // 計算
}

return J;
}


// 目的関数 J2 = 2A + Σ_i=1^2 (xi^2 - Acos2πxi)
double J2(int x[TERM_NUM])
{
double x_d[TERM_NUM];
double J = 0.0f;
double A = AMPLITUDE;

J += (2*A);
for (int i=0; i<TERM_NUM; i++) {
x_d[i] = (double)x[i]/(double)INT_MAX; // -1~1に正規化

J += pow(x_d[i], 2) - A*cos(2.0*M_PI*x_d[i]); // 計算
}

return J;
}



// 適合度の計算 f = Jmax - J
//double f(double _J)
//{
//
//}

int main(int argc, char** argv)
{
// 初期設定
SGeneration curr_gen;
SGeneration next_gen;
memset(&curr_gen, 0, sizeof(curr_gen));
memset(&next_gen, 0, sizeof(next_gen));


// 遺伝子の初期値(乱数)
srand((unsigned int)time(NULL)); // ランダム性向上
for (int ind_i=0; ind_i<IND_NUM; ind_i++) { // 個体のループ
curr_gen.ind[ind_i].gene = (unsigned short)(rand() % (USHRT_MAX+1)); // 0x0000~0xFFFFまでの乱数

//double J1max = 3.2258;
// 変数へ変換
unsigned short gene = curr_gen.ind[ind_i].gene;
curr_gen.ind[ind_i].x[1] = gene;
gene >>= 8;
curr_gen.ind[ind_i].x[0] = gene;


printf("x[0] = %02X, x[1] = %02X\n", (unsigned char)curr_gen.ind[ind_i].x[0], (unsigned char)curr_gen.ind[ind_i].x[1]);
printf("gene[%d] = %04X\n", ind_i, curr_gen.ind[ind_i].gene);

}


//// 世代発展
double J1max = 3.2258;
for (int gen_i=0; gen_i<GEN_NUM; gen_i++) { // 世代のループ

double fmax=0.0;
unsigned short genemax;
int ind_imax;

// 全個体の適合度を計算
for (int ind_i=0; ind_i<IND_NUM; ind_i++) { // 個体のループ
curr_gen.ind[ind_i].J = J1(curr_gen.ind[ind_i].x); // 目的関数計算
curr_gen.ind[ind_i].f = J1max - curr_gen.ind[ind_i].J; // 適合度計算

if(curr_gen.ind[ind_i].f>fmax){
fmax=curr_gen.ind[ind_i].f;
genemax=curr_gen.ind[ind_i].gene;
ind_imax=ind_i;
}

//if (ind_i == 0) {
printf("[%d][%d] ", gen_i ,ind_i);
printf("x1 = %lf, x2 = %lf, ", (double)curr_gen.ind[ind_i].x[0]/100.0, (double)curr_gen.ind[ind_i].x[1]/100.0);
printf("J = %lf, f = %lf\n", curr_gen.ind[ind_i].J, curr_gen.ind[ind_i].f);

/*getchar();*/
//}
}
//getchar();


// 突然変異
for (int ind_i=0; ind_i<IND_NUM; ind_i++) { // 個体のループ
next_gen.ind[ind_i]= curr_gen.ind[ind_i];
if(ind_i==ind_imax){
continue;
}
for (int g_i=0; g_i<16; g_i++) { // 遺伝子ビット数分ループ
double dice = (double)rand() / (double)RAND_MAX; // 0-1
if (dice <= PROB_M) { // 突然変異確率より小さければ突然変異
unsigned short bit = (unsigned short)pow(2.0f, g_i); // 2^0~2^15 ←対応ビットを立てる

next_gen.ind[ind_i].gene ^= bit; // 排他的論理和でビット反転

}

}

}

// x項への配分
for (int ind_i=0; ind_i<IND_NUM; ind_i++) { // 個体のループ
unsigned short gene = next_gen.ind[ind_i].gene;
next_gen.ind[ind_i].x[1] = gene;
gene >>= 8;
next_gen.ind[ind_i].x[0] = gene;

/*printf("x[0] = %02X, x[1] = %02X, ", (unsigned char)next_gen.ind[ind_i].x[0], (unsigned char)next_gen.ind[ind_i].x[1]);
printf("gene[%d] = %04X\n", ind_i, next_gen.ind[ind_i].gene);*/
/*if (ind_i == 0) {
printf("[%d] ", gen_i);
printf("x1 = %lf, x2 = %lf, ", (double)curr_gen.ind[ind_i].x[0]/100.0, (double)curr_gen.ind[ind_i].x[1]/100.0);
printf("J = %lf, f = %lf\n", curr_gen.ind[ind_i].J, curr_gen.ind[ind_i].f);
}*/
}


curr_gen = next_gen; // 現在の世代を更新
memset(&next_gen, 0, sizeof(next_gen)); // メモリリセット

//getchar();
}



return 0;
}

本講義では、「遺伝的アルゴリズム(GA)」をc言語により表し、遺伝子がどのように動いて最適化されていくかを考えていった。




非線形なものであると難しいので線形なものを目的関数とし、二次関数の頂点を最適解とすることで、最適値を割り出していく。


最初、2バイトの10信号を乱数を用いて作り、これを1個体の遺伝子配列とする。この時、「環境に適した」個体、つまり頂点に近い値ほど適合度の値が大きくなるように設定する。


次に、ルーレットを回し、2匹の親個体を選び出す。この親個体には、適合度の値が大きい個体ほど選ばれる確率が大きくなるようにする。そして、この2匹の親個体に対して、交叉確率により交叉を行う。交叉をする時は、乱数を用いて切断位置を決め、その位置から先の遺伝子を2匹間で交換する。次に、突然変異確率により、遺伝子の各ビットに対してビットを反転させる。これらの作業をM匹の個体に対して行い、このM匹を1世代とし、世帯数Gの数だけ繰り返し行う。





次に、私は「進化的アルゴリズム(EA)」のプログラムを作ることになった。


進化的アルゴリズムは、遺伝的アルゴリズムの中の親個体をルーレットで決定する部分、切断・交叉する部分を取り払い、突然変異のみで最適化されていくというアルゴリズムである。


この2つのアルゴリズムを考えていくことで突然変異体は必要な個体であるということがよくわかった。


突然変異した個体が集団から離れていくと、なぜかその集団は突然変異体の方へ流れていく時がある。突然変異したキリンが、たまたま首が長く、そのキリンが高い木の枝の食べ物を食べることができて子孫繁栄ができたという話は面白い。

人間もこの地球環境に適応していく上で、この先、どのように体が変形、体質変化していくか興味がそそられた。





最後に、多目的最適化問題について述べていた。


本講義に出てきた2つの変数、2つの目的関数の例を取ってみると、多目的最適化問題の効用集合は楕円の形となる。この楕円が、2つの目的関数が取り得る値の組がなす集合である。


 この最適解の候補に、

線形結合解、

パレート最適解、

ナッシュ平衡解

などが挙げられる。


線形結合解は、重み付けによって2つの目的関数を線形結合し、解が一意に決定される。

パレート最適解とは、どの目的についてもその解より優れているような解が無い場合のそれらの解の集合である。

ナッシュ平衡解は、他のプレーヤーの戦略を所与とした場合、どのプレーヤーも自分の戦略を変更することによってより高い利得を得ることができない戦略の組み合わせである。


ナッシュ平衡解がパレート最適解に必ずしも属するとは限らない。その代表例が囚人のジレンマである。囚人のジレンマは、非ゼロ和ゲームの代表例でもある。


この問題自体はモデル的であるが、社会でもこれと似たような状況が頻繁に出現する。


囚人2人にとって、自白し合って中途半端な3年の刑を受けるよりは、互いに黙秘し合って1年の刑を受ける方が得であるが、囚人達が自分の利益のみを追求している限り、互いに裏切り合うという結末を迎えるというものである。双方にとって、黙秘し合って1年の刑を受ける方が得であるにも関わらず、黙秘を実現することができない事がジレンマと言われる所以である。


この問題は、ゲーム理論において、11つの最適な選択が、全体として最適な選択とはならない状況の例としてよく挙げられる。


ゲーム理論は、主に経済学、社会学で応用されている。また、最近では広い分野で応用されており、工学の分野では、最適設計において応用されている。





ここで、私の研究室の最適設計におけるジレンマを考えてみる。


私の研究室では、空気圧駆動システムを中心とした人に優しいロボットや機械システムの構築を研究目標としている。具体的には、次に示す項目について研究している。「空気圧ゴム人工筋を用いたウエアラブルパワーアシスト装置」、「空気式パラレルマニピュレータを用いた医療福祉支援システム」、「空気圧小型コンプレッサ」などである。


このような数ある研究の中で、私が今年度から担当になったのは「上肢パワーアシストウェアの開発」である。


本研究では、高齢者の方やALS患者さんのような被介護者が、自立した日常生活を送れることを目的とし、肩・肘・手首の支援を行う。支援装置の名前は、「パワーアシストウェア」とし、着ても違和感がなく、服のように軽量であることを目指す。


この「パワーアシストウェア」に対して、金属などの固い物質を材料としたフレームを用いたアシスト装置の研究もされている。これは、フレームを体の回りに取り囲み、装置と体とをフィットさせることにより、支援する力や支援効率を上げるものである。


このようなメリットもある一方、患者さんにとっては、フレームによる重さや動きにくさ、高価になるなどのデメリットも出てくる。フレームをなくし、服のような装置にすれば、軽く、着やすく、動きやすいメリットもある一方、支援する力や支援効率が下がってしまうというデメリットが出てきてしまう。


これが、私の研究室のジレンマであると考える。


私の研究は、ウェアのような支援装置の開発なので、デメリットである支援する力や支援効率を考えながら装置の開発を頑張っていこうと思う。

このように、私の研究室でも支援力と患者さんがアシスト装置を着る時の違和感を考える場合が多々ある。支援力も十分に確保したいし、患者さんにも着やすい装置を提供したい。


両者だけでなく、すべてを最適にした装置が開発できたら素晴らしいことだと思う。








「ロボットはなぜ生き物に似てしまうのか」

の感想や関連事項の考察等



本書では、題名にあるように、ロボットが生き物に似てしまうということを詳細に述べていた。


ロボット作りは、最初、人間をはじめとする生命体に似せることから始めたのであるが、似せようとしていなかったロボットまでもが、なぜか生き物に似てしまうのである。


これまで生き物は、この地球という環境に適応するために、体の形状や特性を変化させながら進化してきた。この進化の過程で最終的に行き着いたのが現在の形である。現在の地球の環境で、役に立つロボットを作るとしたならば、確かに、何億年もかけて形を変えてきて、現在の形に行き着いた生命に似てしまうのは、しょうがない事のような気もしないではないが、不思議な話である。




私が、本書で1番気になったのは、第3章の「足を動かす順序まで似てしまう!?」である。

日常生活では、手や足を動かす順序は気にならず、無意識に行っていることである。しかし、この無意識な動作は、ロボットを作っていく上では解析しなければならず、動かす順序も決定しなければロボットは正常に動いてくれないのである。


4脚歩行をする生き物で静歩行する場合、必ず「後脚→同サイドの前脚→逆サイドの後脚→同サイドの前脚」の順番で脚を動かす。人間の祖先も4脚歩行であったので、私も試しに四つん這いになって歩いてみたのだが、確かにこの順番通りであった。本書に書かれている通り、順序を反転させても歩いてみたのだが、やはり慣れるのに時間がかかった。


この順序で歩くことで、3本の脚で作る「支持多角形」の内部に体の重心を持ってくる状況を1番作りやすいということが、身を持って理解できたのである。


6脚歩行の場合も同様に見てみると、3本の脚で支持多角形を作っていることがわかる。代表的な例が、「交互三脚歩行」と呼ばれる脚の動かし方である。片方のグループの3脚が遊脚状態にある時、もう一方のグループの3脚が接地する。このようにして、常に大きな支持三角形を作れるので、安定した静歩行が実現できるのである。


これらに対し、人間は2脚歩行である。遊脚状態にした場合、接地脚は1本だけになってしまい、これでは、支持多角形は作れない。しかし、足の裏に面積があることにより、その面積に重心を持っていくことで静歩行が可能となるのである。ホンダが開発したアシモも同じように足裏には面積があり、この面積内に体の重心が来るようにして静歩行を実現している。このように、ロボットを歩行させる上で、前に示すような人間の歩き方に酷似してしまう点がいくつもあるのである。




本書を読むことで、ロボットの仕組みや生き物の体の構造がわかり、また、両者の共通点や類似点を知ることができた。もしロボットが生命に似ないで、別の物体に似るような時が来たとするならば、人間は生命進化の歴史を超越し、生命の進化をある程度予測できているに違いない。その時、生命が最適化されていく上で、ロボットと同じ形態を辿り、生命がロボットの進化を準えるのかもしれない。

「アクチュエータが未来を創る」に記載された研究課題(所属する研究室の課題は除く)の1つを取り上げ、自分の研究分野への適用可能性について考える。




私は、本書に記載された研究課題の中から、p143から記載されてある「4.4球面モータ」についての課題を取り上げ、私の研究テーマにどのように活かせるかを検討した。



現在、私は「上肢パワーアシストウェアの開発」という研究テーマで研究を進めている。


近年、人口構造が高齢化、また、障害者の方を支援する働きが高まることで、福祉に対する意識が強まる社会となってきている。そこで、特に高齢者の方や重度のALS(筋萎縮性側索硬化症)患者さんの役に立つ、支援装置の研究、開発を進めていきたいと思っている。患者さんは、円滑なコミュニケーションや身の回りのことを、他人の手助けなしに行いたいと願っているはずである。


よって、本研究では、高齢者の方やALS患者さんのような被介護者が、自立した日常生活を送れることを目的とし、肩・肘・手首の支援を行う。支援装置の名前は、「パワーアシストウェア」とし、着ても違和感がなく、服のように軽量であることを目指す。このアシストウェアを用いて、食事支援装置への応用を考える。


食べ物を口の前まで持ってくるには、肩の屈曲動作、外転動作、肘の屈曲をすることで可能となっている。まず、肩の屈曲をするには、上腕に棒を取り付け、この棒を太ももに取り付けた人工筋肉に引っ張らせる形で、屈曲動作をさせる。次に、この棒を左の上腕に取り付けた人工筋肉に引っ張らせることにより、外転動作をさせる。


ここで、人工筋肉の代わりに球面モータを使用することを考えた。


人間の肩や手首の関節は、1関節で 3 自由度を有する。よって、先に示すように人工筋肉も複数取り付けなくてはならず、大型の装置となってしまうのだ。球面モータだと1つのアクチュエータで3自由度確保できるので、かさばらず、大型の装置とならない、また、細やかな動きを支援できると考える。装置は、球面モータを肩に取り付け、ギアと棒を手首に固定することによって支援できるようなものを考えた。しかし、本書に載っている球面モータを使用すると大型で、重い装置となってしまうので、このモータを使用するには、小型化かつ軽量化しなければならないと思った。


次に、上腕と前腕に取り付けた人工筋肉を収縮させることにより、肘の屈曲が可能となっている。肘関節は1自由度なので、人工筋肉で事足りる。以上により、食べ物を口の位置まで持ってくることが可能である。


次に、口の前の位置に持ってきた食べ物を口の中に入れる動作を支援することを考える。これには、前腕の回外動作、手首部の背屈、尺屈動作が必要である。前腕部の回外動作は人工筋肉で作成したツイストアクチュエータを用いることにより支援できる。手首部は、先ほど述べたように3自由度を有するため、ここにも球面モータを取り付けたい。


以上により、食べ物を口の前に持っていき、口の中に運ぶ動作の支援が可能となる。



球面モータは、このように3自由度必要な時に応用可能だが、本書を読むと、モータ効率や小型化、オープンループであることなどの課題も多い。これらの課題がクリアできた場合、上記のように応用できると考える。