Hard skills/Machine Learning (21)


딥러닝을 통한 5년 뒤 유방암 발생 예측

A Deep Learning Mammography-based Model for Improved Breast Cancer Risk Prediction



딥러닝의 응용분야 중 대표적인 분야가 바로 Computer-aided diagnosis (CAD) 라고 하는 자동 진단 분야일 것이다. 대표적인 예로는 구글의 당뇨병 망막증 진단 시스템을 들 수 있다. 딥러닝을 통한 진단 시스템이 하는 일은 의료 영상에서 병변이 발생한 부위를 찾고 질병이 있는지 없는지를 판단하는 것인데, 핵심은 사람이 이미 하고 있는 것을 딥러닝에게 시켜서 사람과 동일한 수준, 혹은 '조금 더 높은 수준' 을 얻고자 하는 것이다. 


하지만 이 논문에서 구현한 딥러닝 모델은 진단을 넘어 미래에 유방암이 발생할지 안할지를 예측하는 모델이다. 미래 유방암 발생 예측이 진단 시스템과 다른 점은 사람이 아직까지 사람이 하기 힘든 분야, 즉 정확도가 높지 않은 분야라는 것이다. 영상의학전문의는 의료 영상을 보고 현재 병 여부에 대해 진단할 수는 있지만, 미래에 유방암이 발생할 확률이 높은지 낮은지에 대해서 판단하는 것은 상대적으로 매우 힘들다고 할 수 있다. 


지금까지 유방암의 위험도 예측에 있어서 임상 정보, 예를 들어, BMI, 나이, 가족력, 과거력, 호르몬 치료 여부, 폐경 나이, 첫출산 나이 등을 통해 미래의 유방암이 발생할 확률이 어느정도일 것이다를 예측하고, 고위험군에 속하는 사람들에게 주의를 주는 식으로 예방이 이루어졌다. 이 논문의 내용은 이러한 임상정보 기반 유방암 예측에 비해 유방 영상 기반의 위험도 평가가 더 정확하다는 것이다. 


데이터 수집


이 논문에서는 2009년 1월 -  2012년 12월 31일에 수집된 60886 명의 환자 (134924 개의 검진자료)에 대해서 연구가 수행되었다. 이 환자들 중, 5년 안에 유방암 (ductal carcinoma in situ or invasive breast carcinoma) 이 발생한 환자를 case 로, 또한 최소 5년 이상 follow-up 이 되었고, 5년 안에 유방암이 발생하지 않은 환자를 control 로 정의하였다. 


데이터 분할 및 분포


위 기준에 맞는 총 39558 명 (89112 개 검진자료) 을 트레이닝 셋 31,806 명, 검증 셋 3,804 명, 테스트셋 3,978 명으로 7:1.5:1.5  의 비율로 나누었다.


이미지 전처리


의영상 데이터 포맷은 Dicom 포맷이 주로 활용된다. Python 에서는 Dicom 포맷을 다룰 수 있는 패키지로는 pydicom 패키지가 많이 사용되는데, 이 논문에서는 DCMTK 이라는 프로그램을 통해 이미지 전처리를 수행했다. Dicom 파일을 PNG16 형태로 바꾸는 작업을 수행하였는데, 이 때 PNG16은 각 픽셀을 최대 16bit 로 나타내는 방법을 의미한다. 즉, gray-scale 이미지로서 각 픽셀이 0~65535 의 값을 갖는 이미지의 형태이다. 


위험도 예측 모형


이 논문에서는 총 4개의 유방암 위험도 예측 모형을 구축한 뒤 그 예측력을 비교한다. 한 가지 유의할 점은, 이 논문에서는 사람을 단위로 위험도 예측력을 비교한 것이아니라, 검진자료를 기준으로 위험도 예측력을 비교하였다. 


(1) Tyrer-Cuzick Model 


Tyrer-Cuzick Model (TC model) 은 Tyrer와 Cuzick 이라는 사람이 개발한 유방암 위험도 예측 모형으로, 일반적인 임상 정보 기반의 모형이다 (링크). GUI 프로그램 형태로 이용할 수 있는데, 이 논문은 연구자용 Command-line 프로그램을 이용해 유방암 위험도를 예측했다. 이 프로그램은 크게 세 가지 결과를 내준다. 


1) 85세까지 유방암 발생 위험을 동일한 나이의 일반 여성과 비교한 결과 

2) 10년 위험도 

3) 평생 위험도 


이 논문에서 따로 언급을 하고 있지는 않지만, 아마 10년 위험도를 결과로 이용했을 것으로 추정된다. 


(2) Risk Factor Logistic Regression Model (RF-LR)


두 번째로는, Risk factor 기반 Logistic regression model 을 만들었다. 이것이 Tyrer-Cuzick Model 과 다른점은 크게 두 가지로 볼 수 있다. 


1) RF-LR 모델은 현재 데이터를 통해 LR model 의 계수를 추정한다. 

2) TC 모델에 사용된 변수에 Prior history of breast cancer, Race 두 가지 변수를 추가하였다. 


일반적으로, 외부 데이터의 계수를 이용한 예측 모형의 경우 예측력이 높기는 쉽지 않다. TC model 은 유명한 유방암 위험도 예측모형이긴 하지만, 현재 데이터를 통해 모형을 구축하는 것이 더 예측력이 높을 것임을 예측해볼 수 있다. 이 때, Feature engineering 방법으로 연속형 변수를 포함한 모든 변수를 범주화한 후, one-hot encoding 을 이용하였다. 이렇게 한 이유는 아마 missing value 가 많았기 때문으로 예측된다. one-hot encoding 의 장점은 missing value 에 대해 모든 값을 0으로 주면 되기 때문에, imputation 등의 step 이 필요없이, 간단히 missing 을 고려한 데이터의 패턴을 찾을 수 있기 때문이다. 



위 표처럼 모든 변수를 범주화하였다. 논문에서 범주를 나누는 기준에 대한 언급은 없었다. 


(3) Image-only model


이 모델에 딥러닝 모델인 Resnet18 이 이용되었다. Resnet18 은 18 개의 레이어를 가진 Resnet 을 말하며 Imagenet 데이터로 트레이닝된 pretrained model을 이용하였다고 한다. 아래는 일반 CNN 과 비교하여 Resnet 의 아이디어를 나타낸 그림이다. Resnet 관련해서는 다른 포스트에서 한 번 다루어 보려고한다. 


일반적인 Resnet 의 인풋사이즈는 224 x 224 인데, 이 논문의 이미지의 사이즈는 1664 x 2048 를 사용하였다. 어떻게 이미지를 resizing 해서 사용했는지에 대해서 자세한 설명은 언급하고 있지 않다. 딥러닝 모델은 앞서 데이터 수집 부분에서 정의한 기준에 따라 "유방암 5년 이내 발생 여부" 를 class 로 하여 트레이닝 되었다. 보통 유방암 검진에서는 한 사람당 총 4장의 이미지 (RCC,RMLO,LCC,LMLO)를 촬영하게 되는데, 이 모델에서는 이 중에 가장 위험도가 높게 나온 이미지를 이용해 위험도 평가를 하였다. 그리고 한 사람당 여러번의 검진을 받을 수 있는데, 만약 2번의 검진을 받았으면 총 8장의 이미지 중 가장 위험도가 높게 나온 이미지를 이용했다고 보면 된다.  


