{"cells":[{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# 학습 데이터 위치 확인\n! ls ../input/state-farm-distracted-driver-detection/","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 학습 데이터 위치 확인\n! ls ../input/state-farm-distracted-driver-detection/imgs","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 랜덤하게 배정된 GPU 확인\n! nvidia-smi","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 학습 데이터 운전자 기반 교차 검증\n- 기존 : 26명의 데이터를 8:2 랜덤 셔플\n- 개선 : 26명을 8:2로 분리 하여, 학습 운전자 / 검증 운전자 "},{"metadata":{"trusted":true},"cell_type":"code","source":"# 필요한 라이브러리 설치\n! pip install torch\n! pip install torchvision","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 필요한 라이브러리 import\nimport pandas as pd\nimport subprocess\nimport torch\nimport torch.optim as optim\nfrom torch import nn\nfrom torch.utils.data import Dataset\nfrom torch.utils.data.sampler import SubsetRandomSampler\nimport torchvision\nfrom torchvision import transforms\n\nimport os\nimport random\nfrom glob import glob\nimport cv2\nimport numpy as np\nfrom tqdm.notebook import tqdm","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 주요 파라미터 지정\nEPOCH = 20\nBATCH_SIZE = 16\nPATIENCE = 5\nDATA_PATH = '../input/state-farm-distracted-driver-detection'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 이미지 로딩시 전처리 함수 정의\n# Normalize는 ImageNet 데이터에 기학습된 모델을 활용하기 위한 함수\ntransform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 학습 데이터 중, 운전자 기준으로 20%를 검증 데이터로 사용\nclasses = [f'c{i}' for i in range(10)]\nseed = 2020\nvalidation_split = 0.2\n\n# 운전자 정보 읽어오기\ndriver_list = pd.read_csv(f'{DATA_PATH}/driver_imgs_list.csv')\ndrivers = np.unique(driver_list['subject'].values)\n\n# 운전자 기준 split\nsplit = int(np.floor(validation_split * len(drivers)))\nnp.random.seed(seed)\ntrn_idx, val_idx = drivers[split:], drivers[:split]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 운전자 기준으로 학습/검증 데이터 분리\nsplit_dir = 'driver_split'\nif not os.path.exists(split_dir):\n    cmd = f'mkdir {split_dir}'\n    subprocess.call(cmd, shell=True)\n    for d in ['train', 'valid']:\n        cmd = f'mkdir {split_dir}/{d}'\n        subprocess.call(cmd, shell=True)\n        for cl in classes:\n            cmd = f'mkdir {split_dir}/{d}/{cl}'\n            subprocess.call(cmd, shell=True)\n\ntrn_cnt = 0\nval_cnt = 0\nfor i, driver_info in driver_list.iterrows():\n    driver = driver_info['subject']\n    label = driver_info['classname']\n    img_path = driver_info['img']\n    # symlink를 통해서 이미지 파일을 지정\n    if driver in trn_idx:\n        if not os.path.exists(f'{split_dir}/train/{label}/{img_path}'):\n            os.symlink(os.path.abspath(f'{DATA_PATH}/imgs/train/{label}/{img_path}'), f'{split_dir}/train/{label}/{img_path}')\n        trn_cnt += 1\n    else:\n        if not os.path.exists(f'{split_dir}/valid/{label}/{img_path}'):\n            os.symlink(os.path.abspath(f'{DATA_PATH}/imgs/train/{label}/{img_path}'), f'{split_dir}/valid/{label}/{img_path}')\n        val_cnt += 1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 운전자 구분을 위해 임의로 생성한 디렉토리 확인\n! ls -ahl driver_split","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 학습 데이터, 검증 데이터 로더 정의\ntrain_dataset = torchvision.datasets.ImageFolder(f'./{split_dir}/train',\n                                                 transform=transform)\ntrain_loader = torch.utils.data.DataLoader(train_dataset,\n                                           batch_size=BATCH_SIZE,\n                                           shuffle=True,\n                                           num_workers=2)\nvalid_dataset = torchvision.datasets.ImageFolder(f'./{split_dir}/valid',\n                                                 transform=transform)\nvalid_loader = torch.utils.data.DataLoader(valid_dataset,\n                                           batch_size=BATCH_SIZE,\n                                           num_workers=2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# GPU 사용을 위한 device 변수 정의\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# CNN 모델 로딩\n# ImageNet에서 기학습된 Resnet50 모델과 파라미터를 그대로 사용하기\nmodel_conv = torchvision.models.resnet50(pretrained=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 최종 layer를 우리 문제에 알맞게 재구성\nnum_ftrs = model_conv.fc.in_features\nmodel_conv.fc = nn.Sequential(\n        nn.Linear(num_ftrs, num_ftrs),\n        nn.ReLU(),\n        nn.Dropout(0.5),\n        nn.Linear(num_ftrs, num_ftrs),\n        nn.ReLU(),\n        nn.Dropout(0.5),\n        nn.Linear(num_ftrs, len(classes)))\nprint(f'# model : {model_conv}')\nmodel_conv = model_conv.to(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 학습 옵션 : 손실 함 수 및 optimizer 정의\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.SGD(model_conv.parameters(), lr=0.0001, weight_decay=1e-6, momentum=0.9)\nsoftmax = nn.Softmax(dim=1)\nbest_valid_score = 999\npatience = 0","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def train(model_conv, train_loader, optimizer, criterion, trn_cnt):\n    running_loss = 0.\n    running_acc = 0.\n\n    # 학습 진도 확인을 위한 progress_bar\n    pbar = tqdm(total=trn_cnt)\n    cnt = 0\n    for i, data in enumerate(train_loader, 0):\n        # 데이터 로더에서 BATCH_SIZE 만큼 학습 데이터를 로딩\n        inputs, labels = data\n        # GPU로 데이터 이동\n        inputs = inputs.to(device)\n        labels = labels.to(device)\n\n        # 학습을 위한 준비\n        optimizer.zero_grad()\n        model_conv.train()\n        outputs = model_conv(inputs)\n        probs = softmax(outputs)\n\n        # 정확도 계산\n        _, preds = probs.max(axis=1)\n        running_acc += sum(labels == preds) / (1. * BATCH_SIZE)\n\n        # 손실 함수 계산\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()  # Gradient Descent HERE!\n\n        running_loss += loss.item()\n        cnt += 1\n\n        pbar.update(BATCH_SIZE)\n    pbar.close()\n    return running_loss / cnt, running_acc / cnt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def evaluate(model_conv, valid_loader, criterion):\n    # 1 Epoch마다 검증 데이터에 대하여 평가\n    with torch.no_grad():\n        model_conv.eval()\n        valid_loss = 0.0\n        valid_acc = 0.0\n        cnt = 0\n        pbar = tqdm(total=val_cnt)\n        for data in valid_loader:\n            inputs, labels = data\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n\n            outputs = model_conv(inputs)\n            probs = softmax(outputs)\n\n            _, preds = probs.max(axis=1)\n            valid_acc += sum(labels == preds) / (1. * BATCH_SIZE)\n\n            labels.require_grad = False\n            loss = criterion(outputs, labels)\n            valid_loss += loss\n            cnt += 1\n            pbar.update(BATCH_SIZE)\n        pbar.close()\n    return valid_loss / cnt, valid_acc / cnt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# EPOCH 횟수 만큼 학습 진행\nfor epoch in range(EPOCH):\n    print(f'# Epoch : {epoch}..')\n    # 1 Epoch 학습\n    trn_loss, trn_acc = train(model_conv, train_loader, optimizer, criterion, trn_cnt)\n    print(f'# train loss : {trn_loss} train acc : {trn_acc}')\n    \n    # 검증 데이터 기준 평가\n    valid_loss, valid_acc = evaluate(model_conv, valid_loader, criterion)\n    print(f'# valid loss | valid_loss : {valid_loss} valid_acc : {valid_acc}')\n\n\n    # 검증 데이터의 평가 척도 기준으로 최적의 모델 선정\n    if valid_loss < best_valid_score:\n        best_valid_score = valid_loss\n        print(f'# Saving best model.. epoch {epoch} | valid_loss {valid_loss}')\n        torch.save(model_conv, './model.baseline.driver_split')\n        patience = 0\n    patience += 1\n\n    # early_stopping\n    if patience == PATIENCE:\n        break\n\nprint('Finished Training')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from glob import glob\n\nTEST_SIZE = 79726\nBATCH_SIZE = 128\n\n# 캐글 제출을 위한 test_id 읽어오기\ntest_ids = [os.path.basename(fl) for fl in glob(f'{DATA_PATH}/imgs/test/img_*.jpg')]\ntest_ids.sort()\n\n# 테스트 데이터의 전처리 함수 정의\ntransform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])\n\n# 테스트 데이터 로딩을 위한 데이터 로더 정의\ntest_dataset = torchvision.datasets.ImageFolder(f'{DATA_PATH}/imgs',\n                                                transform=transform)\ntest_loader = torch.utils.data.DataLoader(test_dataset,\n                                          batch_size=BATCH_SIZE,\n                                          num_workers=2)\n\n# GPU 사용\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(device)\n\n# 저장된 모델 로딩\nmodel_conv = torch.load('model.baseline.driver_split')\nprint(f'# model : {model_conv}')\nmodel_conv = model_conv.to(device)\nsoftmax = nn.Softmax(dim=1)\n\npbar = tqdm(total=TEST_SIZE)\nend_flag = False\nwith open('submission.csv', 'w') as out:\n    # write header\n    out.write('img,c0,c1,c2,c3,c4,c5,c6,c7,c8,c9\\n')\n\n    for i, data in enumerate(test_loader, 0):\n        # get the inputs; data is a list of [inputs, labels]\n        inputs, labels = data\n        inputs = inputs.to(device)\n        labels = labels.to(device)\n\n        with torch.no_grad():\n            model_conv.eval()\n            outputs = model_conv(inputs)\n            probs = softmax(outputs)\n\n            # 클래스별 확률 예측값 저장하기\n            for j, prob in enumerate(probs):\n                if BATCH_SIZE * i + j >= TEST_SIZE:\n                    end_flag = True\n                    break\n                    \n                test_id = test_ids[i * BATCH_SIZE + j]\n                prob = ','.join([str(round(val, 3)) for val in prob.cpu().detach().numpy()])\n                out.write(f'{test_id},{prob}\\n')\n\n        pbar.update(BATCH_SIZE)\n\n        if end_flag:\n            break\npbar.close()\n\nprint('Finished Eval')","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}