레이블이 Kaggle인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Kaggle인 게시물을 표시합니다. 모든 게시물 표시

2019년 3월 18일 월요일

H2O Driverless AI를 통한 인물별 의료비 예측


H2O Driverless AI (이하 H2O DAI)의 또다른 유력한 use case인 보험 업무에 대해 살펴보겠습니다.   보험사에게 있어 의료비가 많이 들 것 같은 사람을 골라내는 것은 보험사의 수익과 직결되는 무척 중요한 일입니다.  아래 Kaggle site에서 얻은 환자 기본 정보 및 각 환자에게 든 의료비에 대한 dataset을 이용하여 H2O DAI가 예상 의료비를 얼마나 정확히 측정하는지 테스트 해보겠습니다.

https://www.kaggle.com/mirichoi0218/insurance

이 dataset에는 다음과 같은 칼럼들이 있습니다. 

age  나이
sex  성별
bmi  비만도
children  자녀수
smoker  흡연여부
region  사는 지역
charges  의료비

보험사에서 예측하고 싶은 것은 물론 맨 마지막 칼럼인 charges 부분일 것입니다.  이제 H2O DAI를 이용하여 어떻게 이 dataset으로부터 특정 조건을 가진 인물의 예상 의료비를 뽑아낼 수 있는지 step by step으로 알아보시도록 하겠습니다.

1) 웹 브라우저를 통해 H2O DAI에 접속합니다.   맨 처음 menu는 'DATASET'이며, 여러가지 입력 방법 중에서 저는 제 laptop으로부터 upload하는 menu를 택하겠습니다.



2) 저는 위의 Kaggle dataset (insurance.csv)에서 미리 일부 row들을 떼어내어 insurance_test.xlsx를 만들었고, 그 나머지를 insurance_training.xlsx로 저장해놓았습니다.  이것들을 선택하여 H2O DAI에 dataset으로 등록합니다.



3) insurance_training.xlsx와 insurance_test.xlsx가 dataset으로 등록되었습니다.   우측의 'Click for Actions' 부분을 눌러 나오는 sub-menu 중 'DETAILS' 부분을 클릭해 봅니다.



4) 이 DETAILS' 메뉴에서는 이 dataset이 어떤 정보를 담고 있는지 보실 수 있습니다.  각 칼럼별 평균/최대/최소/편차 등의 기본 정보와 최초 20개 row의 값 등을 보실 수 있습니다. 



5) Dataset 우측의 'Click for Actions' 부분을 눌러 나오는 sub-menu 중 'VISUALIZATION'을 클릭하면 여러가지 다양한 graph가 자동으로 생성됩니다.   가령 'OUTLIERS PLOT'을 보면 전체 data 중 일부가 표준편차에서 크게 벗어난 것을 보실 수 있습니다.  그런 부분은 표에서 보는 것보다는 이렇게 그래프로 시각화해서 보면 직관적으로 이해하기가 쉽지요.  그렇게 크게 벗어난 오렌지색 점을 클릭해보면 해당 row의 상세 정보를 보실 수도 있습니다.






6) H2O DAI에서 가장 중요한 것은 역시 'PREDICT' 메뉴입니다.  이 메뉴를 통해서 예측 모델을 자동 생성하게 됩니다. 



7) 'PREDICT' 메뉴에 들어가면 꼭 하셔야 할 일은 단 하나 밖에 없습니다.  어느 칼럼에 대한 예측 모델을 만드느냐에 대한 선택입니다.  여기서는 당연히 charges 칼럼을 택합니다.





8) 추가로 선택하실 수 있는 메뉴는 중앙 하단의 3개의 라디오 다이얼입니다.  각각의 의미와 강약 조절은 아래와 같습니다.   여기서는 default로 제시된 8-3-8을 그대로 적용하겠습니다.



Accuracy : 어떤 알고리즘들을 몇 개나 적용할지 정합니다.  물론 다이얼 숫자가 높을 수록 더 많은 알고리즘을 적용합니다.
Time : 머신러닝에서 반복 훈련, 즉 iteration을 몇 회나 수행할지 정합니다.  물론 다이얼 숫자가 높을 수록 더 많은 회수가 적용됩니다.
Interpretability : 머신러닝으로 만들어진 모델에 대한 해석의 강도 조절 부분입니다.  다이얼 숫자가 높을 수록 더 단순화해서 해석해줍니다.


9) Launch를 누르면 자동 모델 생성이 시작되고 모델이 training 되는 과정을 보시게 됩니다.  중앙 상단을 보면 그 단계에서 하고 있는 작업과 적용되는 알고리즘 등이 실시간으로 업데이트 되면서 보여집니다.  중앙 하단에는 그 시점까지의 분석 단계에서 판단할 때 가장 중요한 변수/feature, 즉 입력된 dataset의 칼럼 중 어느 칼럼이 가장 중요한 역할을 하더라는 것이 실시간으로 분석되어 보여집니다.   가령 3% 경과된 시점에서는 LIGHTGBM 알고리즘을 처리 중이고, 흡연여부-비만지수-나이 순으로 병원비에 중요한 영향을 끼친다고 나옵니다. 






10) 그러나 auto feature engineering이 본격적으로 시작되면서 경과%가 진행되면서 그 값들은 계속 변화합니다.  가령 79% 경과 진행 중일 때는 무려 497개의 feature에 대해 1582개의 model에 대해서 평가 중이라고 나오지요.  원래 dataset의 칼럼 수, 즉 feature 수가 7개 밖에 없었다는 점을 생각하면 H2O DAI가 정말 다양한 조합의 feature engineering을 자동으로 수행하고 있다는 점을 아실 수 있습니다.  그 결과로 나오는 중앙 하단의 'Variable Importance', 즉 어떤 변수/feature가 병원비 지출액에 가장 중요한 영향을 끼치더라는 점도 비만도와 성별, 그리고 흡연여부가 결합되어 새로 생성된 변수를 가장 중요시하는 것을 보실 수 있습니다.  그 항목들을 자세히 보시면, 정말 모든 경우에 흡연여부가 가장 중요한 영향을 끼친다는 것을 보실 수 있습니다.  흡연인 여러분, 아무래도 담배는 끊으셔야 할 것 같습니다.



