티스토리 뷰

728x90

 학교에서 신경망을 C언어로 구현해보라는 과제를 받아서 한번 해보겠습니다. 기록하면서 개발하면 잘되더라구욤ㅎㅎ..

인공신경망을 구현해보기, C언어로 작성한다.

- 최대 16개의 Layer
- 각 레이어마다 노드는 최대 256개, 레이어마다 따로따로 개수 지정 가능
- 가능한 한 general하게, 나중에 계속 사용할 수 있게 하는 게 좋음
- 레이어와 노드 수 입력은 알아서 구성해도 됨
- C언어 코드 파일, 결과 내용, 실험 결과가 포함된 간단한 설명이 포함된 문서 제출

 

 

설계

전체적인 흐름

신경망에서 계산되는 과정은

 

1. 입력을 받는다.

2. Weight Table(가중치 테이블)과 Input Layer(입력 계층)을 곱한다. (행렬곱)

3. Sigmoid Function(시그모이드 함수)를 적용시킨다.

 

다음 세가지가 반복됩니다. 

Weight Table 설계

모든 Layer 개수를 L개라고 가정했을 때, Weight Table의 개수는 L-1개가 됩니다. 

Weight Table의 크기는 현재 크기가 a인 Xn-1 Layer와 크기가 b인 Xn Layer 사이에 있다고 가정할 때, a*b입니다.

레이어 동적 할당 

과제를 보면 레이어 개수와 노드 개수가 동적으로 할당되는 것을 볼 수 있습니다. 

원할 때마다 배열의 개수를 다르게 하기 위해서 malloc을 사용하겠습니다.

레이어와 weight table, 다음 레이어 묶기

레이어와 레이어 사이에는 계산을 위한 Weight Table이 존재합니다.

또한, 각 레이어는 계산의 편의성을 위해 다음 레이어를 가리킵니다. (SLL 자료구조와 유사)

이를 위해 각 레이어를 구조체로 만들 생각입니다.

 

구조체안에는 다음 세 가지가 들어갑니다.

 

1. 레이어 안에 동적할당돼서 배열처럼있는 노드 값을 가리키는 포인터

2. 동적할당 되어있으면서 랜덤으로 값이 들어간 Weight Table을 가리키는 포인터

3. 다음 레이어를 가리키는 포인터

 

typedef struct Layer {
    double *nodes;            // 노드 값들을 가리키는 포인터
    double **weights;         // Weight Table을 가리키는 이중 포인터
    struct Layer *next_layer; // 다음 레이어를 가리키는 포인터
} Layer;

 

nodes와 weights를 행렬곱을 진행한 후 해당 값을 next_layer의 nodes에 대입하겠습니다.

그리고 맨 마지막 Layer 즉, Output Layer(출력계층)의 경우, weights와 next_layer 값을 null로 지정하겠습니다.

시그모이드 함수

weight table과의 행렬곱 이후로 시그모이드 함수와 계산해야합니다. 

double sigmoid(double x) {
    return 1.0 / (1.0 + exp(-x));
}

구현

<1차 설계>

 구현하기 위해서 하나하나 단계를 정하지 않고 가면 나중에 뭐가 문젠지 모르는 대.참.사가 발생하니 먼저 정하고 가봅시다.

(input)

1. 입력 계층 입력 받아서 root 레이어 구조체 만들기

2. 전체 레이어 개수, 각 레이어마다 노드의 개수 받아서 레이어 구조체 동적할당 해놓기 (맨 마지막 레이어 구조체 tail로 따로 저장해놓기)

3. weight table 동적할당 해놓기 (크기 잡아놓기)

4. 각 레이어 구조체 포인터로 연결하기 (맨 마지막 레이어는 다음 레이어와 weight table 가리키지 않기)

5. weight table에 랜덤값 넣기

(solve)

1. 행렬 곱 구현하기

2. 시그모이드 함수 구현하기

3. SLL로 연결되어있는 입력계층, 레이어 구조체들 연산하기

(output)

1. 레이어 출력하기

<과정>

(input)

✅ 1. 입력 계층 입력 받아서 root 레이어 구조체 만들기 -> 레이어 구조체 생성자 함수 만들고 Layer root에 할당

