stackprobe7s_memo

何処にも披露する見込みの無いものを書き落とす場所

THE MAKING 290 氷砂糖ができるまで で気になったこと

www.youtube.com
先日この動画を観て 11:44 からの部分、バケットの数 (はかりの数) の 14 という半端な数がちょっと気になった。
バケットの数が少なすぎれば 1kg になりにくいし、多すぎれば過剰設備となる。
きっと丁度良い数なのだろう。

シミュレーションしてみた

バケットの数を変えてみて包装袋の重さの分布を出してみた。

見た感じの仕組み

上から
氷砂糖の投入口 ⇒ 氷砂糖を貯めておく皿(Backet_Pool) x 14 ⇒ 量り皿(Backet_Scale) x 14 ⇒ 包装袋
になっていて Backet_Pool , Backet_Scale の底が開閉可能になっているっぽい。
ゲーセンのメダルゲームみたいに Backet_Pool に充填されていく。
Backet_Scale の底が開いて空になったタイミングで Backet_Pool の底が開いて中身が全部(一部?) Backet_Scale に充填される。
1kg 以上でなるべく 1kg に近い組み合わせで Backet_Scale の底を開けて包装する。(1kg 未満で包装したら不良品になってしまうだろうから、1kg 以上になるようにはしていると思う)
氷砂糖の投入速度と、包装される速度は(単位時間での重さが)同じはず。
長時間特定の Backet_Scale が選ばれなかった場合 Backet_Pool にどんどん溜まって行ってしまう。⇒ 一定量に達するとそれ以上貯まらないようになっている。ように見える。
全ての Backet_Pool を合わせても 1kg 未満だったらどうするんだろう?初期状態とか。⇒ 分からない。

実装した仕組み

重さは int , g 単位
Backet_Pool への充填と包装を交互に行う。

UNTIL 包装袋の数が規定数に達する
{
	Charge();     // Backet_Pool への充填を行う。
	TryBagging(); // 包装可能 (Backet_Scaleを組み合わせて1kgを作れるなら) であれば包装を行う。
}

Charge

  • 総量がだいたい 1kg になるように充填する。(充填と梱包の速度が同じになるように)
    • 1つの Backet_Pool について 0 ~ (2kg / バケット数) の範囲でランダムに加算する。
    • 1つの Backet_Pool に 0.3kg の以上貯まらないようにする。
      • 動画見た感じそれくらいじゃないかなと思ったので。

TryBagging

  • 1kg 以上でなるべく 1kg に近い組み合わせで包装する。
  • 1kg 以上になる組み合わせが無ければ、全ての Backet_Pool を開ける。

結果

包装袋の重さの分布 (100000袋)

重さ \ バケット 6 7 8 9 10 11 12 13 14 15 16 17 18
1000 g 3974 8118 16080 30042 50690 73211 88305 94338 96713 97860 98649 98908 99301
1001 g 3811 7639 12991 19464 21837 15361 6677 2791 1553 984 605 495 328
1002 g 3693 6964 10784 13067 10119 4713 1852 1011 546 400 267 193 140
1003 g 3577 6207 8852 8893 5464 1989 876 495 332 216 146 108 65
1004 g 3293 5621 7468 6221 3161 1160 514 316 187 135 58 73 42
1005 g 3161 5244 6171 4551 1918 754 334 234 149 91 69 58 33
1006 g 3068 4582 5132 3281 1372 565 277 143 98 60 53 39 25
1007 g 2926 4370 4155 2368 983 397 186 110 82 50 46 30 18
1008 g 2797 3858 3573 1911 729 264 166 96 67 30 24 23 8
1009 g 2693 3616 2973 1462 543 213 114 77 45 36 18 13 9
1010 g 2591 3281 2530 1193 411 189 97 49 34 25 16 12 10
1011 g 2366 2905 2048 910 346 148 79 45 28 23 9 11 5
1012 g 2306 2795 1739 790 258 136 66 38 29 13 6 8 3
1013 g 2270 2550 1563 671 233 98 62 46 18 12 12 6 1
1014 g 2206 2320 1313 507 225 97 53 40 18 9 8 4 5
1015 g 1969 2028 1151 487 170 64 41 22 16 9 5 1 2
1016 g 2005 1880 1008 423 153 81 39 23 8 6 3 4 1
1017 g 1935 1707 881 367 147 62 29 10 11 7 2 9 2
1018 g 1850 1592 751 316 133 59 27 16 9 8 1 1 1
1019 g 1705 1467 725 273 114 48 19 14 8 11 1 1 0
1020 g 1619 1378 564 271 103 37 28 13 10 8 2 1 0
1021 g 1587 1223 601 200 71 39 16 12 5 1 0 0 0
1022 g 1530 1110 535 187 67 35 23 10 2 0 0 0 1
1023 g 1418 1011 443 174 78 28 21 10 3 2 0 0 0
1024 g 1375 940 422 153 60 22 11 3 4 0 0 0 0
1025 g 1308 842 375 141 55 23 12 7 2 1 0 1 0
1026 g 1306 810 347 126 43 29 10 6 1 0 0 0 0
1027 g 1265 788 328 108 32 28 8 5 4 0 0 1 0
1028 g 1174 777 294 102 53 14 7 3 4 1 0 0 0
1029 g 1103 651 289 87 34 16 6 3 1 0 0 0 0
1030 g 1057 634 244 118 36 10 6 4 2 1 0 0 0
1031 g 1091 578 233 62 29 9 6 2 5 0 0 0 0
1032 g 997 571 232 68 24 6 4 1 1 0 0 0 0
1033 g 1006 478 207 74 18 12 1 0 1 1 0 0 0
1034 g 983 457 157 56 34 4 5 0 0 0 0 0 0
1035 g 865 486 155 68 19 10 1 1 2 0 0 0 0
1036 g 889 389 146 55 23 6 2 0 0 0 0 0 0
1037 g 809 373 158 52 19 12 2 1 1 0 0 0 0
1038 g 793 347 130 46 11 6 4 2 0 0 0 0 0
1039 g 784 329 143 46 13 6 1 0 0 0 0 0 0
1040 g 707 340 108 35 15 5 2 0 0 0 0 0 0
1041 g 750 293 113 39 7 4 1 0 0 0 0 0 0
1042 g 630 255 91 46 15 4 0 1 0 0 0 0 0
1043 g 695 256 98 22 14 1 2 0 0 0 0 0 0
1044 g 651 241 85 39 9 1 1 1 0 0 0 0 0
1045 g 578 246 74 24 6 2 1 0 0 0 0 0 0
1046 g 568 251 68 24 10 2 1 0 0 0 0 0 0
1047 g 559 225 84 26 8 0 2 0 0 0 0 0 0
1048 g 556 201 86 20 6 2 0 0 0 0 0 0 0
1049 g 536 198 46 24 2 3 2 0 0 0 0 0 0
1050 g 以上 16615 4578 1256 310 80 15 1 1 1 0 0 0 0
バケット 重さの平均
6 1028.1799 g
7 1013.6660 g
8 1007.0596 g
9 1003.5477 g
10 1001.6651 g
11 1000.7360 g
12 1000.3376 g
13 1000.1801 g
14 1000.1088 g
15 1000.0669 g
16 1000.0398 g
17 1000.0334 g
18 1000.0195 g

