1-3 퍼셉트론 파이선 코드 알고리듬 재검토-III: Adaptive Linear Neuron(ADALINE)에 의한 Linear Regression 머신 러닝의 시작

codingart(66)
Published in
#kr
Words
1221
Reading
6 min
Listen
Play
8y

noname01.png

cost 함수와 Gradient Decent 알고리듬 도입

퍼셉트론에서 Hypothesis를 계산하여 단순히 그 값이 “+”인지 “-”인지만 따져 보았지만 한편 주어진 100개의 Iris flower 입력 벡터 샘플에 대해서 학습 epoch 별로 임의의 웨이트 값에 대한 Hypothesis 계산 값과 학습을 위해 주어진 라벨 값과의 차이의 제곱을 계산하여 (Least squares) 2로 나눈 후 누적하여 cost 함수를 구성하도록 하자.

noname02.png

2로 나누는 이유는 최소제곱법(Least squares)에 의해 구성된 cost 함수가 웨이트를 포함한 일차식의 제곱 형태가 포함되므로 이 cost 함수를 한번 편미분하게 되면 지수 2가 곱해지게 되어 상쇄 되도록 일종의 편의 목적이다. 한편 구글의 텐서플로우 명령을 살펴보면 2로 나누는 것이 아니고 입력 벡터들의 수만큼으로 나누어 통계학적인 평균값을 취한다.

용어 Hypothesis는 머신 러닝을 처음으로 배울 때 얘로서 자주 거론되는 선형회귀법(Linear Regression)에서 사용된다. 웨이트 벡터와 입력 벡터와의 내적 W∙X 는 다음과 같이 표현된다.

noname03.png

i=0인 입력벡터 성분 x0 값을 1.0 으로 설정하게 되면 그에 해당하는 웨이트 값이 바로 바이아스 b가 된다.
1940년대에 Mulloch-Pitts는 흥분요소와 억제요소로 구성되는 입력벡터들이 시냅스를 통과하면서 웨이팅 된 결과가 신경망에서 비선형적인 활성화과정을 거쳐 출력하게 되는 현상을 최초로 이론화하여 인공신경망(Artificial Neural Network)을 제안하였다. 따라서 신경망의 출력 은 W∙X의 함수인 Hypothesis로 설정되며 선형인 경우와 비선형인 경우들이 있을 수 있다.

아직 최종 웨이트 값들이 결정되지 않은 상태의 Least squares 값들의 합인 cost 함수를 각 웨이트들에 대해 편미분하여 기울기 값을 구한 후 여기에다 작은 값의 learning rate를 곱하여 현재의 웨이트 값들에서 뺄셈하여 그 다음 단계에서 사용하기 위한 웨이트 값으로 업데이트 하도록 하자. 즉 Hypothesis 가 W∙X의 선형 함수일 때 J(W)의 편미분에 learning rate 값 η를 곱하여 다음과 같이 웨이트 값을 업데이트해보자.

noname04.png

이 과정을 되풀이 하여 cost 함수가 최소화가 될 때에 최종적으로 학습이 완료된 웨이트 값들이 결정되며 학습이 끝나게 된다.

업데이트 된 웨이트 값들을 계속 학습시켜 나가게 되면 cost 함수값이 10의 –5승, -6승, ∙∙∙점점 작아지게 된다. 아울러 편미분의 값도 비슷하게 작아지게 되므로 전역적 최소값(global minimum)에서 웨이트 업데이트를 멈추도록 한다.

noname05.png

이 과정을 최소제곱법을 적용한 경사하강법이라고 하며 Adative linear neuron 즉 Adaline 으로 부르도록 하자. 이 알고리듬은 Rosenblatt 의 알고리듬 이후 스탠포드 대학의 Bernard Widrow교수와 박사과정이었던 Tedd Hoff에 의해 고안되어 1960년에 출판되었다. 물론 이때까지만 해도 단일 층 신경망에 대한 해법으로 적용되었으며 결국 선형회귀법(Linear Regression)의 원조라 보면 될 것이다.