11) 모델 생성이 완료되면 우측 하단에 요약 설명이 나옵니다.  그러나 가장 중요한 것은 중앙 상단 메뉴의 3번쨰 항목, 즉 'SCORE ON ANOTHER DATASET' 입니다.  여기에 우리가 원하는 charges 값을 뺀 다른 값들 (연령, 사는 곳, 성별, 흡연 여부 등)이 들어있는 표를 입력하면, 해당 사람들이 얼마나 병원비를 쓸지 예측한 값을 출력해주거든요.   여기서는 미리 입력해둔 insurance_test.xlsx를 test dataset으로 선택하겠습니다.  그러면 곧장 해당 사람들의 병원비(charges) 값을 예측하여 그 결과를 csv 파일로 download 시켜줍니다.





12) 그렇게 해서 얻은 해당 환자들의 예상 병원비와 실제 병원비의 결과는 아래 그래프와 같습니다.  파란 곡선이 실제값이고, 오렌지색 곡선이 H2O DAI가 예측한 값입니다.  놀랍도록 잘 맞춘 것을 보실 수 있습니다.   다만 중간 정도에 H2O DAI는 7천4백불 정도를 예상했는데 실제로는 2만8천불을 사용하여 H2O DAI의 예상이 크게 틀린 환자가 있습니다.  이 환자의 상태를 보면, BMI 지수가 높은 비흡연자입니다.  아마도 H2O DAI가 만든 모델에서는 BMI 지수는 그다지 중요하지 않고 흡연 여부가 가장 중요했는데, 이 환자의 경우는 그 예측이 빗나간 것 같습니다.



13) 위에서 BMI 지수니 흡연여부니 하는 것은 어디까지나 저 개인의 짐작에 불과할 뿐 수학적인 모델로 계산한 결과는 아닙니다.  왜 이 머신러닝 모델이 이런 예측값을 내놓았는지 해석하는 것이 바로 MLI (Machine Learning Interpretation)입니다.  H2O는 K-LIME과 Decision Tree, Random Forest 등 다양한 MLI 기능을 제공합니다.  그런 MLI는 모델 생성 완료시의 메뉴 맨 상단의 'Interpret This Model'을 클릭함으로써 생성할 수 있습니다. 



14) 이런 MLI 해석 결과도 어느 정도 data science에 대한 소양이 있어야 볼 수 있는 것이 사실입니다.  가령 LIME이 무엇인지 알아야 이해를 할 수 있으니까요.  (참고로 LIME은 Locally Interpretable Model-agnostic Explainations을 뜻하는 말로서, 어떤 모델을 생성할 때 사용된 변수 값을 하나씩 바꿔보고 그 모델의 결과에 얼마나 변화가 생기는지 봄으로써 어느 변수가 가장 중요한 역할을 하는지 해석하는 기법입니다.)  하지만 H2O DAI는 일반인들도 이해하기 쉬운 설명도 제시합니다.





15) 가령 Summary 부분의 맨 아래는 원래 dataset의 칼럼 중에서 어느 칼럼이 가장 중요한 역할을 하는지 보여줍니다. 



16) KLIME에서는 각각의 예측값에 대해, 어떤 변수가 어느 정도의 영향을 끼쳤는지를 수식화해서 보여줍니다.



17) 가장 쉬운 설명은 KLIME 메뉴 중 중앙상단의 'Explanations' 버튼을 클릭하면 볼 수 있습니다.   여기서는 흡연여부, BMI 지수 등의 변수의 증감에 따라 우리가 알고자 하는 target (여기서는 병원비)의 증감이 어떻게 변화하는지 최대한 단순화하여 제시합니다.  여기에 제시된 설명에 따르면 흡연여부가 가장 중요하고, 자녀가 있는지 여부가 그 다음이며, 사는 곳이 어디인지도 꽤 큰 영향을 미치는 것 같습니다.  아마 부유한 동네인지 또는 그 지방의 식습관 등이 영향을 주는 것일까요 ?  왜 그런지 모르겠습니다만 이 예측 모델에서는 의외로 BMI 지수, 즉 비만 여부는 상대적으로 그다지 큰 영향을 주지는 않는다고 판단하고 있습니다.



이 포스팅의 결론은 다음과 같습니다.

1. H2O DAI는 무척 정확한 예측 모델을 정말 쉽게 만들어낼 수 있을 뿐만 아니라, 왜 그런 예측을 했는지도 매우 쉽게 풀어서 설명해줍니다.  
2. 흡연자 여러분, 금연 합시다.


2019년 1월 25일 금요일

이공계 연구를 위한 H2O Driverless의 활용 - 분자 에너지 값의 예측


이번에는 화학이나 제조 공정 연구 등에 H2O DriverlessAI를 활용하는 가능성에 대해서 보도록 하겠습니다.  신물질 개발이나 기계적 특성 연구 등에는 다양한 성분 또는 온도, airflow 등의 다양한 조건들의 결합과 그에 따른 결과값 예측이 필요합니다.  그러나 비용과 시간의 문제 때문에 그 엄청난 수의 조합에 대해 모두 다 일일이 테스트를 해볼 수는 없지요.  공장이나 연구실의 각종 계측기를 통해 수집한 data가 어느 정도 축적되어 있다면, machine learning을 통해 기존 data를 분석하여 가장 좋은 결과값을 낼 성분 및 조건 등에 대한 조합을 미리 예측할 수 있습니다.  그를 통해 실제 테스트 회수를 크게 줄일 수 있으므로 비용 절감은 물론이고 더 빠른 개발도 가능합니다.  이때 그 machine learning이 빨리 이루어질 수록, 그리고 그 accuracy가 정확할 수록 그 효과는 커질 것입니다.