2. 전체 레이어 개수, 각 레이어마다 노드의 개수 받아서 레이어 구조체 동적할당 해놓기 (맨 마지막 레이어 구조체 tail로 따로 저장해놓기 + 각 레이어 구조체 포인터로 연결하기 (맨 마지막 레이어는 다음 레이어와 weight table 가리키지 않기) -> 2,4번 과정 합쳐서 한 번에 하기, print함수 만들어서 중간에 제대로 값 들어가는지 segment fault 발생하지는 않는지 확인하기. (동적할당 싫어) 

3. weight table 동적할당 해놓기 (크기 잡아놓기) + weight table에 랜덤값 넣기  -> 3,5 번 과정 합쳐서 한번에 하기, weight 테이블 만들려고 하니까 각 계층의 크기를 알아야해서 Layer 구조체에 layer_size 변수 추가하기

 

(solve)

✅ 1. 행렬 곱 구현하기

✅ 2. 시그모이드 함수 구현하기

3.SLL로 연결되어있는 입력계층, 레이어 구조체들 연산하기

(output)

1. 레이어 출력하기

<결과>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// Layer Struct, 계층 구조체로 구현
typedef struct Layer
{
    int layer_size;           // 노드 개수
    double *nodes;            // 여러개의 노드 값을 가리키는 포인터
    double **weights;         // Weight Table을 가리키는 더블 포인터 (2차원 배열)
    struct Layer *next_layer; // 다음 레이어를 가리키는 포인터
} Layer;

Layer *root;    // 입력 계층
Layer *temp;    // SLL용 포인터

/**
 * 0 이상 값만 받도록 하는 정수형 변수 전용 scanf 함수
 */
int scanf_int(char *prompt) {
    int n;
    while (1) {
        printf("%s", prompt);
        scanf("%d", &n);
        if (n > 0) return n;
        printf("0 이상만 가능\n");
    }
}

/**
 * 0 이상 값만 받도록 하는 double 변수 전용 scanf 함수
 */
double scanf_double(char *prompt) {
    double n;
    while (1) {
        printf("%s", prompt);
        scanf("%lf", &n);
        if (n >= 0) return n;
        printf("0 이상만 가능\n");
    }
}

/**
 * 노드 값을 출력하는 함수
 */
void print_nodes()
{
    Layer *current_layer = root;
    int layer_index = 1;
    
    // 각 레이어의 노드 값을 출력
    while (current_layer != 0)
    {
        printf("<레이어 노드 값 %d>\n",layer_index);
        for (int i = 0; i < current_layer->layer_size; i++)
        {
            printf("Node %d: %.4f\n", i, current_layer->nodes[i]);
        }
        printf("\n");
        current_layer = current_layer->next_layer;
        layer_index++;
    }
}

/**
 *  가중치 테이블을 출력하는 함수
 */
void print_weights()
{
    Layer *current_layer = root;
    int layer_index = 1;

    // 각 레이어마다 가중치 출력
    while (current_layer->next_layer != 0)
    {
        printf("<가중치 테이블 %d - %d>\n",layer_index, (layer_index+1));
        for (int i = 0; i < current_layer->layer_size; i++)
        {
            for (int j = 0; j < current_layer->next_layer->layer_size; j++)
            {
                printf("%.2f ", current_layer->weights[i][j]);
            }
            printf("\n");
        }
        printf("\n");
        current_layer = current_layer->next_layer;
        layer_index++;
    }
}

/**
 * 레이어 구조체 입력 받아서 생성하는 함수
 */
Layer *layer_input()
{
    // 레이어 하나 생성
    Layer *layer = (Layer *)malloc(sizeof(Layer));

    // 레이어에 들어가는 노드 배열 생성
    int n=scanf_int("레이어 총 노드의 개수 : ");
    layer->layer_size = n;
    layer->nodes = (double *)calloc(0.0,n * sizeof(double));
 
    // 가중치 테이블 NULL 할당
    // 현재 다음 레이어 크기를 모르기 때문에 생성하지 않는다.
    layer->weights = 0;

    // 다음 레이어 가리키는 포인터 NULL 할당
    layer->next_layer = 0;

    return layer;
}

/**
 * input 로직 함수
 */
void input()
{
    /* 1. 입력 계층 입력받아서 root 레이어 구조체 만들기 */
    // 1-1. 입력 계층을 입력 받아서 root Layer Struct 만들기
    printf("<입력 계층>\n");
    root = layer_input();

    // 1-2. 입력 계층 노드 값 받는 곳
    for (int i = 0; i < root->layer_size; i++)
    {
        root->nodes[i]=scanf_double("노드 값 : ");
    }

    // 1-3. SLL next_layer 설정하기 위해서 현재 노드 설정
    temp = root;

    /* 2. 전체 레이어 개수, 각 레이어마다 노드의 개수를 받아서 레이어 구조체 동적할당 해놓기 */
    // 2-1. 전체 레이어 개수 입력받기
    int n=scanf_int("입력 계층을 제외한 전체 레이어 개수 : ");

    // 2-2. 각 레이어 생성하기
    for (int i = 0; i < n; i++)
    {
        temp->next_layer = layer_input();
        temp = temp->next_layer;
    }

    /* 3. weight table 동적할당 해놓기 */
    //    가중치 테이블 동적 할당 (노드 개수 x 다음 레이어 노드 개수)
    //    처음부터 돌려가면서 weight 테이블 만들기
    temp = root;
    while (temp->next_layer != 0)
    {
        temp->weights = (double **)malloc(temp->layer_size * sizeof(double *));
        for (int i = 0; i < temp->layer_size; i++)
        {
            temp->weights[i] = (double *)malloc(temp->next_layer->layer_size * sizeof(double));
            for (int j = 0; j < temp->next_layer->layer_size; j++)
            {
                temp->weights[i][j] = ((double)(rand() % 100)) / 100.0; // 0~1 사이의 랜덤 값
            }
        }
        temp=temp->next_layer;
    }

    print_weights();
}

/**
 *  Layer 행렬곱 함수
 */
void matrix_multiply(Layer* temp_layer,Layer* next_layer){
    // 행렬 곱셈 수행
    for (int i = 0; i < temp_layer->layer_size; i++) {        
        for (int j = 0; j < next_layer->layer_size; j++) {        
            next_layer->nodes[j] += temp_layer->nodes[i] * temp_layer->weights[i][j];
        }
    }
}

/**
 * 시그모이드 계산 함수
 */
double sigmoid(double x) {
    return 1.0 / (1.0 + exp(-x));
}

/** 
 * 레이어에 시그모이드 함수를 적용시키는 함수
 */
void sigmoid_layer(Layer* layer){
    for (int i = 0; i < layer->layer_size; i++)
    {
        layer->nodes[i]=sigmoid(layer->nodes[i]);
    }
    
}

/**
 * 신경망 계산하는 로직 함수
 */
void solve(){
    // 3. SLL로 연결되어 있는 입력계층, 레이어 구조체들 연산하기
    temp=root;
    while(temp->next_layer!=0){
        // 1. 행렬곱 구현하기
        matrix_multiply(temp,temp->next_layer);

        temp=temp->next_layer;

        // 2. 시그모이드 함수 구현하기
        sigmoid_layer(temp);
    }
}

/**
 * 동적할당한거 해제하는 함수
 */
void free_layers() {
    Layer *current_layer = root;
    Layer *next_layer;

    while (current_layer != 0) {
        next_layer = current_layer->next_layer;
        // 노드 해제
        if (current_layer->nodes != 0) {
            free(current_layer->nodes);
        }

        // 가중치 테이블 해제
        if (current_layer->weights != 0) {
            for (int i = 0; i < current_layer->layer_size; i++) {
                if (current_layer->weights[i] != 0) {
                    free(current_layer->weights[i]);
                }
            }
            free(current_layer->weights);
        }

        // 현재 레이어 해제
        free(current_layer);

        current_layer = next_layer;
    }
}

int main(void)
{
    input();
    solve();
    print_nodes();
    
    //동적 할당 free
    free_layers();

}
728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함