반응형

선형회귀는 딥러닝의 기본이 되는 개념이지만 용어부터 굉장히 낯설어 접근하기 어렵게 생각할 수 있다.

 

선형회귀란 가장 훌륭한 예측선을 긋는 것으로 볼 수 있다.

 

예를들어 학생들의 시험 성적과 학생들이 공부한 시간과의 관계를 파악하기 위해

4명의 학생에 대해 조사하고 이를 그래프로 나타낸다고 생각해보자.

 

아래 그래프에서 x축 데이터는 공부한 시간, y축 데이터는 성적을 의미한다.

그럼 위의 그래프를 가장 잘 나타낼 수 있는 직선을 그어보자.

즉 추세선을 그어보자.

위와 같이 나타내어질 수 있을 것이다.

 

그럼 해당 데이터들을 가장 잘 나타낼 수 있는 추세선이 있다면 몇시간을 공부했을때 어느정도의 성적이 나올지

예측해볼 수 있다.

 

그럼 우리는 예측의 정확도를 향상시키기 위해 좋은 추세선을 그릴 수 있어야하고 좋은 추세선은 결국

해당되는 데이터들을 가장 잘 나타내는 선으라고 볼 수 있다.

 

즉 해당 데이터들을 가장 잘 나타낼 수 있는 좋은 선을 긋는 과정을 선형회귀라 볼 수 있다.

 

추세선을 식으로 나타내면 y=ax + b 로 표현할 수 있다.

 

그럼 좋은 추세선은 해당 데이터들을 가장 잘 나타내는 것이므로 오차가 가장 적은 선으로 볼 수 있고

이를 위해 최소제곱법을 사용하여 추세선 식을 나타낼 수 있다.

 

 

 

* 최소 제곱법

 

최소제곱법의 공식은 아래와 같고 이를 통해 추세선의 기울기값 a를 구할 수 있다.

 

즉 위의 과정을 통해 해당 데이터를 가장 잘 나타낼 수 있는 직선을 구할 수 있고 이를 통해

임의의 x데이터에 대한 출력을 예측할 수 있다.

 

 

 

 

* 평균제곱오차

 

위의 방식은 한가지의 x데이터 정보에 대해서만 예측이 가능하다는 단점이 있다.

다시말해 성적에는 공부한 시간 외에도 지출한 사교육 비용, 시험 당일 컨디션, 개개인의 집중도 등이

포괄적으로 적용되어 시험성적을 나타내게 되는데 최소제곱법을 통해서는 하나의 조건에 대해서만

예측이 가능하다는 단점을 지니고 있다.

 

그리고 이를 위한 방법으로 평균제곱오차를 다뤄보겠다.

 

이 방법은 일단 추세선을 그리고 조금씩 변화시켜가며 정확한 추세선을 찾아가는 과정이라 볼 수 있다.

이러한 과정을 통해 입력데이터가 여러 종류일때에도 적용하여 출력값을 추정할 수 있다.

 

하지만 이 과정을 진행하기 위해선 나중에 조금 변화시킨 추세선이 이전의 추세선보다 우수한 선임을

판단할 수 있어야한다.

 

 

만약 위의 그래프에서 붉은색으로 나타낸 직선이 가장 우수한 추세선이라고 가정해보자.

 

평균제곱오차 방식을 사용하기 위해 임의의 직선을 하나 그어보자(이는 주황색으로 표시하였다).

 

그리고 어느정도의 오차가 발생하였는지 파악할 수 있어야 어떠한 직선이 더 우수한 직선인지 판단할 수 있으므로

위와 같이 해당 데이터에서의 실제 출력값과 추세선으로 추정된 값과의 차이를 확인한다

점선으로 표시된 직선들의 합이 클수록 잘못 그어진 추세선이고 작을수록 잘 그려진 추세선이라고 판단할 수 있다.

 

오차는 (예측값)-(실제값)으로 표현될 수 있지만 위의 경우를 보면 어떠한 경우는

예측값이 실제값보다 커서 오차가 양수를 갖지만 예측값이 실제값보다 작은 경우에는 오차가 음수를

갖게되어 무작정 오차를 합하게 되면 무의미한 오차값이 될 수 있고 따라서 부호를 없애주는 작업을

진행하여준다.

 

그리고 x데이터의 개수로 나누어 오차 합의 평균을 구하면 이를 평균 제곱 오차(Mean Squared Error, MSE)라