Kaggle에 올라온 public dataset 중에는 분자 및 그 내부의 원자 구조, 그리고 그에 따른 분자의 에너지값을 담은 json file들이 있습니다.

https://www.kaggle.com/burakhmmtgl/predict-molecular-properties/home

이 dataset은 다음과 같은 zip 형태로 download 받을 수 있는데, 이걸 unzip 해보면 10개의 json file들이 들어 있습니다.

[u0017649@sys-96775 files]$ unzip ./predict-molecular-properties.zip
Archive:  ./predict-molecular-properties.zip
  inflating: pubChem_p_00000001_00025000.json
  inflating: pubChem_p_00025001_00050000.json
  inflating: pubChem_p_00050001_00075000.json
  inflating: pubChem_p_00075001_00100000.json
  inflating: pubChem_p_00100001_00125000.json
  inflating: pubChem_p_00125001_00150000.json
  inflating: pubChem_p_00150001_00175000.json
  inflating: pubChem_p_00175001_00200000.json
  inflating: pubChem_p_00200001_00225000.json
  inflating: pubChem_p_00225001_00250000.json

각 json file들의 크기와 row 수는 아래와 같습니다.

[u0017649@sys-96775 files]$ ls -l *.json
-rw-rw-r--. 1 u0017649 u0017649 111430473 Aug 14  2017 pubChem_p_00000001_00025000.json
-rw-rw-r--. 1 u0017649 u0017649 115752953 Aug 14  2017 pubChem_p_00025001_00050000.json
-rw-rw-r--. 1 u0017649 u0017649 119400902 Aug 14  2017 pubChem_p_00050001_00075000.json
-rw-rw-r--. 1 u0017649 u0017649 116769374 Aug 14  2017 pubChem_p_00075001_00100000.json
-rw-rw-r--. 1 u0017649 u0017649 116383795 Aug 14  2017 pubChem_p_00100001_00125000.json
-rw-rw-r--. 1 u0017649 u0017649 122537630 Aug 14  2017 pubChem_p_00125001_00150000.json
-rw-rw-r--. 1 u0017649 u0017649  96286512 Aug 14  2017 pubChem_p_00150001_00175000.json
-rw-rw-r--. 1 u0017649 u0017649 126743707 Aug 14  2017 pubChem_p_00175001_00200000.json
-rw-rw-r--. 1 u0017649 u0017649 129597062 Aug 14  2017 pubChem_p_00200001_00225000.json
-rw-rw-r--. 1 u0017649 u0017649 147174708 Aug 14  2017 pubChem_p_00225001_00250000.json

[u0017649@sys-96775 files]$ wc -l *.json
   4924343 pubChem_p_00000001_00025000.json
   5096119 pubChem_p_00025001_00050000.json
   5258731 pubChem_p_00050001_00075000.json
   5159241 pubChem_p_00075001_00100000.json
   5120705 pubChem_p_00100001_00125000.json
   5401395 pubChem_p_00125001_00150000.json
   4234059 pubChem_p_00150001_00175000.json
   5570529 pubChem_p_00175001_00200000.json
   5698247 pubChem_p_00200001_00225000.json
   6485207 pubChem_p_00225001_00250000.json
  52948576 total

각각의 json 파일에는 En(에너지 값), atoms(원자 종류와 구조), id(일련번호), shapeM(shape multipole)의 4개 값이 들어있습니다.

{
'En': 37.801,
'atoms': [
{'type': 'O', 'xyz': [0.3387, 0.9262, 0.46]},
{'type': 'O', 'xyz': [3.4786, -1.7069, -0.3119]},
{'type': 'O', 'xyz': [1.8428, -1.4073, 1.2523]},
{'type': 'O', 'xyz': [0.4166, 2.5213, -1.2091]},
{'type': 'N', 'xyz': [-2.2359, -0.7251, 0.027]},
{'type': 'C', 'xyz': [-0.7783, -1.1579, 0.0914]},
{'type': 'C', 'xyz': [0.1368, -0.0961, -0.5161]},
...
{'type': 'H', 'xyz': [1.5832, 2.901, 1.6404]}
],
'id': 1,
'shapeM': [259.66, 4.28, 3.04, 1.21, 1.75, 2.55, 0.16, -3.13, -0.22, -2.18, -0.56, 0.21, 0.17, 0.09]
}

이 json 파일 하나하나에는 다음과 같이 18205개의 atoms 항목이 들어있습니다.

[u0017649@sys-96775 files]$ grep atoms pubChem_p_00000001_00025000.json | wc -l
18205

json 파일 하나의 row 수가 4924343이니까 4924343/18205 = 270  즉, 하나의 atoms 항목 안에 270줄의 data가 들어있는 셈이지요.

[u0017649@sys-96775 files]$ grep type pubChem_p_00000001_00025000.json | wc -l
565479

또 json 파일 하나에는 type이라는 단어가 565479 줄이 나옵니다.

[u0017649@sys-96775 files]$ echo "565479/18205" | bc -l
31.06174127986816808569

즉 하나의 atoms 당 평균 31개의 type가 존재한다는 것인데, 그나마 모든 atoms 항목에 균등한 개수의 type이 들어있는 것도 아니라는 뜻입니다.  하긴 각 분자마다 들어있는 원자의 종류와 개수가 각기 다를 수 밖에 없지요.

