반응형
반응형

[10편] 'scikit-learn을 이용해 퍼셉트론 머신러닝 수행하기'에서 scikit-learn의 퍼셉트론으로 아이리스 데이터의 70%를 가지고 머신러닝을 수행하고, 나머지 30%의 데이터로 머신러닝 결과를 테스트해보는 코드를 제시했습니다.


이번에는 70%의 데이터를 가지고 머신러닝을 수행한 결과가 실제 어떻게 데이터들을 분류하고 있는지 그래프로 나타내보고, 퍼셉트론 알고리즘의 유효성에 대해 고찰해보고자 합니다.


먼저, 아래의 코드를 작성하고 mylib 폴더에 plotdregion.py로 저장합니다.


plotdregion.py


  

이 코드는 plot_decision_region 함수의 인자 classifier로 주어진 퍼셉트론 분류기를 이용해 머신러닝을 수행한 결과가 트레이닝 데이터가 분포하고 있는 좌표계에서 어떻게 영역을 구분하는지 보여주기 위함입니다.


원리는 이렇습니다.

아이리스 트레이닝 데이터 X는 (꽃잎길이, 꽃잎너비) 2개의 값으로 되어 있으므로 x축의 값으로 꽃잎길이, y축의 값으로 꽃잎너비로 되어 있는 좌표계에 데이터를 표시할 수 있습니다.


그리고 좌표계를 바둑판으로 생각해보면, 좌표계에서 격자의 교차점도 아이리스 데이터 (꽃잎길이, 꽃잎너비)의 어떤 값이라고 생각할 수 있겠지요. 따라서 이 교차점의 좌표를 인자 classifier로 주어진 퍼셉트론 분류기로 머신러닝을 수행하여 나온 결과도 iris-setosa, iris-versicolor, iris-virginica를 의미하는 0, 1, 2 중 어느 한개의 값이 될 것이고, 0으로 되어 있는 영역은 빨간색, 1로 되어 있는 부분은 파란색, 2로 되어 있는 부분은 초록색으로 표시를 하여 머신러닝의 결과를 영역으로 표시하게 됩니다.


그럼 코드의 주요부분을 설명하겠습니다.


>>> markers = ('s', 'x', 'o', '^', 'v')

>>> colors = ('r', 'b', 'lightgreen', 'gray', 'cyan')

>>> cmap = ListedColormap(colors[:len(np.unique(y))])


markers는 matplotlib에서 정의한 점 표시 모양 5개를 튜플로 정의한 것이고, colors는 색상 5개를 튜플로 정의한 것입니다. ListedColormap은 인자로 주어진 색상을 그래프상에 표시하기 위한 객체입니다. numpy.unique(y)는 y에 있는 고유한 값을 작은 값 순으로 나열합니다. 예를 들어 [1, 0, 0, 2, 2, 0, 0, 1, 2]에는 고유한 값이 0, 1, 2밖에 없으므로 numpy.unique([1, 0, 0, 2, 2, 0, 0, 1, 2])는 [0, 1, 2]가 됩니다. 

따라서 cmap = ListedColormap(colors[:3])이 되어 cmap에는 colors[0], colors[1], colors[2]가 매핑된 ListedColormap 객체가 됩니다. 이는 곧 빨간색, 파란색, 밝은 초록색이 매핑되는 겁니다.



>>> x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1

>>> x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1

>>> xx, yy = np.meshgrid(np.arange(x1_min, x1_max, resolution),

                                np.arange(x2_min, x2_max, resolution)) 


x1_min, x1_max는 트레이닝 데이터 X의 첫 번째값인 꽃잎길이의 최소값-1, 최대값+1 입니다.

마찬가지로 x2_min, x2_max는 트레이닝 데이터 X의 두 번째값인 꽃잎너비의 최소값-1, 최대값+1 입니다. 

numpy.meshgrid()는 격자의 교차점 좌표를 편리하게 다룰 수 있도록 값을 리턴하는 함수입니다. 이해를 돕기 위해 아래의 코드를 봅니다.


>>> x = [0, 1, 2, 3, 4, 5]

>>> y = [0, 1, 2, 3, 4, 5]

>>> xx, yy = np.meshgrid(x, y)

>>> plt.plot(xx, yy, marker='.', color='k', linestyle='none')


이 코드는 x 범위 0~5까지 정수, y 범위 0~5까지 정수를 좌표로 하는 격자의 교차점의 위치를 찍어주는 코드입니다. 실행을 해보면 다음과 같은 결과가 나옵니다.



따라서 원래 코드에서 xx와 yy는 아이리스 트레이닝 데이터의 꽃잎길이, 꽃잎너비가 분포하는 좌표 격자 교차점을 resolution 간격으로 편리하게 만들어줄 수 있는 값을 가지고 있습니다.



>>> Z = classifier.predict(np.array([xx.ravel(), yy.ravel()]).T)

>>> Z = Z.reshape(xx.shape) 


xx, yy를 ravel()을 이용해 1차원 배열로 한줄로 쭉 만든 후 전치행렬로 변환하여 퍼셉트론 분류기의 predict()의 인자로 입력하여 계산된 예측값을 Z로 둡니다. Z를 reshape()을 이용해 원래 배열 모양으로 복원합니다.



>>> plt.contourf(xx, yy, Z, alpha=0.5, cmap=cmap)


Z를 xx, yy가 축인 그래프상에 cmap을 이용해 등고선을 그립니다.


이후 부분은 아이리스 트레이닝 데이터를 matplotlib의 산점도 그리기를 이용해 그래프상에 찍어주는 겁니다. 특히 테스트 데이터는 강조하는 원으로 다시 표시 해주었습니다. matplotlib의 산점도 그리기 포스팅을 참고하면 됩니다.


☞ matplotlib으로 산점도 그리기 바로가기



이제 이전 포스팅의 skl_perceptron0.py의 if __name__ == '__main__':코드 맨 마지막에 아래의 코드를 추가해서 skl_perceptron.py로 저장합니다.

 

 

여기서 numpy.vstack(x, y)는 x배열과 y배열을 수직으로 쌓는 것이고, numpy.hstack(x, y)는 x배열과 y배열을 수평으로 쌓는 것입니다.


skl_perceptron.py 



코드를 실행해보면 다음과 같은 결과를 볼 수 있습니다.






scikit-learn의 퍼셉트론 알고리즘의 아이리스 트레이닝 데이터를 이용해 머신러닝한 결과는 iris-setosa를 빨간색 영역으로, iris-versicolor를 파란색 영역으로, iris-virginica를 초록색 영역과 같이 분류합니다.


그런데 실제 데이터 분포를 보면 알 수 있듯이 x로 표시된 iris-versicolor가 빨간색 영역과 초록색 영역에서도 보이고, 초록색 원으로 표시된 것도 파란색 영역에서 찾을 수 있습니다. 따라서 퍼셉트론으로 아이리스 3가지 품종을 완벽하게 분류하지 못했다는 것을 알 수 있습니다. 테두리가 검정색인 원으로 표시한 것이 테스트 데이터입니다. 테스트 데이터 중 4개가 엉뚱한 영역에 있음을 알 수 있습니다. 


퍼셉트론은 이와 같이 선형분리가 완벽하게 되지 않는 데이터로는 머신러닝을 제대로 수행할 수 없습니다.


반응형
반응형

이미 말했듯이 퍼셉트론이나 이를 발전시킨 아달라인은 머신러닝을 위한 트레이닝 데이터가 완전히 선형 분리가 되어 있지 않으면 제대로 된 머신러닝을 수행할 수 없습니다. 하지만 우리가 접하는 대부분의 데이터들은 선형 분리가 불가능한 것들이며, 이런 이유로 머신러닝을 위한 방법으로써 퍼셉트론은 실제로 잘 활용되지는 않습니다.


그렇지만 퍼셉트론은 분류(classification)를 위한 다양한 알고리즘의 개발과 발전에 기초 개념을 제공했다는 것만으로도 매우 가치가 있는 것이죠~


다양한 분류 알고리즘 중, 해결하고자 하는 문제에 최적인 알고리즘을 선택하는 일은 경험이 필요합니다. 이 분류 알고리즘으로 트라이 해보고, 저 분류 알고리즘으로 한번 트라이 해보고, 이런 식으로 다양한 분류 알고리즘을 적용하여 가장 최적인 결과가 나오는 것을 선택하는 것이죠. 이 후, 이와 유사한 작업을 할 경우에는 최적인 알고리즘을 채택해서 적용하게 되는 것입니다. 따라서 우리가 획득하고 트레이닝 시킬 데이터의 종류에 따라 최적인 분류 알고리즘을 찾는 것이 중요합니다.


분류를 위한 머신러닝을 수행할 때 일반적으로 아래의 단계를 따릅니다.

  1. 트레이닝 데이터에서 트레이닝을 수행할 적절한 특성(feature)값(실제 머신러닝을 위해 입력되는 값) 선택하기
  2. 성능 메트릭(performance metric) 선택하기
  3. 분류기(classifier)를 선택하고 알고리즘 최적화하기
  4. 머신러닝 성능 측정하기
  5. 알고리즘 튜닝하기


위에서 설명한 각 단계의 자세한 내용은 차근차근 진행하도록 하도록 하고, 이번 포스팅에서는 우리가 이미 직접 파이썬으로 구현해보았던 퍼셉트론을 scikit-learn API를 이용해 수행해보는 것입니다.




scikit-learn은 파이썬으로 구현한 머신러닝을 위한 확장 라이브러리입니다. scikit-learn은 아래와 같이 머신러닝을 위한 다양한 API를 제공합니다.


  • Classification
  • Regression
  • Clustering
  • Dimensional reduction
  • Model selection
  • Preprocessing


이와 관련한 많은 내용을 알고 싶다면 scikit-learn 공식 사이트를 방문해서 살펴보도록 하세요~


☞ scikit-learn 공식 사이트 바로 가기


여러분의 컴퓨터에 scikit-learn이 아직 설치되어 있지 않았다면 윈도우 명령 프롬프트를 실행하고 다음과 같이 pip를 이용해 scikit-learn을 설치합니다.


pip install scikit-learn



자 그러면 아래의 코드를 작성하고 skl_perceptron0.py로 저장합니다.


skl_perceptron0.py 


>>> from sklearn import datasets

>>> from sklearn.cross_validation import train_test_split

>>> from sklearn.preprocessing import StandardScaler

>>> from sklearn.linear_model import Perceptron

>>>from sklearn.metrics import accuracy_score


scikit-learn의 필요한 모듈을 임포트합니다.

이후에 등장하는 코드는 이전 포스팅에서 퍼셉트론이나 아달라인을 잘 이해했다면, scikit-learn에서 제공하는 API를 이용하는 것 이외에는 거의 동일한 프로세스로 머신러닝을 수행하게 됩니다.



>>> iris = datasets.load_iris()


scikit-learn에는 이전 포스팅에서 학습 데이터로 다루었던 아이리스에 대한 데이터를 자체적으로 가지고 있습니다. 이 데이터는 datasets 모듈의 load_iris() 함수를 통해 읽어올 수 있는데, scikit-learn에서 정의한 특이한 클래스 형태로 저장되어 있습니다.



>>> X = iris.data[:, [2, 3]]

>>> y = iris.target   


X에 아이리스 데이터에서 꽃잎길이와 꽃잎너비 데이터를 대입합니다. 그리고 y에는 품종 데이터를 담습니다. y를 화면에 출력해보면 알겠지만 y에 저장된 아이리스 품종데이터는 0, 1, 2의 값으로만 구성되어 있습니다. 이는 iris-setosa, iris-versicolor, iris-verginica에 해당하는 값입니다. 즉, scikit-learn에서 품종 데이터를 이미 숫자로 바꾸어 저장해둔 것입니다.