(4) Hybrid DL model


다음은 RF-LR 와 Image-only model 을 결합하여 Hybrid DL model 을 구축하였다. 이 모델의 Input은 Risk factors 파트와 Image features 파트로 나누어 볼 수 있다. Risk factors 파트는 (2) 에서 이용한 feature 들을 그대로 가져왔고, image features 파트는 (3) 에서 구축한 image-only model 을 feature extractor 로 이용해서 이미지를 수백개의 feature (일반적으로 resnet 의 최종 feature 는 512개) 로 요약해 이를 risk factor 와 concatenate 시켜 모델의 인풋으로 투입했다. 


그 뒤로는 일반적인 Supervised neural network 를 구축해 "유방암 5년 이내 발생 여부" 를 예측했다. 



모델 평가


이 논문에서는 크게 3가지의 지표로 모델을 평가하였다. 


1) AUC (Area under curve) : AUC 의 경우, Future risk 를 구하기 위해 검진 이후 최소 3년 ~ 5년 내 유방암 발생을 case 로 하여 예측력을 구했다. 전체 테스트셋에 대한 AUC 를 4개의 위험도 모형에서 비교하였고, 또한 (인종, 가족력, 폐경여부) 로 층화하여, 각각 4개의 위험도 모형을 비교하였다. 

2) Confusion Matrix 

3) Top-decile, bottom-decile Hazard ratio & incidence 



결과


- 전체 테스트셋 (15%) 에 대한 위험도 평가 결과 


샘플 수 : 총 3937명 중 269명 case 

  • Hybrid DL 모델은 TC 모델, RF-LR 모델에 비해 AUC 가 유의하게 높았다. (DeLong test 결과)
  • Image-only 모델은 TC 모델에 비해 AUC 가 유의하게 높았다. 
  • Decile 분석에서 Hybrid 가장 좋은 성능을 보여주었다. (특히 classic model 인 TC 모델에 비해 Top decile 과 Bottom decile 에서의 성능이 매우 좋다.)

- 하위그룹 단위 (인종, 가족력, 폐경 여부) 위험도 평가 결과


  • 인종별 분석 : 백인 (총 3157명 중 233명 case) 과 아프리카계 미국인 (총 202명 중 11명 case)에 Hybrid DL 모델을 적용해 AUC 는 각각 0.71, 0.71 이였고, TC 모델의 경우, 0.62, 0.45 였다. TC 모델의 경우 인종별로 예측력이 상당한 차이를 보이는 잔면, Hybrid 모델은 인종에 상관없이 비슷한 예측력을 갖는다는 것을 알 수 있다. 또한 RF-LR 모델로 비교를 해보아도 AUC가 0.66, 0.58 로 Risk factor 기반의 모델에서 아프리카계 미국인은 예측력이 떨어지는 경향이 있는데, Image-only 모델과 Hybrid 모델은 이와 반대로 인종 별로 Robust 한 결과를 보여준다. 하지만 아프리카계 미국인의 샘플 수가 매우 작기 때문에, 다소 confidence interval 이 크다는 것을 알 수 있다. 
  • 가족력별 분석 : 가족력이 있는 사람 (총 1767 명 중 141명 case) 과 가족력이 없는 사람 (총 2170명 중 128명 case) 에 대해 예측력을 비교하였다. 가족력이 있는 사람의 경우, TC 와 Hybrid DL 의 AUC 증가값을 유의했다. 하지만 가족력이 없는 사람의 경우, TC 와 Hybrid DL 의 AUC 증가값은 유의하지 않았다.
  • 폐경 여부 별 분석 : 폐경전 여성 (1649 명 중 62명 case, figure에 오류가 있음), 폐경 여성 (2513명 중 207명  case) 별로 나누어 분석을 한 결과, Hybrid 모델은 각각 0.79, 0.70 의 AUC 를 보인 반면, TC 모델의 경우 각각 0.73, 0.58 로 폐경후 여성에 대해서는 예측력이 낮게 나타났다. 


Confusion matrix 를 통한 분석에서는 Hybrid model 의 예측력을 density 와 TC model 과 비교하여 보여주고 있다.  


Hybrid DL vs. breast density. 


결과를 보면 Hybrid DL 이 Breast density 에 비해 더욱 유방암 위험도에 관한 많은 정보를 준다는 것을 알 수 있다. row 별로 변하는 incidence 의 차이보다, column 별로 변하는 차이가 더 크다는 것을 볼 수 있다. 현재까지 맘모그램에서 확인할 수 있는 유방암 발생의 위험요인으로 가장 잘 알려진 것이 유방 밀도인데, 딥러닝 모델이 유방밀도를 넘어서는 유방의 특성을 찾아냈다고 해석할 수 있다. 왼쪽에서 두 번째 그림, High Risk, Non Dense 그림을 보면, breast density 가 낮은데도 불구하고 높은 위험도를 보이는 유방 영상의 예를 보여주고 있다. 

Hybrid vs. versus TC. 


TC 모델에 의해 높은 위험도를 갖는다고 예측되고, Hybrid DL 모델에 의해 낮은 위험도를 갖는다고 예측된 그룹은 1.6 %의 incidence 를 보였다. 반면, Hybrid 모델에 의해 높은 위험도를 갖는다고 예측되고, TC 모델에 의해 낮은 위험도를 갖는다고 예측된 그룹은 3.7 % 의 incidence 를 보여주었다. 단지 이 결과만 보더라도, Hybrid 모델이 TC 보다 유방암 위험도를 더 잘 예측한다는 것을 직관적으로 알 수 있다. 


고찰


요약 

  • Hybrid DL (딥러닝 기반 이미지 분석+Risk factor) 모형이 기존 클래식한 유방암 위험도 평가 모델에 비해 좋았다 (AUC 0.70 vs. 0.62). 
  • Hybrid DL 을 사용하면 고위험군을 효과적으로 선별할 수 있다. 
  • 또한 기존 유방암 예측모형의 경우 대부분 백인을 대상으로 구축된 것이기 때문에, 다른 인종에 대해 유방암을 예측할 때 한계가 있었는데, Hybrid DL 모델 또는 Image-only model 모두 인종에 robust 한 유방암 예측력을 보여주었다. 

연구의 의미 

  • 굳이 Hybrid LD 이 아니더라도 이미지만을 통해서 유방암 위험도를 예측했을 때, Risk factor 기반 모형보다 예측력이 좋았다.
  • Image-only 모델은 임상정보를 모두 번거롭게 수집할 필요가 없이, 유방암 검진 과정에서 유방 영상에 즉각적으로 사용할 수 있기 때문에 실제 적용이 어렵지 않다.
  • 현재 미국에서는 유방암 검진을 받는 여성에게 유방밀도를 의무적으로 공지해야하는 법안이 있는데, 50 % 정도의 여성이 치밀유방이기 때문에 유방암 위험도가 더 높다고 전달받는다. Image 기반의 위험도 예측모형이 적용된다면 보다 정밀한 위험도 공지가 가능할 것이다. 
  • 만약 다른 위험인자들을 수집할 수 있다면, 이미지 모델과 결합해 Hybrid model 처럼 더 정밀하게 위험도를 예측할 수 있을 것이다. 