단일 층에 대한 신경망 해법은 layer가 하나이기 때문에 Backpropagation을 별도로 고려할 필요가 없다는 점이다. Backporpagation을 고려하려면 적어도 layer가 2개 이상은 되어야 새로운 웨이트 업데이트 알고리듬이 필요해 지는 것이다. 이 점은 후일 1969년경에 Minsky 교수가 단일 퍼셉트론에 의한 XOR 로직 문제를 풀 수 없다는 점을 지적하면서 적어도 2개 이상의 신경망을 연결해야 하지만 과연 어떻게 웨이트 업데이트가 가능하겠는가 하는 강력한 의문제기와 함께 머신 러닝이 쇠락기에 접어들었던 시기의 주제이기도 하다.

파이선 ADALINE 코딩

텐서플로우를 사용하여 선형회귀형의 문제를 풀 때에 learning rate를 적절히 작은 값을 설정해야 한다. 너무 커도 웨이트 업데이트 과정이 불안해지고 너무 작으면 학습횟수가 지나치게 많아지게 되는 문제점이 있다. Iris flowers data set를 사용한 퍼셉트론 문제에서 learning rate 가 0.01 과 0.0001인 경우에 대해서 Adaline 알고리듬을 사용하여 웨이트 업데이트 과정에서 누적되는 에러를 평가해 볼 수 있도록 즉 Adaline 알고리듬을 구현할 수 있도록 퍼셉트론 파이선 코드를 소폭 수정해 보자. 퍼셉트론 코드 중에서 학습을 진행하는 fit(self, X, y)에 손질이 필요하다.

class AdalineGD(object)

object 내용은 퍼셉트론과 동일하므로 파라메터 설정은 그대로이며 속성 설정에서 웨이트에 더해 cost 항을 추가할 필요가 있다.

cost_ : list
Sum-of-squares cost function value in each epoch.

def init()은 아무런 수정도 필요치 않다. 이어서 함수 def fit(self, X, y)를 cost를 도입해서 수정하도록 하자.

def fit(self, X, y):
    rgen = np.random.RandomState(self.random_state)
    self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
    self.cost_ = []

퍼셉트론 알고리듬에서 erros 항처럼 cost 함수를 학습단계 별로 평가하기로 한다.

    for i in range(self.n_iter):
        net_input = self.net_input(X)
        output = self.activation(net_input)

net_input(X)는 주어진 입력 벡터 X에 대해 내적 계산을 실행한다. 함수 activation()은 지금은 적용을 해도 아무런 변화가 없이 그냥 1.0을 곱해 주는 효과이나 차후에 Sigmoid 함수 적용을 위해 설정해둔 상태이다.

        errors = (y - output)
        self.w_[1:] += self.eta * X.T.dot(errors)

erros 가 2개의 성분을 가지는 벡터라면 100개의 입력 벡터로 이우어진 X와의 연산을 위해서는 Transpose를 취할 필요가 있을 것이다. 즉 에러 벡터와 Transpose 형태의 X의 각 성분 매트릭스와의 연산이 가능해진다.

        self.w_[0] += self.eta * errors.sum()
        cost = (errors**2).sum() / 2.0
        self.cost_.append(cost)
    return self

Adaline 알고리듬 코드를 사용하여 leraning rate 0.01과 0.0001 인 경우 학습 epoch 별로 누적 에러를 계산하여 다음과 같이 작도 결과를 살펴보자. 퍼셉트론에서는 learning rate 값에 따라 수혐하는 결과를 주지 못하고 계속 loop를 돌 수도 있겠지만 Adaline 알고리듬에서는 결과가 확실하게 나타난다.

noname06.png

그림 왼쪽에서는 사용 로그 값임에도 불구하고 epoch 가 증가할수록 그 값이 커진다. 즉 cost 값 최소화에 실패한다는 경향을 보여준다. 반대로 오른쪽에서는 누적 에러 값이 지속 줄어들고는 있으나 기껏 50 근방에서 40 근방까지 감소했을 따름이다. 안되는 것은 아니지만 너무 느려 컴퓨팅 부담이 지나치게 큰 편이다.

