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


이런 문제점을 극복하기 위한 방법으로 확률적 경사하강법(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​


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


>>>t = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]


>>>x = np.array(t)


>>>type(x)


>>>x.shape

(3, 4)


>>>x.ravel()

array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])


>>>x.ravel().shape

(12,)


>>>x.shape = (6, 2)


>>>x

array([[1, 2],

[3, 4],

[5, 6],

[7, 8],

[9, 10],

[11, 12]])


>>>x.transpose()

array([[1, 3, 5, 7, 9, 11],

[2, 4, 6, 8, 10, 12]])


>>>x.resize((2, 6))

>>>x

array([[1, 2, 3, 4, 5, 6],

[7, 8, 9, 10, 11, 12]

'Program > Python' 카테고리의 다른 글

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 0: invalid start byte  (0) 2017.12.18
python iloc 와 loc 의 차이  (0) 2017.12.17
numpy 수학함수  (0) 2017.12.02
numpy 함수  (0) 2017.12.02
numpy meshgrid  (0) 2017.12.02


# 파이프라인을 가진 스트리밍 워크플로우

'''
위스콘신 유방암 데이터
이 데이터는 악성(malignant)과 양성(benign)의 암세포로 구성된 569개 샘플을 포함.
~2 열까지는 각 샘플의 ID숫자와 이들의 진단 결과값을 저장
3 ~ 32 열은 30개의 실제 값으로 이루어진 피처
'''

import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header=None)

from sklearn.preprocessing import LabelEncoder
X = df.loc[:, 2:].values
y = df.loc[:, 1].values
print('df: ', df)
print('X: ', X)
print('y: ', y)
le = LabelEncoder()
y = le.fit_transform(y)
print('y-transform: ', y)

print(le.transform(['M', 'B']))

from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=1)
print('X_train: ', X_train)
print('y_train: ', y_train)
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
pipe_lr = Pipeline([('scl', StandardScaler()),
('pca', PCA(n_components=2)),
('clf', LogisticRegression(random_state=1))])
pipe_lr.fit(X_train, y_train)
print('Test Accuracy: %.3f' % pipe_lr.score(X_test, y_test))


# k-fold 교차검증을 사용하여 모델 성능 평가
# - 일반화 성능을 만족시키는 최적의 하이퍼파라미터 값을 찾기 위한 모델 튜닝에 사용
import numpy as np
from sklearn.cross_validation import StratifiedKFold
kfold = StratifiedKFold(y=y_train, n_folds=10, random_state=1)
#print('kfold: ', kfold)
scores = []
for k, (train, test) in enumerate(kfold):
# print('train: ', train, 'test: ', test)
pipe_lr.fit(X_train[train], y_train[train])
score = pipe_lr.score(X_train[test], y_train[test])
scores.append(score)
print('Fold: %s, Class dist.: %s, Acc: %.3f' % (k+1, np.bincount(y_train[train]), score))
print('CV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))


from sklearn.cross_validation import cross_val_score
scores = cross_val_score(estimator=pipe_lr, X=X_train, y=y_train, cv=10, n_jobs=1)
print('CV accuracy scores: %s' % scores)
print('CV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))

# 검증 곡선을 사용해서 높은 바이어스나 높은 분산에 대한 이슈를 어떻게 해소할 수 있는지 확인.
import matplotlib.pyplot as plt
from sklearn.learning_curve import learning_curve
pipe_lr = Pipeline([
('scl', StandardScaler()),
('clf', LogisticRegression(penalty='l2', random_state=0))
])
train_sizes, train_scores, test_scores = learning_curve(estimator=pipe_lr,
X=X_train,
y=y_train,
train_sizes=np.linspace(0.1, 1.0, 10),
cv=10,
n_jobs=1)
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

plt.plot(train_sizes, train_mean, color='blue', marker='o', markersize=5, label='training accuracy')
plt.fill_between(train_sizes, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue')
plt.plot(train_sizes, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy')
plt.fill_between(train_sizes, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green')
plt.grid()
plt.xlabel('Number of training samples')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')
plt.ylim([0.8, 1.0])
plt.show()

# 검증곡선으로 오버피팅과 언더피팅 다루기
'''
검증곡선은 학습곡선과 관련되어 있지만 훈련 정확도와 테스트 정확도를 샘플 크기의 함수로 플롯하는 대신,
로지스틱 회귀의 역정규화 파라피터 C와 같은 모델 파라미터 값들을 다양하게 한다.
'''
from sklearn.learning_curve import validation_curve
param_range = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
train_scores, test_scores = validation_curve(
estimator=pipe_lr,
X=X_train,
y=y_train,
param_name='clf__C',
param_range=param_range,
cv=10
)
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)
plt.plot(param_range, train_mean, color='blue', marker='o', markersize=5, label='training accuracy')
plt.fill_between(param_range, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue')
plt.plot(param_range, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy')
plt.fill_between(param_range, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green')
plt.grid()
plt.xscale('log')
plt.legend(loc='lower right')
plt.xlabel('Parameter C')
plt.ylabel('Accuracy')
plt.ylim([0.8, 1.0])
plt.show()


'''
차원축소를 위한 피처 선택의 방법 중 피처 추출이 있다.
원래보다 낮은 차원의 새로운 피처 부분공간으로 변환시킴으로써 데이터의 정보를 요약할 수 있도록 도와주는
세 가지 기초 기법에 대해 학습한다.
* 비지도적 데이터 압축을 위한 주성분 분석(PCA)
* 최대화 분류 분리를 위한 지도적 차원축소 기법으로써의 선형 분리 분석(LDA)
* 커널(kernel) 주성분 분석을 활용한 비선형 차원축소
'''
# 주성분 분석을 활요한 비지도적 차원축소

# 전체 분산과 설명 분산
import pandas as pd
df_wine = pd.read_csv('wine.data', header=None)

from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import StandardScaler
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
# 표준화
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.fit_transform(X_test)

# 공분산 행렬 만들기
# 아이겐페어(eigenpairs) Numpy linalg.eig 함수 사용
import numpy as np
cov_mat = np.cov(X_train_std.T) # 공분산행렬 계산 (같은 피처를 행으로 만들어서)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
print('\nEigenvalues \n%s' % eigen_vals)
print('\nEigenvectors \n%s' % eigen_vecs)

# 아이겐밸류의 분산 비율을 구한다.
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)] # 내림차순 정렬
cum_var_exp = np.cumsum(var_exp) # 설명 분산의 누적 합 계산
import matplotlib.pyplot as plt
plt.bar(range(1,14), var_exp, alpha=0.5, align='center', label='individual explained variance')
plt.step(range(1,14), cum_var_exp, where='mid', label='cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal components')
plt.legend(loc='best')
plt.show()

# 피처 변환
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i]) for i in range(len(eigen_vals))]
eigen_pairs.sort(reverse=True) # 내림차순 정렬
print('eigen_pairs:\n', eigen_pairs)
w = np.hstack((eigen_pairs[0][1][:, np.newaxis], eigen_pairs[1][1][:, np.newaxis]))
print('Matrix W:\n', w)

# 투영행렬을 사용해서 샘플 x(1x13차원의 행 벡터) PCA 부분공간 x`로 변환할 수 있다.
print('X_train_std[0]:\n', X_train_std[0].dot(w))
# 124 x 13차원의 훈련데이터를 행렬의 내적곱 계산을 사용해서 두 개의 주성분으로 변환
X_train_pca = X_train_std.dot(w)

colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']
for l, c, m in zip(np.unique(y_train), colors, markers):
plt.scatter(X_train_pca[y_train==l, 0], X_train_pca[y_train==l, 1], c=c, label=l, marker=m)
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='upper left')
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))

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)):
plt.scatter(x=X[y==cl, 0], y=X[y==cl, 1], alpha=0.8, c=cmap(idx), marker=markers[idx], label=cl)

from sklearn.linear_model import LogisticRegression
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
lr = LogisticRegression()
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)
lr.fit(X_train_pca, y_train)
plot_decision_regions(X_train_pca, y_train, classifier=lr)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(loc='lower left')
plt.show()

plot_decision_regions(X_test_pca, y_test, classifier=lr)
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.show()

pca = PCA(n_components=None)
X_train_pca = pca.fit_transform(X_train_std)
print('pca.explained_variance_ratio_:\n', pca.explained_variance_ratio_)

# 세 개의 평균 벡터 결과 도출
np.set_printoptions(precision=4)
mean_vecs = []

for label in range(1,4):
mean_vecs.append(np.mean(X_train_std[y_train==label], axis=0))
print('MV %s: %s\n' %(label, mean_vecs[label-1]))

d = 13 # number of features
S_W = np.zeros((d, d))
for label, mv in zip(range(1,4), mean_vecs):
class_scatter = np.zeros((d, d))
for row in X[y==label]:
row, mv = row.reshape(d, 1), mv.reshape(d, 1)
class_scatter += (row-mv).dot((row-mv).T)
S_W += class_scatter
print('Within-class scatter matrix: %sx%s' % (S_W.shape[0], S_W.shape[1]))

print('Class label distribution: %s' % np.bincount(y_train)[1:])

d = 13
S_W = np.zeros((d, d))
for label, mv in zip(range(1,4), mean_vecs):
class_scatter = np.cov(X_train_std[y_train==label].T)
S_W += class_scatter
print('Scaled within-class scatter matrix: %sx%s' % (S_W.shape[0], S_W.shape[1]))

mean_overall = np.mean(X_train_std, axis=0)
d = 13
S_B = np.zeros((d,d))
for i, mean_vec in enumerate(mean_vecs):
n = X[y==i+1, :].shape[0]
mean_vec = mean_vec.reshape(d, 1)
mean_overall = mean_overall.reshape(d, 1)
S_B += n * (mean_vec - mean_overall).dot((mean_vec - mean_overall).T)
print('Between-class scatter matrix: %sx%s' % (S_B.shape[0], S_B.shape[1]))



import pandas as pd
from io import StringIO

csv_data = '''A,B,C,D
1.0,2.0,3.0,4.0
5.0,6.0,,8.0
0.0,11.0,12.0,
'''
df = pd.read_csv(StringIO(csv_data))
print('df:\n ', df)
print('df.isnull().sum():\n ', df.isnull().sum())

# 결측값을 가진 샘플이나 피처 제거
print('df.dropna():\n ', df.dropna()) # 결측값을 가진 행을 제거하는 방법
print('df.dropna(axis=1):\n ', df.dropna(axis=1)) # 결측값을 가진 열을 제거하는 방법
print("df.dropna(how='all'):\n", df.dropna(how='all')) # 모든 열이 결측값을 가진 행만 제거
print('df.dropna(thresh=4):\n', df.dropna(thresh=4)) # 최소 4개의 결측치가 아닌 값을 가지지 못하는 행 제거
print("df.dropna(subset=['C']:\n", df.dropna(subset=['C'])) # C열에서 결측치가 있는 행을 제거

# 결측값의 보정
# 전체 피처열의 평균값으로 결측값을 간단히 대체하는 평균보정법
from sklearn.preprocessing import Imputer
imr = Imputer(missing_values='NaN', strategy='mean', axis=0)
imr = imr.fit(df)
imputed_data = imr.transform(df.values)
print('imputed_data:\n', imputed_data)


import pandas as pd
df = pd.DataFrame([
['green', 'M', 10.1, 'class1'],
['red', 'L', 13.5, 'class2'],
['blue', 'XL', 15.3, 'class1']
])
df.columns = ['color', 'size', 'price', 'classlabel']
print('df:\n', df)

# 순위형 피처 매핑
# ex) XL = L + 1 = M + 2
size_mapping = {'XL': 3, 'L': 2, 'M': 1}
df['size'] = df['size'].map(size_mapping)
print('df:\n', df)

