{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory\n\nimport os\nprint(os.listdir(\"../input\"))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"import ast\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv('../input/test_simplified.csv')\ndf['drawing'] = df['drawing'].apply(ast.literal_eval)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## target 그림 훑어보기\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_show = df.iloc[:25]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n = 5\nfig, axs = plt.subplots(nrows=n, ncols=n, sharex=True, sharey=True, figsize=(8, 8))\nfor i, drawing in enumerate(df_show['drawing']):\n    ax = axs[i//n, i%n]\n    for x, y in drawing:\n        ax.plot(x, -np.array(y))\n    ax.axis('off')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 'drawing'은 그래프상 점의 좌표(x,y)로 구성되어 있음¶\n - 점들을 연결하여 여러 개의 선분을 표현하여 그림을 그리는 것"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_show['drawing'].apply(np.array).apply(np.shape)[:10]\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Neural Net 학습에 용이하게 224x224 정방 사진 형태로 변환¶\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def to_pixel_matrix(drawing_ls, size=224, lw=6, time_color=True):\n    '''\n    점의 좌표(x,y)로 이뤄진 list를 224x224 정방형 pixel matrix로 변환하는 함수\n    \n    arguments\n    ===============================================\n    drawing_ls : list\n        - 사진을 표현한 그래프 좌표(x,y)가 담긴 리스트\n        - 'drawings' column\n    \n    size : int, default=224\n        - pixel matrix의 길이 \n        - default=224는 ResNet101의 default input size\n    \n    lw : int\n        - tickness, 선분의 두께 \n        \n    time_color = bool\n        - True : 선의 색(color)을 decay 시켜 선을 그리는 순서를 표현, 그림을 그리는 패턴을 포착하기 위함\n        - False : 동일한 색으로 선을 표현\n    '''\n    base_size = 256\n    img = np.zeros((base_size, base_size), np.uint8)\n    for t, drawing in enumerate(drawing_ls):\n        for i in range(len(drawing[0]) - 1):\n            color = 255 - min(t, 10)*20 if time_color else 255\n            img = cv2.line(\n                img, \n                (drawing[0][i], drawing[1][i]),\n                (drawing[0][i + 1], drawing[1][i + 1]), \n                color=color, \n                thickness=lw,\n            )\n    \n    return cv2.resize(img, (size, size))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img = to_pixel_matrix(df_show['drawing'][0], time_color=True, lw=5)\nplt.imshow(img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img.shape\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## EDA (RAM의 한계로 수행하지 않음)"},{"metadata":{"trusted":true},"cell_type":"code","source":"from collections import Counter\nimport seaborn as sns","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#df = pd.concat([chunk for chunk in pd.read_csv('total_train.csv', chunksize=100000)])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Word"},{"metadata":{"trusted":true},"cell_type":"code","source":"#len(set(df['word']))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#word_frequency_dic = Counter(df['word'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#most_frequent_words = sorted(((val, key) for key, val in word_frequency_dic.items()), reverse=True)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#most_frequent_words[:10]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#most_frequent_words[-10:]\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### class-imbalance는 심각하지 않은 것으로 보임"},{"metadata":{},"cell_type":"markdown","source":"## Country Code"},{"metadata":{"trusted":true},"cell_type":"code","source":"#len(set(df['countrycode']))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#most_frequent_codes = sorted(((val, key) for key, val in Counter(df['countrycode']).items()), reverse=True)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#most_frequent_codes[:10]\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### US가 전체 데이터의 40% 이상을 차지\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"#most_frequent_codes[-10:]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 상위 30개 국에서, 어떤 그림이 많은지 체크\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"from collections import defaultdict\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"'''\nratio_dic = defaultdict(lambda:[])\n\nfor _, code in most_frequent_codes[:30]:\n    temp_df = df[df['countrycode'] == code]['word']\n    counter = Counter(temp_df)\n    \n    for word, count in counter.items():\n        count /= len(temp_df) # 각 label의 비율을 계산\n        ratio_dic[word].append(count)\n'''","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"'''\nlabel = 'baseball'\nplt.figure(figsize=(8,4))\nsns.distplot(ratio_dic[label], bins=15)\nplt.title(label)\n'''","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 시사점\n - 나라마다 자주 등장하는 그림이 다름\n - 따라서, 데이터의 상당 수를 차지하는 미국의 그림 비율은 유용한 정보가 될 수 있을 것으로 예상됨"},{"metadata":{},"cell_type":"markdown","source":"## Data loading\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## label을 indexing하여 {label:idx} 사전 생성"},{"metadata":{"trusted":true},"cell_type":"code","source":"label2idx = defaultdict(lambda: len(label2idx))\n\npath_to_dir = '../input/train_simplified/'\nfile_name_ls = os.listdir(path_to_dir)\n\nfor file_name in file_name_ls:\n    path_to_file = path_to_dir + file_name\n    label = pd.read_csv(path_to_file, nrows=1, engine='python')['word'][0]\n    \n    label2idx[label] # label2idx 사전에 등록","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for i, (key, val) in enumerate(label2idx.items()):\n    print(key, val)\n    \n    if i == 5:\n        break","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cc2idx = defaultdict(lambda: len(cc2idx))\ncc2idx['<UNK>'] # unk for less frequent country codes\npath_to_file = '../input/train_simplified/snowman.csv'\n\ncountry_code_ls = list(set(pd.read_csv(path_to_file, engine='python')['countrycode']))\n\n# 가장 데이터 수가 많은 snowman을 기준으로 country code를 매김\nfor code in country_code_ls:\n    cc2idx[code]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(cc2idx)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class QuickDrawDataset(Dataset):\n    def __init__(self, path_to_file, cc2idx, label2idx, size=30000, chunk_idx=0, img_size=224, mode='train'):\n        self.path_to_file = path_to_file\n        self.cc2idx = cc2idx\n        self.label2idx = label2idx\n        self.size = size # resource의 한계로 제한된 양의 데이터만 사용\n        self.chunk_idx = chunk_idx # train, validtaion을 나누기 위해 chunk의 번호를 지정\n        self.img_size = img_size # ResNet101 default input size\n        self.mode = mode\n        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n        \n        # if path_to_file is directory\n        if path_to_file[-1] == '/':\n            file_name_ls = os.listdir(self.path_to_file)\n            self.path_to_file_ls = [self.path_to_file+ file_name for file_name in file_name_ls]\n        # if path_to_file is file_name\n        else :\n            self.path_to_file_ls = [self.path_to_file]\n        \n        chunk_ls = []\n        if self.mode == 'train':\n            use_col_ls = ['countrycode', 'drawing', 'word']\n            for path in self.path_to_file_ls:\n                df = pd.read_csv(path, usecols=use_col_ls, chunksize=self.size)\n                \n                for i, chunk in enumerate(df):\n                    if i == self.chunk_idx:\n                        chunk_ls.append(chunk)\n                        break\n                        \n            self.df = pd.concat(chunk_ls, ignore_index=True)\n            self.label = [label2idx[w] for w in self.df['word']] #[[label, label, label]]\n        else :\n            for path in self.path_to_file_ls:\n                df = pd.read_csv(path, usecols=use_col_ls, chunksize=self.size)\n                \n                for i, chunk in enumerate(df):\n                    if i == self.chunk_idx:\n                        chunk_ls.append(chunk)\n                        break\n                        \n            self.df = pd.concat(chunk_ls, ignore_index=True)\n            \n            \n    @staticmethod\n    def to_pixel_matrix(drawing_ls, size=224, lw=6, time_color=True):\n        '''\n        점의 좌표(x,y)로 이뤄진 list를 224x224 정방형 pixel matrix로 변환하는 함수\n\n        arguments\n        ===============================================\n        drawing_ls : list\n            - 사진을 표현한 그래프 좌표(x,y)가 담긴 리스트\n            - 'drawings' column\n\n        size : int, default=224\n            - pixel matrix의 길이 \n            - default=224는 ResNet101의 default input size\n\n        lw : int\n            - tickness, 선분의 두께 \n\n        time_color = bool\n            - True : 선의 색(color)을 decay 시켜 선을 그리는 순서를 표현, 그림을 그리는 패턴을 포착하기 위함\n            - False : 동일한 색으로 선을 표현\n        '''\n        base_size = 256\n        img = np.zeros((base_size, base_size), np.uint8)\n        for t, drawing in enumerate(drawing_ls):\n            for i in range(len(drawing[0]) - 1):\n                color = 255 - min(t, 10)*20 if time_color else 255\n                img = cv2.line(\n                    img, \n                    (drawing[0][i], drawing[1][i]),\n                    (drawing[0][i + 1], drawing[1][i + 1]), \n                    color=color, \n                    thickness=lw,\n                )\n\n        return cv2.resize(img, (size, size))\n    \n    def to_tensor(self, x, dtype=torch.float32):\n        return torch.tensor(x, dtype=dtype, device=self.device)\n    \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        country_code = self.df['countrycode'][idx]\n        if country_code in self.cc2idx:\n            country_code = self.cc2idx[country_code]\n        else:\n            country_code = self.cc2idx['<UNK>']\n        country_code = self.to_tensor(country_code, dtype=torch.long)\n        \n        drawing_ls = ast.literal_eval(self.df['drawing'][idx])\n        img = self.to_pixel_matrix(drawing_ls, size=self.img_size, lw=6, time_color=True)\n        img = self.to_tensor(img[None]/255, dtype=torch.float32) # expand_dim, bound to 0~1\n            \n        if self.mode == 'train':\n            label = self.label[idx]\n            label = self.to_tensor(label, dtype=torch.long)\n            return img, country_code, label \n        else:\n            return img, country_code","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 데이터의 idx 순서대로 0 ~ 15,000 사용\ntrain_dataset = QuickDrawDataset(\n    path_to_file='../input/train_simplified/',\n    label2idx = label2idx,\n    cc2idx = cc2idx,\n    size=10000,\n    chunk_idx=0\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#15,000 ~ 17,500번째 index만 사용\nval_dataset = QuickDrawDataset(\n    path_to_file='../input/train_simplified/',\n    label2idx = label2idx,\n    cc2idx = cc2idx,\n    size=2000,\n    chunk_idx=5\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=0)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=True, num_workers=0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"import torchvision\nimport torch","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"resnet = torchvision.models.resnet18(pretrained=True)\n# in-channel customizing (gray-scale 1D)\nresnet.conv1 = torch.nn.Conv2d(1, 64, (7,7), stride=(2,2), padding=(3,3), bias=False) \nresnet.fc = torch.nn.Linear(512, 384, bias=True) # output_dim customizing","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class DrawingClassifier(torch.nn.Module):\n    def __init__(self, img_net):\n        super(DrawingClassifier, self).__init__()\n        self.img_net = img_net\n        self.device = 'cuda' if torch.cuda.is_available() else 'cpu'\n        \n        self.cc_embedding = torch.nn.Embedding(\n            num_embeddings=190, #number of country code \n            embedding_dim=128,\n        )\n        \n        self.fc = torch.nn.Linear(384+128, 340, bias=True)  # 384(resent) + 128(cc_embedding)\n    \n    def to_tensor(self, x, dtype=torch.float32):\n        return torch.tensor(x, dtype=dtype, device=self.device)\n    \n    def forward(self, img, cc):\n        img = self.img_net(img)\n        cc = self.cc_embedding(cc)\n        x = torch.cat((img, cc), dim=1) # img + country_code\n        \n        out = self.fc(x)\n        return torch.log_softmax(out, dim=-1)        ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = DrawingClassifier(resnet)\nmodel.to(model.device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for batch in train_loader:\n    break","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.imshow(np.array(batch[0][0].cpu().numpy()*255, dtype=np.uint8)[0])\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import time\n\nclass Fitter() : \n    def __init__(self, model, train_loader, test_loader): \n        self.criterion = torch.nn.NLLLoss()\n        self.optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n        self.scheduler = torch.optim.lr_scheduler.MultiStepLR(\n            self.optimizer, milestones=[25000], gamma=0.5\n        )\n        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n        self.model = model.to(model.device)\n        \n        self.train_loader = train_loader\n        self.test_loader = test_loader\n    \n    def train_and_evaluate(self, n_epoch, test_epoch=1):       \n        for epoch in range(1, n_epoch+1):\n            print('=====================================================================================')\n            print('Epoch: %s\\n Train'%epoch)\n            train_loss, train_score = self.train()\n            \n            if epoch % test_epoch == 0:\n                print('=====================================================================================')\n                print('Test')\n                test_loss, test_score = self.evaluate()\n        return\n    \n    def mapk(self, output, target, k=3):\n        with torch.no_grad():\n            batch_size = target.size(0)\n\n            _, pred = output.topk(k, dim=1)\n            pred = pred.t()\n            correct = pred.eq(target.view(1, -1).expand_as(pred))\n\n            for i in range(k):\n                correct[i] = correct[i]*(k-i)\n\n            score = correct[:k].view(-1).float().sum(0, keepdim=True)\n            score.mul_(1.0 / (k * batch_size))\n        return score\n    \n    def train(self):\n        self.model.train()\n        start_time = time.time()\n        \n        epoch_loss, score = 0, 0\n        n_batch = 0\n        \n        for img_batch, cc_batch, y_batch in self.train_loader:\n            output = self.model(img_batch, cc_batch) \n            loss = self.criterion(output, y_batch)\n\n            self.optimizer.zero_grad() \n            loss.backward()\n            self.optimizer.step()\n            self.scheduler.step()\n            \n            epoch_loss += loss.item()\n            score += self.mapk(output, y_batch).item()\n            n_batch += 1\n            \n            if n_batch % 1000 == 0:\n                print('Batch : %s, Loss : %.03f, Score : %.03f, Train Time : %.03f'\\\n                      %(n_batch, epoch_loss/n_batch, score/n_batch, time.time()-start_time))\n\n            # 10000 batch씩만 학습\n            if n_batch % 10000 == 0:\n                break \n                \n        return epoch_loss/n_batch, score/n_batch\n    \n    def evaluate(self):\n        model.eval() # stop the every change in gradient of model\n        start_time = time.time()\n        \n        epoch_loss, score = 0, 0\n        n_batch = 0\n        \n        for img_batch, cc_batch, y_batch in self.test_loader:\n            output = self.model(img_batch, cc_batch) \n            loss = self.criterion(output, y_batch)\n\n            epoch_loss += loss.item()\n            score += self.mapk(output, y_batch).item()\n            n_batch += 1\n            \n            # 1000 배치씩만 테스트\n            if n_batch % 1000 == 0:\n                print('Batch : %s, Loss : %.03f, Score : %.03f, Test Time : %.03f'\\\n                      %(n_batch, epoch_loss/n_batch, score/n_batch, time.time() - start_time))\n                break\n                \n        return epoch_loss/n_batch, score/n_batch","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"args = {\n    'model' : model,\n    'train_loader' : train_loader,\n    'test_loader' : val_loader,\n}\n\nfitter = Fitter(**args)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fitter.train_and_evaluate(n_epoch=3)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Predict (Test)"},{"metadata":{"trusted":true},"cell_type":"code","source":"len(df)","execution_count":110,"outputs":[{"output_type":"execute_result","execution_count":110,"data":{"text/plain":"112199"},"metadata":{}}]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dataset = QuickDrawDataset(\n    path_to_file= '../input/test_simplified.csv',\n    label2idx = label2idx,\n    cc2idx = cc2idx,\n    mode='test',\n    size=112199\n)\n\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict(model, test_loader):\n    result = np.array([])\n    \n    for i, (img_batch, cc_batch) in enumerate(test_loader):\n        output = model(img_batch, cc_batch) \n        output = output.topk(3, dim=1)[1].cpu().numpy()\n        \n        if i == 0:\n            result = output\n        else:\n            result = np.vstack((result, output))\n    \n    return result\n        ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred_ls = predict(model, test_loader)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# decode\nidx2label = {val : key for key,val in label2idx.items()}\npred_ls = [[idx2label[pred] for pred in preds] for preds in pred_ls] # idx2label\npred_ls = [' '.join(preds) for preds in pred_ls] # join\n\n# save    \nsubmission = pd.read_csv('test_simplified.csv')\nsubmission.drop(['countrycode', 'drawing'], axis=1, inplace=True)\nsubmission['word'] = pred_ls","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","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}