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冊本を読んでみて理解できなそうだったら諦めようくらいの気持ちで本屋へ
- 本を読んで「多層パーセプトロン」というものが私のイメージしていた「ディープラーニングと呼ばれるモノ」と一致すると分かったので、その原理を理解しつつちゃんと動くものを実装することを目標にしました。
- 参考にした本
- 夢見るディープラーニング ニューラルネットワーク[Python実装]入門 単行本 - 発行日 2018/6/8 第1版 第1刷
- ISBN 978-4-7980-5433-9 C3055
- 夢見るディープラーニング ニューラルネットワーク[Python実装]入門 単行本 - 発行日 2018/6/8 第1版 第1刷
多層パーセプトロン(教師あり学習)について
多層パーセプトロン(教師あり学習)について何も知らない人に説明する体で書いてみます。
- 伝言ゲームを思い浮かべて下さい。
- 3人以上の参加者を一列に並べ、最初の人を「入力層」、最後の人を「出力層」、中間にいる人達を「隠れ層」と呼びます。
5人 = 入力層, 隠れ層 × 3, 出力層 の場合
(入力層) |
⇒ 伝達 ⇒ | (隠れ層) | ⇒ 伝達 ⇒ | (隠れ層) | ⇒ 伝達 ⇒ | (隠れ層) | ⇒ 伝達 ⇒ | (出力層) |
---|
- 伝言ゲームでは正確に伝えることを目指しますが、ここでは「連想したもの」を伝えることにします。なので「連想伝言ゲーム」とでも呼ぶべきでしょうか。
- ゲームは以下のように進行します。
- まず入力データを入力層に伝えます。入力層は次の隠れ層にそのまま伝えます。
- 隠れ層は伝えられたデータから「連想したもの」を次の隠れ層(又は出力層)に伝えます。これを出力層に達するまで繰り返します。
- 出力層は伝えられたデータをそのまま出力データとして出力します。
- 隠れ層が多ければ多いほど入力データからかけ離れた(想像を超える面白い)出力データが得られるでしょう。
- ですが、そんな出力データは何の役にも立ちません。
- そこで「こういう入力データのときは、こういう出力データがほしい」ということを決めておきます。(教師データ)
- ゲームが終了したあと、実際の出力データと期待した出力データに乖離がある場合、期待した出力データに近づくように隠れ層を教育することにします。隠れ層は教育される度に連想のし方が少しずつ変化して行く訳です。
- ゲームの実施 ⇒ 教育 を何度も何度も繰り返せば(教育方法に間違いが無ければ)いずれ入力データから期待した出力データを得られるようになるはずです。
分かりやすく簡単に説明するとしたらこのようになります。
ですが、これでは「隠れ層をどのようにプログラムで実装するのか」「どうやって隠れ層を教育するのか」が全く見えてきません。
そこでもう少し実装寄りの説明をします。
- 1つの層(入力層、出力層、隠れ層)は1人の人間によって構成されていましたが、複数の人間(これを「ニューロン」と呼びます)によって構成されるものと置き換えて下さい。
入力層 2ニューロン, 隠れ層 3ニューロン × 3, 出力層 2ニューロン の場合
入力層 | 隠れ層 | 隠れ層 | 隠れ層 | 出力層 | ||||
---|---|---|---|---|---|---|---|---|
(ニューロン) (ニューロン) |
⇒ 伝達 ⇒ | (ニューロン) (ニューロン) (ニューロン) |
⇒ 伝達 ⇒ | (ニューロン) (ニューロン) (ニューロン) |
⇒ 伝達 ⇒ | (ニューロン) (ニューロン) (ニューロン) |
⇒ 伝達 ⇒ | (ニューロン) (ニューロン) |
- ゲームは以下のように進行します。
- ほとんどの場合、出力データは正解(期待された出力データ)と異なります。
- なので学習を行います。
- 学習は全ての隠れ層と出力層の全てのニューロンについて行います。
- 各ニューロンは前の層の全てのニューロンから受け取ったデータを元に連想を行いました。各ニューロンは、各データについて以下の考察と判断を行います。
- もしこのデータをもっと重視していたら、自分が出力した連想データは変わっていたはずだ。その連想データを次の層に伝えていたら最終的な出力データはどうなっていたかを試してみる。⇒ その結果、より正解に近づいたなら ⇒ 重要なデータを出力している可能性が高い、入力元のニューロンの重要度を上げ、次からは多く取り入れることにしよう。
- もしこのデータをもっと軽視していたら、自分が出力した連想データは変わっていたはずだ。その連想データを次の層に伝えていたら最終的な出力データはどうなっていたかを試してみる。⇒ その結果、より正解に近づいたなら ⇒ 不要なデータを出力している可能性が高い、入力元のニューロンの重要度を下げ、次からはあまり取り入れないようにしよう。
- 各ニューロンは前の層の全てのニューロンから受け取ったデータを元に連想を行いました。各ニューロンは、各データについて以下の考察と判断を行います。
このような学習を行えばだんだん正解に近づいて行く…ような気がしませんか。
プログラムに置き換えると以下のようになります。
- データ
- ニューロン
// 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通り
検証データ:学習データと同じ
結果
// 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=24 | pattern=49 | pattern=71 | pattern=82 |
---|---|---|---|
pattern=139 | pattern=176 | pattern=225 | pattern=226 |
pattern=432 | pattern=463 | pattern=520 | pattern=783 |
pattern=808 | pattern=817 | pattern=867 | pattern=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' は送れない。
- 一方通行 (クライアント ⇒ サーバー)
使い方
- サーバー側プロセスで MRecver.MRecv(ident, recved) を実行しておく。
- クライアント側プロセスで 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 { } } } } }