# 역매핑
inv_size_mapping = {v: k for k, v in size_mapping.items()}
print('inv_size_mapping:\n', inv_size_mapping)


# 분류 레이블 인코딩
import numpy as np
class_mapping = {label:idx for idx, label in enumerate(np.unique(df['classlabel']))}
print('class_mapping:\n', class_mapping)

df['classlabel'] = df['classlabel'].map(class_mapping)
print('df:\n', df)
# 역매핑
inv_class_mapping = {v: k for k, v in class_mapping.items()}
df['classlabel'] = df['classlabel'].map(inv_class_mapping)
print('df:\n', df)

from sklearn.preprocessing import LabelEncoder
class_le = LabelEncoder()
y = class_le.fit_transform(df['classlabel'].values)
print('y:\n', y)

print('class_le.inverse_transform(y):\n', class_le.inverse_transform(y))


# 명목형 피처에 원핫 인코딩 수행
X = df[['color', 'size', 'price']].values
color_le = LabelEncoder()
X[:, 0] = color_le.fit_transform(X[:, 0])
print('X:\n', X)


# one hot encoding
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(categorical_features=[0])
print('ohe.fit_transform(X).toarray():\n', ohe.fit_transform(X).toarray())

print('df:\n', df)
print("pd.get_dummies(df[['price', 'color', 'size']]):\n", pd.get_dummies(df[['price', 'color', 'size']]))


# 데이터를 훈련용과 테스트용으로 분할하기
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
df_wine.columns = ['Class label', 'Alcohol',
'Malic acid', 'Ash',
'Alcalinity of ash', 'Magnesium',
'Total phenols', 'Flavanoids',
'Nonflavanoid phenols', 'Proanthocyanins',
'Color intensity', 'Hue',
'OD280/OD315 of diluted wines', 'Proline']
print('Class labels', np.unique(df_wine['Class label']))
print('df_wine.head():\n', df_wine.head())

# 테스트용과 훈련용으로 임의 분할하기 위한 편리한 방법 한 가지는 사이킷런 cross_validation 서브 모듈의 train_test_split 함수
from sklearn.cross_validation import train_test_split
X, y = df_wine.iloc[:, 1].values, df_wine.iloc[:, 0].values
print('X:\n', df_wine.iloc[:, 1].values) # Alcohol
print('y:\n', df_wine.iloc[:, 0].values) # Class label
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
print('X_train:\n', X_train)
print('X_test:\n', X_test)

# 최소-최대 스케일링 프로시저
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
X_train = np.array(X_train).reshape((len(X_train), 1))
X_test = np.array(X_test).reshape((len(X_test), 1))
X_train_norm = mms.fit_transform(X_train)
X_test_norm = mms.transform(X_test)
print('X_train_norm:\n', X_train_norm)
print('X_test_norm:\n', X_test_norm)

# 표준화 프로시저
from sklearn.preprocessing import StandardScaler
stdsc = StandardScaler()
X_train_std = stdsc.fit_transform(X_train)
X_test_std = stdsc.transform(X_test)
print('X_train:\n', X_train)
print('X_train_std:\n', X_train_std)


############ 오버피팅을 줄일 수 있는 일반적인 방법

from sklearn.linear_model import LogisticRegression
LogisticRegression(penalty='l1')
print("LogistincRegression(penality='l1'):\n", LogisticRegression(penalty='l1'))
lr = LogisticRegression(penalty='l1', C=0.1)
lr.fit(X_train_std, y_train)
print('Training accuracy:', lr.score(X_train_std, y_train))
print('Test accuracy:', lr.score(X_test_std, y_test))
lr.intercept_
print('lr.intercept_: ', lr.intercept_)
lr.coef_
print('lr.coef_: ', lr.coef_)


# 여러가지 정규화 강도를 위한 다양한 피처들의 가중계수인 정규화의 경로를 플롯
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.subplot(111)
colors = ['blue', 'green', 'red', 'cyan',
'magenta', 'yellow', 'black',
'pink', 'lightgreen', 'lightblue',
'gray', 'indigo', 'orange']
weights, params = [], []
for c in np.arange(-4, 6):
lr = LogisticRegression(penalty='l1', 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)
for column, color in zip(range(weights.shape[1]), colors):
plt.plot(params, weights[:, column], label=df_wine.columns[column+1], color=color)

plt.axhline(0, color='black', linestyle='--', linewidth=3)
plt.xlim([10**(-5), 10**5])
plt.ylabel('weight coefficient')
plt.xlabel('C')
plt.xscale('log')
plt.legend(loc='upper left')
ax.legend(loc='upper center', bbox_to_anchor=(1.38, 1.03), ncol=1, fancybox=True)
plt.show()
# 연속형 피처 선택 알고리즘
'''
SBS(Sequential Backward Selection) : 계산상 효율을 위해 초기 피처 부분 공간의 차원을 축소시킬 때
분류기 성능이 무너지는 것을 최소화하는 것을 목표로 한다.
'''
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
'Alcalinity of ash', 'Magnesium', 'Total phenols', 'Flavanoids',
'Nonflavanoid phenols', 'Proanthocyanins', 'Color intensity', 'Hue',
'OD280/OD315 of diluted wines', 'Proline']

from sklearn.cross_validation import train_test_split
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values

from sklearn.base import clone
from itertools import combinations
import numpy as np
from sklearn.cross_validation import train_test_split
from sklearn.metrics import accuracy_score
class SBS():
def __init__(self, estimator, k_features, scoring=accuracy_score,
test_size=0.25, random_state=1):
self.scoring = scoring
self.estimator = clone(estimator)
self.k_features = k_features
self.test_size = test_size
self.random_state = random_state

def fit(self, X, y):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=self.test_size,
random_state=self.random_state)
dim = X_train.shape[1]
#print('dim:\n', dim)
self.indices_ = tuple(range(dim))
#print('self.indices:\n', self.indices_)
self.subsets_ = [self.indices_]
#print('self.subsets:\n', self.subsets_)
score = self._calc_score(X_train, y_train, X_test, y_test, self.indices_)
#print('score:\n', score)
self.scores_ = [score]

while dim > self.k_features:
scores = []
subsets = []

for p in combinations(self.indices_, r=dim-1):
score = self._calc_score(X_train, y_train, X_test, y_test, p)
scores.append(score)
subsets.append(p)
best = np.argmax(scores)
self.indices_ = subsets[best]
self.subsets_.append(self.indices_)
dim -= 1

self.scores_.append(scores[best])
#print('self.scores_:\n', self.scores_)
self.k_score_ = self.scores_[-1]

return self

def transform(self, X):
return X[:, self.indices_]

def _calc_score(self, X_train, y_train, X_test, y_test, indices):
self.estimator.fit(X_train[:, indices], y_train)
y_pred = self.estimator.predict(X_test[:, indices])
score = self.scoring(y_test, y_pred)
return score

from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
knn = KNeighborsClassifier(n_neighbors=2)
sbs = SBS(knn, k_features=1)
sbs.fit(X_train_std, y_train)
#print('sbs.subsets_:\n', sbs.subsets_)
k_feat = [len(k) for k in sbs.subsets_]
plt.plot(k_feat, sbs.scores_, marker='o')
plt.ylim([0.7, 1.1])
plt.ylabel('Accuracy')
plt.xlabel('Number of features')
plt.grid()
plt.show()

k5 = list(sbs.subsets_[8])
#print('k5:\n', k5)
print('df_wine.columns[1:][k5]:\n', df_wine.columns[1:][k5])

knn.fit(X_train_std, y_train)
print('Training accuracy:', knn.score(X_train_std, y_train))
print('Test accuracy:', knn.score(X_test_std, y_test))

knn.fit(X_train_std[:, k5], y_train)
print('Training accuracy:', knn.score(X_train_std[:, k5], y_train))
print('Test accuracy:', knn.score(X_test_std[:, k5], y_test))

#### 랜덤 포레스트를 활용한 피처 중요도의 평가
'''
랜덤 포레스트를 사용하면 데이터가 선형적으로 분리 가능한지 여부와 상관없이
포레스트 내의 모든 의사결정나무로부터 계산된 평균 불순도 감소분으로 피처의 중요도를 측정할 수 있다.
나무 기반의 모델은 표준화나 정규화가 필요하지 않다는 것을 기억하자.
'''
from sklearn.ensemble import RandomForestClassifier
feat_labels = df_wine.columns[1:]
forest = RandomForestClassifier(n_estimators=10000, random_state=0, n_jobs=-1)
forest.fit(X_train, y_train)
importances = forest.feature_importances_
indices = np.argsort(importances)[::-1]
for f in range(X_train.shape[1]):
print("%2d) %-*s %f" % (f+1, 30, feat_labels[f], importances[indices[f]]))

plt.title('Feature Importances')
plt.bar(range(X_train.shape[1]), importances[indices], color='lightblue', align='center')
plt.xticks(range(X_train.shape[1]), feat_labels, rotation=90)
plt.xlim([-1, X_train.shape[1]])
plt.tight_layout()
plt.show()

X_selected = forest.transform(X_train, threshold=0.15)
print('X_selected.shape:\n', X_selected.shape)


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()


https://docs.scipy.org/doc/numpy/reference/routines.math.html

'Program > Python' 카테고리의 다른 글

python iloc 와 loc 의 차이  (0) 2017.12.17
ravel(), transpose(), resize()  (0) 2017.12.13
numpy 함수  (0) 2017.12.02
numpy meshgrid  (0) 2017.12.02
window에서 패키지 설치 에러 해결하기  (0) 2017.11.21
NumPy에서 배열 생성을 위한 주요 함수들은 다음과 같다.
함수 이름설명
array()입력 데이터(리스트, 튜플, 배열, 순차형 데이터 등)를 이용하여 배열을 생성한다.
생성시 적절한 데이터의 타입을 추정한다.
예시) arr = np.array([1, 2, 3])
arange()내장 range와 유사하지만 리스트 대신 ndarray를 반환한다.

arr = np.arange(10) # 0부터 9까지의 정수 10개를 생성한다.

np.arange(9).reshape((3, 3)) # 3행 3열의 numpy.ndarray를 반환한다.(0부터 시작)

# 1.0부터 4.5까지 0.5씩 증가시키기
x = np.arange( 1.0, 4.1, 0.5 )
ones()주어진 dtype과 주어진 모양을 가지는 배열을 생성하고, 값을 모두 1로 초기화 시킨다.
zeros((행수, 열수))요소의 모든 내용이 0인 배열을 생성해준다.
예시) zeros((2,2)) : 요소의 값이 모두 0인 2 * 2의 배열을 생성한다.
empty()배열을 생성하되, ones나 zeros처럼 값을 초기화 하지는 않는다.


NumPy가 지원하는 자료형 목록은 다음과 같다.
종류Type Code설명
int8, unit8i1, u1부호가 있는 8비트 정수형과 부호가 없는 8비트 정수형
int16, unit16i2, u2부호가 있는 16비트 정수형과 부호가 없는 16비트 정수형
int32, unit32i4, u4부호가 있는 32비트 정수형과 부호가 없는 32비트 정수형
int64, unit64i8, u8부호가 있는 64비트 정수형과 부호가 없는 64비트 정수형
float16f2반정밀도 부동 소수점
float32f4 또는 f단정밀도 부동 소수점, C 언어의 float와 호환
float64f8 또는 d배정밀도 부동 소수점, C 언어의 double와 호환, 파이썬의 float 객체와 호환 