향후 연구 과제


  • 이 연구에서 BRCA1/2 유전자를 변수로 포함하기는 했으나, 대부분의 경우 값이 없었다. 따라서 대규모 유전자 정보 (gene panel) 를 결합을 했을 때도 이러한 이미지 기반 위험도 평가가 가능할지에 대한 것도 향후 연구 과제이다. 
  • 이 연구로 알 수 있는 것은 이미지가 무언가 유방암 위험도 예측에 상보적인 정보를 준다는 것인데, 이것이 무엇인지가 중요한 주제이다. 논문에서는 단순히 유방 밀도을 통해서 딥러닝이 위험도를 예측하지 않는다고 주장하고 있고, 매우 세밀한 조직 패턴에 의존하는 것이 아닌가 추측하고 있다. 
  • 최근 딥러닝 연구에서 어떤 부분이 딥러닝의 판단에 영향을 주는지 localiazation 하는 것이 중요한 연구 주제이며, 이와 관련된 다양한 방법들이 많이 제시가 되고 있기 때문이 이를 이용하여 맘모그램의 어떤 부분이 informative 한지를 연구하는 것이 향후 연구 과제이다. 

연구 한계 및 결론

  • 이 연구의 한계는 한 기관의 데이터, 그리고 한 제조사의 유방 영상만 이용한 것이다. 실제로 임상에서 활용되기 위해서는 더 많은 기관, 더 많은 유방영상 촬영기기 제조사 데이터를 이용해서 검증될 필요가 있다.  
  • 이 연구의 새롭게 찾은 결론으로 볼 수 있는 것은, 유방 영상에는 traditional risk factor 에서 찾을 수 없는 미래 유방암 발생을 예측할 수 있는 정보가 있고, 이러한 패턴을 딥러닝이 찾아낼 수 있었다는 것이다.
  • 또한 결과로 미루어보아 유방영상 기반 모델이 전통적인 위험도 예측모형을 대체 혹은 보완할 수 있는 잠재력이 있다고 할 수 있다. 


딥러닝에서 클래스 불균형을 다루는 방법


현실 데이터에는 클래스 불균형 (class imbalance) 문제가 자주 있다. 어떤 데이터에서 각 클래스 (주로 범주형 반응 변수) 가 갖고 있는 데이터의 양에 차이가 큰 경우, 클래스 불균형이 있다고 말한다. 예를 들어, 병원에서 질병이 있는 사람과 질병이 없는 사람의 데이터를 수집했다고 하자. 일반적으로 질병이 있는 사람이 질병이 없는 사람에 비해 적다. 비단 병원 데이터뿐 아니라 대부분의 "현실 데이터" 에 클래스 불균형 문제가 있다. 


클래스 균형이 필요한가?


왜 데이터가 클래스 균형을 이루어야할까? 그리고 언제 클래스 균형이 필요할까? 핵심은 다음과 같다. 클래스 균형 클래스 균형은 소수의 클래스에 특별히 더 큰 관심이 있는 경우에 필요하다. 


예를 들어 현재 재정 상황 및 집의 특성 등을 토대로 집을 사야할지 말아야할지를 예측하는 모델을 만들고 싶다고 하자. 사지말라고 예측하는 것과 사라고 예측하는 것은 그 무게가 다르다. 집을 사라고 예측하는 것은 훨씬 더 큰 리스크를 수반한다. 잘못된 투자는 큰 손실로 이루어질 수 있기 때문이다. 따라서 '집을 사라' 라고 예측하는 것에 대해서는 더 큰 정확도를 가져야한다. 하지만 데이터가 '집을 사지마라' 클래스에 몰려있는 경우, '집을 사지마라' 예측에 있어서는 높은 정확도를 가질 수 있어도 '집을 사라' 라고 예측하는 것에 관해서는 예측 성능이 좋지 않게 된다. 따라서 클래스 불균형이 있는 경우, 클래스에 따라 정확도가 달라지게 된다. 이를 해결하기 위해서는 따라서 '집을 사라' 클래스에는 더욱 큰 비중 (weight) 를 두고 정확한 예측을 할 수 있도록 만들어야한다.   


만약 소수 클래스에 관심이 없다면 어떻게 할까? 예를 들어, 이미지 분류 문제를 예로 들어보자. 그리고 오직, 전체 예측의 정확도 (accuracy) 에만 관심이 있다고 하자. 이 경우에는 굳이 클래스 균형을 맞출 필요가 없다. 왜냐하면 트레이닝 데이터에 만에 데이터를 위주로 학습하면, 모델의 정확도가 높아질 것이기 때문이다. 따라서 이런 경우에는 소수 클래스를 무시하더라도 전체 성능에 큰 영향을 주지 않기 때문에, 클래스 균형을 맞추는 것이 굳이 필요하지 않다고 할 수 있다. 


클래스 균형이 필요한 상황과 불필요한 상황을 예로 들어 설명했다. 다음으로는 딥러닝에서 클래스 균형을 맞추기 위한 두 가지 테크닉을 소개한다. 


(1) Weight balancing


Weight balancing 은 training set 의 각 데이터에서 loss 를 계산할 때 특정 클래스의 데이터에 더 큰 loss 값을 갖도록 하는 방법이다. 예를 들어, 이전 예에서 집을 사라는 클래스에 관해서는 더 큰 정확도가 필요하므로, 트레이닝 할 때, 집을 사라는 클래스의 데이터에 관해서는 loss 가 더 크도록 만드는 것이다. 이를 구현하는 한 가지 간단한 방법은 원하는 클래스의 데이터의 loss 에는 특정 값을 곱하고, 이를 통해 딥러닝 모델을 트레이닝하는 것이다.  


예를 들어, "집을 사라" 클래스에는 75 %의 가중치를 두고, "집을 사지마라" 클래스에는 25 %의 가중치를 둘 수 있다. 이를 python keras 를 통해 구현하면 아래와 같다. class_weight 라는 dictionary 를 만들고, keas model 의 class_weight parameter 로 넣어주면 된다. 


import keras

class_weight = {"buy": 0.75,
                "don't buy": 0.25}

model.fit(X_train, Y_train, epochs=10, batch_size=32, class_weight=class_weight)


물론 이 값을 예를 든 값이며, 분야와 최종 성능을 고려해 가중치 비율의 최적 세팅을 찾으면 된다. 다른 한 가지 방법은 클래스의 비율에 따라 가중치를 두는 방법인데, 예를 들어, 클래스의 비율이 1:9 라면 가중치를 9:1로 줌으로써 적은 샘플 수를 가진 클래스를 전체 loss 에 동일하게 기여하도록 할 수 있다. 