특히 atoms와 shapeM라는 항목들은 그 하나하나가 비정형 array로 되어 있습니다.  즉 분자마다 들어있는 원자 개수도 다르고 그에 따라 shape multipole 값도 다릅니다.  이런 비정형 array로 되어 있는 string 값을 분석하여 일정한 pattern을 모델링한다는 것은 매우 어려운 일이 될 것입니다.

이를 수치적으로 분석하려고 해도, atoms 항목 내의 저 많은 값들을 어떻게 분리하고 어떤 이름의 column으로 재정비해야 할지 골치가 아플 수 밖에 없습니다.  원래 그런 고민스러운 작업을 feature engineering이라고 하지요.  이 feature engineering을 어떻게 하느냐에 따라 machine learning으로 만들어낸 model의 성능과 accuracy가 크게 좌우됩니다.  이런 숙제는 data scientist들에게 돌아가는데, 숙련된 data scientist에게도 이는 크게 골치 아픈 일이며 또 과중한 업무 부담으로 이어지게 됩니다.

하지만 이 모든 것을 간단하게 해결해주는 것이 바로 H2O DriverlessAI입니다 !  H2O DriverlessAI의 가장 큰 혜택 중 하나가 자동화된 feature engineering 아니겠습니까 ?

하지만 전에 H2O DriverlessAI는 comma(,)나 pipe(|) 등으로 분리된 CSV 파일이나 Excel(xls, xlsx) 파일만 다룰 수 있다고 하지 않았던 가요 ?  저런 json 파일도 가공없이 통째로 분석할 수 있나요 ?   불행히도 그렇게는 안됩니다.

하지만 json 파일을 csv 파일로 가공하는 것은 매우 쉽습니다.  저는 개발자가 아니며 python program이라고는 Hello World 조차 제대로 할 줄 모르는 젬병이지만, 구글링해보면 구할 수 있는 아래의 샘플 코드 하나로 금방 이걸 전환할 수 있었습니다.

https://gist.github.com/pbindustries/803464d20f48a0a23d5934e3d11dadd6

위의 github에 올려진 sample을 이용하여 아래와 같이 j2c.py라는 이름의 매우 간단한 python code를 짰습니다.  짠 것이 아니라 그대로 copy & paste 했습니다.

[u0017649@sys-96775 files]$ vi j2c.py

import csv, json, sys
#check if you pass the input file and output file
if sys.argv[1] is not None and sys.argv[2] is not None:
    fileInput = sys.argv[1]
    fileOutput = sys.argv[2]
    inputFile = open(fileInput) #open json file
    outputFile = open(fileOutput, 'w') #load csv file
    data = json.load(inputFile) #load json content
    inputFile.close() #close the input file
    output = csv.writer(outputFile) #create a csv.write
    output.writerow(data[0].keys())  # header row
    for row in data:
        output.writerow(row.values()) #values row

이제 이를 이용하여 json 파일들을 csv 파일 형태로 변환하겠습니다.

[u0017649@sys-96775 files]$ for i in `ls *.json`
> do
> python j2c.py ./$i ./${i}.csv
> done

거의 날로 먹기지요 ?  아래와 같이 순식간에 csv 파일들이 새로 생성되었습니다.

[u0017649@sys-96775 files]$ ls *.csv
pubChem_p_00000001_00025000.json.csv  pubChem_p_00125001_00150000.json.csv
pubChem_p_00025001_00050000.json.csv  pubChem_p_00150001_00175000.json.csv
pubChem_p_00050001_00075000.json.csv  pubChem_p_00175001_00200000.json.csv
pubChem_p_00075001_00100000.json.csv  pubChem_p_00200001_00225000.json.csv
pubChem_p_00100001_00125000.json.csv  pubChem_p_00225001_00250000.json.csv

csv 파일 속의 Row 수는 header까지 포함하여 18206, 즉 atoms 개수대로 만들어졌습니다.

[u0017649@sys-96775 files]$ wc -l pubChem_p_00000001_00025000.json.csv
18206 pubChem_p_00000001_00025000.json.csv

각 csv의 형태는 아래와 같습니다.  En, id, shapeM, atoms의 4개 column으로 되어있는데, shapeM과 atoms는 여전히 무지막지한 형태의 비정형 string으로 되어 있습니다.  id column은 분석에는 사실상 무의미한 column이지요.  (보시기 편하도록 제가 shapeM에는 빨간색, atoms에는 파란색으로 글자색을 바꿨습니다.)