단항 유니버설 함수는 하나 이상의 스칼라 값을 받아서 스칼라 결과 값을 반환해주는 함수를 말한다.
함수설명
abs, fabs각 원소의 절대값(정수, 실수, 복소수)을 구한다. 복소수가 아닌 경우 빠른 연산을 위하여 fabs를 사용한다.
sqrt(배열)각 원소의 제곱근을 계산한다. arr ** 0.5와 동일한 결과이다.
square각 원소의 제곱을 계산한다. arr ** 2와 동일한 결과이다.
exp각 원소에서 지수 e*x를 계산한다.
print(np.exp(1)) #2.71828182846
log, log10, log2, log1p각각 자연 로그, 로그 10, 로그 2, 로그(1+x)
sign각 원소의 부호를 계산한다. 1(양수), 0(영), -1(음수)
ceil각 원소의 소수 자리를 올린다.
floor각 원소의 소수 자리를 버린다.
rint각 원소의 소수 자리를 반올림한다.
modf각 원소의 몫과 나머지를 각각의 배열로 반환한다.
isnan각각의 원소가 숫자인지 아닌지를(NaN, Not A Number) 나타내는 Boolean 배열을 반환한다.
isfinite, isinf배열의 각 원소가 유한한지(non-inf, non-Nan), 무한인지를 나타내는 Boolean 배열을 반환한다.
cos, cosh, sin, sinh, tan, tanh일반 삼각 함수와 쌍곡 삼각 함수
arccos, arccosh, arcsin, arcsinh, arctan, arctanh역 삼각 함수
logical_not각 원소의 논리 부정(not) 값을 계산한다.


이항 유니버설 함수는 2개의 인자를 취해서 단일 배열을 반환해주는 함수를 말한다.
함수설명
add(배열a, 배열b)두 배열에서 같은 위치의 요소끼리 더한다.
subtract(배열a, 배열b)첫 번째 배열의 요소에서 두 번째 배열의 원소를 뺀다.
multiply(배열a, 배열b)배열의 요소끼리 곱한다.
divide(배열a, 배열b), floor_divide첫 번째 배열의 요소에서 두 번째 배열의 원소를 나눈다.
power첫 번째 배열의 요소에 두 번째 배열의 요소를 제곱한다.
maximum, fmax두 원소 중에서 큰 값을 반환한다. fmax는 NaN을 무시한다.
minimum, fmin두 원소 중에서 작은 값을 반환한다. fmin은 NaN을 무시한다.
mod첫 번째 배열의 요소에 두 번째 배열의 원소를 나눈 나머지를 구한다.
copysign첫 번째 배열의 요소의 기호를 두 번째 배열의 요소의 기호로 바꾼다.
greater, greater_equal, less, less_equal, euqal, not_equal각각 두 원소 간의 >, >=, < , <=, ==, != 비교 연산 결과를 Boolean 배열로 반환한다.
logical-and, logical_or, logical_xor각각 두 원소 간의 논리 연산 &, |, ^ 결과를 반환한다.


기본 배열 통계 메소드는 다음과 같은 항목들이 있다.
메소드설명
sum배열 전체 혹은 특정 축에 대한 모든 원소의 합을 계산한다.
크기가 0인 배열에 대한 연산 결과는 0이다.
mean산술 평균을 구한다.
크기가 0인 배열에 대한 연산 결과는 NaN이다
std, var각각 표준 편차와 분산을 구한다.
선택적으로 자유도를 줄 수 있으면 분모의 기본 값은 n이다.
min, max최소 값, 최대 값
argmin, argmax최소 원소의 색인 값, 최대 원소의 색인 값
cumsum누산 메소드라고 하는데, 각 원소의 누적 합을 구해준다.(cumulative sum)
cumprod각 원소의 누적 곱



자주 쓰는 선형 대수 함수는 다음과 같은 항목들이 있다.
함수설명
numpy.diag정사각 행렬의 대가/비대각 원소를 1차원 배열로 반환하거나 1차원 배열을 대각선 원소로 하고, 나머지는 0으로 채운 단위 행렬을 반환한다.
numpy.dot()행렬과 행력 및 행렬과 벡터 간의 곱셈을 연산한다.
numpy.trace행렬의 대각원 원소의 합을 계산한다.
numpy.linalg.det행렬식을 게산한다.
numpy.linalg.eig정사각 행렬의 고유 값과 고유 벡터를 계산한다.
numpy.linalg.inv정사각 행렬의 역행렬을 계산한다.
numpy.linalg.pinv행렬의 무어=펜로즈 유사역원 역행렬을 계산한다.
numpy.linalg.qrQR 분해를 계산한다.
numpy.linalg.svd특이값 분해(SVD)를 계산한다.
numpy.linalg.solveA가 정사각 행렬일 때 Ax = b를 만족하는 x를 구한다.
numpy.linalg.lstsqy = xb를 만족하는 최소 제곱해를 구한다.


다음은 numpy.random 함수에 포함되어 있는 일부분이다.
함수설명
seed()난수 발생기의 시드를 지정한다.
예시) np.random.seed(1000)
permutation순서를 임의로 바꾸거나 임의의 순열을 반환한다.
shuffle리스트나 배열의 순서를 뒤섞는다.
rand균등 분포에서 표본을 추출한다.
randint주어진 최소/치대 범위 안에서 임의의 난수를 추출한다.
randn()표준 편차가 1이고 평균 값이 0인 정규 분포(matlab과 같은 방식)에서 표본을 추출한다.
예시 : arr = np.random.randn( 10 )
binomial이항 분포에서 표본을 추출한다.
normal정규 분포(가우시안)에서 표본을 추출한다.
beta베타 분포에서 표본을 추출한다.
chisquare카이 제곱 분포에서 표본을 추출한다.
gamma감마 분포에서 표본을 추출한다.
uniform균등(0, 1) 분포에서 표본을 추출한다.
standard_normal()정규 분포 샘플 데이터를 생성한다.
예시) y = np.random.standard_normal( 20 )



numpy에서 제공하는 집합 함수는 다음과 같다.
함수설명
unique(x)배열 x에서 중복된 원소를 제거한 후 정렬하여 반환한다.
intersect1d(x, y)배열 x와 y에 공통적으로 존재하는 원소를 정렬하여 반환한다.
union1d(x, y)두 배열의 합집합을 반환한다.
in1d(x, y)x의 원소 중 y의 원소를 포함하는 지를 나타내는 불리언 배열을 반환한다.
setdiff1d(x, y)x와 y의 차집합을 반환한다.
setxor1d(x, y)한 배열에는 포함되지만, 두 배열 모두에는 포함되지 않는 원소들의 집합인 대칭 차집합을 반환한다.


'Program > Python' 카테고리의 다른 글

ravel(), transpose(), resize()  (0) 2017.12.13
numpy 수학함수  (0) 2017.12.02
numpy meshgrid  (0) 2017.12.02
window에서 패키지 설치 에러 해결하기  (0) 2017.11.21
python 가상환경  (0) 2017.11.21
import numpy as np

samples = np.arange(-10, 10, 0.2)
print('sample: ', samples)

xs, ys = np.meshgrid(samples, samples)
print('xs: ', xs)
print('ys: ', ys)

sample:  [ -1.00000000e+01  -9.80000000e+00  -9.60000000e+00  -9.40000000e+00

  -9.20000000e+00  -9.00000000e+00  -8.80000000e+00  -8.60000000e+00

  -8.40000000e+00  -8.20000000e+00  -8.00000000e+00  -7.80000000e+00

  -7.60000000e+00  -7.40000000e+00  -7.20000000e+00  -7.00000000e+00

  -6.80000000e+00  -6.60000000e+00  -6.40000000e+00  -6.20000000e+00

  -6.00000000e+00  -5.80000000e+00  -5.60000000e+00  -5.40000000e+00

  -5.20000000e+00  -5.00000000e+00  -4.80000000e+00  -4.60000000e+00

  -4.40000000e+00  -4.20000000e+00  -4.00000000e+00  -3.80000000e+00

  -3.60000000e+00  -3.40000000e+00  -3.20000000e+00  -3.00000000e+00

  -2.80000000e+00  -2.60000000e+00  -2.40000000e+00  -2.20000000e+00

  -2.00000000e+00  -1.80000000e+00  -1.60000000e+00  -1.40000000e+00

  -1.20000000e+00  -1.00000000e+00  -8.00000000e-01  -6.00000000e-01

  -4.00000000e-01  -2.00000000e-01  -3.55271368e-14   2.00000000e-01

   4.00000000e-01   6.00000000e-01   8.00000000e-01   1.00000000e+00

   1.20000000e+00   1.40000000e+00   1.60000000e+00   1.80000000e+00

   2.00000000e+00   2.20000000e+00   2.40000000e+00   2.60000000e+00

   2.80000000e+00   3.00000000e+00   3.20000000e+00   3.40000000e+00

   3.60000000e+00   3.80000000e+00   4.00000000e+00   4.20000000e+00

   4.40000000e+00   4.60000000e+00   4.80000000e+00   5.00000000e+00

   5.20000000e+00   5.40000000e+00   5.60000000e+00   5.80000000e+00

   6.00000000e+00   6.20000000e+00   6.40000000e+00   6.60000000e+00

   6.80000000e+00   7.00000000e+00   7.20000000e+00   7.40000000e+00

   7.60000000e+00   7.80000000e+00   8.00000000e+00   8.20000000e+00

   8.40000000e+00   8.60000000e+00   8.80000000e+00   9.00000000e+00

   9.20000000e+00   9.40000000e+00   9.60000000e+00   9.80000000e+00]

xs:  [[-10.   -9.8  -9.6 ...,   9.4   9.6   9.8]

 [-10.   -9.8  -9.6 ...,   9.4   9.6   9.8]

 [-10.   -9.8  -9.6 ...,   9.4   9.6   9.8]

 ..., 

 [-10.   -9.8  -9.6 ...,   9.4   9.6   9.8]

 [-10.   -9.8  -9.6 ...,   9.4   9.6   9.8]

 [-10.   -9.8  -9.6 ...,   9.4   9.6   9.8]]

ys:  [[-10.  -10.  -10.  ..., -10.  -10.  -10. ]

 [ -9.8  -9.8  -9.8 ...,  -9.8  -9.8  -9.8]

 [ -9.6  -9.6  -9.6 ...,  -9.6  -9.6  -9.6]

 ..., 

 [  9.4   9.4   9.4 ...,   9.4   9.4   9.4]

 [  9.6   9.6   9.6 ...,   9.6   9.6   9.6]

 [  9.8   9.8   9.8 ...,   9.8   9.8   9.8]]


이것이 meshgrid


'Program > Python' 카테고리의 다른 글

numpy 수학함수  (0) 2017.12.02
numpy 함수  (0) 2017.12.02
window에서 패키지 설치 에러 해결하기  (0) 2017.11.21
python 가상환경  (0) 2017.11.21
파이썬 내장함수  (0) 2017.11.20

Windows에서 pip로 패키지를 설치할 때 에러가 발생하는 경우가 많습니다. 예를 들어 다음은 notebook 패키지를 설치할 때 의존 패키지 중 MarkupSafe에서 에러가 난 상황입니다. 에러 메시지를 잘 보면 "Running setup.py install for MarkupSafe ..." 부분이 error로 표시됩니다.

C:Usersdojang>pip install notebook
...생략...
  Running setup.py install for simplegeneric ... done
  Running setup.py install for pandocfilters ... done
  Running setup.py install for MarkupSafe ... error
Exception:
Traceback (most recent call last):
...생략...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb6 in position 68: invalid start byte

이 문제를 해결하려면 MarkupSafe 패키지를 Windows 전용으로 만든 whl 파일을 설치해야 합니다. 먼저 웹 브라우저를 실행하고 다음 주소로 이동합니다.

  • Python Extension Packages for Windows
    http://www.lfd.uci.edu/~gohlke/pythonlibs/

웹 사이트가 열리면 Ctrl+F를 눌러서 방금 에러가 발생했던 패키지인 MarkupSafe를 검색합니다. 그다음에 MarkupSafe가 나오면 링크를 클릭합니다.

