第1回【C言語】「ディープラーニングと物理学」を読み始める
目的
ここでは「ディープラーニングと物理学」(田中章詞、富谷昭夫、橋本幸士著)を読み進める上で私が「コーディングして再現できるかなあ」「これは皆にも役立つんじゃないか?」といったことをまとめたものであり、記念すべきZenn第1回目の投稿です。はじめは次節からはじまるように25ページの式(2.1.4)について考えます。
25ページの式(2.1.4)
この節では教師あり学習を定式化することを目的としており次のような思考実験が行われます。
次のような2つのサイコロを用意しましょう。
- サイコロ
は確率A ですべての目がでる1/6 - サイコロ
は確率B で1 のみが出る6
この上で次の操作を考えます。
- 確率
で1/2 かA を選び、それぞれB 、d=0 でラベルする。1 - サイコロを振り、出た目を
とする。s -
を記録する。(d, s)
ここでデータの総数、すなわち試行回数を
この試行によりそれぞれの実現値に対する確率
これを実際にC言語で試してみました。次の節では実際のコードを示します。
C言語による再現
全体像
まずは全体像を提示しておきます。私の環境ではclang version 12.0.5でC言語をコンパイルしています。
/*
* === TITLE: On p.24 in DEEP LEARNING AND PHYSICS ===
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Maximum and minimum number of dice
#define DICE_NUM_MAX 6
#define DICE_NUM_MIN 1
#define NUM_OF_ATTEMPTS 1000
// prototype declaration
void DICE_A(int *dice_num);
void DICE_A(int *dice_num);
void DOUBLE_DICE(int *d, int *s);
// main function
int main (void) {
int i, d, s, ds[2][DICE_NUM_MAX+1] = {0};
int j, k; // used as index for array
srand((unsigned)time(NULL)); // To vary a seed, we should introduce this.
// count realizations
for(i = 0; i <= NUM_OF_ATTEMPTS; i++) {
DOUBLE_DICE(&d, &s);
fprintf(fp, "%d %d\n", d, s);
for(j = 0; j <= 1; j++) {
for (k = DICE_NUM_MIN; k <= DICE_NUM_MAX; k++) {
if(j == d && k == s) {
ds[d][s]++;
}
}
}
}
double prob_ds[2][NUM_OF_ATTEMPTS+1];
for(j = 0; j <= 1; j++) {
for (k = DICE_NUM_MIN; k <= DICE_NUM_MAX; k++) {
prob_ds[j][k] = ds[j][k]/(double)NUM_OF_ATTEMPTS;
}
}
printf(" d=0 \t d=1\n");
printf("-----------------------------\n");
for(k = DICE_NUM_MIN; k <= DICE_NUM_MAX; k++) {
printf("%d | %f \t %f\n", k ,prob_ds[0][k], prob_ds[1][k]);
}
return 0;
}
/* === On DICE_A() and DICE_B() ===
*
* DICE_A() takes numbers 1 to 6 randomly, while DICE_B() always takes a number 6.
*/
void DICE_A(int *dice_num) {
/*
* *srand(time(NULL));
* Reason why we use time(NULL) is that it can always vary as time goas,
* and thus, rand() can be more randomized.
* If you would like to know it, see e.g., https://9cguide.appspot.com/21-02.html
*/
*dice_num = DICE_NUM_MIN + rand() % DICE_NUM_MAX;
}
void DICE_B(int *dice_num) {
*dice_num = 6;
}
/*-----*/
void DOUBLE_DICE(int *d, int *s) {
/*
* We choose a dice A or B which we will use.
* d = 1 -> Dice A
* d = 0 -> Dice B
*/
*d = rand() % 2;
int dice_num;
if (*d == 0) {
DICE_A(&dice_num);
*s = dice_num;
}else if (*d == 1){
DICE_B(&dice_num);
*s = dice_num;
}else{
printf("Something wrong.\n");
exit(1);
}
}
サイコロの試行回数
// Maximum and minimum number of dice
#define DICE_NUM_MAX 6
#define DICE_NUM_MIN 1
#define NUM_OF_ATTEMPTS 1000
のNUM_OF_ATTEMPTSを変化させることができます。
関数DOUBLE_DICEについて
関数void DOUBLE_DICE(*int, *int)は確率的にえられる
これはさらに2つの関数void DICE_A(*int)とvoid DICE_B(*int)によって構成されております。
DICE_Aではランダムに
原理ついてはまだ把握していないのですが、rand()関数は擬似乱数を生成するため完全な乱数ではありません。シード値が等しければ同じ乱数を生成してしまうため、それを回避しよりランダムなふるまいを手にいれるために、srand()を使用します。
srand((unsigned)time(NULL))の引数に対して現在時刻をいれることで毎回の計算ごとにシード値を変化させることができます。
出た目のカウント
// count realizations
for(i = 0; i <= NUM_OF_ATTEMPTS; i++) {
DOUBLE_DICE(&d, &s);
fprintf(fp, "%d %d\n", d, s);
for(j = 0; j <= 1; j++) {
for (k = DICE_NUM_MIN; k <= DICE_NUM_MAX; k++) {
if(j == d && k == s) {
ds[d][s]++;
}
}
}
}
ここでDOUBLE_DICEから得られる
確率の計算
double prob_ds[2][NUM_OF_ATTEMPTS+1];
for(j = 0; j <= 1; j++) {
for (k = DICE_NUM_MIN; k <= DICE_NUM_MAX; k++) {
prob_ds[j][k] = ds[j][k]/(double)NUM_OF_ATTEMPTS;
}
}
に従って計算します。
最後にこの系の確率分布がprintf()で表示されるようになっています。
Reference
田中章詞、富谷昭夫、橋本幸士「ディープラーニングと物理学」(2019)講談社
Discussion