C言語でゼロから作る機械学習♯01【単純パーセプトロン】

今回は機械学習でも基本の”き”とも言えるであろう単純パーセプトロンについて書こうと思います。
と言いましても、僕自身C言語は久しぶりにさわるのでここはHello World！でも表示させてみましょう～

```c
//firstprog.c
#include <stdio.h>

int main(void){
  printf("\nHello World !\n");
  return 0;
}
```
コンパイルと実行結果はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1720496/459e7f6a-e5a4-6373-c56b-c74f37e8140c.png)
※GCC[^1]でコンパイルしています。

最初のプログラムなのでとりあえずfirstprogとして"Hello World！"を表示できました！
やっぱり久しぶり/はじめての言語でのプログラミングはこれをやらないとやる気が出ませんっ個人的に(笑)

<h1>単純パーセプトロンとは</h1>
それでは、本題の単純パーセプトロンに入っていきましょう。
まず、パーセプトロンとは人間の脳にある神経細胞(ニューロン)の働きをコンピュータで人工的に再現したもので、一般にパターン認識を行います。
これは入力層、中間層(隠れ層)、出力層という3つの層から構成されます。
それぞれの層は複数のニューロンを持っており、プログラミングする際は入力データの数で入力ニューロン数を、期待する出力の種類などで出力ニューロン数を決定します。中間層に関しては、これ！といったBESTな設計はなく、自身のPCのスペックなどと相談しつつ中間層の数とそれぞれのニューロン数を決定しBETTERな設計をしていきます。
基本的に各層のニューロンの結合の際には、前の層のすべてのニューロンの値にそれぞれの重みを乗算して総和をとり、なんらかの活性化関数に通してすべてのニューロンから次の層へと出力します。
一層の構造は、
・前の層から重み付けされた値を全加算する
・活性化関数
の二つからなるとはじめのうちは考えていいと思います。

その中でも単純パーセプトロンとは、入力層と出力層の2層からなるパーセプトロンで線形分離可能な問題を解決することができます。
つまり<strong>論理回路を単純パーセプトロンで設計可能</strong>ということになります。
今回のプログラムでは論理回路の
・AND
・OR
・NAND
・NOR
~~・XOR~~
を単純パーセプトロンで表現していきます。
分かりやすく0/1の２入力にたいして、0/1の１出力、つまり入力層２ニューロン-出力層１ニューロンの単純パーセプトロンを作っていきます。
今回、重みはすべて１で初期化します。
各論理回路の真理値表は以下の通り↓

| 入力 | AND | OR | NAND | NOR |~~XOR~~ |
|:---:|:---:|:---:|:---:|:---:|:---:|
| 0 0 |  0 | 0 | 1 | 1 | 0 |
| 0 1 | 0 | 1 | 1 | 0 | 1 |
| 1 0 | 0 | 1 | 1 | 0 | 1 |
| 1 1 | 1 | 1 | 0 | 0 | 0 |

<h1>要件定義・設計</h1>
◇任意0/1の２入力に対して各論理回路(AND・OR・NAND・NOR・~~XOR~~)の出力を順に出力する。
◇任意0/1の入力はプログラム実行時、引数として渡す。
◇任意0/1の２入力が入力されていない場合はプログラムを終了する。

<h1>プログラミング～♪</h1>
それでは作っていきましょう。
今回の全体のコードは[こちら](https://github.com/SyuSaginomiya/Machine-learning-made-from-scratch-in-C-language)
<h2>入力値を取得</h2>
まず任意の入力を取り込むために以下のようなプログラムに変更します。

```c
//firstprog.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
  int in1,in2;

  in1 = atoi(argv[1]);
  in2 = atoi(argv[2]);

  printf("\nin1 : %d \t in2 : %d\n",in1,in2);
  
  return 0;
}
```
上記プログラムをコンパイルして実行すると↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1720496/ad4d1329-042e-9cfb-4116-31d387833e57.png)
このように、引数として入力した任意の入力を取り込んで表示できていることが確認できます。
はじめに入力を任意に設定できるようにすることで、のちに各論理回路のプログラムを作った際、確認が容易になります！
※引数は文字列ですので　`int atoi(const char* str);` によってint型に変換しています。

続いて引数を忘れて実行してしまう事もあるかと思います。その時、バグの原因となるので正常にプログラムを終了させるプログラムを追加します。↓

```c
  if(argc!=3){
    puts("PROGRAM_FAILURE");
    exit(-1);
  }
```
今回は２入力が必ずある想定なので、引数が３つではないときプログラムを終了させます。
いちおう、試してみるとこのような表示になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1720496/fa21821f-bc05-1a6f-cae3-4018bee0faa0.png)