▼ 그림 48-4 Unofficial Windows Binaries for Python Extension Packages에서 패키지 검색
그림 48 4 Unofficial Windows Binaries for Python Extension Packages에서 패키지 검색

링크를 클릭하면 MarkupSafe 패키지 항목으로 이동합니다. 여기에 MarkupSafe 패키지의 whl 파일들이 있는데 자신의 파이썬 버전과 비트수에 맞는 whl 파일을 다운로드합니다.

▼ 그림 48-5 Unofficial Windows Binaries for Python Extension Packages에서 whl 파일 받기
그림 48 5 Unofficial Windows Binaries for Python Extension Packages에서 whl 파일 받기

whl 파일은 패키지이름-패키지버전-파이썬버전-ABI태그-플랫폼태그.whl 형식으로 되어 있습니다. 예를 들어 cp36과 cp36m은 파이썬 3.6이라는 뜻이고, win32는 32비트 Windows, win_amd64는 64비트 Windows라는 뜻입니다(이후 새 파이썬 버전이 나오면 해당 버전에 맞춰서 새 whl 파일이 올라옵니다).

현재 설치된 파이썬의 버전을 확인하려면 명령 프롬프트(PowerShell)에서 python -V를 입력합니다.

C:Usersdojang>python -V
Python 3.6.0

그리고 파이썬 비트수를 확인하려면 파이썬 셸에서 다음과 같이 실행합니다.

파이썬이 32비트일 경우

>>> import platform
>>> platform.architecture()
('32bit', 'WindowsPE')

파이썬이 64비트일 경우

>>> import platform
>>> platform.architecture()
('64bit', 'WindowsPE')

여기서는 파이썬 버전 3.6에 32비트라고 가정하고 MarkupSafe-1.0-cp36-cp36m-win32.whl을 받아서 설치하겠습니다(MarkupSafe의 버전은 시간이 지나면 달라질 수 있습니다). 받은 whl 파일을 설치하기 전에 앞에서 설치할 때 에러가 발생한 MarkupSafe 패키지를 삭제합니다.

C:Usersdojang>pip uninstall -y MarkupSafe
Uninstalling MarkupSafe-1.0:
  Successfully uninstalled MarkupSafe-1.0

이제 pip로 whl 파일을 설치 해줍니다(파일을 다운로드한 경로는 C:₩Users₩dojang₩Downloads라고 가정).

C:Usersdojang>pip install C:UsersdojangDownloadsMarkupSafe-1.0-cp36-cp36m-win32.whl
Processing c:\usersdojang\downloadsmarkupsafe-1.0-cp36-cp36m-win32.whl
Installing collected packages: MarkupSafe
Successfully installed MarkupSafe-1.0

여기서 끝이 아닙니다. 처음에 notebook 패키지를 설치하던 중간에 에러가 발생했으므로 notebook 패키지가 완전히 설치되지 않았습니다. 따라서 다음과 같이 notebook 패키지를 다시 설치해줍니다.

C:Usersdojang>pip install notebook
Collecting notebook
...생략...
Installing collected packages: jinja2, mistune, nbconvert, notebook
Successfully installed jinja2-2.9.6 mistune-0.7.4 nbconvert-5.1.1 notebook-5.0.0

whl 파일로 MarkupSafe 패키지를 설치해 놓았으므로 notebook 패키지를 설치할 때 에러가 발생하지 않습니다. 이런 방식으로 Windows에서 pip로 패키지를 설치할 때 에러가 발생하는 문제를 해결할 수 있습니다.



'Program > Python' 카테고리의 다른 글

numpy 함수  (0) 2017.12.02
numpy meshgrid  (0) 2017.12.02
python 가상환경  (0) 2017.11.21
파이썬 내장함수  (0) 2017.11.20
비트 연산자 사용하기  (0) 2017.11.20

파이썬을 사용하다 보면 pip로 패키지를 설치하게 되는데 이 패키지들은 파이썬 설치 폴더(디렉터리)의Lib/site-packages 안에 저장됩니다. 그래서 pip로 설치한 패키지는 모든 파이썬 스크립트에서 사용할 수 있게 됩니다. 평소에는 이런 방식이 큰 문제가 없지만 프로젝트 여러 개를 개발할 때는 패키지의 버전 문제가 발생합니다.

예를 들어 프로젝트 A에서는 패키지X 1.5를 사용해야 하고, 프로젝트 B에서는 패키지X 2.0을 사용해야 하는 경우가 생깁니다. 이 패키지X 1.5와 2.0은 호환이 되지 않는다면 개발하기가 상당히 불편해집니다.

이런 문제를 해결하기 위해 파이썬에서는 가상 환경(virtual environment)을 제공하는데, 가상 환경은 독립된 공간을 만들어주는 기능입니다. 가상 환경에서 pip로 패키지를 설치하면 가상 환경 폴더(디렉터리)의 Lib/site-packages 안에 패키지를 저장해줍니다. 즉, 프로젝트 A와 B 각각 가상 환경을 만들어서 프로젝트 A에는 패키지X 1.5를 설치하고, 프로젝트 B에는 패키지X 2.0을 설치합니다. 이렇게 하면 파이썬 스크립트를 실행할 때도 현재 가상 환경에 설치된 패키지를 사용하므로 버전 문제가 발생하지 않습니다.

▼ 그림 48-3 파이썬 가상 환경
그림 48 3 파이썬 가상 환경

특히 가상 환경에는 파이썬 실행 파일(인터프리터) 자체도 포함되므로 각 가상 환경 별로 다른 버전의 파이썬 인터프리터가 들어갈 수 있습니다. 즉, 스크립트를 실행할 때는 원래 설치된 파이썬 인터프리터가 아닌 가상 환경 안의 파이썬 인터프리터를 사용합니다.

그럼 가상 환경을 만드는 방법을 알아보겠습니다. 가상 환경은 venv 모듈에 가상 환경 이름을 지정해서 만듭니다.

venv는 파이썬 3.3이상부터 사용 가능

  • python -m venv 가상환경이름

여기서는 C:₩project 폴더 아래에 가상 환경을 만들겠습니다. 다음과 같이 명령 프롬프트에서 example 가상 환경을 만들고 example 폴더 안으로 이동합니다. 그다음에 Scripts 폴더 안의 activate.bat 파일을 실행하면 가상 환경이 활성화됩니다.

Windows 명령 프롬프트

C:project>python -m venv example
C:project>cd example
C:projectexample>Scriptsactivate.bat
(example) C:projectexample>

Windows PowerShell에서는 Activate.ps1 파일을 실행합니다.

Windows PowerShell

PS C:project> python -m venv example
PS C:project> cd example
PS C:projectexample> .ScriptsActivate.ps1
(example) PS C:projectexample>

프롬프트 앞을 보면 (example)과 같이 가상 환경 이름이 표시됩니다. 이 상태에서 pip로 패키지를 설치하면 C:₩project₩example₩Lib₩site-packages 안에 패키지가 저장됩니다. 또한, 이 상태에서 스크립트 파일을 실행하면 현재 가상 환경 안에 있는 파이썬 인터프리터와 패키지를 사용합니다.

(example) C:projectexample>pip install numpy
(example) C:projectexample>dir B Libsite-packages
easy_install.py
numpy
numpy-1.12.1.dist-info
pip
pip-9.0.1.dist-info
pkg_resources
setuptools
setuptools-28.8.0.dist-info
__pycache__

가상 환경에 설치된 패키지는 목록을 저장해 두었다가 나중에 다시 설치할 수 있습니다. 다음과 같이 pip freeze로 패키지 목록과 버전 정보를 requirements.txt 파일에 저장합니다(git 등으로 버전 관리를 할 때 저장소에 설치된 패키지를 모두 추가하지 않고, requirements.txt 파일만 관리하면 됩니다).

(example) C:projectexample>pip freeze > requirements.txt

requirements.txt 파일의 내용대로 패키지를 설치하려면 pip install에서 -r 또는 --requirement 옵션을 사용합니다.

(example) C:projectexample>pip install -r requirements.txt

만약 가상 환경 별로 파이썬 인터프리터 버전을 다르게 만들고 싶다면 해당 버전의 파이썬 인터프리터로 venv 모듈을 실행하면 됩니다. 다음은 파이썬 3.4를 사용하는 가상 환경을 만듭니다(파이썬 3.4를 설치했다고 가정).

C:project>C:Python34python.exe -m venv example3

이렇게 하면 venv 모듈을 실행한 파이썬 실행 파일(인터프리터)이 가상 환경 안에 들어갑니다.

이번에는 macOS와 리눅스에서 가상 환경을 만드는 방법입니다. 다음과 같이 python3으로 venv 모듈을 실행여 가상 환경을 만들고, source로 bin 디렉터리 안의 activate 파일을 적용하여 가상 환경을 활성화합니다.

macOS, 리눅스

~$ python3 -m venv example
~$ cd example
~/example$ source bin/activate
(example) ~/example$

가상 환경을 사용할 때 주의할 점이 있는데, 가상 환경을 만들고 나서 폴더(디렉터리)를 다른 곳으로 이동시키면 활성화가 안 됩니다. 왜냐하면 가상 환경을 활성화하는 activate.batActivate.ps1activate 파일 안에 현재 가상 환경 폴더의 경로가 내장되어 있기 때문입니다. 만약 가상 환경 폴더를 다른 곳으로 이동시켰다면 activate.batActivate.ps1activate 파일 안의 VIRTUAL_ENV 부분을 적절하게 수정해줍니다.

참고로 가상 환경을 사용하는 IDLE을 실행하려면 가상 환경을 활성화 시킨 뒤 idlelib 모듈을 실행하면 됩니다. 이렇게 하면 IDLE에서도 현재 가상 환경의 패키지를 사용할 수 있습니다.

Windows

(example) C:projectexample>pythonw.exe -m idlelib

macOS, 리눅스

(example) ~/example$ python3 -m idlelib
참고 | 아나콘다 가상 환경 만들기

아나콘다는 아나콘다 전용 가상 환경을 제공하며 conda를 사용하여 가상 환경을 만듭니다. conda는 아나콘다 설치 폴더의 Scripts 안에 들어있습니다.

  • conda create --name 가상환경이름

C:project>C:UsersdojangAnaconda3Scriptsconda.exe create --name example

conda는 가상 환경을 현재 폴더에 생성하지 않고 아나콘다 설치 폴더의 envs 안에 생성합니다.

  • 예) C:₩Users₩dojang₩Anaconda3₩envs₩example

가상 환경을 활성화할 때는 아나콘다 설치 폴더의 Scripts₩activate에 가상 환경 이름을 지정하여 실행해야 합니다.

  • activate 가상환경이름

C:project>C:UsersdojangAnaconda3Scriptsactivate example
(example) C:project>

아나콘다 가상 환경에 패키지를 설치할 때는 pip 대신 conda를 사용해야 합니다. 만약 pip를 사용하면 아나콘다 설치 폴더의 Lib/site-packages 안에 패키지가 저장되므로 주의해야 합니다.

  • conda install 패키지

(example) C:project>conda install numpy

다음은 conda의 주요 명령입니다.

  • 현재 환경 정보 출력: conda info

  • 패키지 검색: conda search 패키지

  • 특정 버전의 패키지를 설치:

    • conda install 패키지=버전 (예: conda install numpy=1.11.3)

    • conda install 패키지=버전=파이썬버전 (예: conda install numpy=1.11.3=py36_0)

  • 패키지 업데이트: conda update 패키지

  • 패키지 목록 출력: conda list

    패키지 삭제: conda remove 패키지

  • 패키지 목록 및 버전 정보 저장: conda list --export > package-list.txt

  • 패키지 목록으로 설치: conda install --file package-list.txt