Weight balancing 에 사용할 수 있는 다른 방법은 Focal loss 를 사용하는 것이다. Focal loss 의 메인 아이디어는 다음과 같다. 다중 클래스 분류 문제에서, A, B, C 3개의 클래스가 존재한다고 하자. A 클래스는 상대적으로 분류하기 쉽고, B, C 클래스는 쉽다고 하자. 총 100번의 epoch 에서 단지 10번의 epoch 만에 validation set 에 대해 99 % 의 정확도를 얻었다. 그럼에도 불구하고 나머지 90 epoch 에 대해 A 클래스는 계속 loss 의 계산에 기여한다. 만약 상대적으로 분류하기 쉬운 A 클래스의 데이터 대신, B, C 클래스의 데이터에 더욱 집중을 해서 loss 를 계산을 하면 전체적인 정확도를 더 높일 수 있지 않을까? 예를 들어 batch size 가 64 라고 하면, 64 개의 sample 을 본 후, loss 를 계산해서 backpropagation 을 통해 weight 를 업데이트 하게 되는데 이 때, 이 loss 의 계산에 현재까지의 클래스 별 정확도를 고려한 weight 를 줌으로서 전반적인 모델의 정확도를 높이고자 하는 것이다. 



Focal loss 는 어떤 batch 의 트레이닝 데이터에 같은 weight 를 주지 않고, 분류 성능이 높은 클래스에 대해서는 down-weighting 을 한다. 이 때, gamma (위 그림) 를 주어, 이  down-weighting 의 정도를 결정한다. 이 방법은 분류가 힘든 데이터에 대한 트레닝을 강조하는 효과가 있다. Focal loss 는 Keras 에서 아래와 같은 custom loss function 을 정의하고 loss parameter 에 넣어줌으로써 구현할 수 있다. 

import keras
from keras import backend as K
import tensorflow as tf

# Define our custom loss function
def focal_loss(y_true, y_pred):
    gamma = 2.0, alpha = 0.25
    pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))
    pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))
    return -K.sum(alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1))-K.sum((1-alpha) * K.pow( pt_0, gamma) * K.log(1. - pt_0))

# Compile our model
adam = Adam(lr=0.0001)
model.compile(loss=[focal_loss], metrics=["accuracy"], optimizer=adam) 


(2) Over and under sampling


클래스 불균형을 다루기 위한 다른 방법은 바로 샘플링을 이용하는 것이다.


Under and and Over Sampling


예를 들어, 위 그림에서 파란색 데이터가 주황색 데이터에비해 양이 현저히 적다. 이 경우 두 가지 방법 - Undersampling, Oversampling 으로 샘플링을 할 수 있다. 


Undersampling 은 Majority class (파란색 데이터) 의 일부만을 선택하고, Minority class (주황색 데이터) 는 최대한 많은 데이터를 사용하는 방법이다. 이 때 Undersampling 된 파란색 데이터가 원본 데이터와 비교해 대표성이 있어야한다. Oversampling 은 Minority class 의 복사본을 만들어, Majority class 의 수만큼 데이터를 만들어주는 것이다. 똑같은 데이터를 그대로 복사하는 것이기 때문에 새로운 데이터는 기존 데이터와 같은 성질을 갖게된다.  


Reference

https://towardsdatascience.com/handling-imbalanced-datasets-in-deep-learning-f48407a0e758

https://towardsdatascience.com/methods-for-dealing-with-imbalanced-data-5b761be45a18

  • GyeongHo Kim 2020.08.27 19:55

    좋은 정보와 글 감사합니다

  • 김병현 2021.05.09 16:28

    많은 도움 됐습니다.


Gradient Boosting Algorithm의 직관적인 이해


실패를 통해 성공을 발전시켜라. 낙담과 실패는 성공으로 가는 가장 확실한 두 개의 디딤돌이다. -데일 카네기


Gradient Boosting Algorithm (GBM)은 회귀분석 또는 분류 분석을 수행할 수 있는 예측모형이며 예측모형의 앙상블 방법론 중 부스팅 계열에 속하는 알고리즘입니다. Gradient Boosting Algorithm은 Tabular format 데이터 (엑셀형태와 같이 X-Y Grid로 되어있는 데이터)에 대한 예측에서 엄청난 성능을 보여주고, 머신러닝 알고리즘 중에서도 가장 예측 성능이 높다고 알려진 알고리즘입니다. 그렇기 때문에 Gradient Boosting Algorithm을 구현한 패키지들이 많습니다. LightGBM, CatBoost, XGBoost 같은 파이썬 패키지들이 모두 Gradient Boosting Algorithm을 구현한 패키지들입니다. GBM은 계산량이 상당히 많이 필요한 알고리즘이기 때문에, 이를 하드웨어 효율적으로 구현하는 것이 필요한데, 위 패키지들은 모두 GBM을 효율적으로 구현하려고한 패키지들이라고 볼 수 있습니다.


Boosting 이란?


주요 앙상블 알고리즘은 bagging과 boosting으로 나눌 수 있고, Gradient boosting은 boosting 계열의 앙상블 알고리즘입니다. Gradient Boosting 알고리즘은 Gradient 를 이용하여 Boosting하는 알고리즘입니다. Gradient boosting 외의 boosting 계열의 알고리즘의 예로는 Adaptive boosting을 들 수 있습니다. 


먼저 Boosting의 개념부터 살펴봅시다. Boosting이란 약한 분류기를 결합하여 강한 분류기를 만드는 과정입니다. 분류기 A, B, C 가 있고, 각각의 0.3 정도의 accuracy를 보여준다고 합시다. A, B, C를 결합하여 더 높은 정확도, 예를 들어 0.7 정도의 accuracy를 얻는 게 앙상블 알고리즘의 기본 원리입니다. Boosting은 이 과정을 순차적으로 실행합니다. A 분류기를 만든 후, 그 정보를 바탕으로 B 분류기를 만들고, 다시 그 정보를 바탕으로 C 분류기를 만듭니다. 그리고 최종적으로 만들어진 분류기들을 모두 결합하여 최종 모델을 만드는 것이 Boosting의 원리입니다. 



GBM의 직관적인 이해


GBM을 이해하는 가장 쉬운 방법은 Residual fitting으로 이해하는 것입니다. 아주 간단한 모델 A를 통해 y를 예측하고 남은 잔차 (residual)을 다시 B라는 모델을 통해 예측하고 A+B 모델을 통해 y를 예측한다면 A보다 나은 B 모델을 만들 수 있게 되죠. 이러한 방법을 계속하면 잔차는 계속해서 줄어들게되고, training set을 잘 설명하는 예측 모형을 만들 수 있게 됩니다. 하지만 이러한 방식은 bias는 상당히 줄일 수 있어도, 과적합이 일어날 수도 있다는 단점이 있습니다. 따라서 실제로 GBM을 사용할 때는 sampling, penalizing 등의 regularization 테크닉을 이용하여 더 advanced 된 모델을 이용하는 것이 보편적입니다. 하지만 본 포스팅의 목적은 GBM 에 대하여 직관적인 이해를 해보는 것이기 때문에 이 부분은 다음에 기회가 있을 때 정리해보도록 하겠습니다. 

위 그림을 보시면 tree 1을 통해 예측하고 남은 잔차를 tree2를 통해 예측하고, 이를 반복함으로서 점점 잔차를 줄여나가는 것을 볼 수 있습니다. 이 때, 각각의 모델 tree1,2,3 을약한 분류기 (weak learner), 이를 결합한 분류기를 강한 분류기 (strong learner)라고도 합니다. 보통 약한 분류기로는 간단한 의사결정나무 (decision tree)를 많이 사용합니다. 이를 Gradient boosting tree라고도 하는데, 구현한 대표적인 라이브러리로 XGboost를 들 수 있습니다. XGBoost는 python, R로 이용할 수 있습니다. 



