머신 러닝 파이프라인을 만들기 위하여, 타이타닉 데이터를 사용해 보도록 하겠습니다.
타이타닉 데이터는 캐글(Kaggle)에서 입문자용으로 가장 많이 사용하는 예제입니다. 캐글은 데이터 사이언스나 머신 러닝을 공부하는 사람들이 많이 사용하는 데이터 분석 경연 플랫폼입니다.
타이타닉 데이터는 다양한 사람들이 다양한 관점에서 다양한 방법으로 데이터를 분석하고 있기 때문에, 이 데이터를 가지고 공부를 하다보면, 데이터 분석의 전반적인 과정을 이해하는데 많은 도움이 될 것입니다.
이 장에서는 타이타낵 생존 예측 결과를 캐글에 제출하기 위한 파이프라인을 구성해 볼 것입니다. 그리고 재사용 컴포넌트를 만들고, 파이프라인에서 재사용 컴포넌트를 사용할 것입니다. 데이터 분석 보다는 전체적인 파이프라인을 만드는 것에 중점을 두고 설명하겠습니다.
사전 준비
캐글 접속하기
캐글 사이트의 주소는 https://www.kaggle.com/ 입니다. 원활한 사용을 위하여, 회원 가입이 필요합니다. 회원 가입을 하시고, 로그인하시기 바랍니다.