https://dojang.io/mod/page/view.php?id=1168

'Program > Python' 카테고리의 다른 글

numpy meshgrid  (0) 2017.12.02
window에서 패키지 설치 에러 해결하기  (0) 2017.11.21
파이썬 내장함수  (0) 2017.11.20
비트 연산자 사용하기  (0) 2017.11.20
주피터 노트북 한글 폰트 설정  (0) 2017.11.20

함수

설명

abs(숫자)

숫자의 절댓값을 반환

all(반복가능한객체)

반복 가능한 객체의 요소가 모두 참이면 True, 하나라도 거짓이면 False

예) all([1, 2, 3])은 Trueall([1, 0, 3])은 False

any(반복가능한객체)

반복 가능한 객체의 요소가 하나라도 참이면 True, 모두 거짓이면 False

예) any([1, 0, 0])은 Trueany([0, 0, 0])은 False

bin(정수)

정수를 2진수로 된 문자열로 만들어서 반환

bool(값)

값을 TrueFalse로 변환하여 반환. 0이 아닌 숫자, 내용이 있는 객체는 True를 반환, 0None, 비어있는 객체는 False를 반환

bytes(값, 인코딩)

바이트 객체를 반환, bytes는 바이트 단위(8비트)로 문자열을 저장하는 자료형

예) bytes('안녕', encoding='utf-8')은 b'\xec\x95\x88\xeb\x85\x95'

chr(코드값)

ASCII 코드값에 해당하는 문자를 반환. 예) chr(97)은 'a'

dict(반복가능한객체)

반복 가능한 객체로 딕셔너리를 생성하여 반환

dir(객체)

객체의 네임스페이스를 반환, 객체를 지정하지 않으면 현재 네임스페이스를 반환

divmod(a, b)

두 숫자의 몫과 나머지를 튜플로 반환

enumerate

객체에 들어있는 요소의 순서(인덱스)와 요소를 튜플로 묶어서 enumerate 객체를 반환

예) list(enumerate(['a', 'b', 'c']))는 [(0, 'a'), (1, 'b'), (2, 'c')]

eval(문자열)

문자열 형태의 파이썬 코드를 실행하고 결과를 반환(문자열을 파이썬 인터프리터에서 실행). 예) eval('print(1, 2)')는 1 2 출력

filter(함수, 반복가능한객체)

반복 가능한 객체에서 특정 조건에 맞는 요소만 가져옴

예) list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4]))는 [2, 4]

float(값)

숫자나 문자열을 실수로 변환하여 반환

help(객체)

객체의 이름, 매개변수, 독스트링(__doc__ 속성)을 도움말 형태로 출력

hex(정수)

정수를 16진수로 된 문자열로 만들어서 반환

id(객체)

객체의 고유한 값을 반환(CPython에서는 메모리 주소)

input(문자열)

사용자의 입력을 받아서 문자열을 반환

int(값)

숫자나 문자열을 정수로 변환하여 반환, int(문자열, 진법)은 해당 진법으로 된 문자열을 10진수로 변환하여 반환

isinstance(객체, 클래스)

객체가 클래스의 인스턴스인지 확인, 클래스의 인스턴스가 맞으면 True, 아니면 False

issubclass(파생클래스, 기반클래스)

클래스가 특정 클래스의 파생 클래스인지 확인, 파생 클래스가 맞으면 True, 아니면 False

iter(객체)

객체에서 이터레이터를 반환. 객체의 __iter__ 메서드를 호출해줌

len(객체)

객체의 길이(요소 개수)를 반환

list(반복가능한객체)

반복 가능한 객체로 리스트를 생성하여 반환

map(함수, 반복가능한객체)

반복 가능한 객체의 요소를 지정된 함수로 처리한 뒤 map 객체를 반환

예) list(map(lambda x: x + 10, [1, 2]))는 [11, 12]

min(반복가능한객체)

max(반복가능한객체)

min은 반복 가능한 객체의 요소 중에서 가장 작은 요소를 반환, max는 반복 가능한 객체의 요소 중에서 가장 큰 요소를 반환

next(이터레이터)

이터레이터에서 요소를 차례대로 가져와서 반환, 이터레이터의 __iter__ 메서드를 호출해줌

oct(정수)

정수를 8진수로 된 문자열로 만들어서 반환

open(파일이름, 파일모드)

지정된 파일을 열어서 파일 객체를 반환, encoding을 지정하면 파일을 해당 인코딩으로 처리(텍스트 모드에만 적용됨)

예) open('hello.txt', 'w', encoding='utf-8')은 파일의 인코딩을 UTF-8로 저장

ord(문자)

문자의 ASCII 코드를 반환. 예) ord('a')는 97

pow(값, 지수)

값을 지수만큼 거듭제곱한 값을 반환

range(횟수)

지정된 횟수만큼 숫자를 생성하는 반복 가능한 객체를 반환, range(시작, 끝, 증가폭)처럼 숫자의 범위와 증가폭을 지정할 수 있음

repr(객체)

파이썬 인터프리터에서 실행할 수 있는 문자열을 반환. repr에서 반환된 문자열은 eval로 실행할 수 있음

reversed(반복가능한객체)

반복 가능한 객체에서 요소의 순서를 뒤집어서 반환

set(반복가능한객체)

반복 가능한 객체로 세트를 생성하여 반환

sorted(반복가능한객체)

반복 가능한 객체의 요소를 오름차순 정렬하여 반환, reversed=True를 지정하면 내림차순 정렬 예) sorted([8, 5, 2])는 [2, 5, 8]

str(값)

값을 문자열로 변환하여 반환

super()

현재 클래스의 기반 클래스 객체를 반환

sum(반복가능한객체)

반복 가능한 객체에 들어있는 모든 요소의 합을 반환

tuple(반복가능한객체)

반복 가능한 객체로 튜플을 생성하여 반환

type(객체)

객체의 클래스(자료형) 객체를 반환. type('클래스이름', 기반클래스튜플, 속성메서드딕셔너리)는 클래스 객체를 생성하여 반환

zip(반복가능한객체, ...)

같은 개수로 된 반복 가능한 객체를 여러 개 넣으면 순서대로 각 요소를 튜플로 묶어서 zip 객체를 반환

예) list(zip([1, 2, 3], [97, 98, 99]))는 [(1, 97), (2, 98), (3, 99)]

컴퓨터는 2진수(0과 1)를 사용하므로 대부분의 프로그래밍 언어는 2진수를 다루는 연산자를 제공합니다. 그런데 2진수로 변환하는 방법을 잘 모른다면 어떻게 해야 할까요? 걱정하지 않아도 됩니다. 파이썬에서는 간단하게 2진수와 10진수를 서로 변환할 수 있습니다.

다음과 같이 10진수를 2진수로 된 문자열로 변환할 때는 bin을 사용합니다. 반대로 2진수에서 10진수로는 입력 즉시 변환됩니다. 만약 2진수가 문자열 형태라면 int에 문자열과 2를 지정하여 10진수로 변환할 수 있습니다. 그리고 코드에서 2진수를 직접 입력할 때는 맨 앞에 0b를 붙입니다(binary의 b).

  • bin(정수)
  • int('2진수문자열', 2)
>>> bin(13)    # 10진수 13을 2진수로 변환
'0b1101'
>>> 0b1101     # 2진수 1101을 10진수로 변환
13
>>> int('1101', 2)    # 2진수로 된 문자열 1101을 10진수로 변환
13

10진수 13을 2진수로 표현하면 1101이 됩니다. 여기서 2진수의 자릿수는 일정한 값을 가지는데 낮은 자릿수부터 2의 거듭제곱 순서로 커집니다(20부터 시작). 따라서 1101은 8, 4, 1이 위치한 자릿수가 1이므로 8 + 4 + 1 = 13입니다.

이제 2진수의 각 자릿수를 AND, OR, XOR, NOT 연산을 하는 비트 논리 연산자를 사용해보겠습니다.

  • a & b
  • a | b
  • a ^ b
  • ~x
>>> bin(0b1101 & 0b1001)    # 비트 AND
'0b1001'
>>> 13 & 9                  # 비트 AND
9
>>> bin(0b1101 | 0b1001)    # 비트 OR
'0b1101'
>>> 13 | 9                  # 비트 OR
13
>>> bin(0b1101 ^ 0b1001)    # 비트 XOR
'0b100'
>>> 13 ^ 9                  # 비트 XOR
4
>>> bin(~0b1101)            # 비트 NOT
'-0b1110'
>>> ~13                     # 비트 NOT
-14

먼저 &|^~ 연산자로 각 비트를 연산했을 때의 결과(진리표)는 다음과 같습니다. 진리표의 내용이 많아 보여도 각 연산자의 특성만 이해하면 진리표 전체를 외우지 않아도 됩니다.

▼ 표 48-1 &, |, ^, ~의 연산 결과

연산자

비트1

비트2

결과

&

0

0

0

0

1

0

1

0

0

1

1

1

|

0

0

0

0

1

1

1

0

1

1

1

1

^

0

0

0

0

1

1

1

0

1

1

1

0

~

0

 

1

1

 

0

예를 들어 & 연산자는 비트 AND이므로 두 값이 모두 1일 때 1입니다. 따라서 하나라도 0이면 0이 나옵니다. 즉, 0b1101과 0b1001을 비트 AND 연산했을 때 0 & 1은 0 그리고 1 & 1은 1이 나오므로 0b1001이 됩니다. 10진수로 표현하면 13 & 9는 9가 되겠죠?

>>> bin(0b1101 & 0b1001)
0b1001
>>> 13 & 9
9

다음과 같이 비트 논리 연산자는 각 자릿수를 연산하여 결과를 만듭니다. 이때 각 자릿수의 연산 결과는 다른 자릿수에 영향을 미치지 않습니다.

▼ 그림 48-1 비트 단위 연산
그림 48 1 비트 단위 연산

이번에는 비트의 위치를 이동시키는 시프트 연산자입니다. 시프트 연산자는 << 또는 >> 다음에 비트를 이동시킬 횟수를 지정합니다. <<는 비트를 왼쪽으로 이동시키고, >>는 비트를 오른쪽으로 이동시킵니다.

  • a << b
  • a >> b
>>> 0b0011 << 2    # 비트를 왼쪽으로 2번 이동
12
>>> bin(12)
'0b1100'
>>> 0b1100 >> 2    # 비트를 오른쪽으로 2번 이동
3
>>> bin(3)
'0b11'

참고로 비트를 오른쪽으로 이동시켰을 때 1이 들어갈 공간이 없다면 1은 사라집니다. 즉, 계속 비트를 오른쪽으로 이동시키면 1은 모두 사라지고 최종 결과는 0b0이 됩니다.

다음은 파이썬의 비트 연산자입니다.

▼ 표 48-2 파이썬 비트 연산자

연산자

기능

문법

설명

&

비트 AND

a & b

a와 b의 비트를 AND 연산

|

비트 OR

a | b

a와 b의 비트를 OR 연산

^

비트 XOR

a ^ b

a와 b의 비트를 XOR 연산(배타적 OR, Exclusive OR)

~

비트 NOT

~x

x의 비트를 뒤집음

<< 

비트 왼쪽 시프트

a << b

a의 비트를 b번 왼쪽으로 이동시킴

>> 

비트 오른쪽 시프트

a >> b

a의 비트를 b번 오른쪽으로 이동시킴

&=

비트 AND 연산 후 할당

a &= b

a와 b의 비트를 AND 연산한 후 결과를 a에 할당

|=

비트 OR 연산 후 할당

a |= b

a와 b의 비트를 OR 연산한 후 결과를 a에 할당

^=

비트 XOR 연산 후 할당

a ^= b

a와 b의 비트를 XOR 연산한 후 결과를 a에 할당