Gradient란 무엇인가?


그렇다면 residual fitting과 gradient가 무슨 관계인지 궁금하실텐데요. residual은 loss function을 squared error로 설정하였을 때, negative gradient 입니다. 따라서 residual에 fitting해서 다음 모델을 순차적으로 만들어 나가는 것 negative gradient를 이용해 다음 모델을 순차적으로 만들어 나가는 것으로 볼 수 있습니다. 우선 아래 식을 통해 이를 확인해봅시다. 


loss function을 아래와 같이 정의하면,


$$ j(y_i, f(x_i)) = \frac{1}{2}(y_i - f(x_i))^2 $$ 


이 때의 negative gradient는 residual이 됩니다.


residual이 $$ y_i - f(x_i) $$ 이기 때문에 negative gradient가 residual이 된다는 것을 알 수 있습니다. 


따라서 gradient boosting은 다음 모델을 만들 때, negative gradient를 이용해 만들기 때문에 gradient boosting 이라고 할 수 있습니다. 또한 residual fitting model은 gradient boosting 모델의 한 가지 종류입니다. 왜냐하면 다른 loss function을 이용해서 gradient boosting을 할 수 있기 때문입니다. 예를 들어 회귀 문제가 아닌 분류 문제라면 loss function으로 squared error 를 이용할 수 없기 때문에 새로운 loss function을 만들어야하는데, 이 경우에도 negative gradient를 이용해서 새로운 model 을 fitting하고 이를 합산해 나가시는 식으로 최종 모델을 만들게 됩니다. 


직관적으로 이것은 무엇을 뜻할까요? 왜 negative gradient를 이용해서 새로운 모델을 만드는 걸까요? negative gradient는 pseudo-residual이라고도 불리며, 이것은 어떤 데이터 포인트에서 loss function이 줄어들기 위해 f(x)가 가려고하는 방향입니다. 이 방향에 새로운 모델을 fitting해서 이것을 이전 모델과 결합하면, f(x) 는 loss function이 줄어드는 방향으로 업데이트가 되겠죠. 이것이 gradient boosting의 아이디어입니다. Gradient boosting을 gradient descent + boosting 이라고 하기도 합니다. 어쨌든, loss function을 줄이는 방향의 negative gradient를 얻고, 이를 활용해 boosting을 하는 것이기 때문에 gradient descent와 boosting이 결합된 방법이다. 라고 이해하셔도 괜찮습니다. 



위 그림은 위키피디아에서 gradient boosting에 대하여 설명한 알고리즘도입니다. 이제 위 내용을 이해할 수 있습니다. gradient boosting에서는 learning rate를 통하여 pseudo-residual에 fitting된 모델을 어느정도로 update할지에 대해서 정하게 되는데, 이 값은 위 알고리즘도의 3번처럼, loss를 최소화하는 값을 optimization 과정을 통해 얻을 수도 있겠지만, 일반적으로 0.001~0.01 정도로 매우 작은 값으로 설정하는 것이 보편적입니다. 왜냐하면 작은 값으로 설정하여야 굉장히 세밀한 분류기를 얻을 수 있습니다. learning rate가 높을 수록 빠르게 모델의 bias를 줄여나가지만, learning rate가 적으면 디테일한 부분을 놓칠 수 있다고 이해하시면 됩니다. 이와 관련해서는 gradient boosting 과정을 simulation 해볼 수 있는 사이트를 소개합니다. 이 곳에서 직접 gradient boosting 방법이 어떻게 함수를 fitting 해나가는지를 시각적으로 확인할 수 있습니다. 


참고

https://machinelearningmastery.com/gentle-introduction-gradient-boosting-algorithm-machine-learning/

 



  • dd 2020.01.17 15:42

    매우작은값은 hm 아닌가요? 아래 그림에서

    • Deepplay 2020.01.17 17:01 신고

      위 알고리즘에서 gamma 로 표시된 multiplier 는 learning rate 라고도 불리고 새로운 model 인 hm 에 곱해져 이전까지의 모델과 합쳐지게 됩니다. 위 알고리즘에서 hm 은 m 번째 모델을 의미합니다 :)

  • 티비다시보기 2020.03.27 15:14

    대단하시네요~~

  • 한장훈 2020.06.12 11:41

    감사합니다!

  • GyeongHo Kim 2020.12.10 22:49 신고

    설명을 참 잘해주셔서 감사합니다

  • dongwookim 2021.01.15 06:57

    위 그림은 위키피디아에서 gradient boosting에 대하여 설명한 알고리즘도입니다. 이제 위 내용을 이해할 수 있습니다. gradient boosting에서는 learning rate를 통하여 pseudo-residual에 fitting된 모델을 어느정도로 update할지에 대해서 정하게 되는데, 이 값은 위 알고리즘도의 3번처럼, loss를 최소화하는 값을 optimization 과정을 통해 얻을 수도 있겠지만, 일반적으로 0.001~0.01 정도로 매우 작은 값으로 설정하는 것이 보편적입니다. 왜냐하면 작은 값으로 설정하여야 굉장히 세밀한 분류기를 얻을 수 있습니다. learning rate가 높을 수록 빠르게 모델의 bias를 줄여나가지만, learning rate가 적으면 디테일한 부분을 놓칠 수 있다고 이해하시면 됩니다. 이와 관련해서는 gradient boosting 과정을 simulation 해볼 수 있는 사이트를 소개합니다. 이 곳에서 직접 gradient boosting 방법이 어떻게 함수를 fitting 해나가는지를 시각적으로 확인할 수 있습니다.

    출처: https://3months.tistory.com/368 [Deep Play]

    부분에서,

    learning rate가 적으면 디테일한 부분을 놓칠 수 있다고 이해하시면 됩니다. 가 맞나요?
    - learning rate가 작아야(low) 디테이한 부분을 놓치지 않는 것 아닌가요?
    - 충분하지않고 모자르다 뜻의 '적으면(not enough)'이면 이해가 되지만, 높을수록(high)의 반대인 낮으면(low) 이라면 아니지 않나요?

    궁금해서 여쭙니다.

Segmentation


classification이 사진에서 어떠한 물체가 '존재하는지' 를 판단하는 것이라면 Segmentation은 이미지에서 픽셀단위로 해당 픽셀이 어떤 Class에 속하는지를 예측하는 태스크로 볼 수 있다.


Input


Output


예를 들어, 위 그림을 보자. 오토바이를 타고 있는 사람이 보인다. 이 데이터를 Input으로 주면 아래와 같은 Output을 주는 것이 Segmentation의 태스크이다. 이를 트레이닝하기 위해서는 픽셀레벨로 해당 픽셀이 어디에 속하는지 label을 달은 데이터가 필요하다. 이는 사람이 직접 label을 하는 경우가많다. 위 pascal challenge에서도 사람이 직접 label한 데이터를 제공하였다.


Object Segmentation과 Class Segmentation