>>> X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,

                                                              random_state=0)


train_test_split()은 원래 트레이닝 데이터와 결과값인 X와 y를 무작위로 섞은 후 test_size로 주어진 비율만큼 각각 나눕니다. 예를 들어 test_size = 0.3으로 되어 있으면 X와 y를 무작위로 섞은 후에 70%는 X_train과 y_train으로, 나머지 30%는 X_test, y_test로 둡니다. random_state의 값은 난수 발생을 위한 seed의 인자값입니다.


아이리스 데이터를 70%는 트레이닝 데이터로, 나머지 30%는 테스트 데이터로 나누는 이유는 70%의 데이터로 머신러닝을 수행하여 학습이 마무리되면, 나머지 30%의 데이터에 대한 예측값과 실제 결과값을 비교함으로써 머신러닝이 얼마나 잘되었는지 확인하기 위함입니다.



>>> sc = StandardScaler()


scikit-learn의 preprocessing 모듈이 제공하는 StandardScaler()는 표준화를 적용하기 위한 클래스입니다. 표준화에 대해서는 이미 이전 포스팅에서 자세히 다루었습니다.



>>> sc.fit(X_train)


X_train의 평균과 표준편차를 구합니다.



>>> X_train_std = sc.transform(X_train)

>>> X_test_std = sc.transform(X_test)


X_train과 X_test를 표준화한 데이터를 각각 X_train_std, y_test_std에 대입합니다.



>>> ml = Perceptron(eta0=0.01, n_iter=40, random_state=0)

>>> ml.fit(X_train_std, y_train)


scikit-learn의 linear_model에는 머신러닝을 위한 다양한 선형모델 알고리즘들을 제공합니다. 여기에는 Perceptron 클래스도 포함되어 있습니다. learning rate을 0.01로 두고 퍼셉트론 객체를 생성합니다. X_train과 y_train을 이용해 머신러닝을 수행합니다.



>>> y_pred = ml.predict(X_test_std)


퍼셉트론으로 머신러닝을 수행한 후 X_test_std를 이용해서 이에 대한 예측값을 계산하고 y_pred로 둡니다.



>>> print('총 테스트 개수:%d, 오류개수:%d' %(len(y_test), (y_test != y_pred).sum()))


테스트 값에 대한 실제 결과값인 y_test와 머신러닝을 수행한 퍼셉트론에 의해 예측된  값인 y_pred를 비교하여 다른 값을 가진 개수를 출력합니다.



>>> print('정확도: %.2f' %accuracy_score(y_test, y_pred))


scikit-learn의 metrics 모듈에서 제공하는 accuracy_score()는 인자에 y_test, y_pred를 순서대로 입력하면 y_test와 y_pred를 비교하고 값이 같은 비율, 즉 정확도를 계산해줍니다.



이 코드를 수행하면 아래와 같은 결과가 나옵니다.


총 테스트 개수: 45, 오류개수: 4

정확도: 0.91




다음 포스팅에서 실제 아이리스 트레이닝 데이터가 어떻게 분포하는지 산점도로 그래프를 그려보고, 퍼셉트론 머신러닝에 의해 학습된 분류가 어떻게 이 데이터들을 구분하는지 그 영역을 그래프로 그려서 확인하고 그 유효성에 대해 살펴보도록 하겠습니다. 


반응형
반응형

우리가 실제로 다루는 데이터는 매우 큰 용량의 데이터가 대부분이며 , 이런 대규모 데이터에 대한 머신러닝을 이전 포스팅에서 설명한 배치 경사하강법을 이용한 아달라인을 그대로 활용하는 것은 성능면에서 문제가 있습니다.


이런 문제점을 극복하기 위한 방법으로 확률적 경사하강법(Stochastic Gradient Descent)을 적용한 아달라인이 있습니다.


이전 포스팅의 내용 중 아달라인의 가중치 업데이트 수식을 다시 상기해 봅니다.



이 수식의 계산을 위해, 

모든 트레이닝 데이터의 실제 결과값과 예측값의 차를 더한 값을 구해야 합니다.


그런데, 확률적 경사하강법에서는 아래와 같이 i번째 트레이닝 데이터에 대해서만 계산한 값을 이용합니다.


  

예를 들어 이해를 해봅니다.


100만개의 트레이닝 데이터가 있다고 가정합니다. 이를 배치 경사하강법으로 계산하려면 100만 x 100만 = 1조 번의 연산이 필요합니다. 이는 가중치를 업데이트 하는 식에서 y^(i)를 계산하기 위해 100만번의 덧셈을 해야하고, wj를 최종적으로 업데이트하기 위해 전체적으로 또 100만번의 덧셈을 수행해야 하기 때문입니다.


그런데, 확률적 경사하강법을 적용하게 되면 100만개의 모든 데이터를 활용하여 머신러닝을 수행하더라도 100만번의 연산만 필요합니다. 


확률적 경사하강법을 이용하면 배치 경사하강법의 근사치로 계산되지만 가중치를 업데이트하는 시간이 빠르기 때문에 실제로는 비용함수의 수렴값에 더 빨리 도달하게 됩니다.


그리고, 확률적 경사하강법에서 비용함수의 값을 계산할 때 값이 순환되는 것을 피하기 위해 학습을 반복할 때마다 트레이닝 데이터의 순서를 랜덤하게 섞어서 수행하는 것이 좋습니다.


또한 크라우드 소싱(crowd sourcing)으로 머신러닝을 적용하는 웹에서도 확률적 경사하강법을 활용할 수 있습니다. 어떤 사람이 웹을 통해 데이터를 제공하고, 이 데이터에 대한 예측 결과를 그 사람에게 제시하여 피드백을 받아 재학습을 시키는 것이 바로 그 예입니다. 이에 대해서는 이후 포스팅에서 다루어 보도록 하겠습니다.


그러면 실제 코드를 보면서 이해해봅니다. 아래 코드를 작성하고 mylib 폴더에 adalinesgd.py로 저장합니다.


adalinesgd.py 


이전 포스팅에서 소개한 표준화를 적용한 배치 아달라인의 AdalineGD 클래스와 여기서 구현한 AdalineSGD 클래스의 다른 부분만 설명합니다.

주요 변경 부분은 AdalineSGD 클래스의 fit() 함수의 로직과 _suffle() 함수가 추가된 것입니다.


>>> from numpy.random import seed


난수 발생기 초기화를 위해 numpy.random 모듈의 seed 함수를 임포트합니다.



>>> if random_state:

       seed(random_state)


random_state의 값이 있으면 이 값으로 난수 발생기를 초기화합니다.




>>> if self.shuffle:

       X, y = self._shuffle(X, y)


self.shuffle이 True로 설정되어 있으면, self._shuffle() 함수를 이용해 트레이닝 데이터 X와 y를 랜덤하게 섞습니다.



>>> cost = []
       for xi, target in zip(X, y):
          output = self.net_input(xi)
          error = target - output                
          self.w_[1:] += self.eta * xi.dot(error)
          self.w_[0] += self.eta * error                
          cost.append(0.5 * error**2)


       avg_cost = sum(cost)/len(y)

       self.cost_.append(avg_cost)


이 부분이 가중치를 업데이트하는 아래 식을 구현한 것입니다.



모든 트레이닝 데이터에 대해 비용함수의 값을 더해주고 for구문이 완료되면 평균값을 구하여 최종적인 비용함수 값으로 취합니다. 



>>> def _shuffle(self, X, y):

       r = np.random.permutation(len(y))

       return X[r], y[r]


numpy.random.permutation은 주어진 인자 미만의 정수(0을 포함)로 순열을 만드는 함수입니다. 따라서 r의 값은 0~len(y) 미만까지 정수를 랜덤하게 섞은 결과이므로, X[r], y[r]은 X와 y를 랜덤하게 섞은 numpy 배열이 됩니다.


나머지 부분은 이전 포스팅의 코드와 동일합니다.


자, 그러면 아래의 파일을 작성하고 std_adalinesgd_iris.py로 저장합니다. 보면 알겠지만, AdalineSGD를 임포트한 것과 AdalineSGD 클래스에서 random_state=1 로 설정한 것 빼고는 이전 포스팅의 std_adaline_iris.py와 로직이 동일합니다. 


std_adalinesgd_iris.py 

 


이 코드를 실행하면 아래와 같은 결과가 나옵니다. 



  


위 그래프를 보면, 비용함수의 값이 빠르게 줄어드는 것을 볼 수 있습니다. 이유는 한번의 반복문에서 가중치를 업데이트하는 빈도수가 더 많이 일어나기 때문이지요. 학습 반복회수가 늘어나면 결국 배치 경사하강법을 적용한 아달라인과 비슷해짐을 알 수 있습니다.


이로써 단순 인공신경망인 퍼셉트론과 아달라인에 대해 간략하게 살펴보았습니다.

물론 수식들이 나오고 내용도 이해하기 난해하지만 이제 어려운 부분들은 모두 끝났습니다. 앞으로 다루게 될 내용들은 이 개념들의 확장들이고, Scikit-Learn과 같은 라이브러리들은 사용자 친화적인 API들이므로 알고리즘 내부를 알 필요없이 다양한 머신러닝을 적용할 수 있습니다.


그렇더라도 퍼셉트론과 아달라인의 개념을 이해하게 되면 scikit-learn에서 제공하는 API의 활용도 더 편리하게 사용할 수 있고, 나아가 텐서플로우(TensorFlow)와 같은 오픈소스로 공개된 러닝 시스템에서도 여태까지 배운 개념이 적용되기도 합니다.


다음에는 머신러닝과 관련된 사용자 친화적인 scikit-learn API를 이용하여 머신러닝을 수행하는 다양한 알고리즘과 관련된 내용을 소개하도록 하겠습니다. 




반응형
반응형
[7편] 단순 인공신경망 아달라인을 파이썬으로 구현하기에서는 단순한 단층 인공신경망인 아달라인을 구현해 보았습니다. 하지만 매우 작은 값의 learning rate을 적용해야 비용함수가 특정값에 수렴하게 되어 머신러닝이 유효하게 되지만, 이는 매우 많은 수의 반복 학습이 필요하게 되어 머신러닝을 수행하는데 있어 성능상의 문제가 발생한다고 했습니다.


하지만 통계학에서 말하는 표준화(Standardization)를 이용해 트레이닝 데이터를 표준화된 값으로 변환하여 머신러닝을 수행하게 되면 비교적 큰 값의 learning rate에도 머신러닝이 유효하게 동작합니다.


우리는 고등학교 수학시간에 정규분포라는 통계학 용어를 접해보았을 것입니다. 정규분포라는 것이 통계학에 있어서는 매우 중요한데, 자연상에 존재하는 다양한 값들의 분포는 일반적으로 정규분포를 따른다고 알려져 있습니다.


예를 들어 지구상에 존재하는 사람의 키에 따른 사람의 수를 그래프로 그려보면 평균키를 기준으로 좌우로 대칭인 곡선으로 나타나는데, 평균에서 멀어지면 급격하게 그 수가 감소하는 양상으로 나타납니다. 이는 비단 사람의 키 뿐만 아니라 사람의 수명이라든가, 어느 한 종류 군집의 특징적인 값들의 분포들이 정규분포를 따른다고 봐도 됩니다.