[u0017649@sys-96775 files]$ head -n 2 pubChem_p_00000001_00025000.json.csv
En,id,shapeM,atoms
37.801,1,"[259.66, 4.28, 3.04, 1.21, 1.75, 2.55, 0.16, -3.13, -0.22, -2.18, -0.56, 0.21, 0.17, 0.09]","[{u'xyz': [0.3387, 0.9262, 0.46], u'type': u'O'}, {u'xyz': [3.4786, -1.7069, -0.3119], u'type': u'O'}, {u'xyz': [1.8428, -1.4073, 1.2523], u'type': u'O'}, {u'xyz': [0.4166, 2.5213, -1.2091], u'type': u'O'}, {u'xyz': [-2.2359, -0.7251, 0.027], u'type': u'N'}, {u'xyz': [-0.7783, -1.1579, 0.0914], u'type': u'C'}, {u'xyz': [0.1368, -0.0961, -0.5161], u'type': u'C'}, {u'xyz': [-3.1119, -1.7972, 0.659], u'type': u'C'}, {u'xyz': [-2.4103, 0.5837, 0.784], u'type': u'C'}, {u'xyz': [-2.6433, -0.5289, -1.426], u'type': u'C'}, {u'xyz': [1.4879, -0.6438, -0.9795], u'type': u'C'}, {u'xyz': [2.3478, -1.3163, 0.1002], u'type': u'C'}, {u'xyz': [0.4627, 2.1935, -0.0312], u'type': u'C'}, {u'xyz': [0.6678, 3.1549, 1.1001], u'type': u'C'}, {u'xyz': [-0.7073, -2.1051, -0.4563], u'type': u'H'}, {u'xyz': [-0.5669, -1.3392, 1.1503], u'type': u'H'}, {u'xyz': [-0.3089, 0.3239, -1.4193], u'type': u'H'}, {u'xyz': [-2.9705, -2.7295, 0.1044], u'type': u'H'}, {u'xyz': [-2.8083, -1.921, 1.7028], u'type': u'H'}, {u'xyz': [-4.1563, -1.4762, 0.6031], u'type': u'H'}, {u'xyz': [-2.0398, 1.417, 0.1863], u'type': u'H'}, {u'xyz': [-3.4837, 0.7378, 0.9384], u'type': u'H'}, {u'xyz': [-1.9129, 0.5071, 1.7551], u'type': u'H'}, {u'xyz': [-2.245, 0.4089, -1.819], u'type': u'H'}, {u'xyz': [-2.3, -1.3879, -2.01], u'type': u'H'}, {u'xyz': [-3.7365, -0.4723, -1.463], u'type': u'H'}, {u'xyz': [1.3299, -1.3744, -1.7823], u'type': u'H'}, {u'xyz': [2.09, 0.1756, -1.3923], u'type': u'H'}, {u'xyz': [-0.1953, 3.128, 1.7699], u'type': u'H'}, {u'xyz': [0.7681, 4.1684, 0.7012], u'type': u'H'}, {u'xyz': [1.5832, 2.901, 1.6404], u'type': u'H'}]"

이 10개의 파일들로부터 마지막 5줄씩을 미리 잘라내어 총 50줄 (column까지 합하면 51줄)의 pubChem_test1.xlsx라는 test용 dataset을 만들어두겠습니다.

그리고나서 5줄씩 줄어든 이 10개의 파일들을 upload 하기 편하도록 하나의 pubChem1.zip 파일로 zip으로 압축하겠습니다.

[u0017649@sys-96775 files]$ zip pubChem1.zip *.csv
  adding: pubChem_p_00000001_00025000.json.csv (deflated 78%)
  adding: pubChem_p_00025001_00050000.json.csv (deflated 78%)
  adding: pubChem_p_00050001_00075000.json.csv (deflated 78%)
  adding: pubChem_p_00075001_00100000.json.csv (deflated 78%)
  adding: pubChem_p_00100001_00125000.json.csv (deflated 77%)
  adding: pubChem_p_00125001_00150000.json.csv (deflated 78%)
  adding: pubChem_p_00150001_00175000.json.csv (deflated 77%)
  adding: pubChem_p_00175001_00200000.json.csv (deflated 78%)
  adding: pubChem_p_00200001_00225000.json.csv (deflated 78%)
  adding: pubChem_p_00225001_00250000.json.csv (deflated 78%)


이제 H2O DAI의 web interface에 접속합니다.  Dataset 메뉴에서 이 pubChem1.zip을 H2O DAI 서버로 upload하고 'Details' 항목을 보겠습니다.



보시는 바와 같이 En과 id는 각각 real과 integer로 인식되는데, shapeM과 atoms는 무지막지한 길이와 형태의 string으로 인식됩니다.



하지만 그냥 H2O DAI가 알아서 제대로 해줄 것이라고 믿고 그냥 그대로 prediction (training)으로 들어가겠습니다.  우리가 예측하려는 분자의 energy 값인 En을 Target column으로 정하고 Accuracy와 Time, Interpretability는 각각 10, 7, 7 정도로 세팅해서 돌리겠습니다.



참고로 이렇게 10-7-7로 맞출 경우의 algorithm과 iteration 회수, 그리고 model 및 feature 개수 등은 아래와 같이 설정됩니다.  위 사진의 왼쪽 상세 부분인데 글자가 너무 작아 잘 안 보이실 것 같아서 확대해서 캡춰했습니다.



이제 'Launch experiment'를 클릭하여 training을 시작합니다.  제가 가난하여 GPU가 없는 관계로, 이 training은 모두 2-core짜리 POWER8 가상 머신에서 수행했습니다.  (SMT8 때문에 H2O는 이를 2 * 8 = 16-core 짜리 장비라고 인식합니다.)  그래서 꽤 오랜 시간이 걸렸습니다.



Training 중간 과정을 보면 중앙 하단에 'Variable Importance'라는 항목이 보입니다.  이는 dataset 내부의 여러 column 중 어느 column이 En 값 예측에 가장 중요한지 중요도 순으로 보여주는 것인데, 이 값들은 training이 진행됨에 따라 변하기도 하고 새로 나타나기도 합니다.  보시면 우리가 우겨넣은 column은 분명히 En, id, shapeM과 atoms 4개 밖에 없었는데, 이 메뉴에 보여지는 column 이름들은 atoms_0, atoms_18 등 새로운 column 이름들이 많이 나온 것을 보실 수 있습니다.  즉, H2O DAI가 내부에서 자동으로 feature engineering을 수행한 것이지요.



이제 experiment, 즉 training이 끝났습니다.  Train된 model을 이용하여 prediction을 해보도록 하겠습니다.

'Score on Another Dataset'이라는 항목을 클릭한 뒤, 아까 따로 잘라놓았던 50줄짜리 pubChem_test1.xlsx를 선택합니다.  그러면 이 excel 표의 shapeM과 atoms column을 분석하고 아까 만들어진 model에 대입한 뒤, En 값이 어떨지 예측을 하여 그 결과를 csv 파일로 download 시켜줍니다.

실제 En 값과 H2O DAI가 예측한 En 값을 비교하여 그래프로 만들면 아래와 같습니다.  나름 꽤 그럴싸한 예측을 했다는 것을 보실 수 있습니다. 