pascal voc2011 challenge에서는 segmentation을 object segmentation과 class segmentation으로 나누었다. object segmentation은 이미지에서 '어떤 물체가 있다는 사실' 을 픽셀단위로 예측하는 것이고, class segmentation은 어떤 물체가 '어떤 클래스에 속하는 지'까지 예측하는 것이다. 예를 들어 아래 그림을 보면, object segmentation에서는 어떤 object별로 모두 다른 색깔로 표현하고 있지만, class segmentation에서는 object들 중에서 같은 class인 경우 같은 색깔로, 표시하고 있다. (예를 들어 같은 '화분' 이라는 class를 갖고 있는 object는 같은 색깔로 표시하였다.) 



Dilated Convolution


Dilated Convolution은 필터 내부에 zero padding을 추가해 강제로 receptive field를 늘리는 방법이다. 위 그림은 파란색이 인풋, 초록색이 아웃풋인데, 진한 파랑 부분에만 weight가 있고 나머지 부분은 0으로 채워진다. receptive field란 필터가 한 번의 보는 영영으로 볼 수 있는데, 결국 필터를 통해 어떤 사진의 전체적인 특징을 잡아내기 위해서는 receptive field는 높으면 높을 수록 좋다. 그렇다고 필터의 크기를 크게하면 연산의 양이 크게 늘어나고, 오버피팅의 우려가있다. 그래서 일반적인 CNN에서는 이를 conv-pooling의 결합으로 해결한다. pooling을 통해 dimension을 줄이고 다시 작은 크기의 filter로 conv를 하면, 전체적인 특징을 잡아낼 수 있다. 하지만 pooling을 수행하면 기존 정보의 손실이 일어난다. 이를 해결하기 위한것이 Dilated Convolution으로 Pooling을 수행하지 않고도 receptive field의 크기를 크게 가져갈 수 있기 때문에 spatial dimension의 손실이 적고, 대부분의 weight가 0이기 때문에 연산의 효율도 좋다. 공간적 특징을 유지하는 특성 때문에 Dilated Convolution은 특히 Segmentation에 많이 사용된다.


이 그림을 통해 pooling-conv후 upsampling을 하는 것과 dilated convolution(astrous convolution)을 하는 것의 차이를 볼 수 있다. 위 그림에서 볼 수 있듯 공간적 정보의 손실이 있는 것을 upsampling 하면 해상도가 떨어진다. 하지만 dilated convolution의 그림을 보면 receptive field를 크게 가져가면서 convolution을 하면 정보의 손실을 최대화하면서 해상도는 큰 output을 얻을 수 있다.



Dilated Convolution을 어떻게 Segmentation에 활용할까?


출처-http://blog.naver.com/sogangori/220952339643


첫번째 그림은 classification을 위한 CNN VGG-16의 아키텍쳐이다. conv-pooling을 반복적으로 수행한 후, 마지막으로 Fully Connected Layer에 통과하여 최종 classification 결과를 얻는 과정을 보여주고있다. 그 아래의 그림은 Dilated Convolution을 통하여 이미지를 segmentation하는 예를 보여주고 있다. 이 아키텍쳐의 아웃풋의 사이즈는 28x28xN 이며, (N은 segmentation 원하는 클래스의 수) 이를 다시 upsampling하여 원래의 크기로 복원한다. (이부분에서 공간적 정보의 손실이 있다.)


이 아키텍쳐와 classification 아키텍쳐의 다른점은 우선 다이아몬드 모양으로 표시한 dilated convolution으 통해 공간적 정보의 손실을 최소화하였다. 그리고 dilated convolution 2번을 적용한 뒤 나온 28x28x4096 에 대하여 1x1 convolution으로 channel의 dimension reduction을 수행한다. 최종적으로 28x28xN이 나오고 이를 8x upsampling하여 최종적인 segmention 결과를 output으로 내놓는다. 이 때 1x1 convolution 은 공간적인 정보를 잃지 않기 위해 사용되며, classifiction의 Fully Connected Layer(FC)와 비슷한 역할을 한다. 하지만 classification에서는 공간적인 정보는 중요하지 않기 때문에 Flatten하여 사용하는 것이다.


  • jwm 2020.05.23 09:53

    여지까지본 dilated conv 에 대한 최고의 설명 ..감사합니다


Deconvolution

CNN에서 convolution layer는 convolution을 통해서 feature map의 크기를 줄인다. 하지만 Deconvolution은 이와 반대로 feature map의 크기를 증가시키는 방식으로 동작한다.


Deconvolution은 아래와 같은 방식으로 동작한다.


1 각각의 픽셀 주위에 zero-padding을 추가한다. 

2. 이렇게 padding이된 것에 convolution 연산을 한다.


위 그림에서 아래쪽의 파란색 픽셀이 input 초록색이 output이다. 이 파란색 픽셀 주위로 흰색 zero-padding을 수행하고, 회색 filter를 통해 convolution 연산을 수행하여 초록색 output을 만들어낸다.


Deconvolution은 일반 cnn의 최종 결과물을 다시 반대로 되돌려 input과 같은 사이즈를 만들어내고 싶을 때 쓰인다. 주로 semantic segmentation등에 활용할 수 있다. Deconvolution은 Upsampling 등으로도 불린다.


Spatial Transformation Network


CNN을 통해 이미지 분류를 할 때, 특정 부분을 떼어내서 집중적으로 학습시키는 것은 Spatial Transformation Network라고 한다. 이미지 분류 문제에서는 spatial invariance(이미지가 변환되어도 그 이미지로 인식하는 것. 고양이를 90도 회전시켜놓아도 고양이로 인식하는 능력)가 중요한데 일반적인 CNN에서는 이를 해결하기 위해 max pooling layer가 필요하다. Spartial Transformation은 max pooling layer보다 더 좋은 spatial invariance 능력을 갖게 하기 위해 이미지의 특정 부분을 자르고 변환해서 그 부분만 떼어서 트레이닝을 시킨다. 이 변환하는 과정을 affine transformation이라고 하는데 아래 그림을 보면 이해할 수 있다. 



CNN 안에 하나의 layer로 spartial transformation module이 들어가는데 이 layer는 위의 affine transformation을 하는데 필요한 parameter을 배운다. 이는 일반적인 regression 과정이다. 정교하게 하기 위해서는 많은 트레이닝 데이터가 필요하다.



위 그림은 DeepMind에서 Spartial Transformation Network 관한 논문을 낼때 들어간 그림인데 일부러 지저분하게 변경한 MNIST 데이터에 이 spatial transformation을 적용해서 원하는 부분을 잘라내서 변환하는 것을 보여주고 있다. MNIST 같은 simple한 데이터셋에서는 큰 효과가 없을지 몰라도 원하는 데이터에 여러가지 잡음이 있는 경우 spartial transformation이 좋은 성능을 보여줄 것으로 기대된다.


참고

https://adeshpande3.github.io/adeshpande3.github.io/The-9-Deep-Learning-Papers-You-Need-To-Know-About.html

https://www.slideshare.net/xavigiro/spatial-transformer-networks?from_action=save

https://github.com/oarriaga/spatial_transformer_networks/blob/master/src/mnist_cluttered_example.ipynb

/* 2017.7.2 */