위 그림은 정규분포 곡선을 나타낸 것인데 정규분포곡선은 평균값을 기준으로 좌우 대칭인 종모양의 곡선이며, 이 곡선의 폭을 결정하는 것이 표준편차입니다. 표준편차는 값들의 분포가 평균값을 기준으로 얼마나 흩어져 있느냐를 말해주는 수치입니다.


그런데 모든 정규분포 곡선은 어떤 변환을 이용하면 평균값이 0, 표준편차가 1인 정규분포 곡선으로 바꿀 수 있습니다. 평균값이 0이고 표준편차가 1인 정규분포를 표준 정규분포라고 하며, 표준 정규분포로 변환하는 작업을 표준화(Standardization)라고 부릅니다.


이전 포스팅에서 사용한 아이리스 품종 데이터는 Iris-setosa, versicolor, verginica 각각 50개씩 총 150개입니다. 만약 이 데이터가 매우 많고 이들의 꽃잎 크기나 꽃받침 크기 분포를 그려보면 아마도 정규분포 곡선 모양이 될 것입니다.


일반적으로 데이터를 표준화하여 처리하게 되면 일관성이 생길뿐만 아니라 처리 성능까지 향상되는 결과를 가져올 수 있습니다. 어떤 데이터 집합 x가 있으면 표준화 된 데이터 X는 아래의 식에 의해 구해집니다.



여기서 μ는 데이터 집합 x의 평균이며 σ는 데이터 집합 x의 표준편차입니다.

 

이제, 아이리스 품종 데이터를 표준화하여 아달라인을 적용하는 코드를 작성해 보겠습니다.

이전 포스팅의 adaline_iris.py 에서 if __name__ == '__main__' 을 아래와 같이 수정합니다.


std_adaline_iris.py


코드에서 아이리스 품종 데이터를 읽어들이는 부분도 동일합니다. 바뀐 부분만 살펴봅니다.


>>> X_std = np.copy(X)


X를 하나 복사하여 X_std로 둡니다.

 


>>> X_std[:, 0] = (X[:, 0] - X[:, 0].mean()) / X[:, 0].std()

>>> X_std[:, 1] = (X[:, 1] - X[:, 1].mean()) / X[:, 1].std()


이 부분이 바로 아이리스 데이터에서 꽃받침 길이와 꽃잎 길이의 표준화한 값을 X_std에 할당하는 부분입니다. numpy의 mean()은 numpy 배열에 있는 값들의 평균을, numpy의 std()는 numpy 배열에 있는 값들의 표준편차를 구해주는 함수입니다.



>>> adal = AdalineGD(eta=0.01, n_iter=15).fit(X_std, y)


learning rate을 0.01로, 반복회수를 15로 두고, X_std를 아달라인으로 머신러닝을 수행합니다.



>>> plt.plot(range(1, len(adal.cost_) + 1), adal.cost_, marker='o')


이전 포스팅에서는 y축의 값으로 log 스케일로 그렸지만, 지금은 그냥 그대로 그려봅니다.

코드를 수행하면 아래와 같은 그래프가 화면에 그려집니다.




표준화를 이용하면 learning rate을 0.01로 두어도 비용함수의 값이 발산하지 않고 특정한 값에 수렴하고 있음을 알 수 있습니다.


여기까지 살펴본 아달라인 내용은 우리가 가지고 있는 모든 트레이닝 데이터를 한꺼번에 일괄 적용하여 학습을 수행하는 것이었습니다. 이를 배치 경사하강법(Batch Gradient Descent)을 이용한 아달라인이라 부릅니다. 배치 경사하강법을 이용하는 경우, 데이터의 개수가 매우 많아지고, 데이터 입력값의 종류도 다양하다면 머신러닝을 효율적으로 수행할 수 없고 시간도 매우 오래 걸리게 됩니다.


다음 포스팅에서는 이를 극복하기 위한 확률적 경사하강법(Stochastic Gradient Descent)을 이용하는 아달라인에 대해 다루도록 하겠습니다. 


반응형
반응형

이번 포스팅에서는 퍼셉트론을 구현한 코드를 조금 수정하여 아달라인 코드를 파이썬으로 구현해 봅니다. 아래의 코드를 작성하고 mylib 폴더에 adaline.py로 저장합니다.


adaline.py 


기본인자의 디폴트 값, thresholds를 0.0으로 고정한 부분을 제외하면 퍼셉트론을 구현한 Perceptron 클래스와 아달라인을 구현한 AdalineGD 클래스에서 다른 부분은 아래의 fit() 내에서 구현한 부분 밖에 없습니다.


>>> for i in range(self.n_iter):

          output = self.net_input(X)

          errors = (y - output)

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

          self.w_[0] += self.eta * errors.sum()

          cost = (errors**2).sum() / 2

          self.cost_.append(cost)


이 부분이 아달라인에서 가중치를 업데이트 하는 아래 식을 구현한 것입니다.



adline.py에서 리스트로 정의된 self.cost_ 는 비용함수 J(w)가 어떤 값으로 수렴하는지 확인하기 위해 매 반복마다 계산되는 비용함수값을 저장하기 위한 변수입니다.


X.T는 행렬 X의 전치행렬을 의미합니다.



행렬 X와 행렬 Y의 행렬 곱셈이 가능하려면 X의 행의 숫자와 Y의 열의 숫자가 같아야 하는데, X.T로 X의 행과 열을 바꾸어야 가중치가 저장된 errors와 곱셈이 가능하기 때문입니다.


이것이 다입니다. 퍼셉트론의 코드를 다 이해하고, 아달라인의 가중치 업데이트 식을 알고 있다면 아달라인 코드는 이해하기가 쉽습니다.


이제 퍼셉트론에서 다루었던 아이리스 품종 데이터를 이용해서 아달라인으로 머신러닝을 수행하는 아래의 코드를 작성하여 adaline_iris.py로 저장합니다.


adaline_iris.py


if __name__ == '__main__': 에 있는 코드는 퍼셉트론에서와 마찬가지로 아이리스 품종 데이터 100개를 읽어와서 하나는 learning rate을 0.01로 두고 아달라인을 수행하여 얻은 비용함수 J(w)의 값 변화와 또 하나는 learning rate을 0.0001로 두고 아달라인을 수행하여 얻은 비용함수 J(w)의 값 변화를 matplotlib을 이용해 화면에 그려주는 코드입니다.


matplotlib에 대한 내용은 예전에 따로 시리즈로 포스팅했으니 아래 링크를 참조하기 바랍니다.


☞ Matplotlib을 이용한 데이터 시각화 프로그래밍 시리즈 바로가기



코드를 수행하면 다음과 같은 그래프가 화면에 나타납니다.




왼쪽 그래프를 보면 알수 있듯이 learning rate을 0.01로 수행한 아달라인의 비용함수 J(w) 값은 계속 커집니다. 즉 이 경우에는 J(w) 값이 수렴하지 않고 발산해버립니다.


반면, learning rate의 값을 0.0001로 수행한 아달라인의 비용함수 J(w)값은 계속 작아져서 어느 특정한 값에 수렴하게 된다는 것을 알 수 있습니다.


따라서 learning rate을 매우 작게 해야 아달라인의 J(w) 값이 작아지고 우리가 원하는 머신러닝의 결과를 얻을 수 있습니다.


이렇게 되는 이유는 아래의 그림으로 설명이 가능합니다.




이전 포스팅에서 아달라인에서 스텝 크기 △w는 아래와 같이 정의된다고 하였습니다.



여기서 η가 learning rate 입니다.


아달라인이 제대로 수행되려면 위 그림의 왼쪽 그림과 같이 초기 가중치로부터 곡선을 따라 아래로 차근차근 내려가야 하는데, 이렇게 되려면 △w의 값이 작아져야 합니다. 만약 △w의 값이 크게 되면 위 그림의 오른쪽 그림과 같이 초기 가중치가 위치한 부분에서 곡선의 최소값을 건너뛴 위치에서 다시 스텝을 계산하게 되고 이런 식으로 스텝의 크기가 갈수록 커져버려서 비용함수 J(w)의 값은 발산하게 됩니다.


learning rate의 값이 매우 작으면 J(w)의 값은 수렴하지만, 의미 있는 값으로 수렴하는 것을 확인하려면 매우 많은 반복 회수가 필요합니다.


다음에는 정규분포에서 사용되는 표준화(standardization)를 이용해 이런 단점을 극복하는 아달라인 알고리즘을 적용해보도록 합니다. 



반응형
반응형

아달라인(Adaline)


단층 인공신경망인 퍼셉트론이 발표된지 몇 년 후인 1960년에 Bernard Widrow와 Tedd Hoff는 퍼셉트론 알고리즘을 향상시킨 새로운 인공신경망 알고리즘에 대한 논문을 발표합니다.


이 인공신경망을 Adaptive Linear Neuron, 줄여서 Adaline(아달라인)이라 불렀습니다. 

아달라인은 분류(classification)를 위한 보다 발전된 머신러닝 알고리즘인 회귀(regression), 로지스틱 회귀(logistic regression), SVM(Support Vector Machine)에 대한 알고리즘의 토대를 마련하게 됩니다.


퍼셉트론과 알다라인의 차이점은 바로 가중치 업데이트를 위한 활성 함수(Activation Function)가 다른 것입니다. 아래 그림은 이전 포스팅에서 설명했던 퍼셉트론 알고리즘을 도식화 한 것입니다.




아래 그림은 아달라인 알고리즘을 도식화 한 것입니다. 




위의 두 그림을 보면 알 수 있듯이,


퍼셉트론에서는 활성 함수가 순입력 함수의 리턴값을 임계값을 기준으로 1 또는 -1로 리턴한 값과, 실제 결과값을 임계값을 기준으로 1 또는 -1로 리턴한 값을 비교하여 가중치를 업데이트 하도록 하거나 결과를 출력하는 기능을 하였습니다.


그런데 아달라인에서는 순입력 함수의 리턴값과 실제 결과값을 비교하여 이 오차가 최소가 되도록 가중치를 업데이트 하는 기능을 활성 함수가 수행하게 됩니다. 이를 위해 아달라인을 발표한 논문에서는 최소제곱법을 이용한 비용함수(cost function) J(w)를 아래와 같이 정의하였고, J(w) 값이 최소가 되도록 가중치를 업데이트 하는 것이 핵심입니다.



여기서 y(i)는 i번째  트레이닝 데이터에서 주어진 실제 결과값이고 y^(i)은 i번째 트레이닝 데이터에 대해 수행한 순입력 함수의 리턴값입니다. 따라서 실제로 이 식은 아래와 같은 식이 되는 것이죠~


                                                     


가중치 w는 n개로 이루어진 변수이지만, 이를 그냥 한개의 변수로 단순화시켜 생각해보면 J(w)는 결국 w에 대한 2차 함수가 되며 이 값이 최소가 되는 w를 찾으면 됩니다. 2차항의 계수가 양수인 2차 함수의 최소값은 중학교 수학정도만 알면 누구나 쉽게 구할수 있지만, w는 n개이기 때문에 2차함수의 최소값을 구하는 공식을 바로 적용할 수는 없습니다.

여기서는 경사하강법(gradient descent)이라 불리는 최적화 알고리즘을 이용하여 J(w)가 최소가 되는 가중치를 찾게 됩니다.


경사하강법은 어떤 곡선에서 기계적으로 최소값을 찾아가는 방법인데, 곡선상의 어떤 한지점에서 경사방향으로 한걸음 내려가고, 내려간 그 지점에서 또 다시 경사방향으로 한걸음 내려가고 하는 식으로 진행해서, 결국은 최소지점까지 도달하는 방법입니다.