이렇게 H2O DAI는 data scientist의 업무 부담을 크게 줄여주고 더 빠른 feature engineering과 modeling을 통해 화학, 생명, 제조 등의 연구실에서도 유용하게 사용하실 수 있습니다.


2018년 12월 19일 수요일

H2O Driverless AI를 이용한 Kaggle 도전 : Creditcard fraud detection

이번 posting에서는 H2O Driverless AI의 정확도가 과연 어느 정도인지 좀더 현실적으로 보여주는 테스트를 돌려보겠습니다.

Kaggle이라는 것은 일종의 data science 경쟁 대회라고 보시면 됩니다.  국가기관이나 기업 등에서 자신들이 해석하기 어려운 (주로 비인식처리된) 실제 data를 올려놓고 전세계 scientist들에게 그 분석 방법에 대해 경쟁을 벌이도록 하는 것입니다.  저는 data scientist와는 100만 광년 정도의 거리에 있는 일개 시스템 엔지니어입니다만, H2O Driverless AI의 힘을 빌어 아래의 신용카드 사기 예측 모델 생성에 도전해보겠습니다.

https://www.kaggle.com/mlg-ulb/creditcardfraud/home

여기서 제공되는 data는 https://www.kaggle.com/mlg-ulb/creditcardfraud/downloads/creditcardfraud.zip 에 올려져 있습니다.  Facebook이나 Google 등으로 로그인해야 download가 가능합니다.

압축을 풀면 다음과 같이 28만이 넘는 row를 가진 csv 파일을 얻을 수 있습니다.  이 중 마지막 3천 row를 뚝 잘라 creditcard_test1.csv로 만들고, 나머지 것을 creditcard_train.csv으로 쓰겠습니다.

[bsyu@redhat74 data]$ wc -l creditcard.csv
284808 creditcard.csv

[bsyu@redhat74 data]$ ls -l creditcard*.csv
-rw-rw-r-- 1 bsyu bsyu 150828752 Mar 23  2018 creditcard.csv

[bsyu@redhat74 data]$ head -n 1 creditcard.csv > creditcard_test.csv

[bsyu@redhat74 data]$ tail -n 3000 creditcard.csv >> creditcard_test1.csv

[bsyu@redhat74 data]$ head -n 283808 creditcard.csv > creditcard_train.csv

각 row는 아래와 같이 비식별화처리를 거친 모종의 값으로 되어 있습니다.  여기서 비식별화처리가 되지 않은 column은 맨마지막 2개, 즉 Amount와 Class입니다.  Amount는 카드 사용금액이고, Class가 0이면 정상거래, 1이면 사기거래입니다.

[bsyu@redhat74 data]$ head -n 2 creditcard_train.csv
"Time","V1","V2","V3","V4","V5","V6","V7","V8","V9","V10","V11","V12","V13","V14","V15","V16","V17","V18","V19","V20","V21","V22","V23","V24","V25","V26","V27","V28","Amount","Class"
0,-1.3598071336738,-0.0727811733098497,2.53634673796914,1.37815522427443,-0.338320769942518,0.462387777762292,0.239598554061257,0.0986979012610507,0.363786969611213,0.0907941719789316,-0.551599533260813,-0.617800855762348,-0.991389847235408,-0.311169353699879,1.46817697209427,-0.470400525259478,0.207971241929242,0.0257905801985591,0.403992960255733,0.251412098239705,-0.018306777944153,0.277837575558899,-0.110473910188767,0.0669280749146731,0.128539358273528,-0.189114843888824,0.133558376740387,-0.0210530534538215,149.62,"0"

이 dataset은 Class 측면에서 보면 굉장히 빈도가 낮은 것입니다.  즉, 전체 28만건이 넘는 거래들 중, 사기거래, 즉 Class가 1인 거래는 고작 492건에 불과합니다.  Percentage로 따지면 고작 0.173%에 불과합니다.   보통 machine learning에서 만든 모델의 정확도가 90%를 넘어가면 꽤 성공적이라고들 하는데, 이런 경우에는 어떤 거래를 판정하라는 요청을 받았을 때 그냥 무조건 정상거래라고 판정하면 99.8% 이상의 정확도를 나타냈다고(?) 주장할 수도 있습니다.  과연 이런 극악한 조건에서 H2O Driverless AI는 제대로 된 모델을 만들 수 있을까요 ?

[bsyu@redhat74 data]$ cut -d"," -f31 creditcard.csv | grep 1 | wc -l
492

우리의 목표는 이 data를 이용해서 어떤 거래가 사기인지 정상인지 판별하는 model을 만드는 것입니다.  그 성공 여부 판별을 위해 잘라낸 test dataset 3천 row 중에 실제 사기 건수는 얼마나 될까요 ?  고작 4건입니다.  588번째, 871번째, 874번째, 그리고 921번째 row입니다.

[bsyu@redhat74 data]$ cut -d"," -f31 creditcard_test1.csv | grep 1
"1"
"1"
"1"
"1"

[bsyu@redhat74 data]$ cut -d"," -f31 creditcard_test1.csv | head -n 589 | tail -n 5
"0"
"0"
"0"
"1"
"0"

[bsyu@redhat74 data]$ cut -d"," -f31 creditcard_test1.csv | head -n 875 | tail -n 7
"0"
"0"
"1"
"0"
"0"
"1"
"0"

[bsyu@redhat74 data]$ cut -d"," -f31 creditcard_test1.csv | head -n 923 | tail -n 5
"0"
"0"
"1"
"0"
"0"