確かに 14 くらいでいいんじゃないかと思った。
そんだけ

【機械学習・ディープラーニング】多層パーセプトロン(教師あり学習)の実装 (C#)

数学素人が自分なりに腑に落ちるところまで辿り着いた内容を吐き出したものです。
専門家に言わせれば、おかしい表現や用語があるかもしれませんが、根本の考え方は抑えたつもりです。
間違っているところがあれば、指摘していただけると幸いです。


  • 昨年の今頃、故あって実装した多層パーセプトロン(教師あり学習)を手直ししつつ改めて実装してみました。
  • 当時の動機と目的
    • ディープラーニングという言葉を見聞きするまでコンピュータというものは手順(プログラム)を与えなければ動作しないというのが私の認識でした。
    • 昔からAIと呼ばれるものはあったけれど、私の知るそれはあくまでも人間が手順を与え、入力や出力をパラメータにフィードバックすることによって動作が変化しているに過ぎません。
    • ディープラーニングについての当初の認識は...
      • 入力と出力の組み合わせを大量に投入すれば、入力から正しい出力を吐くようになる。
      • 入力と出力に相関関係があれば、教えていない入力についても正しい出力を吐くようになる。<--- これがすごいと思った。
    • 手順を与えること無くどうやって正しく動作させるのか皆目見当がつかなかったので、この目で動くことを確かめてからせめて原理くらいは理解したいなぁと思ったわけです。
    • 素人でもDLしてすぐに動かせるようなもの (src or bin?) を探してみたけれど見つからず、やっぱり横着は良くないな…と思い直し、とりあえず1冊本を読んでみて理解できなそうだったら諦めようくらいの気持ちで本屋へ
    • 本を読んで「多層パーセプトロン」というものが私のイメージしていた「ディープラーニングと呼ばれるモノ」と一致すると分かったので、その原理を理解しつつちゃんと動くものを実装することを目標にしました。
  • 参考にした本


多層パーセプトロン(教師あり学習)について

多層パーセプトロン(教師あり学習)について何も知らない人に説明する体で書いてみます。

  • 伝言ゲームを思い浮かべて下さい。
  • 3人以上の参加者を一列に並べ、最初の人を「入力層」、最後の人を「出力層」、中間にいる人達を「隠れ層」と呼びます。

5人 = 入力層, 隠れ層 × 3, 出力層 の場合

 
(入力層)
 
⇒ 伝達 ⇒ (隠れ層) ⇒ 伝達 ⇒ (隠れ層) ⇒ 伝達 ⇒ (隠れ層) ⇒ 伝達 ⇒ (出力層)
  • 伝言ゲームでは正確に伝えることを目指しますが、ここでは「連想したもの」を伝えることにします。なので「連想伝言ゲーム」とでも呼ぶべきでしょうか。
  • ゲームは以下のように進行します。
    • まず入力データを入力層に伝えます。入力層は次の隠れ層にそのまま伝えます。
    • 隠れ層は伝えられたデータから「連想したもの」を次の隠れ層(又は出力層)に伝えます。これを出力層に達するまで繰り返します。
    • 出力層は伝えられたデータをそのまま出力データとして出力します。
  • 隠れ層が多ければ多いほど入力データからかけ離れた(想像を超える面白い)出力データが得られるでしょう。
  • ですが、そんな出力データは何の役にも立ちません。
  • そこで「こういう入力データのときは、こういう出力データがほしい」ということを決めておきます。(教師データ)
  • ゲームが終了したあと、実際の出力データと期待した出力データに乖離がある場合、期待した出力データに近づくように隠れ層を教育することにします。隠れ層は教育される度に連想のし方が少しずつ変化して行く訳です。
  • ゲームの実施 ⇒ 教育 を何度も何度も繰り返せば(教育方法に間違いが無ければ)いずれ入力データから期待した出力データを得られるようになるはずです。

分かりやすく簡単に説明するとしたらこのようになります。
ですが、これでは「隠れ層をどのようにプログラムで実装するのか」「どうやって隠れ層を教育するのか」が全く見えてきません。
そこでもう少し実装寄りの説明をします。

  • 1つの層(入力層、出力層、隠れ層)は1人の人間によって構成されていましたが、複数の人間(これを「ニューロン」と呼びます)によって構成されるものと置き換えて下さい。

入力層 2ニューロン, 隠れ層 3ニューロン × 3, 出力層 2ニューロン の場合

入力層 隠れ層 隠れ層 隠れ層 出力層
(ニューロン)

(ニューロン)
⇒ 伝達 ⇒  
(ニューロン)

(ニューロン)

(ニューロン)
 
⇒ 伝達 ⇒ (ニューロン)

(ニューロン)

(ニューロン)
⇒ 伝達 ⇒ (ニューロン)

(ニューロン)

(ニューロン)
⇒ 伝達 ⇒ (ニューロン)

(ニューロン)
  • ゲームは以下のように進行します。
    • まず入力データを分割して入力層のニューロン達に伝えます。
    • 次の層のニューロンは前の層の全てのニューロンからデータを受け取ります。つまり n 個のニューロンの層と m 個のニューロンの層の間では n*m 回の伝達が行われることになります。
    • ニューロンは受け取った全てのデータを元に、そこから連想される1つのデータを次の層に伝えるデータとします。これを出力層に達するまで繰り返します。
    • 出力層のニューロンは出力データを連結して、最終的な出力データとします。
  • ほとんどの場合、出力データは正解(期待された出力データ)と異なります。
  • なので学習を行います。
  • 学習は全ての隠れ層と出力層の全てのニューロンについて行います。
    • ニューロンは前の層の全てのニューロンから受け取ったデータを元に連想を行いました。各ニューロンは、各データについて以下の考察と判断を行います。
      • もしこのデータをもっと重視していたら、自分が出力した連想データは変わっていたはずだ。その連想データを次の層に伝えていたら最終的な出力データはどうなっていたかを試してみる。⇒ その結果、より正解に近づいたなら ⇒ 重要なデータを出力している可能性が高い、入力元のニューロンの重要度を上げ、次からは多く取り入れることにしよう。
      • もしこのデータをもっと軽視していたら、自分が出力した連想データは変わっていたはずだ。その連想データを次の層に伝えていたら最終的な出力データはどうなっていたかを試してみる。⇒ その結果、より正解に近づいたなら ⇒ 不要なデータを出力している可能性が高い、入力元のニューロンの重要度を下げ、次からはあまり取り入れないようにしよう。

このような学習を行えばだんだん正解に近づいて行く…ような気がしませんか。
プログラムに置き換えると以下のようになります。

  • データ
    • 実数です。
    • 入力層にはニューロンの数分の実数を入力し、出力層からはニューロンの数分の実数が出力されます。
  • ニューロン
    • 入力された全てのデータ(実数)をそれぞれの重要度を掛けて合計し、活性化関数に入力して、その出力を自身の出力とします。
    • 活性化関数にはいろいろあるようですが、シグモイド関数以外ちゃんと動かせなかった試していないので、本投稿では 活性化関数=シグモイド関数 と考えて下さい。
    • 式(というかコード)っぽく書くと以下のようになります。
// n == 前の層のニューロン数

// 隠れ層のニューロンの場合
出力データ = sigmoid ( 入力データ[0] * (入力データ[0]の重要度) + 入力データ[1] * (入力データ[1]の重要度) + 入力データ[2] * (入力データ[2]の重要度) + ... + 入力データ[n-1] * (入力データ[n-1]の重要度) )

// 出力層のニューロンの場合
出力データ = 入力データ[0] * (入力データ[0]の重要度) + 入力データ[1] * (入力データ[1]の重要度) + 入力データ[2] * (入力データ[2]の重要度) + ... + 入力データ[n-1] * (入力データ[n-1]の重要度)



学習、学習係数、バイアスについても書くべきかと思うのですが断念します。力尽きた時間が無いので、、、いつか追記するかも。。。
学習については次のアルゴリズムで示します。

多層パーセプトロン(教師あり学習)のアルゴリズム

疑似コードで示します。
入力データを多層パーセプトロンに投入して、出力データを取り出すことを「評価」
入力データと教師データを使って多層パーセプトロンに学習させることを、単に「学習」と呼んでいます。

データ構造

マルチレイヤ
class MultiLayer
{
	NeuronLayer[] NeuronLayers = new NeuronLayer[ レイヤ数 ];       // 入力層, 隠れ層, 出力層 のリスト
	AxonLayer[]   AxonLayuers  = new AxonLayer  [ レイヤ数 - 1 ];   // 層と層の間なのでレイヤ数より1つ少ない
}
入力層 隠れ層 隠れ層 ... 隠れ層 出力層
NeuronLayers[0] AxonLayers[0] NeuronLayers[1] AxonLayers[1] NeuronLayers[2] AxonLayers[2] ... NeuronLayers[ レイヤ数 - 2 ] AxonLayers[ レイヤ数 - 2 ] NeuronLayers[ レイヤ数 - 1 ]
ニューロンレイヤ (入力層、隠れ層、出力層)
class NeuronLayer
{
	Neuron[] Neurons = new Neuron[ この層のニューロン数 ];
}
軸索レイヤ
class AxonLayer
{
	Axon[][] Axons   = new Axon[ 前の層のニューロン数 ][ 次の層のニューロン数 ];
	Axon[] BiasAxons = new Axon[ 次の層のニューロン数 ];
}
ニューロン
class Neuron
{
	double InputValue;  // 活性化関数を通す前の値
	double OutputValue; // 活性化関数を通した後の値

	double InputRateOfChange;  // 出力ニューロンの出力の「変化率」
	double OutputRateOfChange; // 出力ニューロンの出力の「変化率」
}
軸索
class Axon
{
	double Weight; // 重要度
}

評価

// 入力

{
	cl = MultiLayer.NeuronLayers[0]; // 入力層

	for ( c = 0 ; c < (clのニューロン数) ; c++ )
	{
		cl.Nurons[c].OutputValue = 入力データ [ c ] ; // 入力層では活性化関数を使用しないので OutputValue にセットする。
	}
}

// 活性化

for ( layerIndex = 0 ; layerIndex + 1 < レイヤ数 ; layerIndex++ )
{
	cl = MultiLayer.NeuronLayers[layerIndex];     // このレイヤ
	nl = MultiLayer.NeuronLayers[layerIndex + 1]; // 次のレイヤ

	al = MultiLayer.AxonLayers[layerIndex]; // cl と nl の間の軸索レイヤ

	for ( n = 0 ; n < (nlのニューロン数) ; n++ )
	{
		nl.Neurons[n].InputValue = 1.0 * al.BiasAxons[n].Weight;

		for( c = 0 ; c < (clのニューロン数) ; c++ )
		{
			nl.Neurons[n].InputValue += cl.Neurons[c].OutputValue * al.Axons[c][n].Weight;
		}
		nl.Neurons[n].OutputValue = 活性化関数 ( nl.Neurons[n].InputValue ) ;
	}
}

// 出力

{
	cl = MultiLayer.NeuronLayers[ レイヤ数 - 1 ]; // 出力層

	for ( c = 0; c < (clのニューロン数) ; c++ )
	{
		出力データ [ c ] = cl.Nurons[c].InputValue; // 出力層の活性化関数は恒等関数を使用するので InputValue から取得する。
	}
}

学習

「評価」を行ってから出力データを元に以下を実行します。

for ( outputIndex = 0 ; outputIndex < 出力層のニューロン数 ; outputIndex++ )
{
	Train_Output ( outputIndex );
}

// ----

Train_Ouput( outputIndex ) // (outputIndex + 1) 番目の出力層のニューロンについて学習する。
{
	MakeRateOfChangeTable ( outputIndex );
	ChangeWeight_Output ( outputIndex );
}

MakeRateOfChangeTable( outputIndex ) // (outputIndex + 1) 番目の出力層のニューロンについて、出力層・隠れ層の変化率を求める。
{
	// 出力層の変化率を生成
	{
		cl = MultiLayer.NeuronLayers[ レイヤ数 - 1 ]; // 出力層

//		cl.Neurons[outputIndex].InputRateOfChange  = 1.0; // 恒等関数の変化率は 1.0
//		cl.Neurons[outputIndex].OutputRateOfChange = 1.0; // 不使用
	}

	// 最後の隠れ層の変化率を生成
	{
		cl = MultiLayer.NeuronLayers[ レイヤ数 - 2 ]; // 最後の隠れ層
		nl = MultiLayer.NeuronLayers[ レイヤ数 - 1 ]; // 出力層

		al = MultiLayer.AxonLayers[ レイヤ数 - 2 ]; // cl と nl の間の軸索レイヤ

		{
			n = outputIndex;

			for ( c = 0 ; c < (clのニューロン数) ; c++ )
			{
				cl.Neurons[c].OutputRateOfChange = nl.Neurons[n].InputRateOfChange * al.Axons[c][n].Weight;
				cl.Neurons[c].InputRateOfChange = cl.Neurons[c].OutputRateOfChange * 活性化関数の導関数 ( cl.Neurons[c].InputValue ) ;
			}
		}
	}

	for ( layerIndex = レイヤ数 - 3 ; 1 <= layerIndex ; layerIndex-- ) // 残りの隠れ層の変化率を生成
	{
		cl = MultiLayer.NeuronLayers[layerIndex];     // この隠れ層
		nl = MultiLayer.NeuronLayers[layerIndex + 1]; // 次の隠れ層

		al = MultiLayer.AxonLayers[layerIndex]; // cl と nl の間の軸索レイヤ

		for ( n = 0 ; n < (nlのニューロン数) ; n++ )
		{
			for ( c = 0 ; c < (clのニューロン数) ; c++ )
			{
				cl.Neurons[c].OutputRateOfChange = nl.Neurons[n].InputRateOfChange * al.Axons[c][n].Weight;
				cl.Neurons[c].InputRateOfChange = cl.Neurons[c].OutputRateOfChange * 活性化関数の導関数 ( cl.Neurons[c].InputValue ) ;
			}
		}
	}
}

ChangeWeight_Output( outputIndex ) // (outputIndex + 1) 番目の出力層のニューロンについて、軸索層の重みを更新する。
{
	d = (期待される出力値) - MultiLayer.NeuronLayers[ レイヤ数 - 1 ].Neurons[outputIndex].InputValue ; // 誤差 == 期待される出力値 - 現在の出力値

	// 最後の隠れ層 ~ 出力層 の 重みを更新する。
	{
		cl = MultiLayer.NeuronLayers[ レイヤ数 - 2 ]; // 最後の隠れ層
		nl = MultiLayer.NeuronLayers[ レイヤ数 - 1 ]; // 出力層

		al = MultiLayer.AxonLayers[ レイヤ数 - 2 ]; // cl と nl の間の軸索レイヤ

		{
			n = outputIndex;

			for ( c = 0; c < (clのニューロン数) ; c++ )
			{
				al.Axons[c][n].Weight += GetChangeWeight ( cl.Neurons[c].OutputValue , 1.0 , d );
			}

			// バイアスの重みの変更
			{
				al.BiasAxons[n].Weight += GetChangeWeight ( 1.0 , 1.0 , d );
			}
		}
	}

	for ( layerIndex = レイヤ数 - 3 ; 1 <= layerIndex ; layerIndex-- ) // 残りの隠れ層の変化率を生成
	{
		cl = MultiLayer.NeuronLayers[layerIndex];     // この隠れ層
		nl = MultiLayer.NeuronLayers[layerIndex + 1]; // 次の隠れ層

		al = MultiLayer.AxonLayers[layerIndex]; // cl と nl の間の軸索レイヤ

		for ( n = 0 ; n < (nlのニューロン数) ; n++ )
		{
			for ( c = 0 ; c < (clのニューロン数) ; c++ )
			{
				al.Axons[c][n].Weight += GetChangeWeight ( cl.Neurons[c].OutputValue , nl.Neurons[n].InputRateOfChange , d );
			}

			// バイアスの重みの変更
			{
				al.BiasAxons[n].Weight += GetChangeWeight ( 1.0 , nl.Neurons[n].InputRateOfChange , d );
			}
		}
	}
}

/*
	重みの加算値を得る。

	axonInputValue         ... この軸索への入力値
	axonOutputRateOfChange ... ( この軸索の出力 ⇒ 出力ニューロンの出力 ) の変化率
	d                      ... 出力ニューロンの誤差 ( 期待される出力値 - 現在の出力値 )
*/
double GetChangeWeight( axonInputValue , axonOutputRateOfChange , d )
{
	return axonInputValue * axonOutputRateOfChange * d * 学習係数 ;
}

学習係数 は 0.1 を使用しています。適切な値がどのくらいなのかよく分かりませんでした。


重みの加算値の式 (GetChangeWeight) が間違っているんじゃないかなぁと懸念しております。

  • 理屈としては以下のとおり
    • この軸索への入力値が大きければ出力への影響も大きいので、重みの加算値に乗じる。(符号が反映される必要はある)
    • 勾配降下法によれば変化率が大きいほど移動量を増やすべきらしいので、これも重みの加算値に乗じる。
    • 誤差が大きいほど移動量を増やすべきなので、これも重みの加算値に乗じる。
  • なので全部掛け合わせるだけ。なお、正負は移動方向と一致してくれる。


試験

概要

ML構成 ... { 入力層のニューロン数 , 隠れ層のニューロン数 , 隠れ層のニューロン数 , 隠れ層のニューロン数 , ... , 出力層のニューロン数 }

テスト0001 - 全加算器

ML構成:{ 2, 3, 2 }
入力データ:
 A (0, 1) を2進化した1ビットを (0, 1) 信号として
 B (0, 1) を2進化した1ビットを (0, 1) 信号として
 合計2ビット
出力データ:
 A + B を2進化した2ビットを (0, 1) 信号として
学習データ:(0, 0), (0, 1), (1, 0), (1, 1) 4通り
検証データ:学習データと同じ

テスト0002 - 3ビット加算器

ML構成:{ 6, 7, 7, 7, 4 }
入力データ:
 A (0 ~ 7) を2進化した3ビットを (0, 1) 信号として
 B (0 ~ 7) を2進化した3ビットを (0, 1) 信号として
 合計6ビット
出力データ:
 A + B を2進化した4ビットを (0, 1) 信号として
学習データ:(0 ~ 7), (0 ~ 7) 64通り
検証データ:学習データと同じ

テスト0003 - 5ビット加算器

ML構成:{ 10, 20, 20, 20, 6 }
入力データ:
 A (0 ~ 31) を2進化した5ビットを (0, 1) 信号として
 B (0 ~ 31) を2進化した5ビットを (0, 1) 信号として
 合計10ビット
出力データ:
 A + B を2進化した6ビットを (0, 1) 信号として
学習データ:(0 ~ 31), (0 ~ 31) 1024通り
検証データ:学習データと同じ

テスト0004 - FizzBuzz

ML構成:{ 10, 30, 30, 30, 4 }
入力データ:
  (1 ~ 1023) を2進化した10ビットを (0, 1) 信号として
出力データ:
 数値を出力すべきとき = (1, 0, 0, 0)
 Fizzを出力すべきとき = (0, 1, 0, 0)
 Buzzを出力すべきとき = (0, 0, 1, 0)
 FizzBuzzを出力すべきとき = (0, 0, 0, 1)
学習データ:101 ~ 1023
検証データ:1 ~ 100

結果

// 2019/10/28 再実施

テスト0001 - 全加算器 - 正答率
テストNo. \ 学習回数 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000
1 0.250 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
2 0.500 0.500 0.500 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000
3 0.250 0.500 0.500 0.500 0.500 1.000 1.000 1.000 1.000 1.000 1.000
4 0.250 0.750 0.750 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
5 0.250 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
6 0.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
7 0.250 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
8 0.250 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
9 0.500 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
10 0.250 0.500 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000


テスト0002 - 3ビット加算器 - 正答率
テストNo. \ 学習回数 0 30000 60000 90000 120000 150000 180000 210000 240000 270000 300000
1 0.109 0.781 0.844 0.781 0.938 1.000 1.000 1.000 1.000 1.000 1.000
2 0.047 0.578 0.750 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
3 0.078 0.625 0.859 0.938 0.984 0.984 0.984 1.000 1.000 1.000 1.000
4 0.016 0.328 0.578 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
5 0.016 0.531 0.641 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
6 0.016 0.656 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
7 0.109 0.328 0.563 0.906 0.984 1.000 1.000 1.000 1.000 1.000 1.000
8 0.078 0.422 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
9 0.094 0.344 0.469 0.766 0.906 0.984 0.984 0.984 0.984 1.000 1.000
10 0.047 0.359 0.656 0.938 1.000 0.984 1.000 1.000 1.000 1.000 1.000


テスト0003 - 5ビット加算器 - 正答率
テストNo. \ 学習回数 0 30000 60000 90000 120000 150000 180000 210000 240000 270000 300000
1 0.020 0.103 0.289 0.368 0.900 1.000 1.000 1.000 1.000 1.000 1.000
2 0.006 0.164 0.275 0.879 0.990 0.998 1.000 1.000 1.000 1.000 1.000
3 0.013 0.082 0.996 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
4 0.006 0.174 0.371 0.972 0.998 1.000 1.000 1.000 1.000 1.000 1.000
5 0.027 0.153 0.218 0.652 0.732 0.934 0.982 1.000 1.000 1.000 1.000
6 0.019 0.316 0.630 0.671 0.957 0.993 1.000 1.000 1.000 1.000 1.000
7 0.018 0.127 0.533 0.629 1.000 1.000 1.000 1.000 1.000 1.000 1.000
8 0.027 0.104 0.578 0.876 0.989 1.000 1.000 1.000 1.000 1.000 1.000
9 0.010 0.155 0.273 0.346 0.325 0.657 0.824 0.951 1.000 1.000 1.000
10 0.001 0.143 0.272 0.988 1.000 1.000 1.000 1.000 1.000 1.000 1.000


テスト0004 - FizzBuzz - 正答率
テストNo. \ 学習回数 0 100000 200000 300000 400000 500000 600000 700000 800000 900000 1000000
1 0.000 0.530 0.000 0.000 0.550 0.980 0.990 0.990 0.990 0.990 0.990
2 0.000 0.000 0.000 0.530 0.530 0.940 0.960 0.970 0.980 0.980 0.970
3 0.010 0.530 0.530 0.370 0.820 0.950 1.000 1.000 1.000 1.000 1.000
4 0.000 0.530 0.040 0.870 1.000 1.000 1.000 1.000 1.000 1.000 1.000
5 0.530 0.530 0.530 0.000 0.890 0.960 0.990 0.990 0.990 0.990 0.990
6 0.000 0.530 0.530 0.430 0.860 0.930 0.940 0.960 0.970 0.970 0.970
7 0.060 0.530 0.250 0.880 0.970 0.970 0.970 0.970 0.970 0.970 0.970
8 0.000 0.000 0.260 0.520 0.950 0.960 0.970 0.980 0.980 0.980 0.980
9 0.270 0.530 0.530 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
10 0.000 0.110 0.730 0.920 0.910 0.920 0.920 0.920 0.920 0.920 0.930



テスト0004 - FizzBuzz で「教えていない入力と出力」について 1000000 回の学習で 0.9 以上の正答率を出しているので少なくともある程度は正しく実装できているのかなと思います。
全て 1.0 にならないのは、式が間違っているからなのか、そういうものなのでしょうか。。。
あと ML構成、学習係数 をいろいろいじるべきなのかもしれません。

第25回 算数オリンピックファイナル 第3問 を C# で

  • 少し前に問題文をどこかのまとめで見かけたので
  • 頑張れば Linq だけ使って1文で書けるのかもしれない。と思った。
using System;
using System.Collections.Generic;
using System.Linq;

public class Hello
{
	public static void Main()
	{
		// 1~13までの数字が1つずつ書かれた13枚のカードがあります。いま、先生がこの中から2枚をひいて、
		var ps = Enumerable.Range(0, 13 * 13).Select(v => new { A = v / 13 + 1, B = v % 13 + 1 }).Where(p => p.A < p.B).ToList();

		ps = ps.Where(p => ps.Where(q => q.A * q.B == p.A * p.B).Count() != 1).ToList(); // A君「わからないな。」
		ps = ps.Where(p => ps.Where(q => q.A + q.B == p.A + p.B).Count() != 1).ToList(); // B君「ぼくもわからないよ。」
		ps = ps.Where(p => ps.Where(q => q.A - q.B == p.A - p.B).Count() != 1).ToList(); // C君「うーん、やっぱりわからないなあ。」
		ps = ps.Where(p => ps.Where(q => q.A * q.B == p.A * p.B).Count() != 1).ToList(); // A君「まだわからない。」

		// B君,C君「ぼくたちもわからない。」
		{
			var ps2 = ps.Where(p => ps.Where(q => q.A + q.B == p.A + p.B).Count() != 1).ToList();
			var ps3 = ps.Where(p => ps.Where(q => q.A - q.B == p.A - p.B).Count() != 1).ToList();

			ps = ps2.Where(p => ps3.Any(q => q.A == p.A && q.B == p.B)).ToList();
		}

		ps.ForEach(p => Console.WriteLine(p.A + " " + p.B)); // 先生がひいた2枚のカードの数字を2つとも答えなさい。
	}
}

はてなブログでコードの右上に言語名を表示する

  • 割と力技
  • テーマによっては上手く表示されないかもしれない。
    • このテーマでしか試していない。

やり方

設定 ⇒ デザイン ⇒ [スパナのアイコン] ⇒ フッタ
に以下を貼り付ける。

<script>

function PutLangLabels() {
	var pres = document.getElementsByTagName("pre");

	for(var i = 0; i < pres.length; i++) {
		var pre = pres[i];
		var preClassNames = pre.className.split(' ');

		if(preClassNames.indexOf("code") == -1) {
			continue;
		}

		var lang = pre.className;

		/*
			lang-xxx ===> 言語名
		*/
		var langTable = [
			[ "lang-cs", "C#" ],
			[ "lang-c",  "C"  ],

			// ここへ追加...
		];

		for(var c = 0; c < langTable.length; c++) {
			if(preClassNames.indexOf(langTable[c][0]) != -1) {
				lang = langTable[c][1];
			}
		}

		var langLabel = document.createElement("div");

		langLabel.style.position = "absolute";
		langLabel.style.top = "0px";
		langLabel.style.right = "0px";
		langLabel.style.margin = "3px";
		langLabel.style.padding = "3px 7px";
		langLabel.style.color = "#003";           // 言語名の色
		langLabel.style.backgroundColor = "#fff"; // 言語名の背景色
		langLabel.innerText = lang;

		pre.style.position = "relative"; // 親を relative にしないと、子の absolute が効かない。

		pre.appendChild(langLabel);
	}
}

PutLangLabels();

</script>

RPGのダンジョンマップらしきものを自動生成する(C#)

フィールドマップのときとほとんど同じ。
単純なアルゴリズムの割に、それなりに色々なマップができる。

MakeLikeADungeonMap()

public static void MakeLikeADungeonMap(int pattern) // pattern: 0 ~ 1023
{
	const int w = 100; // マップの幅
	const int h = 100; // マップの高さ

	Random rand = new Random();
	int[,] map = new int[w, h];

	for (int x = 0; x < w; x++)
	{
		for (int y = 0; y < h; y++)
		{
			map[x, y] = rand.Next(2);
		}
	}
	for (int c = 0; c < w * h * 30; c++)
	{
		int x = rand.Next(w);
		int y = rand.Next(h);
		int count = 0;

		for (int xc = -1; xc <= 1; xc++)
		{
			for (int yc = -1; yc <= 1; yc++)
			{
				count += map[(x + w + xc) % w, (y + h + yc) % h];
			}
		}
		map[x, y] = (pattern >> count) & 1;
	}
	using (Bitmap bmp = new Bitmap(w, h))
	{
		for (int x = 0; x < w; x++)
		{
			for (int y = 0; y < h; y++)
			{
				bmp.SetPixel(x, y, map[x, y] == 0 ? Color.Blue : Color.Yellow);
			}
		}
		bmp.Save(@"C:\temp\LikeADungeonMap.bmp", ImageFormat.Bmp); // 適当な場所にビットマップで出力する。
	}
}

全パターンでの出力例

http://stackprobe.ccsp.mydns.jp:58946/_rosetta/Hatena/20191021/table-pattern-0-1023.html

出力例 (ピックアップ)

pattern=24pattern=49pattern=71pattern=82
pattern=139pattern=176pattern=225pattern=226
pattern=432pattern=463pattern=520pattern=783
pattern=808pattern=817pattern=867pattern=936

  • (0,1)の初期配置の1の密度を調節すると変わってくるかもしれないので試してみた。
    • あまり変化無し。
      • たまに変化あるのもある。
      • 奇数の場合、ほとんど変化無し。
        • count = 0 (1が無い3x3の中央) に 1 を置くので密度の濃淡が無くなってしまうのだと思う。多分

左から右にかけて密度が上がって行く

コード

public static void MakeLikeADungeonMap(int pattern) // pattern: -1 ~ 1023
{
	// 左端と右端で密度が違うので左右はループしない。上下はループする。
	// 左右の 100 px は緩衝地帯、後で除去する。

	const int w = 700; // マップの幅
	const int h = 100; // マップの高さ

	Random rand = new Random();
	int[,] map = new int[w, h];

	for (int x = 0; x < w; x++)
	{
		for (int y = 0; y < h; y++)
		{
			map[x, y] = rand.NextDouble() < (x - 100) * 1.0 / (w - 201) ? 1 : 0;
		}
	}
	if (pattern != -1)
	{
		for (int c = 0; c < w * h * 30; c++)
		{
			int x = rand.Next(1, w - 1);
			int y = rand.Next(h);
			int count = 0;

			for (int xc = -1; xc <= 1; xc++)
			{
				for (int yc = -1; yc <= 1; yc++)
				{
					count += map[x + xc, (y + h + yc) % h];
				}
			}
			map[x, y] = (pattern >> count) & 1;
		}
	}
	using (Bitmap bmp = new Bitmap(w - 200, h))
	{
		for (int x = 0; x < w - 200; x++)
		{
			for (int y = 0; y < h; y++)
			{
				bmp.SetPixel(x, y, map[x + 100, y] == 0 ? Color.Blue : Color.Yellow);
			}
		}
		bmp.Save(@"C:\temp\LikeADungeonMap_Gradation.bmp", ImageFormat.Bmp); // 適当な場所にビットマップで出力する。
	}
}

色々な密度で

コード

public static void MakeLikeADungeonMap(int pattern, double rate) // pattern: -1 ~ 1023, rate: 0.0 ~ 1.0
{
	const int w = 100; // マップの幅
	const int h = 100; // マップの高さ

	Random rand = new Random();
	int[,] map = new int[w, h];

	for (int x = 0; x < w; x++)
	{
		for (int y = 0; y < h; y++)
		{
			map[x, y] = rand.NextDouble() < rate ? 1 : 0;
		}
	}
	if (pattern != -1)
	{
		for (int c = 0; c < w * h * 30; c++)
		{
			int x = rand.Next(w);
			int y = rand.Next(h);
			int count = 0;

			for (int xc = -1; xc <= 1; xc++)
			{
				for (int yc = -1; yc <= 1; yc++)
				{
					count += map[(x + w + xc) % w, (y + h + yc) % h];
				}
			}
			map[x, y] = (pattern >> count) & 1;
		}
	}
	using (Bitmap bmp = new Bitmap(w, h))
	{
		for (int x = 0; x < w; x++)
		{
			for (int y = 0; y < h; y++)
			{
				bmp.SetPixel(x, y, map[x, y] == 0 ? Color.Blue : Color.Yellow);
			}
		}
		bmp.Save(@"C:\temp\LikeADungeonMap_Percentage.bmp", ImageFormat.Bmp); // 適当な場所にビットマップで出力する。
	}
}

RPGのフィールドマップらしきものを自動生成する(C#)

単純なアルゴリズムの割に、それなりのマップができる。

2値(海と陸のみ)

MakeLikeAFieldMap()

public static void MakeLikeAFieldMap()
{
	const int w = 100; // マップの幅
	const int h = 100; // マップの高さ

	Random rand = new Random();
	int[,] map = new int[w, h];

	for (int x = 0; x < w; x++)
	{
		for (int y = 0; y < h; y++)
		{
			map[x, y] = rand.Next(2);
		}
	}
	for (int c = 0; c < w * h * 10; c++)
	{
		int x = rand.Next(w);
		int y = rand.Next(h);
		int count = 0;

		for (int xc = -1; xc <= 1; xc++)
		{
			for (int yc = -1; yc <= 1; yc++)
			{
				count += map[(x + w + xc) % w, (y + h + yc) % h];
			}
		}
		map[x, y] = count / 5;
	}
	using (Bitmap bmp = new Bitmap(w, h))
	{
		for (int x = 0; x < w; x++)
		{
			for (int y = 0; y < h; y++)
			{
				bmp.SetPixel(x, y, map[x, y] == 0 ? Color.Turquoise : Color.Sienna);
			}
		}
		bmp.Save(@"C:\temp\LikeAFieldMap.bmp", ImageFormat.Bmp); // 適当な場所にビットマップで出力する。
	}
}

出力例

グラデーションあり

MakeLikeAFieldMap()

public static void MakeLikeAFieldMap()
{
	const int w = 100; // マップの幅
	const int h = 100; // マップの高さ

	Color[] tileColors = new Color[] // 各地形の色
	{
		Color.FromArgb(0, 0, 55), // ここから海?
		Color.FromArgb(20, 20, 105),
		Color.FromArgb(40, 40, 155),
		Color.FromArgb(60, 60, 205),
		Color.FromArgb(80, 80, 255),
		Color.FromArgb(120, 80, 40), // ここから地面?
		Color.FromArgb(150, 100, 50),
		Color.FromArgb(180, 120, 60),
		Color.FromArgb(210, 140, 70),
		Color.FromArgb(240, 160, 80),
		Color.FromArgb(0, 180, 0), // ここから森?
		Color.FromArgb(0, 150, 0),
		Color.FromArgb(0, 120, 0),
		Color.FromArgb(10, 90, 0),
		Color.FromArgb(20, 60, 0),
		Color.FromArgb(40, 30, 0),
	};

	Random rand = new Random();
	int[,] map = new int[w, h];

	for (int x = 0; x < w; x++)
	{
		for (int y = 0; y < h; y++)
		{
			map[x, y] = rand.Next(2);
		}
	}
	for (int c = 0; c < w * h * 10; c++)
	{
		int x = rand.Next(w);
		int y = rand.Next(h);
		int count = 0;

		for (int xc = -1; xc <= 1; xc++)
		{
			for (int yc = -1; yc <= 1; yc++)
			{
				count += map[(x + w + xc) % w, (y + h + yc) % h];
			}
		}
		map[x, y] = count / 5;
	}
	for (int x = 0; x < w; x++)
	{
		for (int y = 0; y < h; y++)
		{
			map[x, y] *= tileColors.Length - 1;
		}
	}
	for (int c = 0; c < w * h * 20; c++)
	{
		int x1 = rand.Next(w);
		int x2 = x1;
		int y1 = rand.Next(h);
		int y2 = y1;

		if (rand.Next(2) == 0)
			x2 = (x2 + 1) % w;
		else
			y2 = (y2 + 1) % h;

		if (map[x1, y1] < map[x2, y2])
		{
			map[x1, y1]++;
			map[x2, y2]--;
		}
		else if (map[x2, y2] < map[x1, y1])
		{
			map[x1, y1]--;
			map[x2, y2]++;
		}
	}
	using (Bitmap bmp = new Bitmap(w, h))
	{
		for (int x = 0; x < w; x++)
		{
			for (int y = 0; y < h; y++)
			{
				bmp.SetPixel(x, y, tileColors[map[x, y]]);
			}
		}
		bmp.Save(@"C:\temp\LikeAFieldMap.bmp", ImageFormat.Bmp); // 適当な場所にビットマップで出力する。
	}
}

出力例

ミューテックスだけを使ってプロセス間通信を行う(C#)

説明

特徴

  • ミューテックスだけを使ってプロセス間通信できるんじゃね?と思って試してみたかっただけの代物。
  • ソケットとか共有メモリとかファイルとか一切使えないけど「ミューテックス」だけは使えるという特殊な状況下でプロセス間通信したい時に使えるかも。
  • 遅くて、実用的ではない。
    • コンパクトさを目指したので、多少速くする余地はあると思う。
    • 数十バイト程度の数回の通信なら気にならないと思う。

仕様

  • 送受信データは文字列
  • '\0' は送れない。
  • 一方通行 (クライアント ⇒ サーバー)

使い方

  1. サーバー側プロセスで MRecver.MRecv(ident, recved) を実行しておく。
    • 引数
      • ident -- ミューテックスの名前に使用する。ぶつかりにくく無難な文字列 (UUIDなど) にすること。クライアント側と同じであること。
      • recved -- 文字列を受信する度に呼ばれる。
    • ブロッキングモードで動作する。
      • 停止するには別スレッドで MRecver.MRecvEnd = true; を実行する。
  2. クライアント側プロセスで MSender.MSend(ident, message) を実行する。
    • 引数
      • ident -- サーバー側と同じ文字列
      • message -- 送信するメッセージ

サーバー側 (受信側)

public class MRecver
{
	public bool MRecvEnd;

	public void MRecv(string ident, Action<string> recved)
	{
		Mutex[] hdls = new Mutex[6];
		try
		{
			for (int i = 0; i < hdls.Length; i++)
				hdls[i] = new Mutex(false, ident + i);

			hdls[3].WaitOne();

			MemoryStream mem = new MemoryStream();
			byte chr = 0;
			int waitCount = 1;

			for (int i = 0, c = 0; !this.MRecvEnd; )
			{
				int n = (c + 1) % 3;

				hdls[3 + n].WaitOne();
				hdls[3 + c].ReleaseMutex();

				bool nBit = hdls[n].WaitOne(0);

				if (nBit)
					hdls[n].ReleaseMutex();

				if (waitCount <= 0)
				{
					if (!nBit)
						chr |= (byte)(1 << i);

					if (8 <= ++i)
					{
						if (chr == 0)
						{
							recved(Encoding.UTF8.GetString(mem.ToArray()));
							mem = new MemoryStream();
							waitCount = 1;
						}
						else
							mem.WriteByte(chr);

						i = chr = 0;
					}
				}
				else
				{
					if (nBit)
					{
						i = 0;
						Thread.Sleep(waitCount);
						if (waitCount < 100) waitCount++;
					}
					else if (8 <= ++i)
						i = waitCount = 0;
				}
				c = n;
			}
		}
		finally
		{
			foreach (Mutex hdl in hdls)
			{
				try { hdl.ReleaseMutex(); }
				catch { }
				try { hdl.Close(); }
				catch { }
			}
		}
	}
}

クライアント側 (送信側)

public class MSender
{
	public void MSend(string ident, string message)
	{
		Mutex[] hdls = new Mutex[6];
		try
		{
			for (int i = 0; i < hdls.Length; i++)
				hdls[i] = new Mutex(false, ident + i);

			hdls[3].WaitOne();

			bool[] flgs = new bool[3];
			int c = 0;

			foreach (byte[] bMes in new byte[][]
			{
				new byte[] { 0xff },
				Encoding.UTF8.GetBytes(message.Replace("\0", "")),
				new byte[] { 0x00 }
			})
			{
				for (int i = 0; i / 8 < bMes.Length; i++)
				{
					int n = (c + 1) % 3;

					hdls[3 + n].WaitOne();
					hdls[3 + c].ReleaseMutex();

					if ((bMes[i / 8] & (1 << (i % 8))) != 0)
					{
						if (!flgs[n])
						{
							hdls[n].WaitOne();
							flgs[n] = true;
						}
					}
					else if (flgs[n])
					{
						hdls[n].ReleaseMutex();
						flgs[n] = false;
					}
					c = n;
				}
			}
		}
		finally
		{
			foreach (Mutex hdl in hdls)
			{
				try { hdl.ReleaseMutex(); }
				catch { }
				try { hdl.Close(); }
				catch { }
			}
		}
	}
}