Universal Approximation Theorem


Universal Approximation Theorem이란 1개의 히든 레이어를 가진 Neural Network를 이용해 어떠한 함수든 근사시킬 수 있다는 이론을 말합니다. (물론 활성함수는 비선형함수여야합니다.) 재미로 1개의 히든 레이어를 가진 신경망 모델로 실제 함수를 근사시킬 수 있는지 확인해보았습니다.


import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD
from keras import optimizers
import math

우선 필요한 라이브러리를 임포트합니다.


x = np.linspace(-10, 10, 100)
y = 3*np.sin(x)*np.cos(x)*(6*x**2+3*x**3+x**1)*np.tan(x)

제가 근사시키고 싶은 함수는 위와 같습니다.

y = 3sin(x)cos(x)(6x^2+3x^3+x)tan(x) 라는 함수인데 그냥 생각나는대로 만든 함수입니다.




이 함수의 모양은 이렇게 생겼습니다. Universal Approximation Theorem은 신경망 모델로 이러한 복잡한 함수도 근사시킬 수 있다는 이론인데 한 번 확인해보겠습니다.


우선 여러개의 히든 레이어를 가진 신경망 모델로 확인해보겠습니다.



Multi layer perceptron

model = Sequential()
model.add(Dense(10, input_shape=(1,), activation='relu'))
model.add(Dense(10, input_shape=(1,), activation='relu'))
model.add(Dense(10, input_shape=(1,), activation='relu'))
model.add(Dense(10, input_shape=(1,), activation='relu'))
model.add(Dense(1, activation='linear'))
rms = optimizers.RMSprop(lr=0.01, rho=0.9, epsilon=1e-08, decay=0.0)
model.compile(loss='mean_squared_error', optimizer=rms, metrics=['accuracy'])
model.fit(x, y, epochs=3000)


위와 같은 4개의 히든 레이어로 구성된 신경망 모델을 만들고 RMSProp optimizer을 통해 학습하였습니다. 학습된 모델로 구현된 함수는 아래와 같이 생겼습니다.


prediction = model.predict(x) plt.plot(x, prediction) plt.show()




조금 다른 부분도 있지만 잘 근사한 것을 확인할 수 있습니다. 사실 처음에는 adam optimizer을 사용하였는데, loss 한계점 아래로는 떨어지지가 않았습니다. 그래서 learning rate를 조절할 수 있는 rmsprop optimizer을 이용하였고, learning rate를 다양하게 변화시켜가면서 위와 같은 hyperparameter일 때 가장 잘 학습하는 것을 확인하였습니다.



Singlel hidden layer perceptron

model = Sequential()
model.add(Dense(10000, input_shape=(1,), activation='relu'))
model.add(Dense(1, activation='linear'))
rms = optimizers.RMSprop(lr=0.06, rho=0.9, epsilon=1e-08, decay=0.0)
model.compile(loss='mean_squared_error', optimizer=rms, metrics=['accuracy'])
model.fit(x, y, epochs=5000)


진짜 universal approximation theorem은 "단 한 개"의 히든레이어로도 이런 것을 할 수 있다는 것이기 때문에 한 개의 히든 레이어만 가지고 함수를 근사시켜보겠습니다. 무려 10000개의 node를 갖는 1개의 히든레이어를 5000 epochs 을 돌려서 다음과 같은 근사함수를 만들어냈습니다.




위의 multi layer perceptron 보다는 못하지만 어느정도 근사할 수 있다는 것을 확인할 수 있었습니다. 노드수, learning rate, optimizer 등을 바꿔가면서 해보면 더 잘 함수를 근사시킬 수 있을 것입니다.



Word Embedding


Word Embedding이란 Deep Learning 분야, 이 중에서도 특히 자연어처리에서 필수적으로 알아야할 개념이다. Word Embedding은 Word를 R차원의 Vector로 매핑시켜주는 것을 말한다.



예를 들어 위와 같이 cat이나 mat같은 단어를 특정 차원의 벡터로 바꾸어주는 것이다. 이렇게 단어를 벡터로 바꾸어주는 것은 하나의 Matrix이다. 이를 W라 부른다. W 함수는 매우 중요하다. 이를 통해 단어가 의미 있는 벡터로 변한다. 비슷한 두 개의 단어는 비슷한 벡터로 바뀐다던지 하는 식으로 말이다. W는 Learning을 통해 학습할 수 있다.


예를 들어, 5000개의 단어로 이루어진 단어 세트가 있으면 각각의 단어는 [0,0,0,1,0 ... 0,0,0] (5000열) 과 같이 나타낼 수 있다. 이 때 각각의 단어를 Word Embedding을 통해 32차원 벡터로 나타내고자 하면 W의 차원은 5000*32이다. 이를 학습하여 [0.2,0,4,0.5 ... 0.8,8] 과 같은 32차원의 벡터를 형성해낸다.


Word Embedding은 다음과 같은 놀라운 특성도 가질 수 있다.



woman을 나타내는 벡터와 man을 나타내는 벡터의 차이는 남녀의 차이를 나타내는 벡터이다. 이것이 aunt와 uncle의 차이를 만들어낸다. 예를 들어 he is a man는 옳은문장 she is a man은 틀린 문장을 구분하는 classifier에서 aunt와 uncle에 대해서도 똑같은 일을 할 수 있게 만든다.


참고


http://colah.github.io/posts/2014-07-NLP-RNNs-Representations/

  • kjs1124kr 2017.08.28 20:27

    좋은 정보 감사합니다.

/**

날짜 : 2017.01.30

밑바닥부터 시작하는 딥러닝(한빛미디어) 참고

Softmax 구현 및 성질

*/


Softmax 함수는 3-class 이상의 classification을 목적으로 하는 딥러닝 모델의 출력층에서 일반적으로 쓰이는 활성화 함수(activation function)이다. Softmax 함수는 아래와 같이 나타낼 수 있다.




이를 Latex 표기법으로 나타내면 아래와 같다. (Latex는 라텍스라고 읽으면 안되고 레이택으로 읽는다..)


\begin{align}

y_k = {exp(a_k)}/{\sum_{i=1}^{n}(exp(a_i))}

\end{align}


(위 코드를 Jupyter notebook에서 markdown으로 선택하여 입력 후 run을 누르면 위와 같은 수식이 출력되는 것을 확인할 수 있다. Jupyter notebook에서는 Latex 코드를 수식으로 변환하는 기능을 지원한다.)


""" Softmax 구현 """
import numpy as np

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

a = np.array([0.3, 2.9, 4.0])

print softmax(a) # softmax 결과값 출력
print sum(softmax(a)) # softmax 결과값들의 합은 1이 된다.


Softmax 수식을 그대로 파이썬 코드로 옮기면 위와 같다. 결과가 잘 출력되는 것을 확인할 수 있다. 하지만 Softmax는 수식 안에 e의 지수를 포함하고 있는데, 지수함수는 지수가 커질 수록 매우 큰 폭으로 증가하기 때문에 overflow가 발생하기 쉽다. 아래 코드를 보면 overflow가 발생하는 예와 이를 어떻게 해결하는지를 볼 수 있다.


