반응형

Build의 순서는 아래와 같다.

소스코드 -> 전처리 -> 컴파일 -> 링크 -> 실행

C언어를 처음 공부할 때, #define을 사용하지 않으면 전처리 과정을 거치지 않는다고 생각한 적이 있다.

#으로 시작하는 문장은 전부 전처리기 지시자임에도 #include는 전처리기라고 생각하지 않았다. 항상 C언어를 실습할 때, 의무적으로 #inlcude <stdio.h>를 작성한 폐해이다.

전처리란 무엇인가?

컴파일 전에 처리해야 하는 일이고 전처리를 수행하는 장치를 전처리기라고 한다.

전처리기는 헤더 파일을 불러오거나, 소스 파일 내부의 특정 문자열을 상수 또는 문자로 치환하거나, 조건에 따라서 코드의 일부를 컴파일하거나 컴파일하지 못하게 하는 선택 기능을 제공한다.

궁금증이 하나 발생한다.

#define은 왜 사용하는가? 단순히 변수나 함수를 선언 및 호출하면 되지 않는가? 일반 변수 및 함수와 매크로(#define)의 차이는 무엇인가?

이를 이해하기 위해선 컴파일 단계를 이해할 필요가 있다.

컴파일이란 무엇인가? 기계어로 번역하는 번역기이다. 왜 기계어로 번역하는가? 소스 코드는 사람이 이해하기 쉬운 문장이며, 기계는 이해할 수 없다. 그러므로 기계가 이해할 수 있는 문장으로 번역해줘야 한다.

C언어가 생기기 이 전, 개발자들은 기계(컴퓨터, CPU)를 직접 제어하기 위해 기계어(어셈블리어)를 직접 작성하였다. 하지만 CPU는 다양했고, CPU의 종류에 따라 어셈블리어 작성하는 방법도 각기 달랐다고 하며, 간단한 프로그램에도 코드가 굉장히 길어졌다. 

이 문제점을 해결하기 위해, 기계어를 직접 사람이 작성하지 않아도 되는 번역기를 개발했는데 그것이 컴파일러이다. 개발자들은 더 이상 기계의 종류를 상관하지 않아도 된다.


C언어는 소스코드 중복을 줄이고 코드의 재사용성을 높이기 위해서 함수를 제공한다. 하지만, 함수는 호출될 때 *스택 프레임이 일어나기 때문에 속도가 느려지는 단점이 있다. 따라서 코딩할때는 매크로를 함수와 같은 기능으로 작성하지만 실제 기계어로 번역될 때는 직접 코딩한 것처럼 처리(치환, 전처리 단계에서 매크로가 소스코드로 변환)된다.

함수 호출 시, 스택이라는 메모리 공간에 매개 변수, 반환 포인터 값, 지역 변수 순서로 저장되는 값을 스택 프레임이라고 한다.

즉, 저장 공간인 스택을 메모리 낭비를 줄이고 실행 속도를 높이기 위해 매크로를 사용한다. 하지만 매크로는 소스코드로 바로 변환되므로 실행 파일 크기가 커지며, 재귀호출을 할 수 없다는 단점을 가지므로 적절하게 사용하는 것이 중요하다. 

요즘엔 컴퓨터의 메모리가 굉장히 넉넉하기 때문에 메모리 걱정을 하지 않을 때가 많지만, 메모리 공간이 넉넉치 않은 임베디드 시스템과 같은 환경에서는 매크로를 많이 사용한다.


아래 매크로 함수와 일반 함수 예시를 보겠다.

#include <stdio.h>

#define MUL(x, y) x*y

int mul(x, y)
{
	return x*y;
}
int main()
{
	int a, b;
    
    scanf("%d %d", &a, &b);
    printf("%d", MUL(a+1, b+1));
    printf("%d", mul(a+1, b+1));
}

// result
// a=3, b=4 입력
// 8
// 20

왜 결과값이 8, 20으로 다른 값이 나올까?  매크로는 전처리 단계에서 소스 코드로 변경된다고 했다. 

MUL(a+1, b+1)을 소스 코드로 치환되면 a+1*b+1 이다.

그러므로 3+1*4+1로 계산되어 8이라는 결과가 출력된다. mul과 같은 결과를 내기 위해선, 

#define MUL(x, y) ((x) * (y)) 로 변경하면 된다. 소스코드로 치환하게 되면, ((a+1)*(b+1))이다.

728x90
반응형
반응형

안녕하세요.

 

오늘은 딥러닝에서 가장 많이 사용되는 ReLU를 통해 비선형함수와 선형함수의 차이점을 알아보겠습니다.

 

ReLU 함수

머신러닝에서 선형함수를 통해 회귀문제를 해결할 수 있게 되었고, 더 복잡한 문제를 풀기 위해 비선형 함수 Sigmoid, Tanh, ReLU를 나오기 시작합니다. 

 

현재까지 복잡한 문제를 딥러닝 분야에서 ReLU를 사용하는 것은 당연시 되고 있습니다. 

 

바로, 본론으로 들어가겠습니다.

 

TesorFlow, Keras, Pytorch를 통해 ReLU를 사용하는 것은 굉장히 간단합니다. 

 

신호 및 시스템의 개념적 이야기로 가보겠습니다.

 

ReLU는 왜 비선형 함수일까요?

 

 

 

선형 시스템비선형 시스템의 차이

 

선형성을 가지고 있으면 선형 시스템이고, 그 외는 비선형 시스템입니다.

 

선형성을 가지는 조건은 무엇일까요?

 

세 가지의 조건을 충족하면 선형성을 가진다고 이야기할 수 있습니다. 그 외에는 비선형 시스템으로 분류할 수 있습니다.

 

 

 

 

1. 선형적인 그래프

x = 0 에서 꺽이는 구간을 제외하고 전 구간이 선형성을 가지지만, (x =0) 부분 때문에 선형성을 가진다고 할 수 없습니다. 

 

부끄럽지만, 저는 꺾이는 부분을 제외한 전 구간이 부분 선형형태를 띄우니 선형 함수가 맞지 않나라는 생각을 해 공부를 해서 찾아보았습니다.

 

2. 동차성 ( f(ax) = a * f(x) )

 

ReLU를 간단히 설명하면, x가 0보다 작으면 f(x) = 0, x가 0보다 크면 f(x) = x 입니다. 이를 f(x) = max(0, x)라 표현하겠습니다.

 

 

① a = 3으로 가정하면, 

 

f(3x) = 3x

3 * f(x) = 3x

 

f(3x) = 3 * f(x)  = 3x

 

② a = -3 으로 가정하면

 

f(-3x) = -3x

-3 * f(x) = -3x

 

f(-3x) = -3 * f(x)  = -3x

 

ReLU는 동차성이 성립함을 알 수 있습니다.

 

 

 

 

3. 가산성 ( f(x1 + x2) = f(x1) + f(x2) )

 

x1 = -1, x2 = 2 라고 가정하면,

 

f(-1+2) = 1

 

f(-1) = 0,

f(2) =2 

f(-1) + f(2) = 2

 

f(-1+2) != f(-1) + f(2)

 

ReLU는 가산성이 성립하지 않음을 알 수 있습니다.

 

ReLU는 동차성은 성립하지만, 가산성이 성립하지 않아 선형성을 가지지 않으므로 비선형 함수임을 알 수 있었습니다. 

728x90
반응형

+ Recent posts