Titanic: Machine Learning from Disaster 접속
예제에서 사용할 데이터가 있는 타이나닉 예측 Competition 의 주소는 다음과 같습니다.
[<https://www.kaggle.com/c/titanic>](<https://www.kaggle.com/c/titanic>)
해당 주소로 직접 접속하셔도 되고, 상단에 있는 검색바를 이용하여 검색하셔도 됩니다.
다음은 검색바를 이용하여, “Titanic: Machine Learning from Disaster” 을 검색한 결과입니다.

“Titanic: Machine Learning from Disaster” Competition 페이지 접속하면 다음과 같은 화면을 볼 수 있습니다.

데이터는 “Data” 탭에서 받을 수 있습니다. “Data” 탭을 클릭하면, 데이터에 대한 자세한 설명과 다운로드 받는 방법을 볼 수 있습니다.

주피터 노트북
주피터에서 새로운 Terminal
을 엽니다.
kaggle
을 이용하여 데이터를 받기 위해서, kaggle
패키지를 설치합니다.
pip install kaggle --user
정상적으로 설치되면, 다음과 같은 응답 결과를 확인할 수 있습니다.
Collecting kaggle Downloading <https://files.pythonhosted.org/packages/62/ab/bb20f9b9e24f9a6250f95a432f8d9a7d745f8d24039d7a5a6eaadb7783ba/kaggle-1.5.6.tar.gz> (58kB) .... Successfully built kaggle python-slugify Installing collected packages: urllib3, tqdm, text-unidecode, python-slugify, kaggle WARNING: The script tqdm is installed in '/home/jovyan/.local/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. WARNING: The script slugify is installed in '/home/jovyan/.local/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. WARNING: The script kaggle is installed in '/home/jovyan/.local/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. Successfully installed kaggle-1.5.6 python-slugify-4.0.0 text-unidecode-1.3 tqdm-4.45.0 urllib3-1.24.3
kaggle
명령어를 실행하기 위해서 PATH
에 추가해 줍니다. kaggle
를 user
디렉토리에 설치하였기 때문에, PATH
에 안잡혀 있을 수 있습니다. 사용하는 주피터 노트북 이미지에 따라서 다르기 때문에, 이미 PATH
에 추가되어 있다면, 다음 단계로 넘어가셔도 됩니다.
export PATH=${PATH}:/home/jovyan/.local/bin
캐글 API 토큰 생성하기
주피터 터미널에서 kaggle
명령어를 이용하여, 데이터를 받으려면, API 토큰이 필요합니다. 캐글 사이트의 우측 상단에 있는 “My Profile”을 클릭합니다.
My Account

“My Profile” 페이지 접속하면, 토큰을 생성할 수 있습니다.
“Create New API Token”을 클릭하여 토큰을 생성합니다. 토큰이 생성되면 “kaggle.json” 파일로 자동 다운로드 됩니다.

주피터 노트북에 캐글 API 토큰 추가하기
해당 파일을 주피터 노트북의 ~/.kaggle/kaggle.json
로 복사해 줍니다.
mkdir ~/.kaggle cd ~/.kaggle cat << EOF > kaggle.json {"username":"USERNAME","key":"12345678901234567890"} EOF
주피터 노트북에서 타이타닉 데이터 다운로드하기
작업 디렉토리를 생성한 다음, 타이타닉 데이터를 다운로드 합니다. [titanic.zip](<http://titanic.zip>)
라는 파일이 다운로드 됩니다.
mkdir -p ~/workspace/titanic/kaggle cd ~/workspace/titanic/kaggle kaggle competitions download -c titanic
Downloading titanic.zip to /home/jovyan/workspace/titanic/kaggle 0%| | 0.00/34.1k [00:00<?, ?B/s] 100%|██████████████████████████████████████████████████████████████████████████████████| 34.1k/34.1k [00:00<00:00, 842kB/s]
다운로드한 파일의 압축을 풀겠습니다. 모델 학습을 위한 train.csv
파일과, 예측에 사용할 데이터인 test.csv
그리고, 캐글에 제출할 파일의 형식을 보여주는 gender_submission.csv
파일이 생성됩니다.
unzip titanic.zip
Archive: titanic.zip inflating: gender_submission.csv inflating: test.csv inflating: train.csv
파이썬 패키지 설치
머신 러닝 모델 코드를 작성하기 위한 패키지들을 설치합니다.
pip install pandas seaborn sklearn --user
Kubeflow 파이프라인을 작성하기 위한 패키지를 설치합니다.
pip install kfp --user
데이터 전처리와 모델 작성
문제 정의하기
주어진 데이터를 바탕으로, 타이타닉호에 탑승했던 승객이 타이타닉 침몰에서 살아 남았는지를 예측하는 문제입니다.
데이터 수집(Data Ingestion)
Data Ingestion
은 사용하거나 저장하기 위해서 데이터를 입수하고 가져오는 과정입니다. 간단히 얘기해서 머신 러닝에 사용할 데이터를 가져온다고 할 수 있습니다. 예제에서는 캐글에서 제공하는 데이터를 다운로드 받아 사용합니다.
데이터 분석 및 검증 (Data Analysis and Validation)
데이터 분석이란 데이터의 분포를 이해한다는 것을 의미합니다. 데이터에 대한 통계 정보들, 예를 들어, 각 컬럼들이 어떤한 값들을 얼마 만큼 가지고 있는지, 어떤 컬럼이 포함하거나 포함하지 않는 값들 얼마만큼 가지고 있는지 같은 정보를 파악하는것을 의미합니다. 이런 분석 작업을 통하여 데이터를 검증하게 됩니다.
데이터 검증은 데이터의 품질을 높이기 위하여, 데이터의 오류를 파악하여 수정하는 것을 의미합니다. 데이터 분석에서 얻은 정보들을 기반으로 데이터 검증이 이루어 집니다.
데이터 분석과 검증을 통하여 유효하지 않거나, 유실된 데이터를 처리해야만 데이터의 품질이 좋아질 수 있습니다.
데이터의 품질을 높이기 위해서는 다음과 같은 여러 가지 기능을 사용합니다.
- 기본 사항(선택, 필터, 중복 제거 등)
- 샘플링(균형, 계층화 등)
- 데이터 파티셔닝(학습 세트 + 검증 세트 + 테스트 세트)
- 변환(일반화, 표준화, 스케일링, 피벗, 등)
- Binning (결측값 처리 등)
타이타닉 데이터 분석 및 검증
먼저 타이타닉 데이터에 대해 이해하기 위하여, 캐글에서 제공하고 있는 Data Dictionary
와 Variable Notes
를 살펴 보겠습니다. Data
탭의 Data Description
부분을 펼치면 다음과 같은 내용을 확인할 수 있습니다.
Data Dictionary

변수 노트
각 변수에 대해서 좀 더 자세한 내용이 적혀 있습니다.
- pclass :사회 경제적 지위 (SES)
- 1st = Upper
- 2nd = Middle
- 3rd = Lower
- age : 나이가 1보다 작은 경우는 분수입니다. 추정된 나이일 경우에는 xx.5 형식입니다.
- sibsp : 데이터 세트는 다음과 같은 방식으로 가족 관계를 정의합니다.
- 형제 자매 = 형제, 자매, 의붓 형제, 이복 누이
- 배우자 = 남편, 아내 (정부와 약혼자는 무시 합니다.)
- parch : 데이터 세트는 다음과 같은 방식으로 가족 관계를 정의합니다.
- 부모 = 어머니, 아버지
- 아이 = 딸, 아들, 의붓 딸, 의붓 아들
- 보모와 같이 여행한 어린이는 parch=0 으로 처리합니다.
이제 각 변수들에 대한 의미를 알게되었으니, 주어진 데이터에 대해 간략하게 살펴보겠습니다.
캐글에서 제공하는 타이타닉 데이터 세트는 분석 모델을 만드는데에는 아직 적합하지 않습니다. 결측 값들이 존재하고, 학습에 사용하기 어려운 문자열 값들이 존재하고 있습니다. 이러한 값들을 잘 처리하여야만, 머신 러닝 알고리즘을 적용할 때 최상의 결과를 얻을 수 있습니다.
주피터 노트북을 생성합니다. 노트북의 위치는 ~/workspace/titanic
입니다. 앞으로 작성할 예제에서는 노트북이 ~/workspace/titanic
에 위치하고 있다고 가정하여, 파일 경로를 설정할 것입니다.
주피터 노트북을 이용하여 데이터를 분석 및 검증해 보겠습니다.
판다스를 이용해서 데이터를 읽어 오겠습니다.
In []:
import pandas as pd train = pd.read_csv('~/workspace/titanic/kaggle/train.csv') test = pd.read_csv('~/workspace/titanic/kaggle/test.csv')
head() 메소드를 이용하여 학습용 데이터를 조회해 봅니다.
In []:
train.head()

Cabin
에 Nan(Not a Number) 가 존재하는 것을 알 수가 있습니다.
테스트 데이터도 조회해 봅니다.
In[]:
test.head()

테스트 데이터에는 Survived
컬럼이 없다는 것을 알 수 있습니다. 파일명이 test
이지만 테스트에는 사용할 수 없습니다. 이 데이터는 캐글에 예측 결과값을 제출할때 사용하는 입력 데이터 입니다.
그리고 info()
메소드를 이용하에 데이터의 정보를 조회해 봅니다.
In[]:
train.info()
정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.
<class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 891 non-null int64 1 Survived 891 non-null int64 2 Pclass 891 non-null int64 3 Name 891 non-null object 4 Sex 891 non-null object 5 Age 714 non-null float64 6 SibSp 891 non-null int64 7 Parch 891 non-null int64 8 Ticket 891 non-null object 9 Fare 891 non-null float64 10 Cabin 204 non-null object 11 Embarked 889 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 83.7+ KB
Non-Null
의 값들이 다른 것을 알 수 있습니다. 즉 Null
인 값들이 존재한다는 것입니다.
Null
값들의 개수를 조회해 보겠습니다.
train.isnull().sum()
정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.
PassengerId 0 Survived 0 Pclass 0 Name 0 Sex 0 Age 177 SibSp 0 Parch 0 Ticket 0 Fare 0 Cabin 687 Embarked 2 dtype: int64
Age
와 Cabin
그리고 Embarked
에 결측값이 존재하는 것을 알 수 있습니다.
데이터에 대한 간단한 통계 정보를 보고 싶으면 describe()
메소드를 사용할 수 도 있습니다.
train.describe(include='all')
정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.

조회해 본 결과를 통해서 알 수 있듯이, Age
, Cabin
, Embarked
는 결측값이 존재하는 것을 확인할 수 있습니다. 그리고 여러 유형의 문자열 데이터가 존재하는 것도 확인할 수 있습니다.
데이터는 크게 숫자형 데이터(Numerical Type)와 범주형 데이터(Categorical Type)로 나눌 수 있습니다. 숫자형 데이터는 연속성을 가지는 숫자로 이루어진 데이터를 의미합니다. 예제에서는 Age
와 Fare
같은 것이 여기에 속합니다. 범주형 데이터는 연속적이지 않는 값을 갖는 데이터를 의미합니다. 대부분의 경우 문자형 데이터가 여기에 속합니다. 하지만, 어떤 경우에는 숫자형 데이터도 개념적으로 범주형으로 처리해야 할 경우도 있습니다. 예제에서는 Sex
, Embarked
, Pclass
를 범주형 데이터라고 볼 수 있습니다. Pclass
의 경우 숫자형 데이터로 보이지만, 개념적으로 범주형으로 처리하겠습니다.
데이터 변환 (Data Transformation )
머신 러닝 모델을 학습할 때 사용할 수 있도록 데이터를 변환하고, 결측값들을 처리하도록 하겠습니다
범주형 데이터 변환
One-Hot Encoding 을 사용하겠습니다. 판다스에서는 get_dummies() 메소드를 이용하면 One-Hot Encoding 을 손쉽게 할 수 있습니다.
One-Hot Encoding 은 문자를 숫자로 바꾸어 주는 방법 중의 하나로서, 가변수(dummy variable)을 0과 1로 이루어진 가변수를 만들어 주는 것입니다. 1은 있다는 것을, 0은 없다는것을 나타냅니다.
예를 들어 과일이라는 컬럼이 있습니다. 해당 컬럼은 사과, 바나나, 체리라는 세가지 종류의 값을 가지고 있습니다. 이 값을 One-Hot Encoding 할 경우 사과라는 값은 “1,0,0” 같은 형태로 변환시킬수 있습니다.

그림 출처 : (?)
판다스의 get_dummies() 메소드를 이용하여 Sex
, Embarked
, Pclass
를 One-Hot Encoding 하겠습니다.
train = pd.get_dummies(train, columns=['Sex', 'Embarked', 'Pclass']) test = pd.get_dummies(test, columns=['Sex', 'Embarked', 'Pclass'])
Sex
, Embarked
, Pclass
컬럼들이 아래처럼 변환것을 확인할 수 있습니다.

결측 데이터 처리
결측값들을 처리하는 방법은 크게 두 가지가 있습니다. 결측값이 포함된 데이터를 삭제하거나, 다른값으로 치환하는 것입니다. 판다스에서 결측값이 포함된 데이터를 삭제하고 싶으면 dropna()
메소드를 사용하면 됩니다. 만약 다른값으로 치환하고 싶다면 fillna()
메소드를 사용하면 됩니다.
Age
의 결측값을 처리해 보겠습니다. 간단하게 생존자와 사망자의 나이 평균값을 구한다음, 그 값으로 치환하겠습니다.
survived_1_age_mean = train[(train['Survived'] == 1)]['Age'].mean() survived_0_age_mean = train[(train['Survived'] == 0)]['Age'].mean()
Age
의 결측값을 생존자와 사망자의 나이 평균값을로 치환합니다.
train.loc[train['Survived'] == 1, 'Age'] = train[train['Survived'] == 1].fillna(survived_1_age_mean) train.loc[train['Survived'] == 0, 'Age'] = train[train['Survived'] == 0].fillna(survived_1_age_mean) test['Age'].fillna(test['Age'].mean(), inplace=True)
다시 한번 데이터의 정보를 조회해 보겠습니다.
train.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 17 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 891 non-null int64 1 Survived 891 non-null int64 2 Name 891 non-null object 3 Age 891 non-null float64 4 SibSp 891 non-null int64 5 Parch 891 non-null int64 6 Ticket 891 non-null object 7 Fare 891 non-null float64 8 Cabin 204 non-null object 9 Sex_female 891 non-null uint8 10 Sex_male 891 non-null uint8 11 Embarked_C 891 non-null uint8 12 Embarked_Q 891 non-null uint8 13 Embarked_S 891 non-null uint8 14 Pclass_1 891 non-null uint8 15 Pclass_2 891 non-null uint8 16 Pclass_3 891 non-null uint8 dtypes: float64(2), int64(4), object(3), uint8(8) memory usage: 69.7+ KBtrain['Cabin'].unique()
이제 Cabin
을 제외하고는 결측값을 없습니다.
Cabin
에 어떤 값들이 존재하는 확인해 보겠습니다.
In [] :
train['Cabin'].unique()
Out[] :
array([nan, 'C85', 'C123', 'E46', 'G6', 'C103', 'D56', 'A6', 'C23 C25 C27', 'B78', 'D33', 'B30', 'C52', 'B28', 'C83', 'F33', 'F G73', 'E31', 'A5', 'D10 D12', 'D26', 'C110', 'B58 B60', 'E101', 'F E69', 'D47', 'B86', 'F2', 'C2', 'E33', 'B19', 'A7', 'C49', 'F4', 'A32', 'B4', 'B80', 'A31', 'D36', 'D15', 'C93', 'C78', 'D35', 'C87', 'B77', 'E67', 'B94', 'C125', 'C99', 'C118', 'D7', 'A19', 'B49', 'D', 'C22 C26', 'C106', 'C65', 'E36', 'C54', 'B57 B59 B63 B66', 'C7', 'E34', 'C32', 'B18', 'C124', 'C91', 'E40', 'T', 'C128', 'D37', 'B35', 'E50', 'C82', 'B96 B98', 'E10', 'E44', 'A34', 'C104', 'C111', 'C92', 'E38', 'D21', 'E12', 'E63', 'A14', 'B37', 'C30', 'D20', 'B79', 'E25', 'D46', 'B73', 'C95', 'B38', 'B39', 'B22', 'C86', 'C70', 'A16', 'C101', 'C68', 'A10', 'E68', 'B41', 'A20', 'D19', 'D50', 'D9', 'A23', 'B50', 'A26', 'D48', 'E58', 'C126', 'B71', 'B51 B53 B55', 'D49', 'B5', 'B20', 'F G63', 'C62 C64', 'E24', 'C90', 'C45', 'E8', 'B101', 'D45', 'C46', 'D30', 'E121', 'D11', 'E77', 'F38', 'B3', 'D6', 'B82 B84', 'D17', 'A36', 'B102', 'B69', 'E49', 'C47', 'D28', 'E17', 'A24', 'C50', 'B42', 'C148'], dtype=object)
Cabin
즉 객실 번호는 생존 여부에 영향을 미칠 수 있을거 같습니다. 하지만 결측값이 너무 많기 때문에, 예제에서는 값들을 사용하지 않도록 하겠습니다. Name
과 Ticket
도 삭제하도록 하겠습니다. 좋은 모델을 만드는게 목적이 아니라, Kubeflow 파이프라인을 만드는것이 목적이므로, 과감히 삭제 하도록 하겠습니다.
Cabin
,Name
, Ticket
컬럼을 삭제하겠습니다.
train = train.drop(columns=['Cabin', 'Name', 'Ticket'], axis=1) test = test.drop(columns=['Cabin', 'Name', 'Ticket'], axis=1)
test
데이터의 정보도 조회해 보겠습니다.
test.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 418 entries, 0 to 417 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 418 non-null int64 1 Age 418 non-null float64 2 SibSp 418 non-null int64 3 Parch 418 non-null int64 4 Fare 417 non-null float64 5 Sex_female 418 non-null uint8 6 Sex_male 418 non-null uint8 7 Embarked_C 418 non-null uint8 8 Embarked_Q 418 non-null uint8 9 Embarked_S 418 non-null uint8 10 Pclass_1 418 non-null uint8 11 Pclass_2 418 non-null uint8 12 Pclass_3 418 non-null uint8 dtypes: float64(2), int64(3), uint8(8) memory usage: 19.7 KB
Fare
에 결측값이 있는 것을 확인할 수 있겠습니다. 평균값으로 치환하겠습니다.
In []
test['Fare'].fillna(test['Fare'].mean(), inplace=True)
각 데이터들간에 어떤 관련성이 있는지를 분석하기 위하여, 피어슨 상관 계수(Pearson correlation coefficient)를 사용하겠습니다.
In []:
corr = train.corr(method='pearson') print(corr)
정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.
PassengerId Survived Age SibSp Parch Fare \\ PassengerId 1.000000 -0.005007 0.034016 -0.057527 -0.001652 0.012658 Survived -0.005007 1.000000 -0.065915 -0.035322 0.081629 0.257307 Age 0.034016 -0.065915 1.000000 -0.233212 -0.173876 0.095674 SibSp -0.057527 -0.035322 -0.233212 1.000000 0.414838 0.159651 Parch -0.001652 0.081629 -0.173876 0.414838 1.000000 0.216225 Fare 0.012658 0.257307 0.095674 0.159651 0.216225 1.000000 Sex_female -0.042939 0.543351 -0.081785 0.114631 0.245489 0.182333 Sex_male 0.042939 -0.543351 0.081785 -0.114631 -0.245489 -0.182333 Embarked_C -0.001205 0.168240 0.030613 -0.059528 -0.011069 0.269335 Embarked_Q -0.033606 0.003650 -0.027873 -0.026354 -0.081228 -0.117216 Embarked_S 0.022148 -0.155660 -0.017186 0.070941 0.063036 -0.166603 Pclass_1 0.034303 0.285904 0.323163 -0.054582 -0.017633 0.591711 Pclass_2 -0.000086 0.093349 0.013967 -0.055932 -0.000734 -0.118557 Pclass_3 -0.029486 -0.322308 -0.289806 0.092548 0.015790 -0.413333 Sex_female Sex_male Embarked_C Embarked_Q Embarked_S \\ PassengerId -0.042939 0.042939 -0.001205 -0.033606 0.022148 Survived 0.543351 -0.543351 0.168240 0.003650 -0.155660 Age -0.081785 0.081785 0.030613 -0.027873 -0.017186 SibSp 0.114631 -0.114631 -0.059528 -0.026354 0.070941 Parch 0.245489 -0.245489 -0.011069 -0.081228 0.063036 Fare 0.182333 -0.182333 0.269335 -0.117216 -0.166603 Sex_female 1.000000 -1.000000 0.082853 0.074115 -0.125722 Sex_male -1.000000 1.000000 -0.082853 -0.074115 0.125722 Embarked_C 0.082853 -0.082853 1.000000 -0.148258 -0.778359 Embarked_Q 0.074115 -0.074115 -0.148258 1.000000 -0.496624 Embarked_S -0.125722 0.125722 -0.778359 -0.496624 1.000000 Pclass_1 0.098013 -0.098013 0.296423 -0.155342 -0.170379 Pclass_2 0.064746 -0.064746 -0.125416 -0.127301 0.192061 Pclass_3 -0.137143 0.137143 -0.153329 0.237449 -0.009511 Pclass_1 Pclass_2 Pclass_3 PassengerId 0.034303 -0.000086 -0.029486 Survived 0.285904 0.093349 -0.322308 Age 0.323163 0.013967 -0.289806 SibSp -0.054582 -0.055932 0.092548 Parch -0.017633 -0.000734 0.015790 Fare 0.591711 -0.118557 -0.413333 Sex_female 0.098013 0.064746 -0.137143 Sex_male -0.098013 -0.064746 0.137143 Embarked_C 0.296423 -0.125416 -0.153329 Embarked_Q -0.155342 -0.127301 0.237449 Embarked_S -0.170379 0.192061 -0.009511 Pclass_1 1.000000 -0.288585 -0.626738 Pclass_2 -0.288585 1.000000 -0.565210 Pclass_3 -0.626738 -0.565210 1.000000
분석 결과를 보기 좋게 그래프로 만들어 보겠습니다.
In []:
import seaborn as sns sns.set(context='paper', style='whitegrid', palette='muted', font_scale=1, color_codes=True, rc=None) sns.heatmap(corr,linewidths=.5)

In []:
sns.barplot(x=corr.Survived,y=corr.columns)

모델의 성능은 데이터의 품질에 좌우됩니다. 그래서 위의 상관 그래프나 컬럼별 통계분포 등을 파악하여 보다 많은 데이터의 정제 과정을 거쳐야 합니다. 그리고 피쳐 엔지니어링 과정을 통해서 데이터의 품질을 높여야합니다. 하지만 해당 내용들은 이 책의 범위에서 벗어나기 때문에 다루지 않겠습니다.
모델 학습 (Train)
준비한 데이터를 이용하여 모델을 학습해 보겠습니다.
먼저, 모델 학습을 위하여 데이터를 학습 세트와 테스트 세트로 나누겠습니다. sklearn
의 train_test_split()
를 사용하면, 간단히 나눌 수 있습니다.
In []:
from sklearn.model_selection import train_test_split X = train.drop('Survived', axis=1) y = train['Survived'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=30)
sklearn
의 LogisticRegression
을 사용하여, 모델을 학습 시켜 보겠습니다.
In []:
from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report model = LogisticRegression() model.fit(X_train, y_train) prediction = model.predict(X_test) cr = classification_report(y_test, prediction, output_dict=True) print('accuracy =', cr['accuracy'])
정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.
accuracy = 0.7892376681614349
이제 캐글에서 제공한 test
데이터를 가지고 예측해 보도록 하겠습니다.
pred = model.predict(test)
예측한 결과물을 가지고, 캐글에 제공할 submission.csv
파일을 생성해 보겠습니다.
submission = pd.DataFrame({'PassengerId': test['PassengerId'], 'Survived': pred}) submission.to_csv('submission.csv', index=False)
submission
을 조회해 보겠습니다.
submission.head()
정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.

생성한 submission.csv
파일을 캐글에 제출하겠습니다. kaggle
명령어를 사용하여 제출합니다. kaggle
명령어가 PATH
에 포함되어 있지 않기 때문에 전체 경로를 적어 주었습니다.
!/home/jovyan/.local/bin/kaggle competitions submit -c titanic -f submission.csv -m "Message"
캐글의 “My Submissions” 탭을 클릭하면, 제출한 내용들을 확인할 수 있습니다.

파이프라인 만들기
앞서 작성한 코드들을 바탕으로 하여 파이프라인을 구성해 보겠습니다. 각각의 단계를 컴포넌트로 구성한 다음, 파이프라인을 작성하고 실행해 보겠습니다.
파이프라인의 단계는 다음과 같습니다.
- 데이터 다운로드 : 캐글에서 데이터를 다운로드 합니다.
- 데이터 압축풀기 : 캐글에서 다운로드한 데이터의 압축을 풉니다.
- 데이터 변환 :
train.csv
과test.csv
의 데이터를 머신러닝의 학습에 사용할 수 있도록 변환합니다. - 모델 학습 : 변환된
train.csv
데이터를 이용하여 모델을 학습니다. - 예측 : 변환된
test.csv
데이터를 이용하여 예측합니다. 그리고 예측한 결과를submission.csv
파일로 저장합니다. - 캐글 제출 : 생성된
submission.csv
파일을 캐글에 제출합니다.
파이프라인의 전체적인 흐름은 다음과 같습니다.

기본 이미지 만들기
데이터 변환, 모덱 학습, 예측 컴포넌트에서 사용할 기본 이미지를 만들어 보겠습니다. 파이프라인에서도 필요한 이미지를 빌드할 수 있지만, 기본 이미지를 만들어서 사용하는게 더 효율적입니다. docker
명령어를 이용하여, 컨테이너 이미지를 빌드하기 때문에, docker
명령어가 실행 가능한 곳에서 작업을 해야합니다.
먼저 베이스 이미지 디렉토리를 만듭니다.
mkdir -p ~/workspace/base/sklearn cd ~/workspace/base/sklearn
필요한 파이썬 패키지 목록을 requirements.txt
파일로 작성합니다.
requirements.txt
scikit-learn joblib numpy pandas fire
컨테이너 이미지 빌드를 위하여 Dockerfile
파일을 작성합니다. 파이썬 패키지 목록인 requirements.txt
파일을 추가하고, 해당 파일을 이용하여 패키지를 설치합니다.
Dockerfile
FROM python:3.6-slim WORKDIR /app COPY requirements.txt /app RUN pip --no-cache-dir install -r requirements.txt
컨테이너 이미지를 빌드하고, 컨테이너 레지스트리에 푸시하기 위하여 build_image.sh
파일을 작성합니다.
build_image.sh
#!/bin/bash -e image_name=kangwoo/sklearn image_tag=0.0.1 full_image_name=${image_name}:${image_tag} base_image_tag=3.6-slim cd "$(dirname "$0")" docker build --build-arg BASE_IMAGE_TAG=$base_image_tag -t "$full_image_name" . docker push "$full_image_name"
build_image.sh
파일을 실행하여, 컨테이너 이미지를 빌드하고, 컨테이너 레지스트리에 푸시하겠습니다.
chmod +x build_image.sh ./build_image.sh
컴포넌트 만들기
캐글 데이터 다운로드 컴포넌트
캐글에서 데이터 다운로드하는 컴포넌트를 만들어 보겠습니다. docker
명령어를 이용하여, 컨테이너 이미지를 빌드하기 때문에, docker
명령어가 실행 가능한 곳에서 작업을 해야합니다.
먼저 컴포넌트 디렉토리를 만듭니다.
mkdir -p ~/workspace/components/kaggle/competitions_download mkdir -p ~/workspace/components/kaggle/competitions_download/src cd ~/workspace/components/kaggle/competitions_download
컴포넌트 디렉토리의 하위 src
디렉토리에, 컴포넌트에서 사용할 애플리케이션 코드인 [download.py](<http://download.py>)
파일을 작성합니다. kaggle
명령어를 실행하여, 데이터를 다운로드 합니다. 이미 데이터가 저장 경로에 존재하면 다운로드 하지 않습니다. 데이터를 다운로드 했는지 여부를 출력 결과값으로 반환합니다.
src/download.py
from __future__ import absolute_import, division, print_function, unicode_literals import argparse import os import subprocess from distutils.util import strtobool parser = argparse.ArgumentParser() parser.add_argument('--competition', required=True, type=str) parser.add_argument('--path', default='.', type=str) parser.add_argument('--force', default='False', type=strtobool) parser.add_argument('--downloaded', default='/tmp/outputs/downloaded', type=str) args = parser.parse_args() if not os.path.exists(args.path): os.makedirs(args.path) kaggle_args = ['kaggle', 'competitions', 'download', '--path', args.path] if args.force: kaggle_args.append('--force') kaggle_args.append(args.competition) print(kaggle_args) result = subprocess.check_output(kaggle_args, encoding='utf-8') print('result:', result) downloaded = False if 'Downloading' in result: downloaded = True print('downloaded:', downloaded) if not os.path.exists(os.path.dirname(args.downloaded)): os.makedirs(os.path.dirname(args.downloaded)) with open(args.downloaded, 'w') as writer: writer.write(str(downloaded))
컴포넌트 디렉토리에 컴포넌트 설정 정보가 있는 component.yaml
파일을 작성합니다.
component.yaml
name: Kaggle - Competitions downloader description: Download competition files inputs: - {name: competition, type: String, description: 'Competition URL suffix'} - {name: path, type: String, default: '.', description: 'Folder where file(s) will be download, defaults to current working directory'} - {name: force, type: String, default: 'False', description: 'Skip check whether local version of files is up to date, force file download'} outputs: - {name: downloaded, type: String, description: 'Downloaded'} implementation: container: image: kangwoo/kaggle-competitions-download@sha256:e0c585eaa50d880a0e0ab2245077c9ec487ffc7c5b8c910c7a88798314d6eab9 command: ['python', 'download.py'] args: [ --competition, {inputValue: competition}, --path, {inputValue: path}, --force, {inputValue: force}, --downloaded, {outputPath: downloaded}, ]
컨테이너 이미지 빌드를 위하여 Dockerfile
파일을 작성합니다. 파이썬을 기본 이미지로 사용하고 있으며, 앞서 작성한 src/download.py
파일을 추가합니다.
Dockerfile
ARG BASE_IMAGE_TAG=3.6-slim FROM python:$BASE_IMAGE_TAG RUN pip install kaggle WORKDIR /app ADD src/download.py /app/ ENTRYPOINT ['python', 'download.py']
컨테이너 이미지를 빌드하고, 컨테이너 레지스트리에 푸시하기 위하여 build_image.sh
파일을 작성합니다.
build_image.sh
#!/bin/bash -e image_name=kangwoo/kaggle-competitions-download image_tag=0.0.1 full_image_name=${image_name}:${image_tag} base_image_tag=3.6-slim cd "$(dirname "$0")" docker build --build-arg BASE_IMAGE_TAG=$base_image_tag -t "$full_image_name" . docker push "$full_image_name" # Output the strict image name (which contains the sha256 image digest) # This name can be used by the subsequent steps to refer to the exact image that was built even if another image with the same name was pushed image_name_with_digest=$(docker inspect --format="{{index .RepoDigests 0}}" "$full_image_name") strict_image_name_output_file=./versions/image_digests_for_tags/$image_tag mkdir -p "$(dirname "$strict_image_name_output_file")" echo $image_name_with_digest | tee "$strict_image_name_output_file"
build_image.sh
파일을 실행하여, 컨테이너 이미지를 빌드하고, 컨테이너 레지스트리에 푸시하겠습니다.
chmod +x build_image.sh ./build_image.sh
생성한 로컬 개발 환경의 ~/workspace/components/kaggle/competitions_downloader/component.yaml
파일을, 주피터 노트북의 ~/workspace/components/kaggle/competitions_downloader/component.yaml
로 복사합니다.
캐글 제출 컴포넌트
예측 결과를 캐글에 제출하는 컴포넌트를 만들어 보겠습니다. docker
명령어를 이용하여, 컨테이너 이미지를 빌드하기 때문에, docker
명령어가 실행 가능한 곳에서 작업을 해야합니다.
먼저 컴포넌트 디렉토리를 만듭니다.
mkdir -p ~/workspace/components/kaggle/competitions_submit
컴포넌트 디렉토리의 하위 src
디렉토리에, 컴포넌트에서 사용할 애플리케이션 코드인 submit.py
파일을 작성합니다. kaggle
명령어를 실행하여, 예측 결과가 저장된 submission.csv
파일을 캐글에 제출 합니다.
src/submit.py
from __future__ import absolute_import, division, print_function, unicode_literals import argparse import os import subprocess parser = argparse.ArgumentParser() parser.add_argument('--competition', required=True, type=str) parser.add_argument('--path', default='.', type=str) parser.add_argument('--filename', default='submission.csv', type=str) parser.add_argument('--message', default='Message', type=str) parser.add_argument('--submitted', default='/tmp/outputs/submitted', type=str) args = parser.parse_args() file = os.path.join(args.path, args.filename) kaggle_args = ['kaggle', 'competitions', 'submit', '-c', args.competition, '-f', file, '-m', args.message] print(kaggle_args) result = subprocess.check_output(kaggle_args, encoding='utf-8') print('result:', result) submitted = False if 'submitted' in result: submitted = True print('submitted:', submitted) if not os.path.exists(os.path.dirname(args.submitted)): os.makedirs(os.path.dirname(args.submitted)) with open(args.submitted, 'w') as writer: writer.write(str(submitted))
컴포넌트 디렉토리에 컴포넌트 설정 정보가 있는 component.yaml
파일을 작성합니다.
component.yaml
name: Kaggle - Competitions submitter description: Submit competition file inputs: - {name: competition, type: String, description: 'Competition URL suffix'} - {name: path, type: String, description: 'Path for upload'} - {name: filename, type: String, default: 'submission.csv', description: 'Filename for upload'} - {name: message, type: String, description: 'Message describing this submission'} outputs: - {name: submitted, type: String, description: 'Submitted'} implementation: container: image: kangwoo/kaggle-competitions-submit:0.0.1 command: ['python', 'submit.py'] args: [ --competition, {inputValue: competition}, --path, {inputValue: path}, --filename, {inputValue: filename}, --message, {inputValue: message}, --submitted, {outputPath: submitted}, ]
컨테이너 이미지 빌드를 위하여 Dockerfile
파일을 작성합니다. 파이썬을 기본 이미지로 사용하고 있으며, 앞서 작성한 src/submit.py
파일을 추가합니다.
Dockerfile
ARG BASE_IMAGE_TAG=3.6-slim FROM python:$BASE_IMAGE_TAG RUN pip install kaggle WORKDIR /app ADD src/submit.py /app/ ENTRYPOINT ['python', '/app/submit.py']
컨테이너 이미지를 빌드하고, 컨테이너 레지스트리에 푸시하기 위하여 build_image.sh
파일을 작성합니다.
build_image.sh
#!/bin/bash -e image_name=kangwoo/kaggle-competitions-submit image_tag=0.0.1 full_image_name=${image_name}:${image_tag} base_image_tag=3.6-slim cd "$(dirname "$0")" docker build --build-arg BASE_IMAGE_TAG=$base_image_tag -t "$full_image_name" . docker push "$full_image_name" # Output the strict image name (which contains the sha256 image digest) # This name can be used by the subsequent steps to refer to the exact image that was built even if another image with the same name was pushed image_name_with_digest=$(docker inspect --format="{{index .RepoDigests 0}}" "$full_image_name") strict_image_name_output_file=./versions/image_digests_for_tags/$image_tag mkdir -p "$(dirname "$strict_image_name_output_file")" echo $image_name_with_digest | tee "$strict_image_name_output_file"
build_image.sh
파일을 실행하여, 컨테이너 이미지를 빌드하고, 컨테이너 레지스트리에 푸시하겠습니다.
chmod +x build_image.sh ./build_image.sh
생성한 로컬 개발 환경의 ~/workspace/components/kaggle/competitions_submit/component.yaml
파일을, 주피터 노트북의 ~/workspace/components/kaggle/competitions_submit/component.yaml
로 복사합니다.
컴포넌트 작성하기
주피터 노트북을 생성합니다. 파일 이름은 titanic_pipeline.ipynb
입니다. 노트북의 위치는 ~/workspace/titanic
입니다. 앞으로 작성할 예제에서는 노트북이 ~/workspace/titanic
에 위치하고 있다고 가정하여, 파일 경로를 설정할 것입니다.
패키지 추가
파이프라인과 컴포넌트를 사용하기 위한 패키지를 추가합니다.
In []:
import os import kfp from kfp import dsl from kfp import onprem from kfp import components from kfp.components import func_to_container_op, InputPath, OutputPath from kubernetes.client.models import V1Volume from kubernetes.client.models import V1SecretVolumeSource from kubernetes.client.models import V1VolumeMount
캐글 데이터 다운로드 컴포넌트
캐글에서 데이터를 다운로드 하는 컴포넌트를 만들어 보겠습니다. 이 기능은 필요에 따라 다른 곳에서도 사용기 가능할거 같기 때문에, 재사용 컴포넌트로 만들겠습니다.
In []:
download_op = components.load_component_from_file('../components/kaggle/competitions_downloader/component.yaml')
데이터 압축풀기 컴포넌트
unzip
명령어를 사용하여 압축을 풀겠습니다.
In []:
def unzip_op(filename, exdir): return dsl.ContainerOp(name='Unzip', image='garthk/unzip:latest', command=['unzip', '-o', filename, '-d', exdir])
데이터 변환 컴포넌트
train.csv
과 test.csv
의 데이터를 머신러닝의 학습에 사용할 수 있도록 변환합니다. 캐글 데이터의 경로를 input_path
파라미터로 입력 받아 데이터를 변환합니다. 데이터 변환에 쓰이는 코드들은 앞에서 작성한 코드와 동일합니다. 변환할 데이터를 저장할 경로를 output_path
파라미터로 입력 받아 변환된 데이터를 저장합니다. 컨테이너 이미지를 빠르게 빌드하기 위하여, 필요한 패키지가 포함된 기본 이미지를 미리 만들어서 사용하였습니다.
In []:
def transform_op(input_path, output_path): import os import pandas as pd train = pd.read_csv(os.path.join(input_path, 'train.csv')) test = pd.read_csv(os.path.join(input_path, 'test.csv')) print(train.info()) print("*"*40) print(test.info()) train = pd.get_dummies(train, columns=['Sex', 'Embarked', 'Pclass']) test = pd.get_dummies(test, columns=['Sex', 'Embarked', 'Pclass']) survived_1_age_mean = train[(train['Survived'] == 1)]['Age'].mean() survived_0_age_mean = train[(train['Survived'] == 0)]['Age'].mean() train.loc[train['Survived'] == 1, 'Age'] = train[train['Survived'] == 1].fillna(survived_1_age_mean) train.loc[train['Survived'] == 0, 'Age'] = train[train['Survived'] == 0].fillna(survived_0_age_mean) test['Age'].fillna(test['Age'].mean(), inplace=True) train = train.drop(columns=['Cabin','Name','Ticket'], axis=1) test = test.drop(columns=['Cabin','Name','Ticket'], axis=1) test['Fare'].fillna(test['Fare'].mean(), inplace=True) if not os.path.exists(output_path): os.makedirs(output_path) train.to_csv(os.path.join(output_path, 'train.csv')) test.to_csv(os.path.join(output_path, 'test.csv')) transform_op = components.func_to_container_op(transform_op, base_image='kangwoo/sklearn:0.0.1')
모델 학습 컴포넌트
변환된 train.csv
데이터를 이용하여 모델을 학습니다. 변환된 데이터의 경로를 path
파리미터로 입력받에 데이터를 읽어옵니다. 그리고 데이터를 학습 세트와 테스트 세트로 나눈다음, 모델을 학습니다. 사용할 모델 이름은 model_name
파라미터로 입력 받게 하였습니다. 학습된 모델의 분류 결과를 classification_report.json
파일로 저장하고, 학습된 모델을 model.joblib
파일로 저장하였습니다. 각 파일든은 export_path
파라미터로 입력하 경로에 저장됩니다.
In []:
def train_op(input_path, model_name, model_path): import os import joblib import pandas as pd train = pd.read_csv(os.path.join(input_path, 'train.csv')) print(train.info()) from sklearn.model_selection import train_test_split X = train.drop('Survived', axis=1) y = train['Survived'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=30) from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report model = LogisticRegression() model.fit(X_train, y_train) prediction = model.predict(X_test) cr = classification_report(y_test, prediction, output_dict=True) output_path = os.path.join(model_path, model_name) if not os.path.exists(output_path): os.makedirs(output_path) mode_file = os.path.join(output_path, 'model.joblib') joblib.dump(model, mode_file) cr_file = os.path.join(output_path, 'classification_report.csv') cr_df = pd.DataFrame(cr).transpose() cr_df.to_csv(cr_file) train_op = components.func_to_container_op(train_op, base_image='kangwoo/sklearn:0.0.1')
예측 컴포넌트
변환된 test.csv
데이터를 이용하여 예측합니다. 그리고 예측한 결과를 submission.csv
파일로 저장합니다.
In []:
def predict_op(model_path, test_data_path, submission_path): import argparse import json import os import joblib import pandas as pd reports = {} model_names = os.listdir(model_path) for model_name in model_names: file = os.path.join(model_path, model_name, 'classification_report.csv') print(file) if os.path.isfile(file): with open(file) as csv_file: report = pd.read_csv(csv_file, index_col=0) reports[model_name] = report print('{} found'.format(len(reports))) for item in reports.items(): print('item :', item) print('{} : accuracy={}'.format(item[0], item[1].loc['accuracy']['f1-score'])) def score(x): return reports[x].loc['accuracy']['f1-score'] best_model = max(reports.keys(), key=score) print('Best model is', best_model, reports[best_model].loc['accuracy']['f1-score']) model = joblib.load(os.path.join(model_path, best_model, 'model.joblib')) print(model) test = pd.read_csv(os.path.join(test_data_path, 'test.csv')) pred = model.predict(test) submission_file = os.path.join(submission_path, 'submission.csv') if not os.path.isdir(os.path.dirname(submission_file)): os.makedirs(os.path.dirname(submission_file)) submission = pd.DataFrame({'PassengerId': test['PassengerId'], 'Survived': pred}) submission.to_csv(submission_file, index=False) print("Saved submission :", submission_file) predict_op= components.func_to_container_op(predict_op, base_image='kangwoo/sklearn:0.0.1')
캐글 제출 컴포넌트
생성된 submission.csv
파일을 캐글에 제출합니다.
In[] :
submit_op = components.load_component_from_file('../components/kaggle/competitions_submit/component.yaml')
파이프라인 생성하기
PVC 생성하기
PVC를 생성하기 위한 매니페스트를 작성합니다. 파이프라인 컴포넌트에서 이 PVC를 이용하여, 데이터를 저장하고 읽어 올 것입니다.
kaggle-pvc.yaml
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: kaggle-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 200Mi
다음은 kubeflow
네임스페이스에 PVC 리소스를 생성하는 명령어 입니다.
kubectl -n kubeflow apply -f kaggle-pvc.yaml
캐글 토큰 Secret
생성하기
캐글 API 토큰이 저장되어 있는 kaggle.json
파일을 이용하여, 쿠버네티스 Secret
리소스를 생성합니다. 생성한 Secret
리소스는 파이프라인의 “캐글에서 데이터 다운로드 컴포넌트”와 “캐글 제출 컴포넌트” 에서 사용될 것입니다.
다음은 kubeflow
네임스페이스에 kaggle-secret
이라는 리소스를 생성하는 명령어 입니다.
kubectl -n kubeflow create secret generic kaggle-secret --from-file=kaggle.json
파이프라인 작성하기
주피터 노트북으로 돌아가서 파이프라인을 작성해 보겠습니다.
In []:
@dsl.pipeline( name='Titanic pipeline', description='Titanic pipeline' ) def titanic_pipeline(): pvc_name = "kaggle-pvc" volume_name = 'pipeline' volume_mount_path = '/data' competition_name = 'titanic' kaggle_data_path = os.path.join('/data/competitions', competition_name, 'kaggle') input_data_path = os.path.join('/data/competitions', competition_name, 'input') download_task = download_op(competition=competition_name, path=kaggle_data_path)\\ .add_volume(V1Volume(name='kaggle-secret', secret=V1SecretVolumeSource(secret_name='kaggle-secret')))\\ .add_volume_mount(V1VolumeMount(name='kaggle-secret', mount_path='/root/.kaggle')) with dsl.Condition(download_task.outputs['downloaded'] == 'True'): unzip_task = unzip_op(os.path.join(kaggle_data_path, 'titanic.zip'), kaggle_data_path).after(download_task) transform_task = transform_op(input_path=kaggle_data_path, output_path=input_data_path).after(unzip_task) models = ['sklearn.linear_model.LogisticRegression', 'sklearn.linear_model.SGDClassifier', 'sklearn.naive_bayes.GaussianNB', 'sklearn.neighbors.KNeighborsClassifier', 'sklearn.tree.DecisionTreeClassifier', 'sklearn.svm.SVC', 'sklearn.ensemble.AdaBoostClassifier', 'sklearn.ensemble.RandomForestClassifier'] export_path = os.path.join('/data/competitions', competition_name, 'models') submit_path = os.path.join('/data/competitions', competition_name, 'submit') with dsl.ParallelFor(models) as model: train_task = train_op(input_data_path, model, export_path).after(transform_task) predict_task = predict_op(export_path, input_data_path, submit_path).after(train_task) submit_task = submit_op(competition='titanic', path=submit_path, message='RunId : {{workflow.uid}}')\\ .add_volume(V1Volume(name='kaggle-secret', secret=V1SecretVolumeSource(secret_name='kaggle-secret')))\\ .add_volume_mount(V1VolumeMount(name='kaggle-secret', mount_path='/root/.kaggle')).after(predict_task) steps = [download_task, unzip_task, transform_task, train_task, predict_task, submit_task] for step in steps: step.apply(onprem.mount_pvc(pvc_name, volume_name=volume_name, volume_mount_path=volume_mount_path)) if __name__ == '__main__': kfp.compiler.Compiler().compile(titanic_pipeline, 'titanic-pipeline.zip') client = kfp.Client() my_experiment = client.create_experiment(name='Kaggle Experiment') my_run = client.run_pipeline(my_experiment.id, 'Titanic Pipeline', 'titanic-pipeline.zip')

생성된 링크를 클릭하시면, 다음과 같은 화면을 확인할 수 있습니다.

파이프라인 실행이 완료되면, 예측 결과가 캐글에 제출 됩니다.
캐글의 “My Submissions” 탭을 클릭하면, 제출한 내용들을 확인할 수 있습니다.

안녕하세요~ 블로그 잘 보고 있습니다.
위의 코드를 따라서 실행하려고 하는데, train_op 함수 부분이 누락된 것 같습니다. 한번 확인해 주시면 감사하겠습니다^^
train_op(input_data_path, model, export_path).after(transform_task)
안녕하세요.
한 번 확인해 보겠습니다.
답변이 늦어 죄송합니다.
코드를 붙여 넣는 과정에서 실수가 있었나보네요.
기존 코드가 없어서 새롭게 작성하였습니다.
train_op와 predict_op가 수정되었습니다.