아래 그림에서 보듯이, 경사하강법은 곡선을 따라 일정 크기(이를 스텝 크기라고 함)만큼 접선의 기울기 방향으로 내려가면서 그 값이 최소값인지 판단하는 것인데, 아달라인에서 각 스텝마다 그 다음 스텝 크기의 결정은 그 지점에서 접선의 기울기와 learning rate으로 결정됩니다. 아래 그래프에서 스텝 크기는 화살표 길이의 w성분값 입니다.




아달라인은 이런 경사하강법을 이용하는데, 스텝의 크기 △w는 아래와 같이 정의합니다.



여기서 η는 learning rate이고 △J(w)는 w에서 J(w)의 접선의 기울기, 즉 J(w)의 미분값입니다. 위의 설명에 따라 초기 가중치 w의 값에서 시작하여 아래의 식에 의해 스텝을 달리하며 w를 업데이트 합니다.



n개의 트레이닝 데이터에 대해 아달라인을 수행하고, 이 식에 의해 가중치가 업데이트 됨에 따라 J(w)가 어떤 값에 수렴하게 되면 이는 제대로 머신러닝이 이루어졌다는 것을 의미합니다. 만약 어떤 값에 수렴하지 못하고 발산하게 되면 learning rate을 조정해봄으로써 J(w)가 수렴하게 될 수도 있습니다. 따라서 아달라인에서는 J(w)가 수렴하도록 하는 learning rate을 찾는 것이 중요한데, 보통 매우 작은 값을 적용하면 됩니다.


위의 가중치 업데이트 식에서, J(w)를 w에 대해 편미분하면 아래와 같은 식으로 됩니다.



여기서 xj(i)는 i번째 트레이팅 데이터의 j번째 입력값입니다. 혹시나 이 수식이 어떻게 유도되는지 궁금해 하시는 분들이 있을까봐, 간단하게 설명하자면....(물론 수학이라는 소리만 들어도 두드러기 생기는 분들이 많을 것이라 생각합니다...) △wj 의 값을 아래와 같이 유도하면 됩니다. 아래 수식에서 편의상 i번째 트레이닝 데이터를 의미하는 첨자 i는 생략했습니다.


고등학교 때 배웠던 합성함수의 미분은 아래와 같습니다.




그리고 cost function J(w)는 아래와 같고,


이 식을 합성함수의 미분 공식을 고려하여, wj에 대해서 편미분하면,


                                                          


                                                          


                                                         



따라서,

                                                           


그러므로,


                                                          



위 유도식은 우리가 머신러닝을 진행함에 있어 전혀 몰라도 상관 없으니 그냥 참고만 하시기 바랍니다~



다음 포스팅에서는 퍼셉트론 알고리즘을 구현한 파이썬 코드를 약간 수정하여 아달라인을 구현해 도록 합니다. 

공감 


반응형
반응형

이번에는 아래의 링크에서 얻을 수 있는 150개의 아이리스 데이터를 단순 인공신경망인 단층 퍼셉트론으로 머신러닝을 수행해 보도록 하겠습니다.


https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data


위의 링크를 클릭해보면 아래와 같은 csv 형태로 저장된 150개의 데이터를 볼 수 있습니다.


5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa

...

7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
...

6.2,3.4,5.4,2.3,Iris-virginica
5.9,3.0,5.1,1.8,Iris-virginica 


이 데이터는 4개의 특성값과 이 특성값에 따른 품종으로 되어 있는데, 첫번째부터 차례대로 꽃받침길이, 꽃받침너비, 꽃잎길이, 꽃잎너비, 아이리스 품종을 나타냅니다. 데이터는 각각 iris-setosa에 대한 데이터가 50개, iris-versicolor에 대한 데이터가 50개, iris-virginica에 대한 데이터가 50개로 구성되어 있습니다.


참고로 아이리스(iris)는 우리말로 붓꽃이라 부르며 꽃받침길이와 너비, 꽃잎길이와 너비로 품종을 구분하나 봅니다.




이제 위의 데이터를 아래의 표와 같이 보기 편하게 정리를 해봅니다.



우리가 해볼 것은 이전 포스팅에서 소개한 퍼셉트론 알고리즘을 이용해 꽃받침길이/너비, 꽃잎길이/너비를 이용해 아이리스 품종을 구분할 수 있도록 머신러닝을 수행하는 것입니다.


아시다시피 퍼셉트론은 바이너리 결과를 가지므로 3개의 품종을 동시에 구분할 수 있는 머신러닝을 수행할 수는 없습니다. 따라서 1~100까지의 데이터에서 꽃받침길이와 꽃잎길이 데이터만을 이용해 Iris-Setosa와 Iris-Versicolor의 2개 품종을 구분할 수 있도록 학습시키도록 합니다.


아래의 코드를 작성하고 perceptron_iris.py로 저장합니다. 참고로 코드의 맨 윗줄 %matplotlib inline은 Jupyter 환경에서 코딩하는 경우, maplotlib 결과를 임베드시켜 보여주는 코드이므로  Jupyter 환경이 아닌 경우, 삭제하면 됩니다.

 

perceptron_iris.py 


>>> import pandas as pd

>>> import matplotlib

>>> import matplotlib.pyplot as plt

>>> from matplotlib import style

>>> from mylib.perceptron import Perceptron


필요한 모듈을 임포트 합니다.


>>> krfont = {'family':'NanumGothic', 'weight':'bold', 'size':10}
>>> matplotlib.rc('font', **krfont)

>>> matplotlib.rcParams['axes.unicode_minus'] = False


이 부분은 matplotlib에서 한글을 표현하기 위한 것으로 윈도우 환경에서 구동하는 경우 제거해도 됩니다.



이전 포스팅의 perceptron_and.py와 비교하여 if __name__ == '__main__' 에서 변경된 부분입니다.


>>> df = pd.read_csv('iris.data', header=None)


csv 형식으로 저장된 'iris.data'를 pandas의 read_csv() 함수를 이용해서 읽고, pandas의 DataFrame 객체로 변환합니다. 아이리스 데이터 링크에 걸려 있는 파일을 로컬로 다운로드 받아서 수행하는 경우 위 코드와 같이 하면 되지만 'iris.data' 부분에 아이리스 데이터를 다운로드 받을 수 있는 URL을 그대로 적어도 무방합니다.


참고로 pandas는 데이터 처리와 관련된 다양한 라이브러리를 제공하는 파이썬 확장 모듈입니다. pandas에 대한 내용은 너무 방대하므로 여기서는 다루지 않습니다.



>>> y = df.iloc[0:100, 4].values

>>> y = np.where(y=='Iris-setosa', -1, 1)


아이리스 데이터를 저장한 DataFrame에서 0~99라인까지 5번째 컬럼의 데이터 값을 numpy 배열로 리턴받아 y에 대입합니다. 따라서 아이리스 데이터 파일에서 100개의 데이터를 추출하여 5번째 컬럼의 데이터 값을 numpy 배열로 리턴한 것이죠. 앞에서 보인 표를 보면 5번째 컬럼은 아이리스 품종을 나타내는 문자열 값입니다.(x0은 아이리스 데이터에 있는 값이 아님을 유의하세요~) 


y에 저장된 품종을 나타내는 문자열이 'Iris-setosa'인 경우 -1, 그렇지 않은 경우 1로 바꾼 numpy 배열을 y에 다시 대입합니다. 앞에서 언급했듯이 아이리스 데이터는 1~50까지가 Iris-setosa이며, 51~100까지가 Iris-versicolor 입니다. 따라서 y의 0~49번 인덱스에 해당하는 값은 -1로 50~99번 인덱스에 해당하는 값은 1로 되어 있는 numpy 배열이 됩니다.



>>> X = df.iloc[0:100, [0, 2]].values


아이리스 데이터를 저장한 DataFrame에서 0~99라인까지 1번째, 3번째 컬럼의 데이터 값을 numpy 배열로 리턴받아 이를 X에 대입합니다. 아이리스 데이터의 1번째 값은 꽃받침길이이며, 3번째 값은 꽃잎길이입니다. 즉 이 코드는 꽃받침길이, 꽃잎길이에 따른 아이리스 품종을 머신러닝으로 학습하는 내용이 될 것입니다.



>>> plt.scatter(X[:50, 0], X[:50, 1], color='r', marker='o', label='setosa')

>>> plt.scatter(X[50:100, 0], X[50:100, 1], color='b', marker='x', label='versicolor')

>>> plt.xlabel('꽃잎 길이(cm)')

>>> plt.ylabel('꽃받침 길이(cm)')

>>> plt.legend(loc=4)

>>> plt.show()


이 코드는 X에 저장된 아이리스 데이터의 꽃받침길이, 꽃잎길이에 대한 데이터의 상관관계를 파악하기 위해 Matplotlib의 산점도를 이용해 화면에 그려보는 코드입니다. Matplotlib의 산점도 그리기는 아래의 제 블로그 포스팅을 참고하세요.


☞ 데이터 시각화 프로그래밍 - 산점도 그리기 바로가기

 


>>> ppn = Perceptron(eta=0.1)

>>> ppn.fit(X, y)

>>> print(ppn.errors_)


이 부분은 이전 코드와 변동 없습니다.


코드 설명을 보면 알 수 있듯이, 이전 포스팅에서 AND 연산을 학습한 것과 비교하면 학습하려는 데이터 세트를 구성하는 방법을 제외하고는 거의 동일하다는 것을 알 수 있습니다.


프로그램을 실행하면 다음과 같은 결과가 화면에 나올 것입니다.




[ 0.2   1.4   0.94]
[ 0.    0.8   1.32]
[-0.2  0.2  1.7]
[-0.2   0.32  2.12]
[-0.4  -0.7   1.84]
[-0.4  -0.7   1.84]
[-0.4  -0.7   1.84]
[-0.4  -0.7   1.84]
[-0.4  -0.7   1.84]
[-0.4  -0.7   1.84]
[1, 3, 3, 2, 1, 0, 0, 0, 0, 0]


위 실행결과를 보면 알 수 있듯이 6번째 학습후에 가중치의 값이 최종적으로 결정된다는 것을 알 수 있습니다.

50개의 Iris-setosa 데이터, 50개의 Iris-versicolor 데이터로 수행한 머신러닝의 결과는 다음과 같이 요약할 수 있습니다.


*** 머신러닝 결과 *** 

-0.4 + (-0.7) x (꽃받침길이) + 1.84 x (꽃잎길이) 의 값이 0보다 작거나 같으면 Iris-setosa, 이 값이 0보다 크면 Iris-versicolor로 분류함



여태까지 언급한 퍼셉트론은 입력값과 가중치, 그리고 하나의 노드(또는 뉴런)와 출력값을 가지는 단층 퍼셉트론이며 퍼셉트론에 대한 내용은 이정도에서 마무리 하도록 합니다.  


참고로 단층 퍼셉트론은 선형 분리가 가능한 데이터에 대해서만 유효하며,  비선형적으로 분리되는 데이터에 대해서는 가중치 값을 계산하지 못하는 경우가 대부분입니다. 예를 들어 단층 퍼셉트론으로 AND 연산에 대한 학습은 가능하였지만 XOR 연산에 대한 학습은 불가능하다고 증명되었습니다. 단층 퍼셉트론의 많은 약점을 보완하기 위해 다층 퍼셉트론 개념이 나왔는데 이에 대해서는 시간이 되는대로 차후에 살펴보도록 합니다. 


반응형
반응형

이번 포스팅에서는 [3편]에서 보인 퍼셉트론을 파이썬으로 구현한 perceptron.py 코드에 대해 설명을 합니다.



perceptron.py 



>>> import numpy as np