<<=

비트 왼쪽 시프트 후 할당

a <<= b

a의 비트를 b번 왼쪽으로 이동시킨 후 결과를 a에 할당

>>=

비트 오른쪽 시프트 후 할당

a >>= b

a의 비트를 b번 오른쪽으로 이동시킨 후 결과를 a에 할당



https://dojang.io/mod/page/view.php?id=1160

font_name = mpl.font_manager.FontProperties(fname='C:\Windows/Fonts/malgun.ttf').get_name()

mpl.rc('font', family=font_name)

파이썬이 1990년 말에 만들어 졌다고 알고 있습니다.

(실제 1.0은 1994년에 나왔군요)
거의 25살 청년이 다 되었네요. 적지 않은 나이이지만,
우리나라에서는 비교적 최근에 유명해 졌다고 할 수 있습니다.
아마도 더 기본적인 것은 지금은 구글을 나왔지만 파이썬의 창시자가
구글에 몸담고 있었고 구글 API를 포함한 많은 것들이 모두 파이썬을
기본으로 시작하여 그렇지 않나 싶습니다.

심지어는 최근에 머신러닝으로 구글이 오픈한 텐서플로우 조차도 
파이썬과 C API 로만 구성되어 있는 것을 보더라도 금방 이해할 수 있습니다.

이것은 파이썬 만큼 글루 언어로서 다른 내부 블랙박스를 하이레벨에서 
핸들링하기 좋은 언어는 많지 않다는 것을 의미하는 것입니다.
파이썬 자체의 속도는 차체하고 (시간이 많이 걸릴 일들은 모두
그 하단의 블랙박스에 맡기면 됩니다) 쉽게 API를 핸들링 하고 
테스트 해 볼 수 있는 장점 등은 한번 사용해 보시면 앱니다.. ^^

제가 이런 말씀을 감히 드릴 수 있는 것은 어찌보면 25년 (곧 26년째)
개발을 하고 있는데 그동안 직접 프로덕트나 프로젝트에 이용해 왔던 언어
및 프레임워크 들:
C, C++, Motif, MFC, awk, perl, Java, C#, VB, Delphi, CGI,
PHP, ASP, JSP,...
등 중에서도 그 쓰임새가 남다르기 때문입니다.

암튼 현재 파이썬이 크게 2.7.11, 3.5.1 과 같이
2.x와 3.x가 공존하고 있습니다.

2.x가 존재하는 이유는 2008년 2.6이었을 때,
기존의 하위 호환성을 포기하는 대신 새로운 기능과
성능으로 나가겠다는 것을 표명한 상태입니다.
또한 공식 블로그 등을 보더라도 2.7.x 이후 버전은 나오지 않을 거라 들은 것 같습니다.

암튼 최근 3.5.x 도 나왔고 기존에 버전2의 것들도 필요에 따라 3으로 옮겨갈 필요가 있었습니다.

약 20개 정도의 기존에 2.7.x 용으로 작성했던 파이썬 코드와 대여섯개의
써드파티 라이브러리 등을 3.5.x 로 변경해 보았는데 대략 15개 정도의 것들만
유념하면 대부분의 경우에 이상없이 잘 동작할 수 있을 것 같았습니다.

그 시행착오를 공유해 봅니다.

1) Exception 예외처리에서 as
<<<
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
>>>
except OSError as e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)

콤마 대신 as 로 주어야 합니다.


2) file() built-in
<<<
si = file(self.stdin, 'r')
>>>
si = open(self.stdin, 'r')

file build-in 함수를 사용한 경우 open 으로 그대로 변경하면 됩니다.


3) print keyword => print build-in function
<<<
print str(err)
>>>
print(str(err))

print 함수로 변경되었기에 print 다음에 괄호가 꼭 나와야 합니다.


4) long() ==> int()

long type is just int
long type 이 없어지고 int가 long 까지 커버를 한다고 하는군요.


5) basestring => str
<<<
isinstance(obj, basestring)
>>>
isinstance(obj, str)

기존에 basestring, unicode 등이 모두 str 자체 (unicode 의미)로 의미 변경되었습니다.


6) xrange ==> range

2.x 에서는 "for i in xrange(10)" 과 같이 range 와 xrange built-in 함수가 있었습니다.
차이점은 range는 [0,1,2,...,9] list를 반환하고x xrange는 generator로서 그 안에
해당 값으로 yield 시키는 차이가 있었습니다.
3.x 에서는 2.x의 range에 해당하는 함수가 없어지고 xrange 개념의 함수가 range 라고
되어 버렸습니다.
따라서 xrange 라고 되어 있던 부분은 그냥 range 라고 변경만 해 주면 됩니다.


7) byte array is not str

TypeError: a bytes-like object is required, not 'str'

>>> print(b'abcde' == 'abcde')
False
>>> print(b'abcde'.decode() == 'abcde')
True

2.x 에 없던 b'abcde' 라는 바이트 스트림 문자열 상수(리터럴)이 존재합니다.
암복호화를 비록하여 아주 많은 함수들이 기존 문자열 대신 이 바이트 스트림을 
패러미터 또는 결과값으로 이용합니다.
만약 바이트 스트림인 경우 이를 다시 문자열로 변환하기 위하여 decode()를 호출하여야
합니다. 패러미터는 디코딩 문자셋인데 디폴트는 "UTF-8" 입니다.


8) ImportError: No module named 'xmlrpclib'

import xmlrpc.client as xmlrpclib

2.x에 xmlrpclib 모듈을 이용했었다면 이제는
xmlrpc.server 와 xmlrpc.client 로 나누어 이용합니다.
클라이언트였다면 위와 같이 할 수 있습니다.


9) AttributeError: 'dict' object has no attribute 'iteritems'
<<<
d.iteritems()
>>>
d.items()

dict 타입에 대하여 iteritems 메서드를 호출한 경우에는 items를 대신 부르면 됩니다.


10.1) AttributeError: 'dict' object has no attribute 'has_key'
<<<
d.has_key(k)
>>>
in d

dict 에 대하여 has_key() 로 해당 키가 존재하는가를 2.x에서 많이 조사하였는데,
이 대신 in 오퍼레이터를 사용합니다.


10.2) 'dict.keys()' object is not list
<<<
ks=d.keys()
isinstance(ks, list)
>>>
False

dict 에 대하여 has_key() 로 해당 키가 존재하는가를 2.x에서 많이 조사하였는데,
기존 2에서 dict.keys() 로 해당 키만 가져오면 디폴트로 list 였지만 3에서는 dict_keys 라는 class가 리턴됩니다.
2에서 처럼 사용하고 싶으면...
<<<
ks=list(d.keys())
isinstance(ks, list)
>>>
True



11) import thread : ImportError
<<<
thread.get_ident()
>>>
threading.current_thread().ident

2.x에서 thread.get_ident() 를 호출하여 현재 쓰레드의 int 형 id를 얻었었는데 
3.x에서는 thread 모듈 자체가 없어졌군요.
대신 threading.current_thread().ident 라고 호출하면 됩니다.


12) TypeError: write() argument must be str, not bytes
또는 EOFError: Ran out of input

from pickle import loads, dumps

d={}

s = dumps(d)
with open('load_dump.pkl', 'bw+') as ofp:
  ofp.write(s)

with open('load_dump.pkl', 'br+') as ifp:
  s = ifp.read()
ld = loads(s)

print(d == ld)

위에 처럼 'w' 대신 'bw+', 'r' 대신 'br+'라고 지정
pickle을 위하여 dumps 혹은 loads를 하여 파일에 저장한다고 하였을 때
해당 저장 파일은 바이너리로 열고 닫아야 합니다.


13) AttributeError: 'function' object has no attribute 'func_name'
<<<
if original_function.func_name not in __ARG_SPEC__:
>>>
if original_function.__name__ not in __ARG_SPEC__:

함수 이름을 Inspect 할 경우 
기존의 func_name 대신 __name__ 이라고 참조합니다.


기타 더 많은 것들이 있을 수 있으나 이 정도면 이상없이 잘 동작했습니다.

또한 다음과 같은 서드파티 모듈들도 모두 3.x 를 잘 지원하였습니다.


pip3 install pudb # for terminal debugger
pip3 install psutil # for system resource monitoring
pip3 install pycrypto # for F33.util.ftcipher
pip3 install termcolor # for console color
pip3 install APScheduler # for F33.util.svc.service
pip3 install ujson # fast json encoder/decoder
pip3 install leveldb # for leveldb key-value storage
pip3 install requests # for task-job
pip3 install celery # for task-job
pip3 install redis # for task-job


14) apply built-in 함수

apply(pack, vs)

와 같이 사용한 소스도 있는데 2.3 에서 부터는 거의 이용되지 않는다고 합니다.
3에서는 아예 없어 졌네요.

apply(function, args, kwargs)

와 같이 사용된 것은

function(*args, **kwargs)

로 이용하면 된다고 합니다.



어느 분께는 도움이 되셨기를...

http://egloos.zum.com/mcchae/v/11195891





Python의 반복문

보통 for 구문을 사용해서 0부터 99까지 반복한다고 하면 다른 프로그래밍 언어에선 이런 느낌이 됩니다.

for (let i = 0; i < 99; ++i) {
  do_something(i);
}

하지만 Python에선 for문이 저런 구조가 아닙니다. 그렇기에 우리는 range를 쓴다고 알고 있습니다.

for i in range(100):
    do_something(i)

그럼, range는 뭘까요? range(100)을 실행하면 어떻게 되길래 우리가 원하는 반복문이 되버리는 걸까요? 일단 Python 2에서 확인해보도록 합시다.

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(range(10))
<type 'list'>
>>> for i in range(10):
...   print i**3
...
0
1
8
27
64
125
216
343
512
729

100은 블로그에 담기엔 예시결과로는 너무 큰 관계로 10으로 줄였습니다. 실행하면 list로 0 이상 10 미만의 숫자가 들어가는 것을 확인할 수 있습니다. 도움말을 읽으면 당연히 알 수 있지만, range 함수는 정해진 범위를 list로 만들어줍니다. for문은 range의 결과로 나온 list에서 1개씩 뽑아서 사용하는 것이구요. Python의 for는 다른 언어에서의 foreach라고 부르는 것들과 비슷합니다.

for (let i of myArray) {
  do_something(i);
}

Python의 이런 반복은 얼핏보면 편하지만 함정이 있습니다. 만약 0부터 1000억까지 반복을 해야한다면 어떨까요? range(100000000)이라고 적으면 0부터 1000억-1 만큼의 인자가 들어있는 list가 생겨날 것입니다. list에 들어있는 것도 결국 컴퓨터가 저장하고 있어야 하는데 이대로 가다간 메모리가 버틸까요? 이 방법으로 접근하면 너무나도 비효율적입니다. Python으론 이런 방법밖에 없는걸까요?

이 문제를 해결하기 위해 Python 2에는 xrange라는 함수가 있습니다. Python 3에서는 기존의 range를 xrange가 대체했습니다. 위의 예제들을 xrange로 바꿔서 다시 해보죠.

>>> xrange(10)
xrange(10)
>>> type(xrange(10))
<type 'xrange'>
>>> for i in xrange(10):
...   print i**3
...
0
1
8
27
64
125
216
343
512
729

일단 반복문이 잘 돌아간다는 것은 알 수 있지만 그 외의 값은 미궁속입니다.

Python 2의 xrange / Python 3 range 동작 살펴보기

일단 xrange가 어떻게 for문에 값을 전달하는지 알아봅시다. 먼저 xrange 값을 하나 만듭니다. 그리고 iter함수로 iterator를 받습니다.

>>> ten = xrange(10)
>>> it = iter(ten)
>>> it
<rangeiterator object at 0x10f27bde0>

