{"cells":[{"metadata":{},"cell_type":"markdown","source":"\n**Quick Draw** 게임을 해보니 제한 시간 안에 그림을 그리면 AI가 \"이건가요?\" \"저건가요?\" 말하다가 \"아 알겠어요 이건 만리장성입니다.\" 이런 게임이다. 사실 엄청나게 놀랬다. 완성된 그림을 주는 게 아니라 한 획, 한 획 그리는 와중에 정답을 말하는 AI의 성능이 대단하다고 생각했다. class가 340개라는 한정이 있지만 모든 사람이 동그라미조차도 같게 그리지 않으므로 만리장성을 맞춘 건 대단하다고 생각한다.\n\n데이터셋을 받아서 구조 분석을 하는 데에는 Getting Started{[image-Based CNN](https://www.kaggle.com/jpmiller/image-based-cnn),[Quick Draw! - Simple EDA](https://www.kaggle.com/dimitreoliveira/quick-draw-simple-eda)} 커널을 참고했다. 나의 예상과는 다르게 인풋 데이터가 이미지가 아닌 선들의 좌표 배열이었다. 이 좌표를 이용해서 다시 이미지로 변환한 다음 input으로 사용할 예정이다. 이미지를 분류하는 데는 CNN이 성능이 가장 좋으니까 CNN을 포커스로 잡고 진행하였다.\n\n데이터가 선 1, 선 2, 선 3 이런 식으로 시간의 속성이 들어가서 LSTM을 적용해도 괜찮을 것 같지만 자신 있는 CNN으로 과제를 진행하고 시간이 남는다면 바꿔보는 것도 괜찮을 것 같다.\n\n***"},{"metadata":{},"cell_type":"markdown","source":"# 1차 사용 방법 \n\n**CNN**<br>\n이미지의 데이터가 많은 상황에서 CNN은 최고의 성능을 낸다. \n\n**keras**<br>\n원래 tensorflow를 사용했었지만, 카글에서 keras를 처음 사용해봤다.<br>\ninput, kernel, output을 블록으로 설명한 부분이 인상 깊었고 tensorflow 기반으로 만들어져서 그런지 습득하고 사용하는데 많은 부담감은 없었다.<br>\n코드가 훨씬 깔끔하고 사용하기 쉽지만 keras가 tensorflow를 커버할 수 없다고 한다. 이는 좀 더 사용해봐야 알 것 같다.\n\n***\n\n# 2차 사용 방법\n\n**배치 사이즈와 에폭수를 늘려보았다.** \n\n- batch_size = 32, epochs = 22 <br> \n Private Score 0.60357 <br>\n Public Score 0.60229\n\n- batch_size = 128, epochs = 50 <br>\nPrivate Score 0.62959 <br>\nPublic Score 0.63262\n\n- batch_size = 128, epochs = 100 <br>\nPrivate Score 0.62893 <br>\nPublic Score 0.62818\n\n하이파라미터에 의존하는 것보단 다른 방법을 생각해봐야겠다.\n\n***"},{"metadata":{},"cell_type":"markdown","source":"\n# 3차 사용 방법 \n\n2차 사용 방법은 '전체 데이터셋을 한 번에 읽어오는 방법'이다. 최종적으로 (680000, 1025) 배열을 만들었다. <br>\nkeras 에서 model.fit_generator() 함수를 보고 스텝 바이 스텝으로 data generator를 만드는 것을 적용해보았다. \n\n==> 실패했다. <br>\n왜 실패한 건지 도무지 모르겠다. 에폭이 증가하지 않는다. <br>\n[Fork of Quick_Draw_FAIL](https://www.kaggle.com/rookiebox/fork-of-quick-draw-cnn-fail)커널에 기록해놨다.<br>\n모델의 input의 shape와 데이터의 shape를 맞춰주면 될 것 같았지만, 실행이 되지 않았다. 에러 메세지도 출력되지 않고 어느 순간 CPU의 점유율이 0%로 떨어졌다.\n\n***\n\n# 4차 사용 방법\n\n클래스 마다(.csv) 2500개의 값 중 (recognized == True) 을 만족하는 2000개의 값만 사용한다. <br>\n이를 무작정 10000 값으로 늘려봤다. 하지만 이 많은 데이터를 한 번에 로드하는 것은 메모리가 버티지 못해 문제가 생겼고(중간에 커널의 연결이 계속 off 되었다)<br>\n'좌표를 이용해서 다시 이미지로 변환'하는 과정이 생각보다 너무 오래 걸려서 다른 방법을 생각해봐야 겠다.\n\n기존 데이터셋의 [None, 32, 32, 1] 배열을 [None, 80, 2]로 바꿔보았다. <br>\n다른 코드를 살펴보니 x, y 값이 차례로 있다고 한다. 연속된 좌표의 패턴을 학습해보자.\n단, 이렇게 input 데이터의 shape를 바꾸면 기존에 사용했던 모델을 변경해야 한다.\n\n==> 성공했다. <br>\n한 클래스당 사용하는 값의 수를 2000에서 10000으로 5배 늘렸다. <br>\n기존 [32,32] shape에서 [80,2] shape로 줄이니 연산속도가 더 빨랐고 더 많은 값을 사용할 수 있었다. <br>\n기존에 사용했던 2D Convolution을 1D Convolution 모델로 변경하고 돌려보았다.\n\n- epochs = 3 <br> \n Private Score 0.71686<br>\n Public Score 0.71671\n- epochs = 10 <br> \n Private Score 0.76240 <br>\n Public Score 0.75738\n- epochs = 30 <br> \n Private Score <br>\n Public Score \n 30에서 강제 새로고침을 당했다...\n- epochs = 25 <br> \n Private Score 0.77515<br>\n Public Score 0.77511\n***\n\n아쉬운 점 <br>\nkeras의 EarlyStopping 함수를 사용해보고 싶었는데 callback으로 넣어주면 자꾸 에러가 생겨 진행이 안 돼서 뺐다. 그래서 어느 시점에 epoch을 멈춰야하는지 일일이 돌려봐야 했다. \n\n***"},{"metadata":{},"cell_type":"markdown","source":"## training data 구조 분석\n'Getting Started' kernel을 참고했다.<br>\n- glob 폴데에 있는 모든 파일 접근해서 list 형태로 변환\n- tqdm for문의 상태바 보여줌"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport re\nfrom glob import glob\nfrom tqdm import tqdm\nimport numpy as np\nimport pandas as pd\nimport ast\nimport matplotlib.pyplot as plt\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n"},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"fnames = glob('../input/train_simplified/*.csv') #<class 'list'>\ncnames = ['countrycode', 'drawing', 'key_id', 'recognized', 'timestamp', 'word']\ndrawlist = []\nfor f in fnames[0:6]: # num of word : 5\n    first = pd.read_csv(f, nrows=10) # make sure we get a recognized drawing\n    first = first[first.recognized==True].head(2) # top head 2 get \n    drawlist.append(first)\ndraw_df = pd.DataFrame(np.concatenate(drawlist), columns=cnames) # <class 'pandas.core.frame.DataFrame'>\ndraw_df","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"그림 데이터는 아래와 같이 숫자들의 배열로 저장한다. \n```\ndrawing.values = [[선1점1, 선1점2, 선1점3, ... 선1점n], [선2점1, 선2점2, 선2점3, ... 선2점n], ..., [선i점1, 선i점2, 선i점3, ... 선i점n]]\n```"},{"metadata":{"trusted":true},"cell_type":"code","source":"draw_df.drawing.values[0]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"evens = range(0,11,2)\nodds = range(1,12, 2)\n# We have drawing images, 2 per label, consecutively\ndf1 = draw_df[draw_df.index.isin(evens)]\ndf2 = draw_df[draw_df.index.isin(odds)]\n\nexample1s = [ast.literal_eval(pts) for pts in df1.drawing.values]\nexample2s = [ast.literal_eval(pts) for pts in df2.drawing.values]\nlabels = df2.word.tolist()\n\nfor i, example in enumerate(example1s):\n    plt.figure(figsize=(6,3))\n    \n    for x,y in example:\n        plt.subplot(1,2,1)\n        plt.plot(x, y, marker='.')\n        plt.axis('off')\n\n    for x,y, in example2s[i]:\n        plt.subplot(1,2,2)\n        plt.plot(x, y, marker='.')\n        plt.axis('off')\n        label = labels[i]\n        plt.title(label, fontsize=10)\n\n    plt.show()  ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%reset -f","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 모델 만들기\n\n이제 CNN 모델을 만들어보자.<br>\ncsv의 데이터에서 x, y점의 좌표를 읽어와 모델의 input으로 주기 위한 전처리 작업이 필요하다.\n\n- Dask 패키지는 Pandas 데이터프레임 형식으로 빅데이터를 처리하기 위한 파이썬 패키지이다.\n\n### **코드의 주석은 1차 사용 방법의 주석입니다. **"},{"metadata":{"trusted":true},"cell_type":"code","source":"\nimport os\nfrom glob import glob\nimport re\nimport ast\nimport numpy as np \nimport pandas as pd\nfrom PIL import Image, ImageDraw \nfrom tqdm import tqdm\nfrom dask import bag\nimport json\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras.models import Sequential\nfrom keras.models import Model\nfrom tensorflow.keras.layers import Dense, Dropout, Flatten\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D\nfrom tensorflow.keras.metrics import top_k_categorical_accuracy\nfrom keras.layers import Input, Conv1D, Dense, Dropout, BatchNormalization, Flatten, MaxPool1D\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> The data are also available in a zip file (automatically extracted inside the Kernels environment).\n> 카글은 대단하다."},{"metadata":{"trusted":true},"cell_type":"code","source":"path = '../input/train_simplified/'\nclassfiles = os.listdir(path)\n\nnumstonames = {i: v[:-4].replace(\" \", \"_\") for i, v in enumerate(classfiles)} # sleeping bag -> sleeping_bag\nfiles = [os.path.join(path, file) for i, file in enumerate(classfiles)]\nword_mapping = {file.split('/')[-1][:-4]:i for i, file in enumerate(files)}\n\nnum_classes = len(files)    #340\nimheight, imwidth = 32, 32 # size of an image\nims_per_class = 2000  #max? # in the code above and above, there existed more than 100 thousand images per class(/label)\nsequence_length = 80","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 1. TRAIN 데이터 만들기 \n\n한 class 마다 (class 개수는 340) sleeping bag.csv 에서 15000 읽어온다. (단, 필요한 col인 'drawing', 'recognized'만 뽑아온다)<br>\n이중 'recognized' 가 'True' 인 애들 탑 10000 개를 뽑는다.\n\n**배열 X**<br>\nsequence of x- 와 y-coordinates 의 패턴을 X 배열에 넣는다. [10000, 80, 2]<br>\nX 배열의 차원을 줄여 [10000, 160] 배열을 만든다.\n\n**배열 y**<br>\ny 배열에는 class를 구별할 수 있는 index 값을 넣어 [10000, 1] 배열을 만든다.\n\nX와 y 배열을 합쳐준다. [10000, 161]\n\n**배열 train_grand**<br>\n[10000, 161] 을 'train_grand'에 append 해준다.<br>\nshape을 변경해준다. [340, 10000, 161] -> [3400000, 161]\n\n\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_grand= []\n\nclass_paths = glob('../input/train_simplified/*.csv')\n\ndf = []\n\nfor i,c in enumerate(tqdm(class_paths[0: num_classes])):\n    train = pd.read_csv(c, usecols=['drawing', 'recognized'], nrows=15000) # [2500 rows x 2 columns]\n    train = train[train.recognized == True].head(10000) # use data only recognized == True -> [2000 rows x 2 columns]\n    \n    X = []\n    for values in train.drawing.values:\n        image = json.loads(values)\n        strokes = []\n        for x_axis, y_axis in image:\n            strokes.extend(list(zip(x_axis, y_axis)))\n        strokes = np.array(strokes)\n        pad = np.zeros((sequence_length, 2))\n        if sequence_length>strokes.shape[0]:\n            pad[:strokes.shape[0],:] = strokes\n        else:\n            pad = strokes[:sequence_length, :]\n        X.append(pad)\n    X = np.array(X)\n    y = np.full((train.shape[0], 1), i)\n    X = np.reshape(X, (10000, -1))\n    X = np.concatenate((y, X), axis=1)\n    train_grand.append(X)\n   \n    \n#     trainarray = np.reshape(trainarray, (ims_per_class, -1)) # (2000, 1024)\n#     labelarray = np.full((train.shape[0], 1), i) # (2000, 1) fill with 'i' 0~339\n#     trainarray = np.concatenate((labelarray, trainarray), axis=1) # (2000, 1025)\n#     train_grand.append(trainarray)\n\ntrain_grand = np.array([train_grand.pop() for i in np.arange(num_classes)]) \nprint(train_grand.shape)\ntrain_grand = train_grand.reshape((-1, sequence_length*2+1))\nprint(train_grand.shape)\n\ndel X\ndel train","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2. TRAIN, VALIDATION data 나누기\n\n전체 데이터 셋 3400000중 10%는 validation data로 90%는 train data로 사용한다.\n  \ny_train : (3060000, 340)\n\nX_train : (3060000, 80, 2) \n\ny_val : (340000, 340) \n\nX_val : (340000, 80, 2)\n"},{"metadata":{},"cell_type":"markdown","source":"> keras.utils.to_categorical() 은 \n> \n> ex: y_tain=[0,2,1,2,0] 이라하면, \n> y_train= [[ 1.,  0.,  0.],\n>          [ 0.,  0.,  1.],\n>          [ 0.,  1.,  0.],\n>          [ 0.,  0.,  1.],\n>          [ 1.,  0.,  0.]] 로 변경된다."},{"metadata":{"trusted":true},"cell_type":"code","source":"valfrac = 0.1 \ncutpt = int(valfrac * train_grand.shape[0])\nprint(cutpt)\n\nnp.random.shuffle(train_grand)\ny_train, X_train = train_grand[cutpt: , 0], train_grand[cutpt: , 1:]\ny_val, X_val = train_grand[0:cutpt, 0], train_grand[0:cutpt, 1:]\n\ndel train_grand\n\ny_train = keras.utils.to_categorical(y_train, num_classes)\nX_train = X_train.reshape(-1, sequence_length,2)\n\ny_val = keras.utils.to_categorical(y_val, num_classes)\nX_val = X_val.reshape(-1, sequence_length,2)\n\nprint(y_train.shape, \"\\n\",\n      X_train.shape, \"\\n\",\n      y_val.shape, \"\\n\",\n      X_val.shape)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 3. keras 를 이용해 model 정의하기\n\nx, y 의 sequece pattern을 파악하기 위해 conv1을 사용했다.\n"},{"metadata":{},"cell_type":"markdown","source":"> Conv2D(32, kernel_size=(3, 3), padding='same', activation='relu', input_shape=(imheight, imwidth, 1))\n> - 입력 : (32, 32) 채널은 1개\n> - 중간 : (3, 3)커널 필터개수 32개\n> - 아웃 : (32, 32) 채널은 32개\n> - 가중치 개수 : 3x3x32 = 288개\n> - 참고로 케라스 코드에서는 가장 첫번째 레이어를 제외하고는 입력 형태를 자동으로 계산하므로 이 부분은 신경쓰지 않아도 됩니다.\n> \n> model.add(MaxPooling2D(pool_size=(2, 2)))\n> - pool_size=(수직, 수평) 비율 즉, 크기를 반으로 줄입니다.\n>\n>Conv1D\tExtracts local features using 1D filters.\n>필터를 이용하여 지역적인 특징을 추출합니다."},{"metadata":{"trusted":true},"cell_type":"code","source":"\n# model = Sequential()\n# model.add(Conv2D(32, kernel_size=(3, 3), padding='same', activation='relu', input_shape=(isequence_length,2)))\n# model.add(MaxPooling2D(pool_size=(2, 2)))\n\n# model.add(Conv2D(64, kernel_size=(3, 3), padding='same', activation='relu'))\n# model.add(MaxPooling2D(pool_size=(2, 2)))\n# model.add(Dropout(0.2))\n\n# model.add(Flatten())\n# model.add(Dense(680, activation='relu'))\n# model.add(Dropout(0.5))\n# model.add(Dense(num_classes, activation='softmax'))\n\n# model.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def createNetwork(seq_len):\n    \n    # Function to add a convolution layer with batch normalization\n    def addConv(network, features, kernel):\n        network = BatchNormalization()(network)\n        return Conv1D(features, kernel, padding='same', activation='relu')(network)\n    \n    # Function to add a dense layer with batch normalization and dropout\n    def addDense(network, size):\n        network = BatchNormalization()(network)\n        network = Dropout(0.2)(network)\n        return Dense(size, activation='relu')(network)\n    \n    \n    # Input layer\n    input = Input(shape=(seq_len, 2))\n    network = input\n    \n    # Add 1D Convolution\n    for features in [16, 24, 32]:\n        network = addConv(network, features, 5)\n    network = MaxPool1D(pool_size=5)(network)\n    \n    # Add 1D Convolution\n    for features in [64, 96, 128]:\n        network = addConv(network, features, 5)\n    network = MaxPool1D(pool_size=5)(network)\n\n    # Add 1D Convolution\n    for features in [256, 384, 512]:\n        network = addConv(network, features, 5)\n    #network = MaxPool1D(pool_size=5)(network)\n\n    # Flatten\n    network = Flatten()(network)\n    \n    # Dense layer for combination\n    for size in [128, 128]:\n        network = addDense(network, size)\n    \n    # Output layer\n    output = Dense(len(files), activation='softmax')(network)\n\n\n    # Create and compile model\n    model = Model(inputs = input, outputs = output)\n#     model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])\n\n#     # Display model\n#     model.summary()\n    return model\n\nmodel = createNetwork(sequence_length)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 4. 정의한 모델 사용하기\n모델을 정의했으니 모델에 손실함수와 최적화 알고리즘을 적용해보자.\n\nmodel.compile()\n- 다중 클래스 문제이므로 ‘categorical_crossentropy’으로 지정\n- 경사 하강법 알고리즘 중 하나인 ‘adam’을 사용\n- 평가 척도를 나타내며 분류 문제에서는 일반적으로 ‘accuracy’으로 지정\n\n모델을 학습시켜보자\n\nmodel.fit() \n- 훈련 데이터셋 , batch 사이즈, epoch 수, 검증 데이터셋, 학습 중 출력되는 문구 설정"},{"metadata":{"trusted":true},"cell_type":"code","source":"def top_3_accuracy(x,y): \n    t3 = top_k_categorical_accuracy(x,y, 3)\n    return t3\n\nreduceLROnPlat = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, \n                                   verbose=1, mode='auto', min_delta=0.005, cooldown=5, min_lr=0)\n\nearlystop = EarlyStopping(monitor='val_loss', mode='auto', patience=2,verbose=0) \n\n#callbacks = [reduceLROnPlat, earlystop]\n#callbacks = earlystop\n\nmodel.compile(loss='categorical_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy', top_3_accuracy])\n\nmodel.summary()\n\n# model.fit(x=X_train, y=y_train,\n#           batch_size = 1000,\n#           epochs = 100,\n#           validation_data = (X_val, y_val),\n#           callbacks = callbacks,\n#           verbose = 1)\nmodel.fit(x=X_train, y=y_train,\n          batch_size = 1000,\n          epochs = 25,\n          validation_data = (X_val, y_val),\n          verbose = 1)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 5. TEST SET 돌리기\n\n잘 돌아가는걸 확인했다.\n\n이제 test set 을 넣어보자.\n\nmodel.predict()"},{"metadata":{"trusted":true},"cell_type":"code","source":"#%% get test set\nttvlist = []\nreader = pd.read_csv('../input/test_simplified.csv', index_col=['key_id'],\n    chunksize=2048)\n\nfor chunk in tqdm(reader, total=55):\n    X =[]\n    for values in chunk.drawing.values:\n        image = json.loads(values)\n        strokes = []\n        for x_axis, y_axis in image:\n            strokes.extend(list(zip(x_axis, y_axis)))\n        strokes = np.array(strokes)\n        pad = np.zeros((sequence_length, 2))\n        if sequence_length>strokes.shape[0]:\n            pad[:strokes.shape[0],:] = strokes\n        else:\n            pad = strokes[:sequence_length, :]\n        X.append(pad)\n        \n    X = np.array(X)\n    X = np.reshape(X, (-1,sequence_length, 2))\n    testpreds = model.predict(X, verbose=0)\n    ttvs = np.argsort(-testpreds)[:, 0:3]\n    ttvlist.append(ttvs)\n#     imagebag = bag.from_sequence(chunk.drawing.values).map(draw_it)\n#     testarray = np.array(imagebag.compute())\n\n#     testarray = np.reshape(testarray, (testarray.shape[0], imheight, imwidth, 1))\n#     testpreds = model.predict(testarray, verbose=0)\n#     ttvs = np.argsort(-testpreds)[:, 0:3]  # top 3\n#     ttvlist.append(ttvs)\n    \nttvarray = np.concatenate(ttvlist)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds_df = pd.DataFrame({'first': ttvarray[:,0], 'second': ttvarray[:,1], 'third': ttvarray[:,2]})\npreds_df = preds_df.replace(numstonames)\npreds_df['words'] = preds_df['first'] + \" \" + preds_df['second'] + \" \" + preds_df['third']\n\nsub = pd.read_csv('../input/sample_submission.csv', index_col=['key_id'])\nsub['word'] = preds_df.words.values\nsub.to_csv('subcnn_small.csv')\nsub.head()","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}