numpy 모듈을 임포트합니다.



>>> class Perceptron()


퍼셉트론을 구현하는 클래스를 정의하고, 주요 알고리즘은 클래스 멤버 함수로 구현할 것입니다.



>>> def __init__(self, thresholds=0.0, eta=0.01, n_iter=10)


Perceptron 클래스의 생성자를 정의합니다. 생성자의 기본인자로 임계값, learning rate, 학습 횟수를 지정하는 thresholds, eta, n_iter의 디폴트 값을 각각 0.0, 0.01, 10으로 지정하고 있습니다.



>>> def fit(self, X, y)


트레이닝 데이터 X와 실제 결과값 y를 인자로 받아 머신러닝을 수행하는 함수입니다. 머신러닝 분야에서 일반적으로 트레이닝 데이터를 대문자 X로, 실제 결과값을 소문자 y로 표현합니다. 여기서도 이 관습을 따라 X, y를 각각 트레이닝 데이터, 실제 결과값을 의미하는 인자로 정의한 겁니다.



>>> self.w_ = np.zeros(1+X.shape[1])


가중치를 numpy 배열로 정의합니다. X.shape[1]은 트레이닝 데이터의 입력값 개수를 의미합니다. 예를 들어 X가 4 x 2 배열인 경우, X.shape의 값은 (4, 2)가 되며 X.shape[1]의 값은 2가 됩니다. 이 경우 self.w_는 np.zeros(3)이 될 것이고, 실제 값은 numpy 배열 [0. 0. 0.]이 됩니다.



>>> self.errors_ = []


머신러닝 반복 회수에 따라 퍼셉트론의 예측값과 실제 결과값이 다른 오류 회수를 저장하기 위한 변수입니다.



>>> for _ in range(self.n_iter):

          errors = 0

          for xi, target in zip(X, y):

             update = self.eta * (target - self.predict(xi))

             self.w_[1:] += update * xi

             self.w_[0] += update

             errors += int(update != 0.0)

          self.errors_.append(errors)

          print(self.w_)


self.n_iter로 지정한 숫자만큼 반복합니다. for 다음에 적힌 _은 아무런 의미가 없는 변수이며 단순히 for문을 특정 회수만큼 반복만 하고자 할 때 관습적으로 사용하는 변수라고 생각하면 됩니다.


초기 오류 횟수를 0으로 정의하고, 트레이닝 데이터 세트 X와 결과값 y를 하나씩 꺼집어내서 xi, target 변수에 대입합니다. xi는 하나의 트레이닝 데이터의 모든 입력값 x1~xn 을 의미합니다. 참고로 x0는 1로 정해져 있습니다.

update는 아래의 식에 대한 값입니다.



여기서 실제 결과값과 예측값에 대한 활성 함수 리턴값이 같게 되면 update는 0이 됩니다. 따라서 트레이닝 데이터 xi의 값에 곱해지는 가중치에 update * xi의 값을 단순하게 더함으로써 가중치를 업데이트 할 수 있는데, 이는 결과값과 예측값의 활성 함수 리턴값이 같아질 경우 0을 더하는 꼴이라 가중치의 변화가 없을 것이고, 결과값과 예측값의 활성 함수 리턴값이 다를 경우 0이 아닌 유효한 값이 더해져서 가중치가 업데이트 될 것이기 때문입니다. 마찬가지로 w0의 값에 해당하는 self.w_[0]에는 x0가 1이므로 update만 단순하게 더하면 됩니다. 여기까지가 아래의 식을 계산한 것이 됩니다.



update의 값이 0이 아닌 경우, errors의 값을 1증가시키고 다음 트레이닝 데이터로 넘어갑니다. 모든 트레이닝 데이터에 대해 1회 학습이 끝나면 self.errors_에 발생한 오류 횟수를 추가한 후, 가중치를 화면에 출력하고 다음 학습을 또 다시 반복합니다.



>>> def net_input(self, X):

          return np.dot(X, self.w_[1:]) + self.w_[0]


numpy.dot(x, y)는 벡터 x, y의 내적 또는 행렬 x, y의 곱을 리턴합니다. 따라서 net_input(self, X)는 트레이닝 데이터 X의 각 입력값과 그에 따른 가중치를 곱한 총합, 즉 이전 포스팅에서 설명한 아래의 순입력 함수 결과값을 구현한 것입니다.




여기서 잠깐!

앞으로 이어질 머신러닝과 딥러닝에 대한 수식을 연산하거나 설명할 때 가장 많이 등장하는 개념이 벡터나 행렬입니다. 우리는 벡터와 행렬에 대해 고등학교 수학에서 배웠습니다. 그리고 이공계 대학을 나왔다면 선형대수학이라는 과목에서 관련된 내용을 모두 배웠으리라 생각합니다. 물론 이 포스팅을 읽고 있는 많은 분들이 벡터나 행렬, 선형대수학에 익숙하지 않은 사람들도 있겠지요~


그래서, 행렬에 대해 간단하게 짚고 넘어가도록 하겠습니다.


행렬이란 수들의 집합을 행과 열방향으로 나열해 놓은 것을 말합니다. 아래는 숫자 1, 2, 3, 4를 이용한 행렬의 한 예시를 보인 것입니다.



여기서 1, 2나 3, 4가 나열된 방향을 행이라 하고, 1, 3이나 2, 4가 나열된 방향을 열이라 합니다.



따라서 위에서 보인 행렬은 행이 2개, 열이 2개인 행렬이며, 이를 간단히 행과 열의 개수를 순서대로 적어 2 x 2(차원) 행렬이라 부릅니다. 만약 어떤 행렬 A가 행의 개수가 m개이고 열의 개수가 n개인 m x n 행렬이라면 다음과 같이 표현할 수 있습니다.



여기서, a11, a12 등을 행렬 A의 성분이라 부릅니다.


행렬이 어떤 것인지에 대해 대충 이해가 되었다면, 이제 행렬간 연산에 대해 알아봅니다. 행렬에서 가장 많이 사용되는 연산은 +, -, x 입니다.


먼저, 행렬 A, B가 있다고 할 때 두 행렬간 덧셈과 뺄셈이 가능하려면 두 행렬의 차원이 동일해야 합니다. A가 2 x 2행렬이면, B도 2 2 행렬이어야 더하기 또는 빼기가 가능하다는 것입니다.


행렬의 덧셈과 뺄셈은 두 행렬에서 일치하는 성분끼리 더하거나 빼면 됩니다. 행렬 A, B가 아래와 같다고 하고,



두 행렬 A, B의 덧셈은 다음과 같습니다.





행렬의 뺄셈도 마찬가지로 하면 되고요,~ 뭐, 다 아는 내용이니까 복습하는겸 해서 가볍게 보면 됩니다.


이제는 두 행렬의 곱셈에 대해서 살펴봅니다. 행렬간 곱셈은 더하기 빼기와는 좀 다르게 동작합니다. 행렬 A, B가 있다고 하고, 이 두 행렬의 곱 AB가 가능하려면 A와 B의 차원에 관계없이 A의 열의 개수와 B의 행의 개수가 동일하면 됩니다.




위의 두 행렬 A, B를 보면, A의 열의 개수와 B의 행의 개수가 n개로 동일하므로 A와 B의 곱 AB는 가능합니다. 하지만 B의 열의 개수와 A의 행의 개수는 다르므로 두 행렬의 곱 BA는 가능하지 않습니다. 이점 유의하세요~~


두 행렬의 곱이 가능한 조건이 충족되면 AB는 다음과 같이 정의 됩니다.




아,, 이거 간단한 수식으로 표현하려다 보니 복잡해보이네요.. 아래 그림을 보시죠..(말로 설명하면 쉽게 될걸 글로 하면 이렇게 어렵습니다..)




A의 1행과 B의 1열을 선택하고 A의 1행에 있는 성분과 B의 1열에 있는 성분끼리 곱하여 모두 더한 값을 결과 행렬의 (1, 1) 성분으로 합니다. 마찬가지로 A의 1행과 B의 2열을 성분끼리 곱한 후 모두 더한 값을 결과 행렬의 (1, 2) 성분으로 합니다. 이런 식으로 A의 모든 행과 B의 모든 열을 곱하여 결과 행렬의 해당되는 자리에 대입하면 됩니다.


따라서 A와 B의 곱 AB는 m x k 행렬이 됩니다.


이제, [3편] 퍼셉트론의 입력값과 가중치를 행렬로 표현해보면 다음과 같이 됩니다. (보통 벡터나 행렬은 굵은 글씨체로 나타냅니다.)


 



이런 식으로 두면 x와 y 행렬은 각각 1 x n 행렬이 되어 곱셈이 되지 않겠지요. 그래서 w를 n x 1 행렬로 바꾸어서 아래와 같이 곱을 정의하면 됩니다.



​파이썬은 이런 형식의 벡터나 행렬연산을 아주 쉽게 해주는 훌륭한 도구가 있는데, 그게 바로 numpy 배열입니다. 결국 numpy는 다양한 차원의 행렬을 numpy 배열로 표현해서 numpy가 제공하는 다양한 메쏘드를 이용하면 번거로운 계산작업을 손쉽게 할 수 있습니다. 

아무튼 입력값이나 가중치는 행렬로 취급하면 계산이 편리해지므로 행렬의 연산방법과 numpy에 대해 익숙해지면 좋겠지요~




>>> def predict(self, X):

          return np.where(self.net_input(X) > self.thresholds, 1, -1)


self.net_input(X)의 값, 순입력 함수 결과값이 임계값인 self.thresholds의 값보다 크면 1, 그렇지 않으면 -1을 리턴하는 코드입니다. 따라서 이 함수는 활성 함수를 구현한 것입니다.


이제 AND 연산에 대해 퍼셉트론을 수행하는 perceptron_and.py 코드입니다.


perceptron_and.py 



>>> from mylib.perceptron import Perceptron


mylib 폴더에 있는 perceptron.py에 있는 Perceptron 클래스를 임포트합니다.



>>> if __name__ == '__main__':

          X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])

          y = np.array([-1, -1, -1, 1])


          ppn = Perceptron(eta=0.1)

          ppn.fit(X, y)

          print(ppn.error_)


X는 AND 연산에 대한 트레이닝 데이터를 정의한 것입니다. 트레이닝 데이터에 대한 실제 결과값에 대한 활성 함수 리턴값은 임계값 0보다 크면 1, 그렇지 않으면 -1이므로 y를 이 값들로 바로 정의합니다.


learning rate 에타값을 0.1로 두고 Perceptron 객체를 생성하고 이 객체의 fit() 멤버 함수를 호출함으로써 퍼셉트론 알고리즘을 구동합니다. perceptron_and.py를 구동하면 아래와 같은 학습 결과가 나옵니다.


[ 0.2  0.2  0.2]
[ 0.   0.4  0.2]
[-0.2  0.4  0.2]
[-0.2  0.4  0.4]
[-0.4  0.4  0.2]
[-0.4  0.4  0.2]
[-0.4  0.4  0.2]
[-0.4  0.4  0.2]
[-0.4  0.4  0.2]
[-0.4  0.4  0.2]

[1, 3, 3, 2, 1, 0, 0, 0, 0, 0]



이 결과를 보면 5번째 이후로 가중치의 업데이트가 발생하지 않기 때문에, 6번째 학습에서 AND 연산의 최종 학습이 마무리 되었다고 판단할 수 있습니다. 


반응형
반응형

이제 [2편]에서 설명했던 퍼셉트론 알고리즘을 파이썬으로 구현해 봅니다.