그러면 이제 받은 값으로 ten의 구성요소에 하나씩 접속해볼 수 있습니다.

>>> next(it)
0
>>> next(it)
1
>>> next(it)
2

그리고 next 함수를 이용해서 값을 원하는 시점에 하나씩 받아올 수 있습니다. for문은 이 방식을 이용해서 xrange값의 iterator를 받고 내부적으로 next를 호출해서 사용합니다.

이것은 Python 3의 range도 동일합니다.

>>> ten = range(10)
>>> ten
range(0, 10)
>>> it = iter(ten)
>>> it
<range_iterator object at 0x106c70ed0>
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2

Generator

하지만 이건 Python에서 기본 지원하는 경우이고, 만약 수없이 많은 자료를 돌아다니면서 반복문을 돌려야한다면 어떨까요? 가령 수백만줄의 파일을 읽어서 한줄씩 처리중이었다면 어떻게 해야할까요? 모두 list에 올려놓고 하기엔 메모리가 버티지 못할 것입니다. 그 경우를 위해 Generator가 있습니다.

가볍게 0에서 9까지의 세제곱을 출력하는 Generator를 만들어서 사용해봅시다.

def gen():
    for i in range(10):
        yield i ** 3

for x in gen():
    print(x)

여기서 gen 함수가 바로 generator입니다.

>>> gen()
<generator object gen at 0x106aec1a8>

yield

Generator에서는 yield라는 키워드를 사용합니다. yield의 동작을 알아봅시다.

def gen():
    yield 'one'
    yield 'two'
    yield 'three'

g = gen()
print(next(g))  # one
print(next(g))  # two
print(next(g))  # three
print(next(g))  # raise StopIteration

yield는 함수 실행 중간에 빠져나올 수 있는 generator를 만들 때 사용합니다. return이었다면 'one'이 반환되고 끝났겠지만 실제로는 그 뒤로도 다시 사용할 수 있었죠.

yield는 단순히 값을 내보낼 수만 있는 것은 아니고, 넣어줄 수도 있습니다.

def gen():
    val = 111111
    while True:
        val = (yield val) * 111111

g = gen()
print(next(g))  # 111111
print(g.send(2))  # 222222
print(g.send(3))  # 333333

뭐에 쓰지?

위에서도 언급했지만 대용량 자료 처리등은 메모리에 모두 올려놓고 할 수 없습니다. 그런 경우 한 줄씩 읽은 뒤 generator를 이용한 반복처리를 하면 편합니다. 실제로도 Flask에서의 대용량 파일 전송, Sphinx의 확장 개발등에 사용됩니다.

요약

  1. yield 키워드를 사용하면 generator를 만들 수 있다.
  2. generator는 한번에 끝나지 않고 여러번에 걸쳐 입출력을 받을 수 있다.
  3. 대용량 처리 함수 제작 등에 편리하다
  4. 따라서, list가 필요한게 아니라면 Python 2에서 반복은 xrange를 쓰자.





function convertJsonStr($row) {

if(is_null($row)) { 

return "{}"

}

 

 

$r = "{"

for($i = 0; $i < count($row); $i++) { 

$r .= "{"

$curr = $row[$i]; 

$end = count($curr); 

$ep = 0; 

foreach($curr as $k => $v) { 

$ep++; 

$r .= "\"" . $k . "\":\"" . $v . "\""

if($ep == $end) {

// 할일없음 

}

else {

$r .= ", "

}

}

 

 

$r .= $row[i]; 

$r .= "}"

if($i < count($row) - 1) {

$r .= ", "

}

}

$r .= "}"

 

 

return $r; 

}


'Program > PHP' 카테고리의 다른 글

API  (0) 2017.02.21
cannot create mssql.so on ubuntu 16.04 and php5.6  (0) 2017.02.17
PHP7 에서 PHP5.6 사용하기  (0) 2016.11.11
SQL Relay php connection  (0) 2016.07.17
PHP PHP EXCEL (PHP 엑셀 읽기 쓰기)  (0) 2016.01.21

자꾸 가물가물해서 퍼왔습니다.


http://byneonkid.tistory.com/98


이번엔 Python을 가상 환경에서 설치하는 방법을 알아봅시다.



왜 Python을 가상 환경에서 설치해야 할까요?



Python의 버전은 현재 2.7 버전과 3.x 버전이 존재합니다. 아마 프로그래밍을 조금 해보신 분들 중에 Java 라는 언어로 프로그래밍을 많이 해보신 분들이라면, 이런 버전의 차이에 굉장히 반감을 하거나 민감하게 반응하실 수 있습니다.


그렇습니다. 그 이유 중에 하나는 바로 여러분들이 프로그램을 개발하다 반드시 사용하게 되는 바로 "라이브러리"라는 녀석 때문이죠. 물론 공개된 라이브러리들은 대부분 최신 버전에 맞춰 수정이 되긴 하지만, 자신이 개발하는 프로젝트 내지 프로그램이 한 라이브러리만 쓰는 것은 아니기 때문에 모든 라이브러리에 버전을 맞추려면 여러 가지 고려해야될 사항이 많아집니다. 


Java의 경우, Java 7 버전과 Java 8 버전을 동시에 설치해야하는 번거로움이 생깁니다. 물론 설치에 대한 번거로움이 끝이 아니죠. 여기에 자신이 어떤 버전의 Java를 쓸 것인지 물어보지도 않고, 그냥 갔다가 컴파일 해버려서 라이브러리 간에 버전 충돌이 일어나는 일이 생기기도 합니다. 여간 불편한 것이 아니죠..


그래서 Python에는 이 개발 환경을 분리시키기 위해 가상 환경이라는 것을 제공합니다.

그림으로 보면, 현재 여러분 PC에 현재 Python이 설치되어 있고, 현재 설치된 Python을 두 공간으로 분리해서 위 프로젝트는 2.7버전, 아래 프로젝트는 3.x 버전으로 환경을 나누게 되면 버전 충돌 문제가 해소됩니다.


Python을 가상 환경으로 구축하는 방법에는 여러 가지가 있지만, 가장 대표적인 방법은 Virtualenv, Anaconda, Docker  방법이 있습니다.


그 중 가장 쉽고 편한 것이 Virtualenv이고, Docker는 최근에 나온 컨테이너 형태의 환경이기 때문에 Python의 가상 환경 뿐만이 아니라 서버의 가상 환경으로도 사용하실 수도 있고, 만약 개발하는 프로젝트가 웹 서버 등의 프로젝트라면, Virtualenv보다는 Docker가 더 좋을 수 있습니다.


이 포스트에서는 간단히 Virtualenv를 사용해 가상 환경을 구축하는 방법을 알아보도록 하겠습니다.



Virtualenv 설치

Virtualenv의 설치는 pip로 간단히 설치할 수 있습니다.


Bash
$ sudo pip install --upgrade virtualenv

Linux나 OS X의 경우, sudo 명령어를 이용하여 virtualenv를 설치하시면 됩니다.

혹은 Ubuntu Repository에서도 받으실 수 있습니다.


Batch
C:\> pip install --upgrade virtualenv

Windows는 관리자 명령으로 명령 프롬포트를 열어주시고, 위 명령어를 타이핑 하시면 됩니다.



가상 환경 구축

이제 Virtualenv를 설치했으니 이를 이용해 가상 환경을 직접 만들어보도록 하겠습니다.

매우 쉬우니, 그냥 보고 따라하기만 하시면 됩니다.


Bash
$ mkdir CVProject

여러분들이 원하시는 경로에 디렉토리를 만들어줍니다. Linux, OS X 에서는 mkdir 명령어를 사용합니다.


Batch
C:\Users\K.I.D> md CVProject

Windows는 md 명령어를 사용합니다. (사실 Windows에서 그냥 새 폴더 하나 만든다고 생각하시면 됩니다 ^^;)


Bash
$ virtualenv -p ${PYTHON_VERSION} ${DIRECTORY}

virtualenv의 p 옵션을 이용해서 원하시는 Python 버전을 입력하시고, 위에서 만든 디렉토리 이름을 치면, 해당 디렉토리에 Python 가상 환경이 생성됩니다.

ex) Python 2.x 버전은 그냥 Python, Python 3.x 버전은 Python3 라고 입력.




가상 환경 실행

이제 구축을 해봤으니 가상 환경이 제대로 실행되는지 확인해보겠습니다.


Bash
$ source CVProject/bin/activate.sh

Linux, OS X는 위 명령어를 이용해 가상 환경을 실행합니다.


Batch
C:\Users\K.I.D> CVProject\Scripts\activate

Windows는 Scripts 폴더에 실행 파일이 미리 만들어져 있으므로 이를 실행하면 됩니다.


Bash
(CVProject) $ python --version

그럼 이제는 쉘 앞에 자신의 프로젝트 폴더명이 앞에 보일 것입니다.


Batch
CVProject) C:\Users\K.I.D> python --version

Windows는 위와 같이 보입니다.


python --version 명령어를 입력해, 자신이 원하는 Python의 버전으로 구축이 되었는지를 확인합니다.


Bash
$ whereis python

Linux에서 whereis 명령어를 사용해 python의 PATH를 확인합니다. 마지막 부분에 자신의 가상 환경 경로가 표시됨을 확인할 수 있습니다.



각종 라이브러리 등의 설치는 반드시 (${가상환경 이름})이 나온 상태에서 진행하셔야만 가상 환경에 라이브러리가 설치됩니다. 그렇지 않은 상태에서는 시스템에 설치되므로 반드시 확인바랍니다.



autoenv 사용 (Linux, OS X only)

virtualenv를 사용하려하니 일일이 source 명령어를 사용하는게 매우 번거롭습니다. 그렇다고 터미널이나 쉘을 띄울 때마다 가상 환경을 실행하자니 그것도 문제이지요?


그래서 autoenv를 사용해보기로 했습니다.


Bash
$ sudo pip install --upgrade autoenv

autoenv는 kennethreitz님이 개발하신 코드로 virtualenv 디렉토리에 접근하면 자동으로 원하는 커맨드를 입력하게 하는 코드입니다.


작동 방식은 다음과 같습니다.


사실 커맨드 실행이 맞는 표현이겠지만, 우리가 원하는 것은 Virtualenv 실행이 목적이기 때문에, 그렇게 적었습니다 ^^;

이제 실행 방식을 알았으니 자신의 virtualenv 공간에 .env 파일을 생성해봅시다.


Bash
$ vim .env

vim 혹은 nano, emacs 같은 에디터를 이용해 .env 파일을 생성합니다.


Bash
source ~/CVProject/bin/activate

간단하게 virtualenv만 실행시키는 커맨드를 적었습니다.


Bash
source ~/CVProject/bin/activate
clear
echo ''
echo ' I'm CVProject'
echo ' enjoy Coding!'
echo ''
echo '           Created by. Neon K.I.D'
echo ''

고급된 방법(?)으로 스크립트를 작성하실 수도 있습니다.

그럼 이제, 터미널과 쉘을 새로 실행시켜, CVProject 폴더에 들어가면..


Bash
$ cd CVProject

Virtualenv 환경에 접근이 가능하다는 것을 알 수 있습니다.


"그런데, 아 나는 이것도 불편해, 간단한 명령어로 처리할 수 있는 방법이 없을까?"


그러시는 분들은, alias를 사용해보는 방법도 있으니, 해당 방법을 써보는 것도 나쁘지 않겠습니다.


Bash
$ alias cv='cd CVProject'

저는 간단히 상대 경로를 넣었지만 원래 alias를 하실 때는 절대 경로를 하셔야 됩니다 ^^;

명령어가 잘 동작하는지 확인했으면, 이제 프로필에 넣어주면 되겠죠?


Bash
$ vim .bashrc