<h2>活性化関数</h2>
活性化関数にはステップ関数を使用します。
※注意：ここで言うステップ関数は階段関数のことです。一般に機械学習で使用されるステップ関数は以下のような関数になります。

```math
f(x) = \left\{
\begin{array}{ll}
1 & (x \geq 0) \\
0 & (x \lt 0)
\end{array}
\right.
```
今回はそれぞれの回路に対応するしきい値を決定し、出力をしたいので
それぞれのしきい値を持つ独自関数を作ります。

<h2>論理回路のプログラミング</h2>
続いてそれぞれの論理回路のプログラミングをしていきます。
上記《パーセプトロンとは》でも説明した通り、重み付き入力ニューロンの値を全加算して活性化関数という形でプログラミングしていきます。将来のためにバイアスｂも書いておきます。
バイアスに関しては後の記事で説明する予定ですので今はそんなものもあるのかと思っていてください。もちろん今は記述しなくても問題ありません。
重み（とバイアス）を…… 

```c
#define W 1
#define b 0
```
と定義しておきます。

<h3>AND回路</h3>
AND回路の真理値表は

| 入力 | AND |
|:---:|:---:|
| 0 0 | 0 |
| 0 1 | 0 |
|1 0 | 0 |
| 1 1 | 1 |
僕が書いたプログラムはこんな感じです。
main関数の下に独自関数として作ってます。

```c
int AND(int p, int q){
  int r;
  //全加算の役割
  r = p*W + q*W + b;
  
  //活性化関数の役割
  if(r>1) {
    return 1;
  }
  else {
    return 0;
  }
}
```
以下同様に書いていきます。
<h3>OR回路</h3>
OR回路の真理値表は

| 入力 | OR |
|:---:|:---:|
| 0 0 | 0 |
| 0 1 | 1 |
|1 0 | 1 |
| 1 1 | 1 |
プログラム↓

```c
int OR(int p, int q){
  int r;
  r = p*W + q*W + b;
  if(r>0) {
    return 1;
  }
  else {
    return 0;
  }
}
```
<h3>NAND回路</h3>
NAND回路の真理値表は

| 入力 | NAND |
|:---:|:---:|
| 0 0 | 1 |
| 0 1 | 1 |
|1 0 | 1 |
| 1 1 | 0 |
プログラム↓

```c
int NAND(int p, int q){
  int r;
  r = p*W + q*W + b;
  if(r<2) {
    return 1;
  }
  else {
    return 0;
  }
}
```
<h3>NOR回路</h3>
NAOR回路の真理値表は

| 入力 | NOR |
|:---:|:---:|
| 0 0 | 1 |
| 0 1 | 1 |
|1 0 | 1 |
| 1 1 | 0 |
プログラム↓

```c
int NOR(int p, int q){
  int r;
  r = p*W + q*W + b;
  if(r<1) {
    return 1;
  }
  else {
    return 0;
  }
}
```
以上で論理回路のプログラムミングが完了です！
僕の場合、main関数の下に書いているので、同じように書く場合はプロトタイプ宣言を忘れずにっ！
実際に作るときは、ひとつひとつ動作を確認しながら作っていくといいと思います。
※XORに関してはORとNANDのANDをとることで実現できますが、これは中間層を1層入れた多重パーセプトロンになるので説明は省略してます。次回以降に説明したいと思います。
はじめのリンクにあるプログラムはXORのプログラムも含まれているのでお時間ある人は見てみてください。


<h2>それぞれの出力を表示！</h2>
それでは、今までに作った関数を組み合わせて出力結果を表示させていきましょう！

```c
  printf("\nIN \t ");
  printf("AND \t OR \t NAND \t NOR\n");
  printf("%d %d \t ",in1, in2);
  printf("%d \t %d \t %d \t %d\n\n",AND(in1,in2), OR(in1,in2), NAND(in1,in2), NOR(in1,in2));
```
コンパイルして実行！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1720496/8fb1da4e-59ca-ab86-a8b8-7a10ca615b59.png)
大丈夫そうですね。他の値についても確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1720496/0b87bae5-688a-287e-252c-68536d7c9c5f.png)
うんうんっ ちゃんと動いてます～

<h1>さいごに</h1>
拙い文章ですが、ここまで読んでくださった方々、本当にありがとうございます！
これからもゆるりと書いていこうと思いますので、よろしくお願いします。
次回は多重パーセプトロンについて書こうかなと考えております。

[^1]: GCCのインストールに関してはこちらをご参照ください：https://katakago.sakura.ne.jp/pgm/gcc.html