먼저, 논리곱 AND 연산에 대해 퍼셉트론 알고리즘으로 머신러닝을 수행하는 예를 보면서 이해를 해보도록 합니다.

참을 1, 거짓을 0으로 정의할 때, 논리곱 AND 연산은 다음과 같이 정의됩니다.


0 AND 0 = 0

0 AND 1 = 0

1 AND 0 = 0

1 AND 1 = 1


퍼셉트론 알고리즘을 이용해 AND 연산을 학습시키는 것이 우리의 목표입니다.

AND 연산을 위해 필요한 트레이닝 데이터는 위에서 보인 4가지이며, 트레이닝 데이터의 특성값은 0과 1 2개입니다. 이를 이전 포스팅에서 정의한 표를 이용해 나타내보면 다음과 같습니다.



퍼셉트론 알고리즘에서 보통 0 또는 매우 작은 값을 가중치의 초기값으로 둔다고 했는데, 우리의 알고리즘에서는 초기 가중치의 값을 모두 0으로 둡니다.


learning rate η는 0.1로, 임계값은 0으로 정의하고, 순입력 함수 결과값이 임계값 0보다 크면 1, 그렇지 않으면 -1로 정의해봅니다.




자, 이전 포스팅에서 소개된 알고리즘 원리에 따라 퍼셉트론 학습을 수행해봅니다.


1. 트레이닝 데이터1에 대해 순입력 함수 리턴값을 계산합니다.

w0 x0 + w1 x1 + w2 x2 = 0.0 x1 + 0.0 x 0 + 0.0 x 0 = 0


2. 순입력 함수의 리턴값과 임계값 0을 비교하면 0보다 크지 않으므로 활성 함수 리턴값은 -1이 됩니다. 이는 실제 결과값에 대한 활성 함수 리턴값과 일치하므로 가중치 업데이트 없이 다음 트레이닝 데이터 2로 넘어갑니다.


3. 트레이닝 데이터2~3까지는 예측된 값의 활성 함수 리턴값과 실제 결과값의 활성 함수 리턴값이 일치하므로 가중치 업데이트 없이 트레이닝 데이터4로 넘어갑니다.


4. 트레이닝 데이터4에 대해 1과 같이 계산한 퍼셉트론 예측값의 활성 함수 리턴값은 -1로 나왔지만 실제 결과값의 활성 함수 리턴값은 1이므로 예측값과 결과값이 다릅니다. 따라서 아래의 식에 의해 가중치를 다시 계산합니다.



w0 = w0 + 0.1(1-(-1))1 = 0 + 0.2 = 0.2

w1 w1 + 0.1(1-(-1))1 = 0 + 0.2 = 0.2

w2 w2 + 0.1(1-(-1))1 = 0 + 0.2 = 0.2


5. 트레이닝 데이터4에서 가중치가 업데이트 되었으므로 새롭게 업데이트 된 가중치를 가지고 트레이닝 데이터1부터 1의 과정을 다시 수행합니다.


이런 식으로 트레이닝 데이터1~4에 대한 예측값의 활성 함수 리턴값과 실제 결과값의 활성 함수 리턴값이 같아지도록 가중치를 계속 업데이트해보면

w0 = -0.4, w1 = 0.4, w2 = 0.2 일 때, 모든 트레이닝 데이터에 대한 예측값과 결과값의 활성 함수 리턴값이 같아집니다.


이와 같은 퍼셉트론 알고리즘은 다음과 같은 파이썬 코드로 구현됩니다. 참고로 퍼셉트론 알고리즘을 구현한 아래의 코드는 Sebastian Raschka의 'Python Machine Learning'책에 있는 코드입니다. 아래의 코드를 작성하고 perceptron.py로 저장합니다.

 

perceptron.py 



나의 경우 mylib이라는 이름의 폴더를 만들고 여기에 perceptron.py를 저장해두었습니다. 여러분들의 작업 폴더가 c:/mywork 이라면 하위 폴더로 c:/mywork/mylib을 만들고 mylib에 여러분들이 작성한 라이브러리들을 저장해두면 관리하기가 쉽습니다.


아래의 코드를 작성하고, perceptron_and.py라는 이름으로 작업 폴더에 저장합니다.


perceptron_and.py



다음 포스팅에서는 perceptron.py와 perceptron_and.py 코드에 대해 가볍게 설명하고 머신러닝을 위한 훌륭한 샘플 데이터인 아이리스(iris; 우리말로 붓꽃)의 품종 분류에 대한 트레이닝 데이터를 이용해 머신러닝을 수행하는 것에 대해 살펴보겠습니다. 


반응형
반응형

 [출처] [2편] 퍼셉트론(Perceptron) - 인공신경망의 기초개념|작성자 옥수별


퍼셉트론(Perceptron)

​인공지능(AI)은 우리 사람의 뇌를 흉내내는 인공신경망과 다양한 머신러닝 알고리즘을 통해 구현됩니다. 이전 포스팅에서 잠시 언급했던 딥러닝 알고리즘이 현재 가장 널리 사용되는 인공지능을 위한 알고리즘이죠. 이런 딥러닝도 수십년전에 개념이 정립되었던 초기 인공신경망으로부터 발전된 것이라 볼 수 있습니다.


여기서 소개할 퍼셉트론과 퍼셉트론을 좀 더 발전시킨 아달라인은 이해하기가 다소 까다로울수 있지만 앞으로 계속 등장할 머신러닝의 다양한 내용을 위한 기초 개념이 되므로 이해하고 넘어가는 것이 좋습니다.


우리 사람의 뇌는 신경계를 구성하는 주된 세포인 뉴런(neuron)을 약 1000억개 정도 가지고 있으며뉴런들은 시냅스라는 구조를 통해 전기화학적 신호를 주고 받음으로써 다양한 정보를 받아들이고그 정보를 저장하는 기능을 수행합니다.

아래 그림은 하나의 뉴런에서 신호를 입력 받고 그에 대한 결과 신호를 출력하는 개념을 도식화 한 것입니다.


​우리 사람의 뇌속에는 위 그림과 같은 뉴런이 1000억개 가까이 서로가 다층적으로 복잡하게 연결되어 있는 것이죠. 이런 뇌 구조로 뇌활동이 일어나는 것이며, 인간이 수행하는 모든 일(생각하고, 배우고, 창조하고, 기억하고, 느끼고,, 아파하고,, 등등 모든 것)에 관여하고 있습니다.

1943년 신경과학자인 Warren S. McCulloch과 논리학자인 Walter Pitts는 하나의 사람 뇌 신경세포를 하나의 이진(Binary)출력을 가지는 단순 논리 게이트로 설명했습니다.

위 그림에서 여러 개의 입력 신호가 가지돌기(Dendrite)에 도착하면 신경세포 내에서 이들을 하나의 신호로 통합하고통합된 신호 값이 어떤 임계값을 초과하면 하나의 단일 신호가 생성되며이 신호가 축삭돌기(Axon)를 통해 다른 신경세포로 전달하는 것으로 이해했습니다이렇게 단순화 된 원리로 동작하는 뇌 세포를 McCulloch-Pitts뉴런(MCP 뉴런)이라 부릅니다.

 

1957년 코넬 항공 연구소에 근무하던 Frank Rosenblatt은 MCP 뉴런 모델을 기초로 퍼셉트론(Perceptron) 학습 규칙이라는 개념을 고안하게 되는데, Rosenblatt은 하나의 MCP 뉴런이 출력신호를 발생할지 안할지 결정하기 위해, MCP 뉴런으로 들어오는 각 입력값에 곱해지는 가중치 값을 자동적으로 학습하는 알고리즘을 제안했습니다.

이 알고리즘은 머신러닝의 지도학습이나 뷴류(classification)의 맥락에서 볼 때, 하나의 샘플이 어떤 클래스에 속해 있는지 예측하는데 사용될 수 있습니다.


아래 그림은 Rosenblatt이 제안한 퍼셉트론 알고리즘 개념을 도식화 한 것입니다.




여기서 x0~xn은 퍼셉트론 알고리즘으로 입력되는 값이며, w0~wn은 각각 x0~xn에 곱해지는 가중치입니다. 입력값은 보통 분류를 위한 데이터의 특성(feature)을 나타내는 값으로 이루어져 있으며, 이 특성값 x0~xn에 가중치 w0~wn을 곱한 값을 모두 더하여 하나의 값으로 만듭니다. 이 값을 만드는 함수를 순입력 함수(net input 함수)라고 부릅니다. 순입력 함수의 결과값을 특정 임계값과 비교를 하고, 순입력 함수 결과값이 이 임계값보다 크면 1, 그렇지 않으면 -1로 출력하는 함수를 정의합니다. 이 함수를 활성 함수(Activation function)라고 부릅니다.


퍼셉트론은 다수의 트레이닝 데이터를 이용하여 일종의 지도 학습을 수행하는 알고리즘입니다. 트레이닝 데이터에는 데이터의 특성값에 대응되는 실제 결과값을 가지고 있어야 합니다. 입력되는 특성값 x0~xn에 대한 실제 결과값을 y라고 한다면 이 y를 활성 함수에 의해 -1 또는 1로 변환합니다. 이렇게 변환한 값과 퍼셉트론 알고리즘에 의해 예측된 값이 다르면 이 두 개의 값이 같아질 때까지 특정식에 의해 가중치 w0~wn을 업데이트 합니다.


위 그림을 보다 단순하게 도식화하면 다음 그림과 같이 입력층, 중간층, 출력층과 같이 나타낼 수 있습니다.


여기서 중간층을 노드 또는 뉴런이라 부르며, 입력층은 다른 노드의 출력값이 입력값으로 입력되는 것이고, 출력층은 이 노드의 출력값이 다른 노드로 전달되는 층이라고 생각하면 됩니다.


이와 같이 중간층이 하나의 노드로 구성되어 중간층과 출력층의 구분이 없는 구조를 단순 또는 단층 퍼셉트론이라 부르며, 중간층을 구성하는 노드가 여러 개이고, 이러한 중간층이 다수로 구성되어 있는 구조를 다층 퍼셉트론이라 부릅니다. 


아래는 다층 퍼셉트론을 응용한 인공신경망 구조의 한 예시입니다.


이러한 다층 인공신경망을 학습하는 알고리즘을 딥 러닝(Deep Learning)이라고 말합니다.


이것이 퍼셉트론의 핵심입니다. 그러면 좀 더 자세하게 살펴보도록 하겠습니다. 


MCP 뉴런과 Rosenblatt의 퍼셉트론 모델은 사람 뇌의 단일 뉴런이 작동하는 방법을 흉내내기 위해 환원 접근법(redcutionist approach)을 이용합니다. 이는 초기 가중치를 임의의 값으로 정의하고 예측값의 활성 함수 리턴값과 실제 결과값의 활성 함수 리턴값이 동일하게 나올 때까지 가중치의 값을 계속 수정하는 방법입니다.


Rosenblatt의 초기 퍼셉트론 알고리즘을 요약하면 다음과 같습니다.

  1. 입력되는 특성값에 곱해지는 가중치 w 값들을 모두 0 또는 작은 값으로 무작위 할당함
  2. 임계값을 정의함(보통 0으로 정의합니다.)
  3. 트레이닝 데이터 샘플 x를 순입력 함수를 이용해 가중치와 각각 곱한 후 그 총합을 구함
  4. 활성 함수를 이용해 트레이닝 데이터 샘플에 대한 예측값을 -1 또는 1로 결과가 나오게 함
  5. 트레이닝 데이터 샘플의 실제 결과값에 대한 활성 함수 리턴값과 4에서 나온 예측값을 비교함
  6. 예측값과 결과값이 다르면 모든 가중치 w 업데이트하고 3의 과정부터 다시 시작함
  7. 예측값과 결과값이 동일하게 나오면 패스