하고 다음과 같이 표시한다.

 

 

* 경사 하강법

 

그럼 평균제곱오차를 통해 두 직선에 대해 어느 직선이 더 우수한지 비교할 수 있는 과정을 진행하였다.

그리고 처음에는 임의의 값을 통해 진행할 수 있었다.

하지만 정말 터무니없는 직선들을 가져와 비교해보며 최적의 추세선을 찾기에는 굉장히 노가다스러움이

느껴지고 연산량도 굉장히 많아질 수 있을 것이다.

 

이를 해결하기 위해 초기 임의의 직선과 그 다음 직선을 선택하는 과정을 경사 하강법을 통해 살펴보자.

 

 

반응형
반응형

[그림.1] 신경망의 예

신경망을 그림으로 나타내면 위의 그림과 같고 입력층, 은닉층, 출력층으로 이루어진 것을 볼 수 있다.

 

[그림.2] 

x1과 x2 의 신호를 입력으로 받아 y를 출력하는 퍼셉트론은 위의 [그림.2] 와 같이 표현될 수 있고

이를 식으로 표현하면 아래와 같다.

 

[식.1]

여기서 b는 편향을 나타내는 매개변수로 뉴런이 얼마나 쉽게 활성화되느냐를 제어하게 되고

w1과 w2는 각 신호의 가중치를 나타내는 매개변수로 각 신호의 영향력을 제어하게 된다.

 

그리고 이 식을 변형시켜서 아래와 같이 표현할 수 있다.

 

[식.2]

 

[식.3]

 

즉 (b+w1x1+w2x2) 가 0보다 작거나 같으면 0, 0보다 크면 1 이므로

[식.1] 은 [식.2]와 [식.3] 으로 표현될 수 있다.

 

이 때, 함수 h(x)를 활성화함수 라 하고 입력신호의 총 합을 출력신호로 변환시키는 작업을 하게 되는 함수이다.

 

따라서 위 [식.2], [식.3] 은

[식.2] -> 가중치가 곱해진 입력신호의 총 합을 계산함

[식.3] -> 그 합을 활성화함수에 입력해 결과를 나타냄

위의 2가지 과정으로 진행이 되고 따라서 아래와 같이 표현이 가능하다.

 

[그림.3]
[식.4]
[식.5]

 

즉 a는 입력신호와 가중치를 합한 값

h(a) 는 a의 값에따라 결과적으로 1인지 0인지를 판별하는 과정을 진행하는 함수

로 정리할 수 있다.

 

 

위의 [식.2], [식.3] 을 확인하여보면 x값에 따른 결과(h(x))가 0 아니면 1을 갖게 되고 이를 그래프로 표시하면

계단함수형태를 나타내게 된다.

 

 

[그림.4]

 

여기까지의 내용은 퍼셉트론에서 다룬 내용과 비슷하다.

 

퍼셉트론과 신경망의 가장 큰 차이는 활성화 함수 h(x)이고 h(x)의 형태가 퍼셉트론의 경우 위와같은 계단함수 형태,

신경망의 경우 부드러운 곡선형의 시그모이드 함수의 형태를 갖게 된다.

 

시그모이드 함수란

[식.6]

활성화 함수 h(x)의 값이 다음과 같이 표시되고 그래프로 나타내게 되면

[그림.5]

위의 [그림.5]와 같이 부드러운 곡선 형태로 그려지게 된다.

 

한가지 짚고 넘어갈 것은 계단함수와 시그모이드 함수 모두 비선형 함수라는 것이다.

선형함수는 층을 아무리 깊게해도 은닉층이 없는 네트워크로 표현되기 때문에 비선형함수이어야 한다.

 

 

* 참고 *

시그모이드 함수는 신경망 분야에서 오래전부터 사용되었으나 최근에는 ReLU함수를 주로 사용하고 있다.

[식.7] ReLU함수
[그림.6] ReLU함수

 

 

 

그럼 이제 신경망을 구현하여 보자.

[그림.7] 입력층에서 1층으로 신호 전달

위의 그림을 보면 편향과 입력값(x1, x2)을 이용하여 1층의 a1 값을 구하게 된다.

이와같은 방식으로 1층의 a2, a3를 구할 수 있을 것이고 여기서 구한 a값을 이용하여

