Skip to content

Instantly share code, notes, and snippets.

@daxiongshu
Created February 22, 2019 00:53
Show Gist options
  • Save daxiongshu/7387eea1a73f24e01edf8160e956c2f0 to your computer and use it in GitHub Desktop.
Save daxiongshu/7387eea1a73f24e01edf8160e956c2f0 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cudf version 0.5.0+18.gcdebbed\n"
]
}
],
"source": [
"import cudf as gd\n",
"import pandas as pd\n",
"import numpy as np\n",
"import math\n",
"import xgboost as xgb\n",
"import seaborn as sns\n",
"from functools import partial\n",
"from sklearn.preprocessing import LabelEncoder\n",
"from sklearn.model_selection import train_test_split\n",
"from termcolor import colored\n",
"from cudf_workaround import cudf_groupby_aggs\n",
"import matplotlib.pyplot as plt\n",
"import os\n",
"import time\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"sns.set()\n",
"print('cudf version',gd.__version__)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**This notebook demos the 8th place solution (8/1094) of Rapids.ai for the __[PLAsTiCC Astronomical Classification](https://www.kaggle.com/c/PLAsTiCC-2018/leaderboard)__. The demo shows up to 140x speedup for ETL and 25x end-to-end speedup over the CPU solution. More details can be found at our __[blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95)__** "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Table of contents\n",
"[1. Global variables](#global)<br>\n",
"[2. Functions](#func)<br>\n",
"[3. ETL & Visualizations](#etl)<br>\n",
"[4. Model training](#train)<br>\n",
"[5. Conclusions](#conclusions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"global\"></a>\n",
"## 1. Global variables "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Original data download and description __[link](https://www.kaggle.com/c/PLAsTiCC-2018/data)__**."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"PATH = '../data'\n",
"#PATH = '../lsst/input'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Tested on V100 with 32 GB GPU memory. If memory capacity is smaller, the input data will be sampled accordingly.**"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"GPU_MEMORY = 32 # GB. \n",
"#GPU_MEMORY = 16 # GB. Both 32 and 16 GB have been tested"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"TEST_ROWS = 453653104 # number of rows in test data\n",
"# no skip if your gpu has 32 GB memory\n",
"# otherwise, skip rows porportionally\n",
"OVERHEAD = 1.17 # cudf 0.5 introduces 17% memory overhead\n",
"SKIP_ROWS = int((1 - GPU_MEMORY/(32.0*OVERHEAD))*TEST_ROWS) \n",
"GPU_RUN_TIME = {}\n",
"CPU_RUN_TIME = {}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"GPU_id = 0\n",
"os.environ['CUDA_VISIBLE_DEVICES'] = str(GPU_id)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"func\"></a>\n",
"## 2. Functions"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def scatter(x,y,values,xlabel='x',ylabel='y',title=None):\n",
" colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k']\n",
" colors = np.array([colors[i] for i in values])\n",
" ps = []\n",
" bs = []\n",
" bands = ['passband_%s'%i for i in ['u', 'g', 'r', 'i', 'z','y']]\n",
" for i in sorted(np.unique(values)):\n",
" mask = values==i\n",
" if len(x[mask]):\n",
" p = plt.scatter(x[mask],y[mask],c=colors[mask])\n",
" ps.append(p)\n",
" bs.append(bands[i])\n",
" plt.legend(ps,bs,scatterpoints=1)\n",
" if title is not None:\n",
" plt.title(title)\n",
" \n",
" plt.xlim([np.min(x)-10,np.min(x)+1500])\n",
" plt.ylabel('y: %s'%ylabel)\n",
" plt.xlabel('x: %s'%xlabel)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def multi_weighted_logloss(y_true, y_preds, classes, class_weights):\n",
" \"\"\"\n",
" refactor from\n",
" @author olivier https://www.kaggle.com/ogrellier\n",
" multi logloss for PLAsTiCC challenge\n",
" \"\"\"\n",
" y_p = y_preds.reshape(y_true.shape[0], len(classes), order='F')\n",
" y_ohe = pd.get_dummies(y_true)\n",
" y_p = np.clip(a=y_p, a_min=1e-15, a_max=1 - 1e-15)\n",
" y_p_log = np.log(y_p)\n",
" y_log_ones = np.sum(y_ohe.values * y_p_log, axis=0)\n",
" nb_pos = y_ohe.sum(axis=0).values.astype(float)\n",
" class_arr = np.array([class_weights[k] for k in sorted(class_weights.keys())])\n",
" y_w = y_log_ones * class_arr / nb_pos\n",
"\n",
" loss = - np.sum(y_w) / np.sum(class_arr)\n",
" return loss\n",
"\n",
"def xgb_multi_weighted_logloss(y_predicted, y_true, classes, class_weights):\n",
" loss = multi_weighted_logloss(y_true.get_label(), y_predicted, \n",
" classes, class_weights)\n",
" return 'wloss', loss"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### CPU ETL functions "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def ravel_column_names(cols):\n",
" d0 = cols.get_level_values(0)\n",
" d1 = cols.get_level_values(1)\n",
" return [\"%s_%s\"%(i,j) for i,j in zip(d0,d1)]\n",
" \n",
"def etl_cpu(df,df_meta):\n",
" df['flux_ratio_sq'] = np.power(df['flux'] / df['flux_err'], 2.0)\n",
" df['flux_by_flux_ratio_sq'] = df['flux'] * df['flux_ratio_sq']\n",
" aggs = {\n",
" 'passband': ['mean'], \n",
" 'flux': ['min', 'max', 'mean'],\n",
" 'flux_err': ['min', 'max', 'mean'],\n",
" 'detected': ['mean'],\n",
" 'mjd':['max','min'],\n",
" 'flux_ratio_sq':['sum'],\n",
" 'flux_by_flux_ratio_sq':['sum'],\n",
" }\n",
" agg_df = df.groupby('object_id').agg(aggs)\n",
" agg_df.columns = ravel_column_names(agg_df.columns)\n",
" \n",
" agg_df['flux_diff'] = agg_df['flux_max'] - agg_df['flux_min']\n",
" agg_df['flux_dif2'] = (agg_df['flux_max'] - agg_df['flux_min']) / agg_df['flux_mean']\n",
" agg_df['flux_w_mean'] = agg_df['flux_by_flux_ratio_sq_sum'] / agg_df['flux_ratio_sq_sum']\n",
" agg_df['flux_dif3'] = (agg_df['flux_max'] - agg_df['flux_min']) / agg_df['flux_w_mean']\n",
" \n",
" agg_df['mjd_diff'] = agg_df['mjd_max'] - agg_df['mjd_min']\n",
" agg_df = agg_df.drop(['mjd_max','mjd_min'],axis=1)\n",
" \n",
" agg_df = agg_df.reset_index()\n",
" df_meta = df_meta.drop(['ra','decl','gal_l','gal_b'],axis=1)\n",
" df_meta = df_meta.merge(agg_df,on='object_id',how='left')\n",
" return df_meta"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### GPU ETL functions "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# To save GPU memory, we drop the column as soon as it is done with groupby\n",
"# this hits performance a little but avoids GPU OOM.\n",
"def groupby_aggs(df,aggs,col):\n",
" res = None\n",
" for i,j in aggs.items():\n",
" for k in j:\n",
" #print(i,k)\n",
" tmp = df.groupby(col).agg({i:[k]})\n",
" if res is None:\n",
" res = tmp\n",
" else:\n",
" res = res.merge(tmp,on=[col],how='left')\n",
" df.drop_column(i)\n",
" return res\n",
"\n",
"def etl_gpu(df,df_meta):\n",
" aggs = {\n",
" 'passband': ['mean'], \n",
" 'detected': ['mean'],\n",
" 'mjd':['max','min'],\n",
" }\n",
" agg_df = groupby_aggs(df,aggs,'object_id')\n",
" # at this step, columns ['passband','detected','mjd'] are deleted \n",
" \n",
" df['flux_ratio_sq'] = df['flux'] / df['flux_err']\n",
" df['flux_ratio_sq'] = df['flux_ratio_sq'].applymap(lambda x: math.pow(x,2))\n",
" df['flux_by_flux_ratio_sq'] = df['flux'] * df['flux_ratio_sq']\n",
" \n",
" aggs2 = {\n",
" 'flux_ratio_sq':['sum'],\n",
" 'flux_by_flux_ratio_sq':['sum'],\n",
" 'flux': ['min', 'max', 'mean'],\n",
" 'flux_err': ['min', 'max', 'mean'],\n",
" }\n",
" agg_df2 = groupby_aggs(df,aggs2,'object_id')\n",
" agg_df = agg_df.merge(agg_df2,on=['object_id'],how='left')\n",
" del agg_df2\n",
"\n",
" agg_df['flux_diff'] = agg_df['max_flux'] - agg_df['min_flux']\n",
" agg_df['flux_dif2'] = (agg_df['max_flux'] - agg_df['min_flux']) / agg_df['mean_flux']\n",
" agg_df['flux_w_mean'] = agg_df['sum_flux_by_flux_ratio_sq'] / agg_df['sum_flux_ratio_sq']\n",
" agg_df['flux_dif3'] = (agg_df['max_flux'] - agg_df['min_flux']) / agg_df['flux_w_mean']\n",
" \n",
" agg_df['mjd_diff'] = agg_df['max_mjd'] - agg_df['min_mjd']\n",
" agg_df.drop_column('max_mjd')\n",
" agg_df.drop_column('min_mjd')\n",
" \n",
" for col in ['ra','decl','gal_l','gal_b']:\n",
" df_meta.drop_column(col)\n",
" \n",
" df_meta = df_meta.merge(agg_df,on=['object_id'],how='left')\n",
" return df_meta"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"etl\"></a>\n",
"## 3. ETL & Visualizations"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Load data for ETL part 1\n",
"**GPU load data**"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 14.7 s, sys: 5.96 s, total: 20.6 s\n",
"Wall time: 21.2 s\n"
]
}
],
"source": [
"%%time\n",
"start = time.time()\n",
"step = 'load data part1'\n",
"ts_cols = ['object_id', 'mjd', 'passband', 'flux', 'flux_err', 'detected']\n",
"ts_dtypes = ['int32', 'float32', 'int32', 'float32','float32','int32']\n",
"\n",
"train_gd = gd.read_csv('%s/training_set.csv'%PATH,\n",
" names=ts_cols,dtype=ts_dtypes,skiprows=1)\n",
"test_gd = gd.read_csv('%s/test_set.csv'%PATH,\n",
" names=ts_cols,dtype=ts_dtypes,skiprows=1+SKIP_ROWS) # skip the header\n",
"GPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**CPU load data**"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 3min 59s, sys: 54.8 s, total: 4min 54s\n",
"Wall time: 3min 49s\n"
]
}
],
"source": [
"%%time\n",
"start = time.time()\n",
"train = pd.read_csv('%s/training_set.csv'%PATH)\n",
"test = pd.read_csv('%s/test_set.csv'%PATH,skiprows=range(1,1+SKIP_ROWS))\n",
"CPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[32mwe achieve 10.829 speedup for load data part1.\u001b[0m\n"
]
}
],
"source": [
"speedup = CPU_RUN_TIME[step]/GPU_RUN_TIME[step]\n",
"line = \"we achieve %.3f speedup for %s.\"%(speedup,step)\n",
"print(colored(line,'green'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Visualizations"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"oid = 615\n",
"mask = train.object_id== oid\n",
"scatter(train.loc[mask,'mjd'].values,\n",
" train.loc[mask,'flux'].values,\n",
" values=train.loc[mask,'passband'].values,\n",
" xlabel='time',ylabel='flux',title='object %d class 42'%oid)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ETL part 1 with 100x speedup"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 8 ms, sys: 12 ms, total: 20 ms\n",
"Wall time: 34.9 ms\n"
]
}
],
"source": [
"%%time\n",
"# to save memory, we need to move dataframe to cpu and only keep the columns we need\n",
"test_gd = test_gd[['object_id','flux']]\n",
"train_gd = train_gd[['object_id','flux']]"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 3.8 s, sys: 936 ms, total: 4.73 s\n",
"Wall time: 5.41 s\n"
]
}
],
"source": [
"%%time\n",
"# GPU\n",
"step = 'ETL part1'\n",
"start = time.time()\n",
"aggs = {'flux':['skew']}\n",
"test_gd = cudf_groupby_aggs(test_gd,group_id_col='object_id',aggs=aggs)\n",
"train_gd = cudf_groupby_aggs(train_gd,group_id_col='object_id',aggs=aggs)\n",
"GPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 12min 50s, sys: 35.8 s, total: 13min 26s\n",
"Wall time: 13min 10s\n"
]
}
],
"source": [
"%%time\n",
"# CPU\n",
"start = time.time()\n",
"test = test.groupby('object_id').agg(aggs)\n",
"train = train.groupby('object_id').agg(aggs)\n",
"CPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[32mwe achieve 146.110 speedup for ETL part1.\u001b[0m\n"
]
}
],
"source": [
"speedup = CPU_RUN_TIME[step]/GPU_RUN_TIME[step]\n",
"line = \"we achieve %.3f speedup for %s.\"%(speedup,step)\n",
"print(colored(line,'green'))"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 96 ms, sys: 16 ms, total: 112 ms\n",
"Wall time: 114 ms\n"
]
}
],
"source": [
"%%time\n",
"test_gd = test_gd.sort_values(by='object_id')\n",
"train_gd = train_gd.sort_values(by='object_id')"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 6.05 s, sys: 200 ms, total: 6.25 s\n",
"Wall time: 157 ms\n"
]
}
],
"source": [
"%%time\n",
"test.columns = ['skew_flux']\n",
"test = test.reset_index()\n",
"test = test.sort_values(by='object_id')\n",
"train.columns = ['skew_flux']\n",
"train = train.reset_index()\n",
"train = train.sort_values(by='object_id')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Evaluation of correctness of ETL**"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3029740 3029740\n"
]
}
],
"source": [
"print(len(test),len(test_gd))"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"test\n",
"object_id, rmse 0.000000\n",
"skew_flux, rmse 0.000002\n",
"train\n",
"object_id, rmse 0.000000\n",
"skew_flux, rmse 0.000006\n"
]
}
],
"source": [
"# RMSE: Root mean square error\n",
"def rmse(a,b):\n",
" return np.mean((a-b)**2)**0.5\n",
"print('test')\n",
"for col in test.columns:\n",
" if col in test_gd.columns:\n",
" print(\"%s, rmse %.6f\"%(col,rmse(test[col].values,test_gd[col].to_pandas().values)))\n",
"print('train')\n",
"for col in train.columns:\n",
" if col in train_gd.columns:\n",
" print(\"%s, rmse %.6f\"%(col,rmse(train[col].values,train_gd[col].to_pandas().values)))"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3029740 3029740\n"
]
}
],
"source": [
"# Rename the variables\n",
"test_flux_skew_gd = test_gd\n",
"test_flux_skew = test\n",
"train_flux_skew_gd = train_gd\n",
"train_flux_skew = train\n",
"print(len(test_gd),len(test))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Load data for the ETL part 2 with 11x speedup"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 20 s, sys: 5.26 s, total: 25.3 s\n",
"Wall time: 19.6 s\n"
]
}
],
"source": [
"%%time\n",
"# read data on gpu\n",
"step = 'load data part2'\n",
"start = time.time()\n",
"ts_cols = ['object_id', 'mjd', 'passband', 'flux', 'flux_err', 'detected']\n",
"ts_dtypes = ['int32', 'float32', 'int32', 'float32','float32','int32']\n",
"\n",
"test_gd = gd.read_csv('%s/test_set.csv'%PATH,\n",
" names=ts_cols,dtype=ts_dtypes,skiprows=1+SKIP_ROWS) # skip the header\n",
"train_gd = gd.read_csv('%s/training_set.csv'%PATH,\n",
" names=ts_cols,dtype=ts_dtypes,skiprows=1)\n",
"\n",
"cols = ['object_id', 'ra', 'decl', 'gal_l', 'gal_b', 'ddf',\n",
" 'hostgal_specz', 'hostgal_photoz', 'hostgal_photoz_err', \n",
" 'distmod','mwebv', 'target']\n",
"dtypes = ['int32']+['float32']*4+['int32']+['float32']*5+['int32']\n",
"\n",
"train_meta_gd = gd.read_csv('%s/training_set_metadata.csv'%PATH,\n",
" names=cols,dtype=dtypes,skiprows=1)\n",
"del cols[-1],dtypes[-1]\n",
"test_meta_gd = gd.read_csv('%s/test_set_metadata.csv'%PATH,\n",
" names=cols,dtype=dtypes,skiprows=1)\n",
"GPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 4min, sys: 1min 3s, total: 5min 4s\n",
"Wall time: 3min 58s\n"
]
}
],
"source": [
"%%time\n",
"# read data on cpu\n",
"start = time.time()\n",
"test = pd.read_csv('%s/test_set.csv'%PATH,skiprows=range(1,1+SKIP_ROWS))\n",
"test_meta = pd.read_csv('%s/test_set_metadata.csv'%PATH)\n",
"\n",
"train = pd.read_csv('%s/training_set.csv'%PATH)\n",
"train_meta = pd.read_csv('%s/training_set_metadata.csv'%PATH)\n",
"CPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[32mwe achieve 12.143 speedup for load data part2.\u001b[0m\n"
]
}
],
"source": [
"speedup = CPU_RUN_TIME[step]/GPU_RUN_TIME[step]\n",
"line = \"we achieve %.3f speedup for %s.\"%(speedup,step)\n",
"print(colored(line,'green'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### ETL part2 with 9x ~ 12x speedup "
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 9.66 s, sys: 2.2 s, total: 11.9 s\n",
"Wall time: 9.56 s\n"
]
}
],
"source": [
"%%time\n",
"# GPU\n",
"start = time.time()\n",
"step = 'ETL part2'\n",
"train_final_gd = etl_gpu(train_gd,train_meta_gd)\n",
"train_final_gd = train_final_gd.merge(train_flux_skew_gd,on=['object_id'],how='left')\n",
"test_final_gd = etl_gpu(test_gd,test_meta_gd)\n",
"del test_gd,test_meta_gd\n",
"test_final_gd = test_final_gd.merge(test_flux_skew_gd,on=['object_id'],how='left')\n",
"GPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 4min 42s, sys: 2min 21s, total: 7min 4s\n",
"Wall time: 2min 20s\n"
]
}
],
"source": [
"%%time\n",
"#CPU\n",
"start = time.time()\n",
"train_final = etl_cpu(train,train_meta)\n",
"train_final = train_final.merge(train_flux_skew,on=['object_id'],how='left')\n",
"test_final = etl_cpu(test,test_meta)\n",
"test_final = test_final.merge(test_flux_skew,on=['object_id'],how='left')\n",
"CPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[32mwe achieve 14.712 speedup for ETL part2.\u001b[0m\n"
]
}
],
"source": [
"speedup = CPU_RUN_TIME[step]/GPU_RUN_TIME[step]\n",
"line = \"we achieve %.3f speedup for %s.\"%(speedup,step)\n",
"print(colored(line,'green'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"train\"></a>\n",
"## 4. Model training"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### train and validation with 5x speedup"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[ 6 15 16 42 52 53 62 64 65 67 88 90 92 95]\n"
]
}
],
"source": [
"# CPU\n",
"X = train_final.drop(['object_id','target'],axis=1).values\n",
"y = train_final['target']\n",
"Xt = test_final.drop(['object_id'],axis=1).values\n",
"assert X.shape[1] == Xt.shape[1]\n",
"classes = sorted(y.unique()) \n",
"# Taken from Giba's topic : https://www.kaggle.com/titericz\n",
"# https://www.kaggle.com/c/PLAsTiCC-2018/discussion/67194\n",
"# with Kyle Boone's post https://www.kaggle.com/kyleboone\n",
"class_weights = {c: 1 for c in classes}\n",
"class_weights.update({c:2 for c in [64, 15]})\n",
"\n",
"lbl = LabelEncoder()\n",
"y = lbl.fit_transform(y)\n",
"print(lbl.classes_)\n",
"\n",
"X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,stratify=y, random_state=126)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [],
"source": [
"cpu_params = {\n",
" 'objective': 'multi:softprob', \n",
" 'tree_method': 'hist', \n",
" 'nthread': 16, \n",
" 'num_class':14,\n",
" 'max_depth': 7, \n",
" 'silent':1,\n",
" 'subsample':0.7,\n",
" 'colsample_bytree': 0.7,}"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [],
"source": [
"func_loss = partial(xgb_multi_weighted_logloss, \n",
" classes=classes, \n",
" class_weights=class_weights)"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[06:09:45] Tree method is selected to be 'hist', which uses a single updater grow_fast_histmaker.\n",
"[0]\teval-merror:0.356688\ttrain-merror:0.276936\teval-wloss:2.04549\ttrain-wloss:1.8956\n",
"Multiple eval metrics have been passed: 'train-wloss' will be used for early stopping.\n",
"\n",
"Will train until train-wloss hasn't improved in 10 rounds.\n",
"[59]\teval-merror:0.284076\ttrain-merror:0.000991\teval-wloss:1.21854\ttrain-wloss:0.091642\n",
"\u001b[32mvalidation loss 1.2185\u001b[0m\n",
"CPU times: user 6min 38s, sys: 3.27 s, total: 6min 41s\n",
"Wall time: 27.1 s\n"
]
}
],
"source": [
"%%time\n",
"start = time.time()\n",
"step = 'training'\n",
"dtrain = xgb.DMatrix(data=X_train, label=y_train)\n",
"dvalid = xgb.DMatrix(data=X_test, label=y_test)\n",
"dtest = xgb.DMatrix(data=Xt)\n",
"watchlist = [(dvalid, 'eval'), (dtrain, 'train')]\n",
"clf = xgb.train(cpu_params, dtrain=dtrain,\n",
" num_boost_round=60,evals=watchlist,\n",
" feval=func_loss,early_stopping_rounds=10,\n",
" verbose_eval=1000)\n",
"yp = clf.predict(dvalid)\n",
"cpu_loss = multi_weighted_logloss(y_test, yp, classes, class_weights)\n",
"ysub = clf.predict(dtest)\n",
"line = 'validation loss %.4f'%cpu_loss\n",
"print(colored(line,'green'))\n",
"CPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [],
"source": [
"# GPU\n",
"y = train_final_gd['target'].to_array()\n",
"y = lbl.fit_transform(y)\n",
"cols = [i for i in test_final_gd.columns if i not in ['object_id','target']]\n",
"for col in cols:\n",
" train_final_gd[col] = train_final_gd[col].fillna(0).astype('float32')\n",
"\n",
"for col in cols:\n",
" test_final_gd[col] = test_final_gd[col].fillna(0).astype('float32')"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [],
"source": [
"X = train_final_gd[cols].as_matrix()\n",
"Xt = test_final_gd[cols].as_matrix()"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [],
"source": [
"X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,stratify=y, random_state=126)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [],
"source": [
"# GPU\n",
"gpu_params = cpu_params.copy()\n",
"gpu_params.update({'objective': 'multi:softprob',\n",
" 'tree_method': 'gpu_hist', \n",
" })"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0]\teval-merror:0.340127\ttrain-merror:0.296333\teval-wloss:2.01898\ttrain-wloss:1.89141\n",
"Multiple eval metrics have been passed: 'train-wloss' will be used for early stopping.\n",
"\n",
"Will train until train-wloss hasn't improved in 10 rounds.\n",
"[59]\teval-merror:0.280255\ttrain-merror:0.002407\teval-wloss:1.24705\ttrain-wloss:0.099147\n",
"\u001b[32mvalidation loss 1.2471\u001b[0m\n",
"CPU times: user 1min 21s, sys: 1.96 s, total: 1min 23s\n",
"Wall time: 6.75 s\n"
]
}
],
"source": [
"%%time\n",
"start = time.time()\n",
"dtrain = xgb.DMatrix(data=X_train, label=y_train)\n",
"dvalid = xgb.DMatrix(data=X_test, label=y_test)\n",
"dtest = xgb.DMatrix(data=Xt)\n",
"watchlist = [(dvalid, 'eval'), (dtrain, 'train')]\n",
"clf = xgb.train(gpu_params, dtrain=dtrain,\n",
" num_boost_round=60,evals=watchlist,\n",
" feval=func_loss,early_stopping_rounds=10,\n",
" verbose_eval=1000)\n",
"yp = clf.predict(dvalid)\n",
"gpu_loss = multi_weighted_logloss(y_test, yp, classes, class_weights)\n",
"ysub = clf.predict(dtest)\n",
"line = 'validation loss %.4f'%gpu_loss\n",
"print(colored(line,'green'))\n",
"GPU_RUN_TIME[step] = time.time() - start"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[32mwe achieve 4.022 speedup for training.\u001b[0m\n"
]
}
],
"source": [
"speedup = CPU_RUN_TIME[step]/GPU_RUN_TIME[step]\n",
"line = \"we achieve %.3f speedup for %s.\"%(speedup,step)\n",
"print(colored(line,'green'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a id=\"conclusions\"></a>\n",
"## 5. Conclustions"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Multiclassification Loss (lower the better):\n",
"CPU: 1.2185 GPU: 1.2471\n"
]
}
],
"source": [
"print(\"Multiclassification Loss (lower the better):\")\n",
"print(\"CPU: %.4f GPU: %.4f\"%(cpu_loss,gpu_loss))"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'load data part1': 229.86804676055908,\n",
" 'ETL part1': 790.7703940868378,\n",
" 'load data part2': 238.17845582962036,\n",
" 'ETL part2': 140.68488955497742,\n",
" 'training': 27.127089023590088}"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"CPU_RUN_TIME"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'load data part1': 21.226726055145264,\n",
" 'ETL part1': 5.412163972854614,\n",
" 'load data part2': 19.614619255065918,\n",
" 'ETL part2': 9.562284231185913,\n",
" 'training': 6.745010137557983}"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"GPU_RUN_TIME"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x7f854ff70668>"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 1440x360 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"steps = ['load data part1','ETL part1','load data part2','ETL part2','training']\n",
"GPU_RUN_TIME['Overall'] = sum([GPU_RUN_TIME[i] for i in steps])\n",
"CPU_RUN_TIME['Overall'] = sum([CPU_RUN_TIME[i] for i in steps])\n",
"steps.append('Overall')\n",
"speedup = [CPU_RUN_TIME[i]/GPU_RUN_TIME[i] for i in steps]\n",
"df = pd.DataFrame({'steps':steps, 'speedup':speedup})\n",
"df.plot.bar(x='steps', y='speedup', rot=0, figsize=(20,5), fontsize=15, title='GPU Speedup')"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x7f854ff5fb00>"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 1440x360 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"gpu_time = [GPU_RUN_TIME[i] for i in steps]\n",
"cpu_time = [CPU_RUN_TIME[i] for i in steps]\n",
"df = pd.DataFrame({'GPU': gpu_time,'CPU': cpu_time}, index=steps)\n",
"df.plot.bar(rot=0,figsize=(20,5), fontsize=15, title='Running time: seconds')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**The rapids solution achieves up to 140x speedup for ETL and 25x end-to-end speedup over the CPU solution with comparable accuracy.**"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@holyglenn
Copy link

holyglenn commented Aug 5, 2019

Hi Thanks for sharing the solution! Congratulations on achieving top 1%! I am very interested in Rapids. Could you include more details on the hardware spec on the 140X speedup number? I can infer that the GPU is one Tesla V100 with potentially 32GB pf GDRAM, what's the CPU? daxiongshu

@holyglenn
Copy link

holyglenn commented Aug 12, 2019

@daxiongshu Could you help answer the question? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment