{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport subprocess\nimport torch\nimport torch.optim as optim\nfrom torch import nn\nimport torchvision\nfrom torchvision import transforms\n\nimport os\nfrom glob import glob\nimport numpy as np\nfrom tqdm.notebook import tqdm\n#from tqdm import tqdm\nimport random\nimport cv2\n\nimport sys","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# 주요 파라미터 지정\nEPOCH = 100\nBATCH_SIZE = 16\nPATIENCE = 3\ncum_patience = 0\nbest_valid_loss = 999\nDATA_PATH = '../input/state-farm-distracted-driver-detection'\nWIDTH = 224\nHEIGHT = 224\nIMGNET_MEAN = [0.485, 0.456, 0.406]\nIMGNET_STD = [0.229, 0.224, 0.225]\nclasses = [f'c{i}' for i in range(10)]\n\nMODE = 'adv_data_aug'\nos.mkdir(MODE)\n\nMODEL_NAME = 'resnet50'\nassert MODEL_NAME in ['resnet50', 'resnet101', 'vgg19', 'densenet161']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# prepare directory\ndef prepare_dirs(path):\n    cmd = f'rm -rf {path}/*'\n    subprocess.call(cmd, shell=True)\n    for d in ['train', 'valid']:\n        cmd = f'mkdir {path}/{d}'\n        subprocess.call(cmd, shell=True)\n        for cl in classes:\n            cmd = f'mkdir {path}/{d}/{cl}'\n            subprocess.call(cmd, shell=True)\n\n\ndef softlink_images(valid_driver, driver_list):\n    trn_cnt = 0\n    val_cnt = 0\n    for 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 == valid_driver:\n            from_ = os.path.abspath(f'{DATA_PATH}/imgs/train/{label}/{img_path}')\n            to_ = f'{MODE}/valid/{label}/{img_path}'\n            if not os.path.exists(to_):\n                os.symlink(from_, to_)\n            val_cnt += 1\n        else:\n            from_ = os.path.abspath(f'{DATA_PATH}/imgs/train/{label}/{img_path}')\n            to_ = f'{MODE}/train/{label}/{img_path}'\n            if not os.path.exists(to_):\n                os.symlink(from_, to_)\n            trn_cnt += 1\n    return trn_cnt, val_cnt\n\n\ndef load_loaders(path):\n    # 이미지 로딩시 전처리 함수 정의\n    # Normalize는 ImageNet 데이터에 기학습된 모델을 활용하기 위한 함수\n    transform = 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    train_dataset = torchvision.datasets.ImageFolder(f'./{path}/train',\n                                                     transform=transform)\n    train_loader = torch.utils.data.DataLoader(train_dataset,\n                                               batch_size=BATCH_SIZE,\n                                               shuffle=True,\n                                               num_workers=2)\n\n    valid_dataset = torchvision.datasets.ImageFolder(f'./{path}/valid',\n                                                     transform=transform)\n    valid_loader = torch.utils.data.DataLoader(valid_dataset,\n                                               batch_size=BATCH_SIZE,\n                                               num_workers=2)\n    return train_loader, valid_loader\n\n\ndef get_model(model_name):\n    # 2-layer FFN을 만드는 함수\n    def get_penultimate_layer(num_ftrs):\n        return nn.Sequential(\n                nn.Linear(num_ftrs, int(num_ftrs/2)),\n                nn.ReLU(),\n                nn.Dropout(0.5),\n                nn.Linear(int(num_ftrs/2), int(num_ftrs/4)),\n                nn.ReLU(),\n                nn.Dropout(0.5),\n                nn.Linear(int(num_ftrs/4), len(classes)))\n\n    # 모델 이름별, 최종 layer의 이름이 다르기 때문에, 아래와 같이 모델별 최종 layer를 맞춤 제작해야한다\n    if model_name == 'resnet50':\n        model_conv = torchvision.models.resnet50(pretrained=True)\n        num_ftrs = model_conv.fc.in_features\n        model_conv.fc = get_penultimate_layer(num_ftrs)\n    if model_name == 'resnet101':\n        model_conv = torchvision.models.resnet101(pretrained=True)\n        num_ftrs = model_conv.fc.in_features\n        model_conv.fc = get_penultimate_layer(num_ftrs)\n    if model_name == 'vgg19':\n        model_conv = torchvision.models.vgg19(pretrained=True)\n        num_ftrs = model_conv.classifier[0].in_features  # fc가 아닌 classifier\n        model_conv.classifier = get_penultimate_layer(num_ftrs)\n    if model_name == 'densenet161':\n        model_conv = torchvision.models.densenet161(pretrained=True)\n        num_ftrs = model_conv.classifier.in_features  # fc가 아닌 classifier\n        model_conv.classifier = get_penultimate_layer(num_ftrs)\n    model_conv = model_conv.to(device)\n    return model_conv\n\n\ndef 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\n        # DATA AUGMENTATION\n        batch = 0\n        for input_, label in zip(inputs, labels):\n            # pick random image from class\n            imgs = glob(f'{MODE}/train/c{label}/*.jpg')\n            rand_img = imgs[random.randint(0, len(imgs) - 1)]\n\n            # read image\n            rand_img = cv2.imread(rand_img, cv2.IMREAD_COLOR)\n            rand_img = cv2.cvtColor(rand_img, cv2.COLOR_BGR2RGB)\n            rand_img = cv2.resize(rand_img, (224, 224), interpolation=cv2.INTER_CUBIC)\n\n            # normalize\n            rand_img = rand_img / 255.\n            for i in range(3):\n                rand_img[:, :, i] = (rand_img[:, :, i] - IMGNET_MEAN[i]) / IMGNET_STD[i]\n            rand_img = np.transpose(rand_img, (2, 0, 1))\n\n            # cutmix\n            new_img = np.zeros((3, HEIGHT, WIDTH))\n            random_cut = random.randint(0, WIDTH - 1)\n            new_img[:, :, :random_cut] = input_[:, :, :random_cut]\n            new_img[:, :, random_cut:] = rand_img[:, :, random_cut:]\n\n            inputs[batch] = torch.tensor(new_img)\n            batch += 1\n\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    trn_loss = running_loss / cnt\n    trn_acc = running_acc / cnt\n    print(f'\\n# train loss : {trn_loss} train acc : {trn_acc}')\n\n\ndef 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\n    valid_loss = valid_loss / cnt\n    valid_acc = valid_acc / cnt\n    print(f'\\n# valid loss | valid_loss : {valid_loss} valid_acc : {valid_acc}')\n    return valid_loss\n\n\ndef save_best_model(save_path, patience, valid_loss, best_valid_loss):\n    # 검증 데이터의 평가 척도 기준으로 최적의 모델 선정\n    if valid_loss < best_valid_loss:\n        best_valid_loss = valid_loss\n        torch.save(model_conv, save_path)\n        patience = 0\n        print(f'\\n# Saving best model.. with valid_loss {valid_loss}')\n    patience += 1\n    return patience, best_valid_loss\n\n\ndef predict_test(test_save_path, model_save_path, device):\n    TEST_BATCH_SIZE = 128\n\n    # 캐글 제출을 위한 test_id 읽어오기\n    test_ids = [os.path.basename(fl) for fl in glob(f'{DATA_PATH}/imgs/test/img_*.jpg')]\n    test_ids.sort()\n    TEST_SIZE = len(test_ids)\n\n    # 테스트 데이터의 전처리 함수 정의\n    transform = 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    # 테스트 데이터 로딩을 위한 데이터 로더 정의\n    test_dataset = torchvision.datasets.ImageFolder(f'{DATA_PATH}/imgs',\n                                                    transform=transform)\n    test_loader = torch.utils.data.DataLoader(test_dataset,\n                                              batch_size=TEST_BATCH_SIZE,\n                                              num_workers=2)\n\n    # 저장된 모델 로딩\n    model_conv = torch.load(model_save_path)\n    model_conv = model_conv.to(device)\n    softmax = nn.Softmax(dim=1)\n\n    pbar = tqdm(total=TEST_SIZE)\n    pred_cnt = 0\n    with open(test_save_path, '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 pred_cnt >= TEST_SIZE:\n                        break\n\n                    test_id = test_ids[i * TEST_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                    pred_cnt += 1\n\n            if pred_cnt >= TEST_SIZE:\n                break\n\n            pbar.update(TEST_BATCH_SIZE)\n    pbar.close()\n\n\ndef move_test_data_to_train_dir(test_save_path):\n    test_results = pd.read_csv(test_save_path)\n    test_cnt = 0\n    class_cnt = {}\n    for i, row in test_results.iterrows():\n        img_path = row['img']\n        preds = row[classes].values\n        pred_class = f'c{np.argmax(preds)}'\n\n        from_ = os.path.abspath(f'imgs/test/{img_path}')\n        to_ = f'{MODE}/train/{pred_class}/{img_path}'\n        if not os.path.exists(to_):\n            os.symlink(from_, to_)\n\n        class_cnt[pred_class] = class_cnt.get(pred_class, 0) + 1\n        test_cnt += 1\n\n    print('# Added below images to each class using semi-supervised :')\n    for k, v in class_cnt.items():\n        print(f'{k} : {v}')\n    return test_cnt","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 운전자 25:1 split\ndriver_list = pd.read_csv(f'{DATA_PATH}/driver_imgs_list.csv')\ndrivers = np.unique(driver_list['subject'].values)\nvalid_driver = drivers[0] # just do 1-fold out of 26-folds\n\n# prepare directory for driver-based split\nprepare_dirs(MODE)\n\n# split data into trn/val and return cnts\ntrn_cnt, val_cnt = softlink_images(valid_driver, driver_list)\n\n# Load loaders\ntrn_loader, val_loader = load_loaders(MODE)\n\n# GPU 사용을 위한 device 변수 정의\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n\n# Define model\nmodel_conv = get_model(MODEL_NAME)\n\n# 학습 옵션 : 손실 함 수 및 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)\n\n# EPOCH 횟수 만큼 학습 진행\nfor epoch in range(EPOCH):\n    print(f'\\n# Epoch : {epoch}..')\n    # 1 Epoch 학습\n    train(model_conv, trn_loader, optimizer, criterion, trn_cnt)\n\n    # 검증 데이터 기준 평가\n    valid_loss = evaluate(model_conv, val_loader, criterion)\n\n    # early_stopping\n    model_save_path = f'{MODE}.{MODEL_NAME}.{valid_driver}'\n    cum_patience, best_valid_loss = save_best_model(model_save_path, cum_patience, valid_loss, best_valid_loss)\n    if cum_patience == PATIENCE:\n        break\n\n# make submission\ntest_save_path = 'submission.csv'\npredict_test(test_save_path, model_save_path, device)","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}