""" Softmax는 Overflow에 취약하다.
    수식에 e^x 들어가기 때문에 Overflow 범위를 넘어설 수 있다. 
    이를 해결하기 위해서는 Softmax의 성질을 이용한다.
"""
def new_softmax(a) : 
    c = np.max(a) # 최댓값
    exp_a = np.exp(a-c) # 각각의 원소에 최댓값을 뺀 값에 exp를 취한다. (이를 통해 overflow 방지)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y
    
a = np.array([1010, 1000, 990]) 
print softmax(a) # overflow
print new_softmax(a) # 정상적으로 출력

# 또한 softmax와 new_softmax 의 결과값을 같다.
test = np.array([1,3,6])
print 'softmax', softmax(test) # [ 0.00637746  0.04712342  0.94649912]
print 'new_softmax', new_softmax(test) # [ 0.00637746  0.04712342  0.94649912]


Softmax를 구현할 때 overflow 가 자주 발생하는 문제를 해결하기 위해 위와 같은 새로운 softmax 함수인 new_softmax를 정의한다. 이는 원소에 어떠한 수를 더하여 exp를 취하더라도 결과 값이 같다는 softmax의 성질을 이용한 것인데, 


위의 등식에서 C를 '원소들의 최댓값의 음수'로 한 것이다. 예를 들어 a = [1010,1000,990] a+c = [0,-10,-20] 이 된다. 이 때, a와 a+c의 softmax 결과값은 같다.



/**

날짜 : 2017.01.25

밑바닥부터 시작하는 딥러닝(한빛미디어) 참고

Numpy 기초, 간단한 신경망 구현

*/



numpy 패키지는 데이터 과학, 딥러닝 분야에서 쓰이는 매우 중요한 파이썬 패키지이다. numpy는 거의 표준이라고 불릴만큼 대부분의 사람들이 애용하는 패키지이다. numpy가 쓰이는 가장 큰 목적 중에 하나는 배열, 행렬, 벡터, 텐서와 같은 '다차원 배열'의 구현이다. 많은 사람들이 다차원 배열을 구현할 때 numpy를 이용한다. numpy에서는 이를 ndarray라고 부른다. 또 파이썬의 기본 자료 구조에는 array가 없기 때문에, 꼭 딥러닝이 아니더라도 numpy array는 파이썬에서 1차원 배열을 구현할 때도 쓰인다.


numpy의 기초 문법을 정리하고 이를 이용하여 인풋 2개, 아웃풋 2개, 히든 레이어가 2개(각각 노드수가 3, 2인)인 간단한 신경망을 구현해 보았다.


import numpy as np

A = np.array([1,2,3,4])

print(A)

# 차원출력
np.ndim(A)

# 배열 형상 출력. 튜플로 반환된다.

print A.shape
# (4L,)

print A.shape[0]
# 4


  • 위와 같은 문법을 통해 numpy array를 만들 수 있고, shape, dimension 등을 확인할 수 있다.


""" 2차원 배열 """
B = np.array([[1,2], [3,4], [5,6]])
print(B)

np.ndim(B)

B.shape


  • 2중 대괄호를 쓰면 2차원 배열을 만들 수 있다.


""" 3차원 배열 이상 """
# 2x3x4 배열을 만들고 0으로 초기화
C = np.zeros((2, 3, 4))

print 'C', C

# 아래와 같이 자유롭게 만들 수 있음
D = np.array([[[ 1., 2., 3., 4.],
        [ 0.,  0.,  2.,  0.],
        [ 4.,  0.,  3.,  0.]],
       [[ 1.,  4.,  0.,  6.],
        [ 0.,  0.,  5.,  0.],
        [ 0.,  0.,  0.,  5.]]])

print 'D', D

# 2x2x3x4 4차원 배열
E = np.zeros((2,2,3,4))
print 'E', E


  • 위와 같이 3차원 배열, 4차원 배열 등의 고차원 배열을 만들 수 있다.


""" 내적 """
A = np.array([[1,2], [3,4]])
A.shape

B = np.array([[5,6], [7,8]])
B.shape

np.dot(A,B)

#array([[19, 22],
#       [43, 50]])


  • np.dot 명령어를 통해 두 행렬의 내적(dot product)을 구할 수 있다. 신경망에서 이전층에서 다음층으로 forward 되는 것을 구현할 때 입력과 출력을 행렬의 내적으로 표현할 수 있다.


""" 간단한 신경망 구현 
    입력층에서 1층으로 가는 신호 
    첫 번째 히든 레이어의 노드 수는 3개
"""

def sigmoid(num) : 
    return 1/(1+np.exp(-num))

X = np.array([1.0,0.5]) # 입력이 2개 (x1, x2)
W1 = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]]) # 입력에 대응하는 weights 
B1 = np.array([0.1,0.2,0.3]) # bias

print X.shape # 1x2
print W1.shape # 2x3
print B1.shape # 1x3

A1 = np.dot(X,W1) + B1
Z1 = sigmoid(A1) # 첫 번째 레이어의 최종 아웃풋

print A1
print Z1


입력층에서 첫 번째 히든 레이어(노드 3개짜리)로 forward하는 것을 구현한 것이다. 입력과 weights, bias는 임의로 정한 것이다. 입력은 x1, x2 이고 각각 1.0,  0.5 이다. weights는 총 6개이다. (이는 input layer의 노드 수 2와 첫 번째 히든레이어의 노드수 3을 곱한 것이다.) bias는 길이 3의 array인데 이를 더해주어 XW에 더하여 XW+B를 만든다. Bias가 왜 필요한지 그리고 신경망에 대한 기본적인 이해는 http://hunkim.github.io/ml/ 홍콩 과기대 김성훈 교수님의 강의를 참조하면 좋다.


""" 1층에서 2층으로 가는 신호 구현
"""
W2 = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]])
B2 = np.array([0.1,0.2])

print Z1.shape # 1x3 첫 번째 레이어의 아웃풋이 인풋이 된다.
print W2.shape # 3x2
print B2.shape # 1x2

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


 첫 번째 히든레이어에서 두 번째 히든레이어로 forward하는 것의 구현이다. 첫번째 히든 레이어의 아웃풋 Z1이 인풋이 되었고 위와 같은 방식으로 Z1*W2+B2를 하면 두 번째 히든 레이어의 output을 구할 수 있다.


""" 2층에서 출력층으로 가는 신호 구현
"""

# 출력층의 활성화 함수로 항등함수를 쓴다. 
# 회귀의 경우 출력층의 활성화 함수로 일반적으로 항등항수를  쓴다.
# 2-class classification에서는 sigmoid
# 3-class 이상의 classification에서는 softmax를 쓴다.
def identity_function(x) :
    return x

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

print Z2.shape # 1x2
print W3.shape # 2x2
print B3.shape # 1x2

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

print Y # 최종 아웃풋 출력 


두 번째 히든 레이어에서 출력층으로 forward 하는 것의 구현이다. 출력층의 activation function은 identity function을 사용하였는데, 이는 '풀고자 하는 문제의 성질' 과 관련이 있다. 회귀에서는 항등함수를, 2클래스 분류에서는 시그모이드를, 다중 클래스 분류에는 소프트 맥스 함수를 사용하는 것이 일반적이다.