그 다음 2층의 a1, a2값을 구할 수 있게 된다.

 

즉 위와 같이 a1이 표현될 수 있고 이 과정을 계속 진행하여 1층의 a원소들은 행렬 A로 아래와 같이 표현된다.

 

 

 

2층, 3층의 계산 과정은 아래의 그림을 참고하도록 하자.

[그림.8](좌) 1층에서 2층으로의 과정                                               [그림.9](우) 2층에서 출력층으로의 과정

### 입력층에서 1층으로까지의 과정
X = np.array([1.0, 0.5])
S1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])

print(W1.shape)
print(X.shape)
print(B1.shape)

A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)

print(A1)
print(Z1)



### 1층에서 2층으로까지의 과정
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])

print(Z1.shape)
print(W2.shape)
print(B2.shape)

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)



### 2층에서 출력층으로까지의 과정
def identity_function(x):   ## 여기서 identity 함수는 입력값을 그대로 출력하게 되는 함수
    return x

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])

A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)

 

 

 

 

 

 

 

 

 

 

그 다음 등장하는 개념은 분류에서 사용되는 소프트맥스 함수(Softmax function) 이다.

 

[식.8] 소프트맥스 함수

위의 식에서 n은 출력층의 뉴런 수, y_k 는 그 중 k번째 출력임을 뜻하고 있다.

이를 그림으로 나타내면 아래와 같다.

[그림.10] 소프트맥스

이를 보면 소프트맥스 함수의 출력은 모든 입력 신호로부터 화살표를 받게 된다.

즉 출력층의 각 뉴런이 모든 입력신호에서 영향을 받는다는 것을 확인할 수 있다.

 

하지만 위의 소프트맥스 함수[식.8]은 지수함수로 이루어져 k의 값이 굉장히 커지게 되면 오버플로우가 발생하게 된다.

(컴퓨터는 수를 4바이트나 8바이트와 같이 크기가 유한한 데이터로 다루기에 표현할 수 있는 수의 범위가 한정적이다)

 

이를 확인하기 위해 프롬프트창에서 다음과 같이 실행시켜보자.

 

>>> a = np.array([1010, 1000, 990])
np.exp(a) / np.sum(np.exp(a))

 

이를 실행시키면

array([ nan, nan, nan])

과 같이 출력될 것이다.

 

그럼

위의 내용에 이어서

>>> c=np.max(a)

>>> a-c

를 입력하면

 

array([0, -10, -20])

이 출력되는 것을 확인할 수 있고 계속 이어서

 

>>> np.exp(a-c) / np.sum(np.exp(a-c))

를 입력하여 확인하여보면

array([ 9.99954600e-01,   4.53978686e-05,   2.06106005e-09  ])

의 값이 출력되는 것을 확인할 수 있다.

 

즉 간단하게 표현하여