아래 그래프는 학습 횟수를 100회로 늘리고 learning rate를 0.0004 로 둔 결과이다.

noname07.png

즉 대단히 민감한 learning rate 같은 파라메터를 하이퍼파라메터(hyperparameter)라 하는데 최적 learning rate를 결정해야 한다는 것인데 머신 러닝에서 다루어야 할 중요한 주제 중의 하나이다.

Adaline 알고리듬이 대단히 특수한 것은 아니며 우리가 늘 머신러닝을 처음에 접하게 되면 해보는 최소제곱법(Least Squares)에 의한 Linear Regression 기법을 의미한다고 보면 된다.

#ch02_Adaline_1_1.py
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

#Implementing an adaptive linear neuron in Python

class AdalineGD(object):
"""ADAptive LInear NEuron classifier.

Parameters
------------
eta : float
  Learning rate (between 0.0 and 1.0)
n_iter : int
  Passes over the training dataset.
random_state : int
  Random number generator seed for random weight
  initialization.


Attributes
-----------
w_ : 1d-array
  Weights after fitting.
cost_ : list
  Sum-of-squares cost function value in each epoch.

"""
def __init__(self, eta=0.01, n_iter=50, random_state=1):
    self.eta = eta
    self.n_iter = n_iter
    self.random_state = random_state

def fit(self, X, y):
    """ Fit training data.

    Parameters
    ----------
    X : {array-like}, shape = [n_samples, n_features]
      Training vectors, where n_samples is the number of samples and
      n_features is the number of features.
    y : array-like, shape = [n_samples]
      Target values.

    Returns
    -------
    self : object

    """
    rgen = np.random.RandomState(self.random_state)
    self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
    self.cost_ = []

    for i in range(self.n_iter):
        net_input = self.net_input(X)
        # Please note that the "activation" method has no effect
        # in the code since it is simply an identity function. We
        # could write `output = self.net_input(X)` directly instead.
        # The purpose of the activation is more conceptual, i.e.,  
        # in the case of logistic regression (as we will see later), 
        # we could change it to
        # a sigmoid function to implement a logistic regression classifier.
        output = self.activation(net_input)
        errors = (y - output)
        self.w_[1:] += self.eta * X.T.dot(errors)
        self.w_[0] += self.eta * errors.sum()
        cost = (errors**2).sum() / 2.0
        self.cost_.append(cost)
    return self

def net_input(self, X):
    """Calculate net input"""
    return np.dot(X, self.w_[1:]) + self.w_[0]

def activation(self, X):
    """Compute linear activation"""
    return X

def predict(self, X):
    """Return class label after unit step"""
    return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)

#A function for plotting decision regions
def plot_decision_regions(X, y, classifier, resolution=0.01):

# setup marker generator and color map
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])

# plot the decision surface
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                       np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape )
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())

#plot class samples
for idx, cl in enumerate(np.unique(y)):
    plt.scatter(x=X[y == cl, 0], 
                y=X[y == cl, 1],
                alpha=0.8, 
                c=colors[idx],
                marker=markers[idx], 
                label=cl, 
                edgecolor='black')

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))

ada1 = AdalineGD(n_iter=10, eta=0.01).fit(X, y)
ax[0].plot(range(1, len(ada1.cost_) + 1), np.log10(ada1.cost_), marker='o')
ax[0].set_xlabel('Epochs')
ax[0].set_ylabel('log(Sum-squared-error)')
ax[0].set_title('Adaline - Learning rate 0.01')

ada2 = AdalineGD(n_iter=10, eta=0.0001).fit(X, y)
ax[1].plot(range(1, len(ada2.cost_) + 1), ada2.cost_, marker='o')
ax[1].set_xlabel('Epochs')
ax[1].set_ylabel('Sum-squared-error')
ax[1].set_title('Adaline - Learning rate 0.0001')

#plt.savefig('images/02_11.png', dpi=300)
plt.show()

1-3 퍼셉트론 파이선 코드 알고리듬 재검토-III: Adaptive Linear Neuron(ADALINE)에 의한 L... | Ecency