자, 아래와 같이 n개의 특성값을 가진 m개의 트레이닝 데이터가 있다고 가정합니다.



이 트레이닝 데이터로 퍼셉트론 알고리즘을 이용해 머신러닝을 수행하려고 합니다. Rosenblatt의 퍼셉트론 알고리즘에서 x0는 실제 트레이닝 데이터의 특성값과는 무관하며 x0의 초기값으로 보통 1로 둡니다. 퍼셉트론 알고리즘에서 x0를 바이어스(bias)라 부릅니다. 따라서 실제 트레이닝 데이터의 특성값은 x1부터 시작한다고 보면 됩니다.


이제 우리가 1로 정한 x0의 값과 n개의 트레이닝 데이터의 각 특성값 x1~xn과 이 특성값에 곱할 가중치 w1~wn의 값을 0과 1사이의 임의의 값으로 랜덤하게 할당한 후 위 표를 아래와 같이 다시 구성해 봅니다.


트레이닝 데이터1~m까지 각 특성값에 대해 퍼셉트론 알고리즘을 구동합니다. 퍼셉트론 알고리즘에서 가중치를 업데이트 하는 식은 다음과 같이 정의됩니다.



여기서 wj 는 트레이닝 데이터의 j번째 특성값 xj와 곱하는 가중치이며, y는 트레이닝 데이터의 실제 결과값에 대한 활성 함수 리턴값, y^은 예측값에 대한 활성 함수 리턴값입니다. (y - y^) 앞에 곱한 그리스 문자 η는 학습률(learning rate)이라 하며, Rosenblatt의 퍼셉트론에서는 learning rate을 매우 작은 값으로 할당합니다.


퍼셉트론 알고리즘은 다음과 같은 방식으로 구동합니다.


  1. ​w0~wn의 값을 0.0, 임계값 0.0, 에타값은 0.1로 둡니다.
  2. 트레이닝 데이터1의 특성값들에 대한 예측값의 활성 함수 리턴값을 계산합니다.
  3. 2에서 계산된 값이 이 실제 결과값의 활성 함수 리턴값과 같으면 가중치 업데이트 없이 다음 트레이닝 데이터에 대해 2번 과정으로 넘어갑니다.
  4. 예측값의 활성 함수 리턴값이 실제 결과값의 활성 함수 리턴값과 다르면 위 식에 의해 w0~wn의 가중치를 업데이트 합니다. 
  5. 트레이닝 데이터2~m에 대해서 2~4를 반복합니다.


위와 같은 절차로 트레이닝 데이터1~m까지 모든 예측값이 실제 결과값과 동일해질 때까지 반복합니다. 트레이닝 데이터1~m까지 예측값에 대한 활성 함수 리턴값이 실제 결과값의 활성 함수 리턴값과 동일하면 퍼셉트론 학습은 종료됩니다.


퍼셉트론에 대한 개략적인 개념을 소개했으니, 다음 포스팅에서는 퍼셉트론 알고리즘을 파이썬으로 구현해보고, 실제 몇 개의 데이터 세트를 이용해 머신러닝을 수행해보도록 하겠습니다. 


반응형
반응형

 [출처] [1편] 인공지능과 머신러닝|작성자 옥수별


2016년 3월, 구글의 자회사 딥마인드가 만든 알파고가 세계적인 바둑기사 이세돌을 4승1패로 이긴 이후, 우리나라뿐만 아니라 전세계적으로 인공지능(AI, Artificial Intelligence)에 대한 관심이 증폭되었습니다.


사실, 인공지능이라는 개념은 1956년 소수의 수학자와 과학자들이 모인 다트머스 회의에서 처음으로 탄생했습니다. 하지만 여러가지 이유로 인해 인공지능은 한동안 관심사에서 멀어지게 되고 등한시되었습니다.


그러다가 IBM의 인공지능 딥블루가 1997년 체스경기에서 인간 챔피언을 이겨버렸고, 이후 컴퓨팅 기술의 발전과 HW의 고사양화, 빅데이터 시대를 맞이하면서 인공지능은 진화하게 됩니다. 다시말하면, 1990년대 중반까지 인공지능이 선험적인 경험과 지식을 활용하는 형태였다면, 2000년대 이후에는 기계 스스로 대량의 데이터를 통해 스스로 지식을 찾아내는 방식으로 진화하게 된 것입니다.


기계 스스로 대량의 데이터로부터 지식이나 패턴을 찾아내어 학습하는 것을 우리는 '머신러닝(Machine Learning)'이라 부르고, 우리말로는 '기계학습'이라 부릅니다. 그리고 머신러닝 연구의 한부분인 인공신경망 분야에 '딥러닝(Deep Learning)'이라는 새로운 방식으로 인해 대전환점을 맞이하게 됩니다.




2017년 IT 키워드중 하나가 바로 인공지능이며, 인공지능의 기반이 되는 머신러닝이 이제 대세가 되는 시대가 된것 같습니다.


이에 따라 이번 포스팅 시리즈는 파이썬을 이용하여 머신러닝을 구현하는데 필요한 내용을 정리하고 학습하도록 하겠습니다. 포스팅에서 다룰 내용은 대부분이 각종 사이트에서 발췌하여 제가 응용한 것들이며, 참고 서적으로는 Sebastian Raschka가 지은 'Python Machine Learning'이라는 책입니다.


또 다시 파이썬 예찬론을 펼치게 되는데요, 파이썬은 언어자체의 문법이 깔끔하고 영어와 유사하며 온갖 용도로 사용되는 가장 인기있는 언어중 하나입니다. 특히, 데이터의 처리와 분석에 관련된 다양한 라이브러리들이 있어 데이터 과학자들 사이에 입지가 튼튼하며, 인공지능을 구현하는데 인기도 면에서 2위를 달리는 프로그래밍 언어입니다. 이쯤에서 파이썬 예찬론은 그만하겠습니다.


파이썬에 대해 완전 초보이신 분들은 제가 정말 쉽게 쉽게 쓴, '초보자를 위한 파이썬200(정보문화사)'로 학습하시면 도움이 될 것 같습니다~^^


아무튼 이번 포스팅에서는, 머신러닝에 대한 대략적인 기초 지식을 살펴보고, 본격적인 내용에 대해 포스팅하도록 하겠습니다. 아마 OpenCV 강좌처럼 장대한(?) 여정이 될 것 같군요~


머신러닝은 주어진 데이터를 바탕으로 새로운 질문에 대해 예측하는 것이 최종 목적이며, 이러한 머신러닝을 학습방법에 따라 구분하면 다음과 같은 것들이 있습니다. 이들에 대한 자세한 내용은 강좌를 진행하면서 서술하겠습니다.


  • 지도학습(Supervised Learning)
    • 컴퓨터에 학습을 시킬 때 사람이 개입하여 정답을 달아주는 방식으로 학습을 진행합니다. 예를 들어 손으로 쓴 숫자를 인식하기 위해 컴퓨터에 학습을 시킬 때 최초 일정량의 데이터에 대해서 사람이 이 녀석인 1이야, 저 녀석은 8이야 하는 식으로 각 숫자에 대해 라벨을 달아주어 컴퓨터에 학습을 시킵니다. 제 블로그의 OpenCV 강좌 중 머신러닝 기초2 - KNN을 이용하여 손글씨 인식하기가 지도학습을 이용한 머신러닝의 한 예입니다.
    • 지도학습에는 다음과 같은 것들이 있습니다.
      • 분류(Classification)
      • 회귀(Regression)
  • 비지도학습(Unsupervised Learning)
    • 사람의 개입없이 컴퓨터 스스로가 라벨링 되어 있지 않은 데이터에 대해 학습을 하는 방식입니다. 이 세상에 존재하는 대부분의 데이터는 라벨이 붙어 있지 않습니다. 따라서 비지도학습은 머신러닝이 나아갈 방향입니다.
    • 비지도학습에는 다음과 같은 것들이 있습니다.
      • 군집화(Clustering)
      • 분포추정(Underlying Probability Density Estimation)
  • 강화학습(Reinforcement Learning)
    • 강화학습은 기계 혹은 컴퓨터로 하여금 현태 상태에서 어떤 행동을 취하는 것이 최적의 것인가를 학습하는 것입니다. 하나의 행동을 취할때 마다 외부에서 보상(Reward)이 주어지며, 이 보상이 최대화하는 방향으로 학습이 진행됩니다. 외부에서 주어지는 보상은 행동을 취한 후 바로 주어질 수도 있고, 조금 있다가 줄 수도 있습니다.  


머신러닝을 구현하기 위한 알고리즘에는 다음과 같은 것들이 있으며, 자세한 내용은 강좌를 진행하면서 서술하겠습니다.

  • 경사하강법(Gradient Descent)
  • 회귀기법
    • 선형 회귀(Linear Regression)
    • 로지스틱 회귀(Logistic Regression)
  • 확률기반
    • 나이브 베이즈 분류기(Naive Bayes Classifier; NBC)
    • 은닉 마르코프 모델(Hidden Markov Model; HMM)
  • 기하기반
    • k-Means Clustering(kMC)
    • k-Nearest Neighbors(kNN)
    • Support Vector Machine(SVM)
  • 인공신경망
    • Perceptron
    • Multi Layer Perceptron(MLP)
    • Deep Neural Network


이번 머신러닝 포스팅 시리즈에 소개되는 대부분의 내용은 Sebastian Raschka의 Python Machine Learning이라는 책을 참고하였으며, 다양한 인터넷 사이트의 내용들을 참고하였습니다.



포스팅에서 다루는 파이썬 버전은 3.5.x 이므로 2.7.x 버전 사용자는 참고하시기 바랍니다. 그리고 앞으로 게시될 머신러닝 포스팅 시리즈에 등장하는 많은 수의 소스코드는 기본적으로 Python Machine Learning에 있는 소스코드를 토대로 작성되었으며, 필요시 제가 수정한 부분도 있다는 사실을 미리 알려둡니다.


먼저, 윈도우 명령 프롬프트를 구동하고 아래와 같이 필요한 패키지부터 설치합니다.


pip install numpy

pip install scipy

pip install matplotlib

pip install scikit-learn

pip install pandas​


이제 준비는 끝났으므로 다음 포스팅부터 본격적으로 공부를 시작해보도록 하겠습니다. 


반응형
반응형

Chapter3. 사이킷런을 사용한 머신러닝 분류기.


사이킷런을 이용한 퍼셉트론 훈련.


from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
#print(iris)
X = iris.data[:, [2, 3]] # petal length, petal width
y = iris.target

print('Class labels: ', np.unique(y))

# 훈련용과 테스트용으로 분리
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

from sklearn.linear_model import Perceptron
ppn = Perceptron(max_iter=40, eta0=0.1, random_state=0)
ppn.fit(X_train_std, y_train)

y_pred = ppn.predict(X_test_std)
print('y_pred: ', y_pred)
print('y_test: ', y_test)
print('Misclassified samples : %d' % (y_test != y_pred).sum()) # 4 / 45(test_data) = 0.089

# 테스트 데이터에 대한 퍼셉트론의 분류 정확도
from sklearn.metrics import accuracy_score
print('Accuracy: %.2f' % accuracy_score(y_test, y_pred)) # y_test : 진분류, y_pred : 예측 분류

# 테스트 데이터로부터의 샘플인 것을 강조하기 위해 테스트 데이터에는 작은 원을 더하도록 변경.
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt

def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
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.4, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())