def softmax(a):
    c=np.max(a)
    exp_a = np.exp(a-c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

 

위와 같이 구현할 수 있다.

 

소프트맥스 함수의 큰 특징은 이를 이용하여 소프트맥스 함수의 결과값을 확률로 해석할 수 있다는 것이다.

소프트맥스 함수의 결과값은 0에서 1.0 사이의 실수이다.

또한 출력의 총합이 1이 되고 출력값이 모든 입력값의 영향을 받기때문에 확률로 해석이 가능하다.

 

또한 소프트맥스 함수를 적용해도  y=exp(x)의 함수는 단조증가함수이기에

각 원소의 대소관계는 변하지 않는다.

 

 

반응형

'Deep Learning' 카테고리의 다른 글

선형회귀  (0) 2021.01.11
퍼셉트론(Perceptron)  (0) 2020.02.04
반응형

퍼셉트론이란 1957년에 고안된 알고리즘으로 신경망(딥러닝)의 기원이 되는 알고리즘이다.

 

퍼셉트론은 다수의 신호를 입력받아 하나의 신호를 출력한다.

(여기서 신호는 흐른다/안 흐른다 (1이나 0)의 값만을 가질 수 있다.)

 

2개의 입력신호를 받는 퍼셉트론의 예

 

X1과 X2는 입력신호, y는 출력신호, w1, w2는 가중치를 뜻하고 아래 식의 theta는 임계값을 나타낸다.

그리고 그림의 원을 뉴런 혹은 노드라고 부르고 입력신호가 뉴런에 보내질 때는 각각 고유한

가중치가 곱해진다.

 

[식.1]

 

 

그럼 퍼셉트론을 활용한 간단한 문제들을 보자.

 

AND 게이트의 진리표

위의 표는 AND게이트의 진리표를 나타낸 표이다.

이 AND게이트를 퍼셉트론으로 표현해보자.

 

입력은 x1, x2 가 존재하고 출력은 y로 존재한다.

이를 만족하는 매개변수 조합은 무수히 많겠지만 몇가지 예를 통해 이해해보도록 하겠다.

 

(w1, w2, theta) 가 (0.5, 0.5, 0.7) 이라고 가정해보자.

 

첫번째로 x1=0, x2=0 일때

w1x1 + w2x2 = 0 이므로 만족한다.

 

다음 x1=1, x2=0 일때

w1x1 + w2x2 = 0.5  이므로 이는 임계값(theta)보다 작으므로 만족하고

따라서 x1=0, x2=1 일때도 역시 만족할 것이다.

 

마지막으로 x1=1, x2=1 일때

w1x1 + w2x2 = 1 이므로 이는 임계값보다 큰 값이 되고 따라서 1이 출력되도록 된다.

 

따라서 (w1, w2, theta) 를 (0.5, 0.5, 0.7) 로 설정하는 것은 AND게이트를 잘 표현한 것이다.

 

 

 

위의 내용을 구현해보자.

def AND(x1, x2):
    w1, w2, theta = 0.5, 0.5, 0.7
    tmp = x1*w1 + x2*w2
    if tmp <= theta:
        return 0
    elif tmp > theta:
        return 1
    
    
print(AND(0,0))
print(AND(0,1))
print(AND(1,0))
print(AND(1,1))

이와 같이 나타낼 수 있을 것이고 출력결과

 

0

0

0

1

 

이 출력되는 것을 확인할 수 있을 것이다.

 

 

 

 

다음으로 위의 [식.1] 을 조금 변형시켜 보도록 하자.

[식.1]의 theta를 (-b) 로 치환시켜보도록 하자.

 

[식.2]

위와 같이 표현할 수 있다.

 

[식.1]과 [식.2] 는 표기방식만 바꿨을 뿐 같은 내용이고 [식.1]에서 theta를 임계값으로 표현하였는데

이제부터 이를 [식.2] 처럼 표시하고 여기서 b를 편향 이라고 하자.

w1과 w2는 여전히 가중치를 의미한다.

 

즉 w1과 w2는 각 입력신호가 결과에 주는 영향력(중요도)을 조절하는 매개변수이고

편향은 뉴련이 얼마나 쉽게 활성화하느냐를 조정하는 매개변수이다.

 

 

NAND 게이트의 진리표
OR 게이트의 진리표

그리고 위와 같은 내용으로 NAND 게이트와 OR게이트가 표현 가능하다.

def NAND(x1, x2):
    x=np.array([x1, x2])
    w=np.array([-0.5, -0.5])
    b=0.7
    
    tmp=np.sum(w*x)+b
    if tmp<=0:
        return 0
    else:
        return 1
    
def OR(x1, x2):
    x=np.array([x1, x2])
    w=np.array([0.5, 0.5])
    b=-0.2
    tmp = np.sum(w*x)+b
    
    if(tmp<=0):
        return 0
    else:
        return 1 

 

 

하지만

위의 경우에는 한계가 존재한다.

 

XOR게이트에 대하여 생각해보자.

XOR게이트의 진리표

 

지금까지 본 퍼셉트론으로는 XOR게이트를 구현할 수 없다.

이를 위해 다층 퍼셉트론  의 개념을 사용하게 되는데

이는 퍼셉트론의 층을 쌓는다고 보면 된다.

 

쉽게말해 AND, OR, NAND 게이트를 조합하여 XOR게이트를 표현하는 것이다.

위의 그림과 같이 NOR게이트를 AND와 NAND, OR 게이트를 이용하여 표현할 수 있고

이를 구현하면 아래와 같다.

def XOR(x1,x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1,s2)
    return y

print(XOR(0,0))
print(XOR(1,0))
print(XOR(0,1))
print(XOR(1,1))

그리고 실행 결과

 

0

1

1

0

 

이 출력됨을 확인할 수 있을 것이다.

반응형

+ Recent posts