bashrc 파일을 로드하고, alias 부근이나 아무 군데에 다음과 같이 추가합니다.


Bash
alias cv='cd CVProject'

추가하고 저장 한 뒤, 새로운 쉘이나 터미널을 실행하고, cv 명령어만 입력하면 자동으로 가상 환경에 들어가집니다. 



마치며... 

여러가지 내용이 섞여있어 어떤 말인지 모르시는 분들이 많으실 수도 있습니다. 개발 환경을 구축하는 데는 마치 코딩과 같아 여러분들이 얼마든지 튜닝만 한다면 편한 개발 환경이 될 수도 있고, 그렇지 않을 수 있습니다.


Python의 가상 환경은 그만큼의 메리트가 존재합니다. (언어의 사용도 그만큼 메리트가 있었으면.... (?) ) Java에도 개인적으로 이런 환경이 나오기를 원하지만, 그 환경을 Docker가 그나마 메꿔주고 있기 때문에 참 Docker 정말 좋습니다 ^^


여기까지 Virtualenv를 이용한 Python 개발 환경 구축이었습니다.



출처: http://byneonkid.tistory.com/98 [긁적긁적.....]

text = 'foo = 23 + 42 * 10'

tokens = [('NAME', 'foo'), ('EQ', '='), ('NUM', '23'), ('PLUS', '+'),
('NUM', '42'), ('TIMES', '*'), ('NUM', '10')]

import re
NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)'
NUM = r'(?P<NUM>\d+)'
PLUS = r'(?P<PLUS>\+)'
TIMES = r'(?P<TIMES>\*)'
EQ = r'(?P<EQ>=)'
WS = r'(?P<WS>\s+)'

master_pat = re.compile('|'.join([NAME, NUM, PLUS, TIMES, EQ, WS]))

from collections import namedtuple
import json
Token = namedtuple('Token', ['type', 'value'])

def generate_tokens(pat, text):
scanner = pat.scanner(text)
for m in iter(scanner.match, None):
yield Token(m.lastgroup, m.group())

for tok in generate_tokens(master_pat, 'foo = 42'):
print(tok)

tokens = (tok for tok in generate_tokens(master_pat, text) if tok.type != 'WS')
#print(list(tokens))
jj = {}
for tok in tokens:
#print(tok)
#t = "'{0}':'{1}'".format(tok.type, tok.value)
#t = ':'.join([tok.type, tok.value])
jj[tok.type] = tok.value

print(jj)

jj2 = json.dumps(jj)
print(jj2)

print("")
# 테스트용 Dictionary
customer = {
'id': 152352,
'name': '강진수',
'history': [
{'date': '2015-03-11', 'item': 'iPhone'},
{'date': '2016-02-23', 'item': 'Monitor'},
]
}

# JSON 인코딩
jsonString = json.dumps(customer)

# 문자열 출력
print(jsonString)
print(type(jsonString)) # class str



이터레이터(iterator): 반복 가능한 객체
이터레이션: 반복 가능한 객체에서 해당 값을 가져오는 행위
iter 함수 : list나 dict를 이터레이터로 만들어주는 함수


제너레이터: 이터레이터를 만들어 주는 것
왜 제너레이터를 사용하는 가? 메모리 효율적 사용을 위해

Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.

>>> import sys
>>> 
>>> a=[i for i in range(100)if i %2]
>>> b=(i for i in range(100)if i %2)
>>> 
>>> type(a)
<class 'list'>
>>> type(b)
<class 'generator'>

>>> sys.getsizeof(a)
528
>>> sys.getsizeof(b)
88

>>> aa=[i for i in range(1000)if i %2]
>>> bb=(i for i in range(1000)if i %2)
>>> sys.getsizeof(aa)
4272
>>> sys.getsizeof(bb)
88
>>> sys.getsizeof([i for i in range(100)if i %2])
528
>>> sys.getsizeof((i for i in range(100)if i %2))
88
>>> sys.getsizeof([i for i in range(1000)if i %2])
4272
>>> sys.getsizeof((i for i in range(1000)if i %2))
88
>>> class Star:
def __init__(self):
self.name="없음"
self.age=0
print("생성자")
def __del__(self):
print("소멸자")
def Disp(self):
print(__name__)

>>> def sub():
s1=Star()

>>> sub()
생성자
소멸자
>>> 
>>> 
>>> class Star:
def __init__(self,name,age):
self.name=name
self.age=age
print("생성자")
def __del__(self):
print("소멸자")
def Disp(self):
print(__name__)
print("name=",self.name,"age=",self.age)

>>> def sub():
s1=Star("송혜교",35)
s1.Disp()

>>> sub()
생성자
__main__
name= 송혜교 age= 35
소멸자
>>> 
http://www.flowdas.com/blog/generators-in-python/


지난 글에서는, 생산자-소비자 패턴(Producer-Consumer Pattern)의 소비자 쪽의 코드를, 함축적이면서도 자연스럽게 기술할 수 있는 방법을 제공하는 이터레이터를 살펴봤습니다. 오늘은 생산자 쪽을 들여다봅니다.

앞서 Range 라는 클래스를 통해 이터레이터를 살펴보았습니다만, 이 코드에는 불편한 부분이 있습니다. 대부분의 작업은 __next__() 메쏘드를 통해 구현되는데, 매번 예전의 상태를 복구한 후, 작업을 수행하고, 다시 상태를 저장하는 작업을 반복하게 됩니다. Range 와 같은 간단한 작업에서야 문제될 것이 없지만, 알고리즘을 기술하는 자연스러운 방식에서 벗어나 있기 때문에, 작업이 복잡해질수록 점점 문제가 됩니다. 이런 스타일의 프로그래밍은 이벤트 주도형 프로그래밍(event-driven programming)에서 자주 등장하는데, 비동기 IO(Asynchronous IO) 나 GUI 프로그래밍을 해 보셨다면 이미 익숙할 겁니다.(지금 우리가 걷고 있는 길은 이 문제들에 대한 직접적인 해결책으로 이어집니다. 하지만 아직은 갈 길이 멉니다.)

파이썬에서는 제너레이터(generator)라는 이터레이터의 확장을 제공합니다. 이터레이터가 단지 프로토콜인데 반해, 제너레이터는 yield 라는 전용의 키워드를 통해 문법적인 특별함을 더합니다. Range 를 제너레이터로 다시 구현해 보겠습니다. (되도록 이터레이터 구현과 유사하게 코드를 구성했습니다.)

>>> def Range(n):
...     c = 0
...     while c < n:
...         yield c
...         c += 1
>>> for x in Range(3):
...     print(x)
0
1
2

파이썬은 함수 정의에 yield 키워드가 등장하면 그 함수 전체를 제너레이터로 인식하고, 특별한 VM 코드를 생성해냅니다. 함수의 코드를 재구성해서 같은 일을 하는 이터레이터를 만든 다음, 원래 함수는 그 이터레이터 인스턴스를 돌려주는 함수로 바꾼다고 보시면 됩니다.

>>> Range(3)
<generator object Range at 0x1017d3370>

제너레이터는 이터레이터입니다.

>>> r = Range(3)
>>> next(r)
0
>>> next(r)
1
>>> next(r)
2
>>> next(r)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
StopIteration

이터레이터 구현과 제너레이터 구현간에는 이런 문법적인 차이가 있습니다.

  • 이터레이터 구현에서 __next__() 의 return 값은, 제너레이터 구현에서는 yield 값으로 대체됩니다.
  • 제너레이터에서 return 문은 StopIteration 예외를 일으킵니다. 함수의 끝에 도달해도 마찬가지 입니다. 파이썬 3 에서는 return 문이 값을 포함할 수도 있지만, 오늘 다룰 범위를 벗어납니다.

제너레이터를 위해 파이썬이 만들어내는 이터레이터를 제너레이터-이터레이터(generator-iterator)라고 부르는데, 이터레이터는 프로토콜인 반면 제너레이터-이터레이터는 실제 구현이 제공되는 것이라, 일반적인 이터레이터에서 지정하지 않은 동작들이 구체적으로 정의되기도 합니다.

가령 예외의 경우, 이터레이터의 __next__() 가 StopIteration 또는 그 외의 예외를 일으킨 후에 이터레이터가 어떤 상태가 되는지는 정의되어있지 않습니다. 하지만 제너레이터의 경우는 항상 이렇게 됩니다.

  • 일단 StopIteration 예외가 일어나면 그 이후에는 계속 StopIteration 예외를 일으킵니다.
  • StopIteration 이외의 예외가 일어나면, 일단 caller 에게 예외가 전달됩니다. 하지만 그 이후로는 계속 StopIteration 예외를 일으킵니다.

재진입(Reentrancy) 문제도 이터레이터에서는 정의되어있지 않습니다. 가령 __next__() 메쏘드 내에서 직접적이던 간접적이던 next(self)를 호출했을 때 어떤 일이 일어나야 할지 지정되어 있지 않다는 것입니다. 그러니 재귀적인 구현도 생각해볼 수 있습니다. 하지만 제너레이터에서는 재진입이 허락되지 않습니다.

>>> this = (lambda: (yield next(this)))()
>>> next(this)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "<console>", line 1, in <lambda>
ValueError: generator already executing

그런데 이 예에서 볼 수 있듯이 lambda 로 제너레이터를 만들어도 문제가 되지는 않습니다. 그런데 lambda 의 바디에는 문장(statement)이 아니라 표현식(expression) 이 온다는 것을 기억하십니까? yield 를 둘러싼 괄호가 yield 가 표현식임을 알려줍니다. 예 지금까지 저희는 yield 를 문장처럼 사용해 왔습니다만, 사실 yield 는 표현식입니다. 그러면 그 값은 뭘까요?

지금까지의 예 에서 yield 표현식(yield expression)의 값은 None 입니다.

>>> def G():
    while True:
        input = yield 'output'
        print(repr(input))
>>> g = G()
>>> next(g)
'output'
>>> next(g)
None
'output'

처음 인쇄된 'output' 은 next(g) 의 반환 값이고, 다음 next(g) 호출의 처음에 인쇄된 None 이 yield 표현식의 결과값입니다. 순서를 잘 기억하세요, 첫 번째 next() 에서 yield 로 값을 전달한 후에, 다음 next() 가 호출될 때야 비로소 yield 표현식이 값을 전달합니다.

caller 가 제너레이터로 값을 전달해 준다면 yield 표현식은 None 이외의 값을 가질 수 있습니다. 이를 위해 제너레이터에는 send() 라는 메쏘드가 제공됩니다.

>>> g = G()
>>> next(g)
'output'
>>> g.send('input')
'input'
'output'

None 이 send() 가 전달한 'input'으로 대체된 것을 확인하실 수 있습니다. 주의할 부분은 첫 번째 next(g) 대신 g.send('input') 을 사용할 수 없다는 것입니다.

>>> g = G()
>>> g.send('input')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator

이유는 앞에서 주의를 부탁한 실행 순서 때문입니다. 첫 번째 yield 표현식은 최초의 yield 가 일어난 후에 전달됩니다. 즉 send() 메쏘드는 yield 로 값을 전달한 후에, caller 가 next() 나 send() 를 호출해주기를 기다리고 있는 제너레이터에만 사용될 수 있습니다. 꼭 send()를 사용해야 한다면 None 을 전달할 수 있습니다.

>>> g = G()
>>> g.send(None)
'output'

next(g) 는 g.send(None) 과 같은 의미로 해석됩니다.

제너레이터에는 send() 외에도 throw() 와 close() 라는 메쏘드가 더 제공됩니다. 이 메쏘드들은 나중에 다룰 기회가 있을 겁니다만, 제너레이터에 이런 기능들이 들어간 목적이 무엇일까요? 그 것은 바로 다음에 다룰 코루틴(coroutine) 때문입니다.

+ Recent posts