다음과 같이 H2O Driverless AI에 creditcard_train.csv를 add 하고 target column은 'Class'로 지정한 뒤, control knob은 Accuracy 10, Time 8, Interpretability 2로 맞추고 training (H2O 용어로는 experiment)를 시작했습니다.   Accuracy 항목에서 Regression으로 수행하고 있기 때문에, 결과 예측값은 0이나 1의 숫자가 아닌, 그 중간의 소수로 나올 것입니다.  그 값이 1에 가까울 수록 사기일 확률이 높고, 0에 가까울 수록 정상거래일 것입니다. 



참고로 이번에는 GPU를 갖춘 시스템을 구하지 못해서, POWER8 가상 머신에서 CPU core 2개를 이용해서 수행했습니다.   SMT=8으로 되어 있기 때문에, OS에게는 이것이 2개의 core가 아닌 16개의 logical CPU로 인식됩니다.  덕분에 CPU 사용률이 매우 높고, 또 시간도 엄청나게 오래 걸리는 것을 보실 수 있습니다.




최종적으로 만들어진 model은 크기가 무려 7GB나 되고, 만드는데 거의 9시간이 걸렸습니다.



이제 이 model을 열어 test dataset으로 판별을 해보겠습니다.  맨 처음에 training을 시작할 때 RMSE (root mean square error)가 0.0194로 시작했었는데, 최종적으로는 0.0191로 약간 줄어있는 것을 보실 수 있습니다.



Score on Anonther Dataset을 클릭하면 dataset을 선택하게 되어 있는데, 미리 서버에 올려둔 creditcard_test1.csv을 선택하면 약 1분 정도 prediction이 수행된 뒤에 그 결과로 (input인  creditcard_test1.csv가 3천 row이므로)  Class column에 대한 3천 row의 예측값을 담은 csv 파일을 download 할 수 있게 해줍니다.   그 결과는 아래와 같습니다.






최소한 정상거래를 사기거래라고 판정한 것은 하나도 없습니다.   대부분의 row는 사기 확률이 0%로 나오고, 실제로는 사기였던 4개 row에 대해서는 위와 같이 나옵니다.  즉 2개는 98%, 99%로서 사기가 확실하고, 나머지 2개는 사기 확률이 36%와 49%로 나오는 것이지요.  이 정도면 상당히 높은 수준의 결과라고 판단됩니다.   정말 멋지지 않습니까 ?

---------------------------

추가로, training dataset을 좀더 작게, 대신 test dataset을 좀더 크게 하여 model을 생성하고 테스트해보겠습니다.   아래와 같이, 전체 28만4천건 중에 26만건을 training dataset으로, 그리고 나머지 2만4천건을 test dataset으로 잘라냈습니다.

[bsyu@redhat74 data]$ wc -l credi*
   284808 creditcard.csv

[bsyu@redhat74 data]$ head -n 260001 creditcard.csv > credit_train.csv

[bsyu@redhat74 data]$ head -n 1 creditcard.csv > credit_test.csv

[bsyu@redhat74 data]$ tail -n 24807 creditcard.csv >> credit_test.csv

[bsyu@redhat74 data]$ wc -l creditcard.csv credit_train.csv credit_test.csv
   284808 creditcard.csv
   260001 credit_train.csv
    24808 credit_test.csv

이것을 다음과 같이 Accuracy 10, Time 7, Interpretability 2로 맞추고 training 했습니다.



그 결과로 (역시 GPU 없이 POWER8 CPU core 2개만 이용해서) training 하는데 총 13시간 정도가 걸렸습니다.



이 자동생성된 model을 이용하여 위에서 준비한 2만4천건의 test dataset인 credit_test.csv에 대한 prediction을 수행했고, 그 결과로 gerewika_preds_799411be.csv 파일을 얻었습니다.


[bsyu@redhat74 data]$ head /tmp/gerewika_preds_799411be.csv
Class
4.00790426897506e-5
2.7836374905953808e-5
4.3067764191826185e-5
6.776587835599978e-5
0.0007914757505702477
0.00026333562696054583
0.0003490925939070681
0.00012611508177717525
3.6875439932445686e-5


위에서 준비했던 credit_test.csv 속에는 과연 사기 건수가 몇건이었을까요 ?  그 column 중 31번째 column인 Class의 값이 1인 것이 몇건인지를 세어보겠습니다.  

[bsyu@redhat74 data]$ cut -f31 -d',' ./credit_test.csv | grep 1 | wc -l
21

위와 같이 총 21건입니다.

과연 우리가 얻은 gerewika_preds_799411be.csv 파일 속에서 사기일 가능성이 높은 칼럼은 몇 개일까요 ?  그것을 세기 위해 다음과 같은 script를 만들었습니다.