#plot all samples
X_test, y_test = X[test_idx, :], y[test_idx]
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=cmap(idx), marker=markers[idx], label=cl)

if test_idx:
X_test, y_test = X[test_idx, :], y[test_idx]
plt.scatter(X_test[:, 0], X_test[:, 1], c='', alpha=1.0, linewidth=1, marker='o', s=55, label='test set')

X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))
#print('X_combined_std: ', X_combined_std)
#print('y_combined: ', y_combined)
plot_decision_regions(X=X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()


# 시그모이드
import matplotlib.pyplot as plt
import numpy as np

def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))

z = np.arange(-7, 7, 0.1)
#print('z: ', z)
phi_z = sigmoid(z)
#print('phi_z: ', phi_z)
plt.plot(z, phi_z)
plt.axvline(0.0, color='k') # x축 중앙선
plt.axhspan(0.0, 1.0, facecolor='1.0', alpha=1.0, ls='dotted')
plt.axhline(y=0.5, ls='dotted', color='k') # y축 중앙선
plt.yticks([0.0, 0.5, 1.0])
plt.ylim(-0.1, 1.1)
plt.xlabel('z')
plt.ylabel('$\phi (z)$')
plt.show()


# 사이킷런으로 로지스틱 회귀 모델 훈련하기
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(C=1000.0, random_state=0)
lr.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=lr, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()


'''오버피팅(overfitting) - 훈련 데이터에 대한 모델의 성능은 좋으나 경험하지 못한 데이터(테스트 데이터)
대해서는 잘 일반화되지 못하는 것.
'''

# 역정규화 파라미터 C에 대해 여러 가지 값을 갖는 10개의 로지스틱 회귀 모델을 피팅했다.
# 파라미터 C를 감소시키면(, 정규화 강도를 증가시키면) 가중계수는 수축한다.
weights, params = [], []
for c in np.arange(-5, 5):
lr = LogisticRegression(C=10**np.float32(c), random_state=0)
lr.fit(X_train_std, y_train)
weights.append(lr.coef_[1])
params.append(10**np.float32(c))

weights = np.array(weights)

plt.plot(params, weights[:, 0], label='petal length')
plt.plot(params, weights[:, 1], label='petal width', linestyle='--')
plt.ylabel('weight coefficient')
plt.xlabel('C')
plt.legend(loc='upper left')
plt.xscale('log')
plt.show()

# 서포트 벡터 머신의 마진 분류 최대화
# SVM 모델 훈련
from sklearn.svm import SVC
svm = SVC(kernel='linear', C=1.0, random_state=0)
svm.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()


# 퍼셉트론, 로지스틱 회귀, 서포트 벡터 머신의 확률적 그래디언트 디센트 버전을 초기 설정
from sklearn.linear_model import SGDClassifier
ppn = SGDClassifier(loss='perceptron')
lr = SGDClassifier(loss='log')
svm = SGDClassifier(loss='hinge')

np.random.seed(0)
X_xor = np.random.randn(200, 2)
#print('X_xor: ', X_xor)
y_xor = np.logical_xor(X_xor[:, 0] > 0, X_xor[:, 1] > 0)
#print('y_xor: ', y_xor)
y_xor = np.where(y_xor, 1, -1)

#print('X_xor[y_xor==1, 0]: ', X_xor[y_xor==1, 0])
#print('X_xor[y_xor==1, 1]: ', X_xor[y_xor==1, 1])
plt.scatter(X_xor[y_xor==1, 0], X_xor[y_xor==1, 1], c='b', marker='x', label='1')
plt.scatter(X_xor[y_xor==-1, 0], X_xor[y_xor==-1, 1], c='r', marker='s', label='-1')
plt.ylim(-3.0)
plt.legend()
plt.show()


svm = SVC(kernel='rbf', random_state=0, gamma=0.10, C=10.0)
svm.fit(X_xor, y_xor)

plot_decision_regions(X_xor, y_xor, classifier=svm)
plt.legend(loc='upper left')
plt.show()


svm = SVC(kernel='rbf', random_state=0, gamma=0.2, C=1.0)
svm.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()

svm = SVC(kernel='rbf', random_state=0, gamma=100.0, C=1.0)
svm.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.show()

# 의사결정나무 학습
import matplotlib.pyplot as plt
import numpy as np
def gini(p):
return (p)*(1 - (p)) + (1 - p)*(1 - (1-p))

def entropy(p):
return - p*np.log2(p) - (1 - p)*np.log2((1 - p))

def error(p):
return 1 - np.max([p, 1 - p])

x = np.arange(0.0, 1.0, 0.01)
ent = [entropy(p) if p != 0 else None for p in x]
sc_ent = [e*0.5 if e else None for e in ent]
err = [error(i) for i in x]
fig = plt.figure()
ax = plt.subplot(111)
for i, lab, ls, c, in zip([ent, sc_ent, gini(x), err],
['Entropy', 'Entropy (scaled)', 'Gini Impurity', 'Misclassification Error'],
['-', '-', '--', '-.'],
['black', 'lightgray', 'red', 'green', 'cyan']):
line = ax.plot(x, i, label=lab, linestyle=ls, lw=2, color=c)

ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=3, fancybox=True, shadow=False)
ax.axhline(y=0.5, linewidth=1, color='k', linestyle='--')
ax.axhline(y=1.0, linewidth=1, color='k', linestyle='--')
plt.ylim([0, 1.1])
plt.xlabel('p(i=1)')
plt.ylabel('Impurity Index')
plt.show()

# 의사결정나무 만들기
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=0)
tree.fit(X_train, y_train)
X_combined = np.vstack((X_train, X_test))
y_combined = np.hstack((y_train, y_test))
print('X_train: ', X_train)
print('y_train: ', y_train)
plot_decision_regions(X_combined, y_combined, classifier=tree, test_idx=range(105, 150))
plt.xlabel('petal length [cm]')
plt.ylabel('petal width [cm]')
plt.legend(loc='upper left')
plt.show()

from sklearn.tree import export_graphviz
export_graphviz(tree, out_file='tree.dot', feature_names=['petal length', 'petal width'])


from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier(criterion='entropy', n_estimators=10, random_state=1, n_jobs=2)
forest.fit(X_train, y_train)

plot_decision_regions(X_combined, y_combined, classifier=forest, test_idx=range(105,150))
plt.xlabel('petal length')
plt.ylabel('petal width')
plt.legend(loc='upper left')
plt.show()

# 유클리디안(Euclidean) 거리 메트릭을 사용하여 사이킷런에서 KNN 모델 구현
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski')
knn.fit(X_train_std, y_train)

plot_decision_regions(X_combined_std, y_combined, classifier=knn, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.show()


반응형
반응형
import numpy as np

class Perceptron(object):
def __init__(self, eta=0.01, n_iter=10):
self.eta = eta
self.n_iter = n_iter

def fit(self, X, y):
self.w_ = np.zeros(1 + X.shape[1])
#print('X.shape: ', X.shape)
#print('X.shape[1]: ', X.shape[1])
#print('self.w_ :', self.w_)
#print('zip(X, y): ', list(zip(X, y)))
self.errors_ = []
for _ in range(self.n_iter):
errors = 0
for xi, target in zip(X, y):
#print('xi :', xi)
#print('target :', target)
update = self.eta * (target - self.predict(xi))
#print('self.predict(xi) :', self.predict(xi))
#print('update :', update)
self.w_[1:] += update * xi
self.w_[0] += update
#print('self.w_[:] : ', self.w_[:])
errors += int(update != 0.0)
self.errors_.append(errors)
print('self.errors_: ', self.errors_)
return self

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

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

import pandas as pd

df = pd.read_csv('https://archive.ics.uci.edu/ml/'
'machine-learning-databases/iris/iris.data', header=None)
df.tail()

####%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

# select setosa and versicolor
y = df.iloc[0:100, 4].values
#print('y :', y)
y = np.where(y == 'Iris-setosa', -1, 1)

# extract sepal length and petal length
X = df.iloc[0:100, [0, 2]].values
#print('X: ', X)
# plot data
plt.scatter(X[:50, 0], X[:50, 1],
color='red', marker='o', label='setosa')
plt.scatter(X[50:100, 0], X[50:100, 1],
color='blue', marker='x', label='versicolor')

plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')

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



ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(X, y)
#print('X: ', X)
#print('y: ', y)
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Number of misclassifications')
plt.show()


from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])

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))
#print('xx1: ', xx1)
#print('xx1.ravel(): ', xx1.ravel())
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())

for idx, cl in enumerate(np.unique(y)):
#print('X[y == cl, 0]: ', X[y == cl, 0])
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.8, c=cmap(idx),
marker=markers[idx], label=cl)

plot_decision_regions(X, y, classifier=ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')
plt.show()


X_std = np.copy(X)
X_std[:, 0] = (X[:, 0] - X[:, 0].mean()) / X[:, 0].std()
X_std[:, 1] = (X[:, 1] - X[:, 1].mean()) / X[:, 1].std()


class AdalineGD(object):
def __init__(self, eta=0.01, n_iter=50):
self.eta = eta
self.n_iter = n_iter

def fit(self, X, y):
self.w_ = np.zeros(1 + X.shape[1])
self.cost_ = []

for i in range(self.n_iter):
output = self.net_input(X)
errors = (y - output)
# print(errors)
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):
return np.dot(X, self.w_[1:]) + self.w_[0]

def activation(self, X):
return self.net_input(X)

def predict(self, X):
return np.where(self.activation(X) >= 0.0, 1, -1)


fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(8, 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.tight_layout()
# plt.savefig('./adaline_1.png', dpi=300)
plt.show()

ada = AdalineGD(n_iter=15, eta=0.01)
ada.fit(X_std, y)
plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.show()

plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Sum-squared-error')
plt.show()


from numpy.random import seed

class AdalineSGD(object):
def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
self.eta = eta
self.n_iter = n_iter
self.w_initialized = False
self.shuffle = shuffle
if random_state:
seed(random_state)

def fit(self, X, y):
self._initialize_weights(X.shape[1])
self.cost_ = []
for i in range(self.n_iter):
if self.shuffle:
X, y = self._shuffle(X, y)
cost = []
for xi, target in zip(X, y):
cost.append(self._update_weights(xi, target))
avg_cost = sum(cost)/len(y)
self.cost_.append(avg_cost)
return self

def partial_fit(self, X, y):
if not self.w_initialized:
self._initialize_weights(X.shape[1])
if y.ravel().shape[0] > 1: #ravel -> [1, 2, 3], [4, 5, 6] -> [1, 2, 3, 4, 5, 6]
for xi, target in zip(X, y):
self._update_weights(xi, target)
else:
self._update_weights(X, y)
return self

def _shuffle(self, X, y):
r = np.random.permutation(len(y)) # 순서를 임의로 바꾸거나 임의의 순열을 반환한다.
return X[r], y[r]

def _initialize_weights(self, m):
self.w_ = np.zeros(1 + m)
self.w_initialized = True

def _update_weights(self, xi, target):
output = self.net_input(xi)
error = (target - output)
self.w_[1:] += self.eta * xi.dot(error)
self.w_[0] += self.eta * error
cost = 0.5 * error ** 2
return cost

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

def activation(self, X):
return self.net_input(X)

def predict(self, X):
return np.where(self.activation(X) >= 0.0, 1, -1)


ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
print('X_std: ', X_std)
print('y: ', y)
ada.fit(X_std, y)
#ada.partial_fit(X_std[0, :], y[0])
plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Stochastic Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.show()
plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Averages Cost')
plt.show()


반응형

+ Recent posts

반응형