[bsyu@redhat74 data]$ cat count.sh
if [[ $# -ne 2 ]]
then
echo "Usage ./count.sh digit filename"
fi
j=1
for i in `cat $2 `
do
m=$(printf "%f" "$i")
if (( $(echo "$m >= $1" | bc -l) ))
then
echo "Row_num is $j and the vlaue is $m"
fi
(( j=j+1 ))
done


여기서 몇 %일 때 이를 사기로 볼 것인지는 여러분이 직접 정하셔야 합니다.   일단 70%, 즉 0.70 이상이면 사기로 간주하는 것으로 해서 세어보겠습니다.

[bsyu@redhat74 data]$ ./count.sh 0.7 /tmp/gerewika_preds_799411be.csv
./count.sh: line 9: printf: Class: invalid number
Row_num is 1058 and the vlaue is 0.740187
Row_num is 1475 and the vlaue is 0.897596
Row_num is 1927 and the vlaue is 0.975030
Row_num is 2562 and the vlaue is 0.996552
Row_num is 2828 and the vlaue is 0.968835
Row_num is 3276 and the vlaue is 0.979910
Row_num is 3326 and the vlaue is 0.939915
Row_num is 3879 and the vlaue is 0.896258
Row_num is 16866 and the vlaue is 0.995111
Row_num is 19865 and the vlaue is 0.958619
Row_num is 20145 and the vlaue is 0.905748
Row_num is 20151 and the vlaue is 0.911095
Row_num is 21146 and the vlaue is 0.964987


70%를 기준으로 하니 13건이 사기로 판명되었습니다.   실제값인 21건을 정확하게 맞추지는 못했습니다.   Training dataset이 28만 row에서 23만 row로 줄어드니 확실히 정확도가 떨어지는 것을 보실 수 있습니다.   그런 상황에서도 적어도 정상 transaction을 사기 transaction으로 평가하는 일은 없군요.   참고로 test dataset 속의 실제 사기 transaction의 row #는 다음과 같습니다.

Row_num is 1058 and the vlaue is 1.000000
Row_num is 1475 and the vlaue is 1.000000
Row_num is 1927 and the vlaue is 1.000000
Row_num is 2562 and the vlaue is 1.000000
Row_num is 2828 and the vlaue is 1.000000
Row_num is 3082 and the vlaue is 1.000000
Row_num is 3276 and the vlaue is 1.000000
Row_num is 3326 and the vlaue is 1.000000
Row_num is 3879 and the vlaue is 1.000000
Row_num is 8377 and the vlaue is 1.000000
Row_num is 12523 and the vlaue is 1.000000
Row_num is 14384 and the vlaue is 1.000000
Row_num is 14477 and the vlaue is 1.000000
Row_num is 15994 and the vlaue is 1.000000
Row_num is 16073 and the vlaue is 1.000000
Row_num is 16866 and the vlaue is 1.000000
Row_num is 19865 and the vlaue is 1.000000
Row_num is 20145 and the vlaue is 1.000000
Row_num is 20151 and the vlaue is 1.000000
Row_num is 21146 and the vlaue is 1.000000
Row_num is 21676 and the vlaue is 1.000000


-----------------------------------------------


이하는 상기 model을 training한 뒤 얻은 MLI (machine learning interpretation) 항목의 결과입니다.

가장 알아보기 쉬운 KLIME의 설명을 보면 다음과 같습니다.  아래 그림에도 있습니다만, Class 항목이 1 증가할 때 V11은 0.0058 증가, V4는 0.0036 증가... 등의 상관관계를 Driverless AI가 분석해냈습니다.

Top Positive Global Attributions
V11   increase of  0.0058
V4    increase of  0.0036
V2    increase of  0.0024










------------------------------------

추가 테스트를 해봤습니다.  다음과 같이 약 250000번째 줄부터 4000 줄을 빼내어 credit_test.csv라는 test용 dataset을 만들고, 그 4000 줄을 뺀 나머지 부분으로 credit_train.csv이라는 training용 dataset을 만들었습니다.

ibmtest@digits:~/files/csv$ head -n 250001 creditcard.csv > credit_train.csv
ibmtest@digits:~/files/csv$ tail -n 30808 creditcard.csv >> credit_train.csv
ibmtest@digits:~/files/csv$ head -n 1 creditcard.csv > credit_test.csv
ibmtest@digits:~/files/csv$ head -n 254809 creditcard.csv | tail -n 4000 >> credit_test.csv
ibmtest@digits:~/files/csv$ wc -l credit_train.csv credit_test.csv
   280809 credit_train.csv
     4001 credit_test.csv
   284810 total

Training용 dataset에는 class가 1, 즉 사기 transaction이 총 484개 들어있고, test용 dataset에는 9개 들어있습니다.

ibmtest@digits:~/files/csv$ cut -f31 -d',' ./credit_train.csv | grep 1 | wc -l
484

ibmtest@digits:~/files/csv$ cut -f31 -d',' ./credit_test.csv | grep 1 | wc -l
9

이제 test용 dataset 중 몇번째 row가 사기 transaction인지 row #를 찾아보겠습니다.

ibmtest@digits:~/files/csv$ j=1

ibmtest@digits:~/files/csv$ for i in `cut -f31 -d',' ./credit_test.csv`
> do
> if [ "$i" == "\"1\"" ]
> then
> echo "Row_num is " $j
> fi
> ((j=j+1))
> done


Row_num is  671
Row_num is  1060
Row_num is  1075
Row_num is  1085
Row_num is  1098
Row_num is  1318
Row_num is  1968
Row_num is  3538
Row_num is  3589

이제 H2O DAI로 training을 한 뒤, 이 test dataset을 넣어서 예측을 해보았습니다.  그 결과는 다음과 같습니다.

30% (0.3)을 기준으로 하면 아래와 같이 9건 중 6건을 찾아냅니다.

ibmtest@digits:~/files/csv$ ./count.sh  0.3 miwatuvi_preds_c5262e68.csv
./count.sh: line 8: printf: Class: invalid number
Row_num is 671 and the vlaue is 1.000000
Row_num is 1060 and the vlaue is 0.950008
Row_num is 1098 and the vlaue is 0.904203
Row_num is 1318 and the vlaue is 1.000000
Row_num is 1968 and the vlaue is 1.000000
Row_num is 3538 and the vlaue is 0.303216

20% (0.2)을 기준으로 하면 아래와 같이 9건 중 7건을 찾아냅니다. 

ibmtest@digits:~/files/csv$ ./count.sh  0.2 miwatuvi_preds_c5262e68.csv
./count.sh: line 8: printf: Class: invalid number
Row_num is 671 and the vlaue is 1.000000
Row_num is 1060 and the vlaue is 0.950008
Row_num is 1098 and the vlaue is 0.904203
Row_num is 1318 and the vlaue is 1.000000
Row_num is 1968 and the vlaue is 1.000000
Row_num is 3538 and the vlaue is 0.303216
Row_num is 3589 and the vlaue is 0.296002