{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"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\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 read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:04:01.437565Z","iopub.execute_input":"2025-07-17T15:04:01.437828Z","iopub.status.idle":"2025-07-17T15:04:55.591653Z","shell.execute_reply.started":"2025-07-17T15:04:01.437808Z","shell.execute_reply":"2025-07-17T15:04:55.589562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"disease = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/label_num_to_disease_map.json')\ntrain = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:04:55.592907Z","iopub.execute_input":"2025-07-17T15:04:55.593232Z","iopub.status.idle":"2025-07-17T15:04:55.646764Z","shell.execute_reply.started":"2025-07-17T15:04:55.593212Z","shell.execute_reply":"2025-07-17T15:04:55.645939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"disease.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:51.412521Z","iopub.status.idle":"2025-07-17T15:03:51.412830Z","shell.execute_reply.started":"2025-07-17T15:03:51.412685Z","shell.execute_reply":"2025-07-17T15:03:51.412699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:21:11.834253Z","iopub.execute_input":"2025-07-17T15:21:11.834545Z","iopub.status.idle":"2025-07-17T15:21:11.853196Z","shell.execute_reply.started":"2025-07-17T15:21:11.834485Z","shell.execute_reply":"2025-07-17T15:21:11.852553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:51.464057Z","iopub.status.idle":"2025-07-17T15:03:51.464313Z","shell.execute_reply.started":"2025-07-17T15:03:51.464195Z","shell.execute_reply":"2025-07-17T15:03:51.464209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train['image_id'][0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:51.465096Z","iopub.status.idle":"2025-07-17T15:03:51.465370Z","shell.execute_reply.started":"2025-07-17T15:03:51.465213Z","shell.execute_reply":"2025-07-17T15:03:51.465226Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport imp\nimport torch\nimport random\nimport torchvision\n\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport torch.nn as nn\nimport torch.optim as optim\n\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom matplotlib import pyplot as plt\nfrom torch.utils.data import DataLoader\nfrom torchvision import transforms, models\n\n# ml lib\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:04:55.647552Z","iopub.execute_input":"2025-07-17T15:04:55.647807Z","iopub.status.idle":"2025-07-17T15:05:01.190524Z","shell.execute_reply.started":"2025-07-17T15:04:55.647781Z","shell.execute_reply":"2025-07-17T15:05:01.189944Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SEED = 127\ntorch.manual_seed(SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:05:01.191924Z","iopub.execute_input":"2025-07-17T15:05:01.192257Z","iopub.status.idle":"2025-07-17T15:05:01.202082Z","shell.execute_reply.started":"2025-07-17T15:05:01.192233Z","shell.execute_reply":"2025-07-17T15:05:01.201387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# img_path = '/kaggle/input/cassava-leaf-disease-classification/train_images/'\n# img_path + train['image_id'][0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:51.469349Z","iopub.status.idle":"2025-07-17T15:03:51.469658Z","shell.execute_reply.started":"2025-07-17T15:03:51.469488Z","shell.execute_reply":"2025-07-17T15:03:51.469519Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# img_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:51.470150Z","iopub.status.idle":"2025-07-17T15:03:51.470438Z","shell.execute_reply.started":"2025-07-17T15:03:51.470289Z","shell.execute_reply":"2025-07-17T15:03:51.470312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# len(os.listdir(img_path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:51.470959Z","iopub.status.idle":"2025-07-17T15:03:51.471235Z","shell.execute_reply.started":"2025-07-17T15:03:51.471092Z","shell.execute_reply":"2025-07-17T15:03:51.471104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_path = '/kaggle/input/cassava-leaf-disease-classification/test_images/2216849948.jpg'\n\nwith Image.open(test_path) as img:\n        width, height = img.size\n        plt.imshow(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:05:01.202932Z","iopub.execute_input":"2025-07-17T15:05:01.203202Z","iopub.status.idle":"2025-07-17T15:05:01.602711Z","shell.execute_reply.started":"2025-07-17T15:05:01.203172Z","shell.execute_reply":"2025-07-17T15:05:01.601933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_img_path = '/kaggle/input/cassava-leaf-disease-classification/train_images/'\n\nimg_path =  os.path.join(train_img_path + train['image_id'][2])\n\nwith Image.open(img_path) as img:\n        width, height = img.size\n        plt.imshow(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:05:01.603561Z","iopub.execute_input":"2025-07-17T15:05:01.603784Z","iopub.status.idle":"2025-07-17T15:05:01.963578Z","shell.execute_reply.started":"2025-07-17T15:05:01.603768Z","shell.execute_reply":"2025-07-17T15:05:01.962826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(os.listdir(train_img_path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:03:53.870686Z","iopub.execute_input":"2025-07-17T15:03:53.870942Z","iopub.status.idle":"2025-07-17T15:03:53.879260Z","shell.execute_reply.started":"2025-07-17T15:03:53.870921Z","shell.execute_reply":"2025-07-17T15:03:53.878087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img.size","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T05:48:57.909541Z","iopub.execute_input":"2025-07-16T05:48:57.909826Z","iopub.status.idle":"2025-07-16T05:48:57.923880Z","shell.execute_reply.started":"2025-07-16T05:48:57.909809Z","shell.execute_reply":"2025-07-16T05:48:57.923218Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Mean and std to reshape and normalize","metadata":{}},{"cell_type":"markdown","source":"**이미지의 Mean, std 평균 한번에 구하기**","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\n\nr_mean_arr = []\ng_mean_arr = []\nb_mean_arr = []\n\nr_std_arr = []\ng_std_arr = []\nb_std_arr = []\n\nfor i in range(len(os.listdir(train_img_path))):\n    img_path =  os.path.join(train_img_path + train['image_id'][i])\n    img = cv2.imread(img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    # print(img)\n\n    r_mean, g_mean, b_mean = np.mean(img, axis =(0,1))\n    r_std, g_std, b_std = np.std(img, axis=(0,1))\n\n    r_mean_arr.append(r_mean)\n    g_mean_arr.append(g_mean)\n    b_mean_arr.append(b_mean)\n    \n    r_std_arr.append(r_std)\n    g_std_arr.append(g_std)\n    b_std_arr.append(b_std)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T05:48:57.924594Z","iopub.execute_input":"2025-07-16T05:48:57.924819Z","iopub.status.idle":"2025-07-16T06:13:48.713021Z","shell.execute_reply.started":"2025-07-16T05:48:57.924800Z","shell.execute_reply":"2025-07-16T06:13:48.712326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(r_mean_arr) #, g_mean_arr, b_mean_arr","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.713879Z","iopub.execute_input":"2025-07-16T06:13:48.714150Z","iopub.status.idle":"2025-07-16T06:13:48.719023Z","shell.execute_reply.started":"2025-07-16T06:13:48.714128Z","shell.execute_reply":"2025-07-16T06:13:48.718468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"r_mean_arr[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.719649Z","iopub.execute_input":"2025-07-16T06:13:48.719878Z","iopub.status.idle":"2025-07-16T06:13:48.732452Z","shell.execute_reply.started":"2025-07-16T06:13:48.719858Z","shell.execute_reply":"2025-07-16T06:13:48.731777Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"R_MEAN = np.mean(r_mean_arr) / 255\nG_MEAN = np.mean(g_mean_arr) / 255\nB_MEAN = np.mean(b_mean_arr) / 255","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.733177Z","iopub.execute_input":"2025-07-16T06:13:48.733377Z","iopub.status.idle":"2025-07-16T06:13:48.751522Z","shell.execute_reply.started":"2025-07-16T06:13:48.733362Z","shell.execute_reply":"2025-07-16T06:13:48.750958Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Red ch mean   = {R_MEAN}\\nGreen ch mean = {G_MEAN}\\nBlue ch mean  = {B_MEAN}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.752076Z","iopub.execute_input":"2025-07-16T06:13:48.752259Z","iopub.status.idle":"2025-07-16T06:13:48.765204Z","shell.execute_reply.started":"2025-07-16T06:13:48.752245Z","shell.execute_reply":"2025-07-16T06:13:48.764487Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**이미지의 std 평균 구하기**","metadata":{}},{"cell_type":"code","source":"# 위에서 한번에\n# r_std_arr = []\n# g_std_arr = []\n# b_std_arr = []\n\n# for i in range(len(os.listdir(train_img_path))):\n#     img_path =  os.path.join(train_img_path + train['image_id'][i])\n#     img = cv2.imread(img_path)\n#     img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n#     # print(img)\n\n#     # r_mean, g_mean, b_mean = np.mean(img, axis =(0,1))\n#     r_std, g_std, b_std = np.std(img, axis=(0,1))\n\n#     # r_mean_arr.append(r_mean)\n#     # g_mean_arr.append(g_mean)\n#     # b_mean_arr.append(b_mean)\n    \n#     r_std_arr.append(r_std)\n#     g_std_arr.append(g_std)\n#     b_std_arr.append(b_std)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.765891Z","iopub.execute_input":"2025-07-16T06:13:48.766048Z","iopub.status.idle":"2025-07-16T06:13:48.778157Z","shell.execute_reply.started":"2025-07-16T06:13:48.766037Z","shell.execute_reply":"2025-07-16T06:13:48.777479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"R_STD = np.mean(r_std_arr) / 255\nG_STD = np.mean(g_std_arr) / 255\nB_STD = np.mean(b_std_arr) / 255","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.778846Z","iopub.execute_input":"2025-07-16T06:13:48.779102Z","iopub.status.idle":"2025-07-16T06:13:48.795430Z","shell.execute_reply.started":"2025-07-16T06:13:48.779070Z","shell.execute_reply":"2025-07-16T06:13:48.794917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Red ch std   = {R_STD}\\nGreen ch std = {G_STD}\\nBlue ch std  = {B_STD}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.796086Z","iopub.execute_input":"2025-07-16T06:13:48.796265Z","iopub.status.idle":"2025-07-16T06:13:48.809032Z","shell.execute_reply.started":"2025-07-16T06:13:48.796251Z","shell.execute_reply":"2025-07-16T06:13:48.808388Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train/test split and samples view","metadata":{}},{"cell_type":"code","source":"X_train, X_test, y_train, y_test = train_test_split(train['image_id'], train['label'], test_size = 0.2, random_state = SEED, stratify = train['label'])\n\nprint(f'Train size = {X_train.shape[0]} \\n Test size = {X_test.shape[0]}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:05:01.964394Z","iopub.execute_input":"2025-07-17T15:05:01.964628Z","iopub.status.idle":"2025-07-17T15:05:01.980465Z","shell.execute_reply.started":"2025-07-17T15:05:01.964611Z","shell.execute_reply":"2025-07-17T15:05:01.979845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"type(X_train)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.836347Z","iopub.execute_input":"2025-07-16T06:13:48.836598Z","iopub.status.idle":"2025-07-16T06:13:48.840870Z","shell.execute_reply.started":"2025-07-16T06:13:48.836571Z","shell.execute_reply":"2025-07-16T06:13:48.840333Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train.iloc[123]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.841486Z","iopub.execute_input":"2025-07-16T06:13:48.841641Z","iopub.status.idle":"2025-07-16T06:13:48.855784Z","shell.execute_reply.started":"2025-07-16T06:13:48.841630Z","shell.execute_reply":"2025-07-16T06:13:48.855234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nfig, axs = plt.subplots(1, 2, figsize=(15, 7))\n\n# y_train, y_test가 정수 클래스(Label)라면\ny_train.value_counts().plot.pie(autopct='%1.1f%%', ax=axs[0], colors=['skyblue', 'orange', 'lightgreen', 'salmon', 'plum'])\naxs[0].set_title(\"Train Label 분포\")\naxs[0].set_ylabel(\"\")  # y축 라벨 제거\n\ny_test.value_counts().plot.pie(autopct='%1.1f%%', ax=axs[1], colors=['skyblue', 'orange', 'lightgreen', 'salmon', 'plum'])\naxs[1].set_title(\"Test Label 분포\")\naxs[1].set_ylabel(\"\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:48.856317Z","iopub.execute_input":"2025-07-16T06:13:48.856514Z","iopub.status.idle":"2025-07-16T06:13:49.199123Z","shell.execute_reply.started":"2025-07-16T06:13:48.856497Z","shell.execute_reply":"2025-07-16T06:13:49.198382Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# KFold 연습 \n\n: train_test_split 대신 **K-Fold 교차검증(K-Fold Cross Validation)** 을 사용\n\n\n| 항목        | `train_test_split`                   | `K-Fold` 교차검증                    |\n| --------- | ------------------------------------ | -------------------------------- |\n| 방식        | 데이터를 한 번 나눔 (예: 80% train, 20% test) | 데이터를 K등분 후, 각 fold가 한 번씩 test 역할 |\n| 일반화 성능 측정 | 운에 따라 결과가 다를 수 있음                    | 여러 번 평가해서 평균을 내므로 더 신뢰도 높음       |\n| 과적합 방지    | 제한적                                  | 모든 데이터를 여러 번 훈련/검증에 사용하므로 방지     |\n| 학습 횟수     | 1회                                   | K회 (시간 오래 걸림)                    |\n| 용도 예시     | 빠른 실험용, 대회 제출용                       | 모델 성능 검증, 파라미터 튜닝 등              |\n","metadata":{}},{"cell_type":"code","source":"# kFold 설정 및 데이터 분할\n\nfrom sklearn.model_selection import StratifiedKFold\n\ntrain_df = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\n\n# 2. fold 컬럼 초기화\ntrain_df['fold'] = -1\n\n# 3. StratifiedKFold 객체 생성\nskf = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)\n\n# 4. fold 값 할당\nfor fold, (train_idx, val_idx) in enumerate(skf.split(train_df, train_df['label'])):\n    train_df.loc[val_idx, 'fold'] = fold","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:54:37.565265Z","iopub.execute_input":"2025-07-17T16:54:37.565944Z","iopub.status.idle":"2025-07-17T16:54:37.595828Z","shell.execute_reply.started":"2025-07-17T16:54:37.565913Z","shell.execute_reply":"2025-07-17T16:54:37.595194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:54:34.634285Z","iopub.status.idle":"2025-07-17T16:54:34.634608Z","shell.execute_reply.started":"2025-07-17T16:54:34.634417Z","shell.execute_reply":"2025-07-17T16:54:34.634432Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_df = train_df.groupby('label').head(1).sort_values(by = 'label')\nsample_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:54:42.327456Z","iopub.execute_input":"2025-07-17T16:54:42.327802Z","iopub.status.idle":"2025-07-17T16:54:42.338976Z","shell.execute_reply.started":"2025-07-17T16:54:42.327781Z","shell.execute_reply":"2025-07-17T16:54:42.338344Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_img_path = '/kaggle/input/cassava-leaf-disease-classification/train_images/'\n\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\nfig, axes = plt.subplots(2, 3, figsize=(12, 8))  # 2행 3열 그림판 만들기\naxes = axes.flatten()  # 축들을 1차원 리스트로 바꾸기\n\nfor i in range(len(sample_df)):  # sample_df 개수만큼 반복\n    img_path = os.path.join(train_img_path, sample_df['image_id'].iloc[i])  # 이미지 경로\n    img = Image.open(img_path)  # 이미지 열기\n    axes[i].imshow(img)  # i번째 칸에 이미지 그리기\n    axes[i].set_title(f\"Label: {sample_df['label'].iloc[i]}\")  # 제목 붙이기\n    axes[i].axis('off')  # 축 눈금 없애기\n\n# 남는 subplot이 있을 경우 빈 칸으로 둠\nfor j in range(i+1, len(axes)):\n    axes[j].axis('off')\n\nplt.tight_layout()  # 그림 간격 딱 맞추기\nplt.show()  # 그림 보여주기\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:09:32.279716Z","iopub.execute_input":"2025-07-17T16:09:32.280183Z","iopub.status.idle":"2025-07-17T16:09:33.110269Z","shell.execute_reply.started":"2025-07-17T16:09:32.280161Z","shell.execute_reply":"2025-07-17T16:09:33.109558Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Transforms","metadata":{}},{"cell_type":"code","source":"train_transforms = transforms.Compose([\ntransforms.Resize(256),\n\ntransforms.RandomCrop(224),\n\ntransforms.RandomHorizontalFlip(),\n\ntransforms.ColorJitter(0.1, 0.1, 0.1, 0.05), # 너무 과하지 않게\n\ntransforms.ToTensor(),\n\ntransforms.Normalize(\n    mean=[0.430, 0.496, 0.313],\n    std=[0.219, 0.224, 0.201]\n)\n])\n\n# 채널별 정규화\n    # 각 채널(R, G, B)의 평균과 표준편차를 기준으로\n    # (값 - 평균) / 표준편차 → 평균 0, 분산 1로 만들어줌\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:26:18.072332Z","iopub.execute_input":"2025-07-17T15:26:18.072730Z","iopub.status.idle":"2025-07-17T15:26:18.077700Z","shell.execute_reply.started":"2025-07-17T15:26:18.072701Z","shell.execute_reply":"2025-07-17T15:26:18.076984Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"1. RandomHorizontalFlip() → 좌우 반전\n\n2. RandomCrop() → 일부 자르기\n\n3. ColorJitter() → 색상/밝기/대비 바꾸기\n\n4. RandomRotation() → 회전\n\n\n**🎯 추천 조합 (성능 타협 + 안정성)**\n\ntransforms.Resize(256),\n\ntransforms.RandomCrop(224),\n\ntransforms.RandomHorizontalFlip(),\n\ntransforms.ColorJitter(0.1, 0.1, 0.1, 0.05),  # 너무 과하지 않게\n\ntransforms.ToTensor(),\n\ntransforms.Normalize(mean, std)","metadata":{}},{"cell_type":"code","source":"test_transforms = transforms.Compose([\n    transforms.Resize((224,224)),\n    # transforms.RandomHorizontalFlip(), 테스트에는 없음 ->원본 문제 그대로 나옵니다. 그걸로 정확도를 봐야 하니까요!\n    \n    transforms.ToTensor(),\n    # 1.이미지 텐서로 변환, 2. (H, W, C) → (C, H, W) 구조 변경 3. 값 범위: 0~255 → 0.0~1.0 (정규화)\n    \n    transforms.Normalize (mean =[0.430, 0.496, 0.313],\n                           std = [0.219,0.224,0.201])\n                                          # 채널별 정규화\n    # 각 채널(R, G, B)의 평균과 표준편차를 기준으로\n    # (값 - 평균) / 표준편차 → 평균 0, 분산 1로 만들어줌\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:05:30.999547Z","iopub.execute_input":"2025-07-17T15:05:31.000067Z","iopub.status.idle":"2025-07-17T15:05:31.004076Z","shell.execute_reply.started":"2025-07-17T15:05:31.000044Z","shell.execute_reply":"2025-07-17T15:05:31.003459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:05:32.986341Z","iopub.execute_input":"2025-07-17T15:05:32.987019Z","iopub.status.idle":"2025-07-17T15:05:32.990192Z","shell.execute_reply.started":"2025-07-17T15:05:32.986997Z","shell.execute_reply":"2025-07-17T15:05:32.989521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.iloc[9]['image_id']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:49.245642Z","iopub.execute_input":"2025-07-16T06:13:49.245833Z","iopub.status.idle":"2025-07-16T06:13:49.264143Z","shell.execute_reply.started":"2025-07-16T06:13:49.245817Z","shell.execute_reply":"2025-07-16T06:13:49.263417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DiseaseData(Dataset):\n    def __init__(self,x,y, transform = None, submission = False):\n        self.x = x.reset_index()\n        self.y = y.reset_index()\n        self.submission = submission\n        self.transform = transform\n\n\n    def __len__(self):\n        return self.x.shape[0]\n\n    def load_image(self, path): # 이미지 한 장 불러오기\n        prefix = train_img_path\n        return Image.open(os.path.join(prefix , path['image_id']))\n\n    def __getitem__(self, index): # 이미지 + 라벨 한 쌍 반환\n        image = self.load_image(self.x.iloc[index])\n        label = self.y.iloc[index]['label']\n        if self.transform:\n            image = self.transform(image)\n        if self.submission:\n            image = np.array(image)\n        sample = {'image' : image, 'label' : label}\n        # return sample\n        return image, label # kfold\n\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:44:39.025475Z","iopub.execute_input":"2025-07-17T16:44:39.026235Z","iopub.status.idle":"2025-07-17T16:44:39.032165Z","shell.execute_reply.started":"2025-07-17T16:44:39.026207Z","shell.execute_reply":"2025-07-17T16:44:39.031559Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# PyTorch용 Dataset 객체를 생성\n\ntrain_data = DiseaseData(X_train, y_train, transform = train_transforms)\ntest_data = DiseaseData(X_test, y_test, transform = test_transforms)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:26:22.413863Z","iopub.execute_input":"2025-07-17T15:26:22.414132Z","iopub.status.idle":"2025-07-17T15:26:22.421694Z","shell.execute_reply.started":"2025-07-17T15:26:22.414113Z","shell.execute_reply":"2025-07-17T15:26:22.421039Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data[8]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:49.295165Z","iopub.execute_input":"2025-07-16T06:13:49.295340Z","iopub.status.idle":"2025-07-16T06:13:49.348626Z","shell.execute_reply.started":"2025-07-16T06:13:49.295327Z","shell.execute_reply":"2025-07-16T06:13:49.347985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  transform까지 모두 적용된 이미지를 시각화 해보기 (transforms.Normalize가 적용되어있어서 색이 다르게 나온다.)\nsample = train_data[8]\n# sample\nimport matplotlib.pyplot as plt\nplt.imshow(sample['image'].permute(1, 2, 0))  # CHW → HWC\nplt.title(f\"Label: {sample['label'].item()}\")\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:26:24.559399Z","iopub.execute_input":"2025-07-17T15:26:24.559954Z","iopub.status.idle":"2025-07-17T15:26:24.716529Z","shell.execute_reply.started":"2025-07-17T15:26:24.559931Z","shell.execute_reply":"2025-07-17T15:26:24.715785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 기존 이미지 확인해보기\n\nsample = train_data[8]  # transform 포함된 이미지\ntransformed_img = sample['image']      # shape: [C, H, W]\nlabel = sample['label']\n\n# Tensor → numpy로 바꿔서 시각화\nimport matplotlib.pyplot as plt\nimport torch\n\n# Normalize 되어 있다면 다시 되돌려야 눈으로 확인 가능\ndef unnormalize(img_tensor):\n    \"\"\"Normalize를 반대로 적용해서 시각화 가능하게 만들기\"\"\"\n    mean = torch.tensor([0.430, 0.496, 0.313]).view(3, 1, 1)\n    std = torch.tensor([0.219, 0.224, 0.201]).view(3, 1, 1)\n    return img_tensor * std + mean  # 채널별로 곱하고 더함\n\nimg_to_show = unnormalize(transformed_img).permute(1, 2, 0)  # CHW → HWC\nplt.imshow(img_to_show.numpy())\nplt.title(f\"Transform된 이미지 (label={label})\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:26:39.711936Z","iopub.execute_input":"2025-07-17T15:26:39.712208Z","iopub.status.idle":"2025-07-17T15:26:40.108341Z","shell.execute_reply.started":"2025-07-17T15:26:39.712186Z","shell.execute_reply":"2025-07-17T15:26:40.107446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trainloader = torch.utils.data.DataLoader(train_data,batch_size=64, num_workers= 2)\ntestloader = torch.utils.data.DataLoader(test_data, batch_size=64, num_workers= 2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:26:44.126559Z","iopub.execute_input":"2025-07-17T15:26:44.126835Z","iopub.status.idle":"2025-07-17T15:26:44.132159Z","shell.execute_reply.started":"2025-07-17T15:26:44.126817Z","shell.execute_reply":"2025-07-17T15:26:44.131207Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**DataLoader가 하는 일**\n\n1. Dataset에서 샘플을 꺼내와서(batch_size 만큼씩 묶음) 묶음 단위(batch)로 전달해줌\n\n예: train_data에서 batch_size개씩 이미지를 뽑아서 하나의 배치로 만듦\n\n2. num_workers=2는 2개의 프로세스를 이용해서 데이터를 읽음 → 데이터 로딩 속도 향상","metadata":{}},{"cell_type":"code","source":"# trainloader 구조 확인해보기\n\nfor index, sample_batch in enumerate(trainloader):\n    print(index,\n          sample_batch['image'].__len__(),\n          sample_batch['label'].__len__(),\n          sample_batch['image'].size(),\n          sample_batch['label'].size()\n          \n          )\n    # if batch > 4:\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:13:49.710840Z","iopub.execute_input":"2025-07-16T06:13:49.711098Z","iopub.status.idle":"2025-07-16T06:13:51.021991Z","shell.execute_reply.started":"2025-07-16T06:13:49.711075Z","shell.execute_reply":"2025-07-16T06:13:51.021258Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model selection","metadata":{}},{"cell_type":"markdown","source":"> Transfer learning : resnet18","metadata":{}},{"cell_type":"code","source":"# create model \nresnet18 = models.resnet18(pretrained = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:27:14.513140Z","iopub.execute_input":"2025-07-17T16:27:14.513703Z","iopub.status.idle":"2025-07-17T16:27:14.733131Z","shell.execute_reply.started":"2025-07-17T16:27:14.513681Z","shell.execute_reply":"2025-07-17T16:27:14.732479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# optimizer and criterion\n\noptimizer = optim.Adam(resnet18.parameters(), lr =1e-4)\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:27:45.776434Z","iopub.execute_input":"2025-07-17T16:27:45.776993Z","iopub.status.idle":"2025-07-17T16:27:45.781282Z","shell.execute_reply.started":"2025-07-17T16:27:45.776972Z","shell.execute_reply":"2025-07-17T16:27:45.780709Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**full fine-tuning**\n\n👉 전체 ResNet18 모델의 모든 레이어를 학습 대상에 포함시킨 것이야.\n\n즉, 너는 🔧 full fine-tuning을 하고 있는 상태\n\n| 작업                | 네 코드에서                                               | 의미                                     |\n| ----------------- | ---------------------------------------------------- | -------------------------------------- |\n| `pretrained=True` | `resnet18 = models.resnet18(pretrained=True)`        | ImageNet으로 사전학습된 가중치 불러옴               |\n| 마지막 fc 레이어 교체     | `resnet18.fc = nn.Sequential(...)`                   | 너의 분류 task (5개 클래스)에 맞게 수정             |\n| 전체 레이어 학습         | `optimizer = optim.Adam(resnet18.parameters(), ...)` | 모든 레이어를 다시 학습 → **full fine-tuning** ✅ |\n\n\n[ conv1 → conv2_x → ... → conv5_x → avgpool → fc(500→2) ]\n     ↑                                           ↑\n  모든 레이어 학습됨                        fc는 새로 학습\n\n\n**💡 팁:**\n\n너가 lr=0.009로 설정했는데, 이건 꽤 큰 값이야.\n\nResNet 같은 사전학습 모델에서는 보통 **1e-4 ~ 1e-3** 정도로 시작하는 걸 권장해:\n\noptimizer = optim.Adam(resnet18.parameters(), lr=1e-4)\n\n→ 과적합이나 발산이 생기면 lr부터 줄여봐.","metadata":{}},{"cell_type":"markdown","source":"**✅ 예시: feature extractor로 쓰고 싶으면?**\n\n**1. 모든 레이어 동결 (학습 안 함)**\n\n\nfor param in resnet18.parameters():\n\n    param.requires_grad = False\n\n**2. 마지막 레이어만 새로 학습**\n\n\nresnet18.fc = nn.Sequential(\n\n    nn.Linear(num_ftrs, 500),\n    \n    nn.ReLU(),\n    \n    nn.Linear(500, 2)\n)\n\n**3. optimizer는 마지막 레이어만 학습**\n\n   \noptimizer = torch.optim.Adam(resnet18.fc.parameters(), lr=1e-4)\n","metadata":{}},{"cell_type":"code","source":"# ResNet18의 마지막 fully connected layer의 입력 feature 수를 가져옴\nnum_ftrs = resnet18.fc.in_features\n\n# ResNet18의 마지막 fully connected layer(fc)를 새로 정의함\n# 원래는 1000개의 ImageNet 클래스를 예측하던 구조 → 지금은 5개 클래스 예측으로 변경\n\nresnet18.fc = nn.Sequential(\n    nn.Linear(num_ftrs, 500),  # 먼저 500차원으로 줄이는 hidden layer 추가\n    nn.Linear(500, 5)          # 마지막 출력층: 5개 클래스 분류\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:28:05.064827Z","iopub.execute_input":"2025-07-17T16:28:05.065287Z","iopub.status.idle":"2025-07-17T16:28:05.071433Z","shell.execute_reply.started":"2025-07-17T16:28:05.065263Z","shell.execute_reply":"2025-07-17T16:28:05.070809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_cycle 함수는 모델을 주어진 데이터셋(trainloader)으로 학습하고, 주기적으로 loss와 accuracy를 출력 및 시각화 \ndef train_cycle(model, optimizer, criterion, p_iter, n_epochs):\n    model.train()              # 모델을 학습 모드로 설정 (Dropout, BatchNorm 등 활성화)\n    model.to(DEVICE)           # 모델을 지정한 디바이스(CPU 또는 GPU)로 이동\n\n    itr = 1                    # 반복(iteration) 횟수 카운터\n    total_loss = 0            # p_iter만큼 누적한 손실값 저장용\n    loss_list = []            # 시각화를 위한 손실 기록 리스트\n    acc_list = []             # 시각화를 위한 정확도 기록 리스트\n\n    # 에폭 단위 루프 (전체 학습 반복 횟수만큼)\n    for epoch in range(n_epochs):\n\n        # 배치 단위 루프\n        for batch_no, data in enumerate(trainloader, 0): # trainloader는 여러 장의 이미지와 라벨을 묶은 **배치(batch)**를 반복해서 줘.batch_no = 0부터 시작하는 인덱스\n\n            # 배치에서 이미지와 라벨 추출\n            samples, labels = data['image'], data['label'] # samples는 [batch_size, 채널, 높이, 너비] 형태의 이미지 데이터\n            samples = samples.to(DEVICE)  # 입력 이미지를 디바이스로 전송, 32개\n            labels = labels.to(DEVICE)    # 정답 레이블도 디바이스로 전송, 32개\n            \n            optimizer.zero_grad()         # 이전 배치에서 누적된 gradient 초기화\n\n            output = model(samples)       # 모델의 forward pass 실행 → 예측값 출력,  순전파 (forward pass) 라고 부르는 단계\n            loss = criterion(output, labels)  # 손실 함수 계산\n            loss.backward()              # 손실에 대한 gradient 계산 (역전파)\n            optimizer.step()             # 계산된 gradient로 가중치 업데이트\n\n            total_loss += loss.item()    # p_iter만큼 손실 누적\n\n            # print(loss)        # tensor(0.3456, grad_fn=<NllLossBackward>)\n            # print(loss.item()) # 0.3456 (float)\n\n            # 일정 iteration마다 로그 출력 및 정확도 계산\n            if itr % p_iter == 0:\n                pred = torch.argmax(output, dim=1)   # 가장 높은 확률값의 클래스 선택\n                correct = pred.eq(labels)            # 예측과 정답 비교 (True/False)\n                acc = torch.mean(correct.float())    # 정확도 계산 (정답 맞춘 비율)\n                acc = acc.to('cpu')                  # 시각화를 위해 CPU로 이동\n\n                # 로그 출력\n                print('[Epoch {}/{}] Iteration {} -> Train Loss: {:.4f}, Accuracy: {:.3f}'\n                      .format(epoch+1, n_epochs, itr, total_loss/p_iter, acc))\n                \n                # 그래프용 기록 저장\n                loss_list.append(total_loss / p_iter)\n                acc_list.append(acc)\n\n                total_loss = 0  # 손실 초기화 (다음 p_iter을 위한)\n\n            itr += 1  # 반복 횟수 증가\n\n    # 훈련 결과 시각화\n    plt.plot(loss_list[1:], label='loss')       # 손실 그래프\n    plt.plot(acc_list[1:], label='accuracy')    # 정확도 그래프\n    plt.legend()\n    plt.title('training loss and accuracy')\n    plt.show()\n\n    print('Finished Training')  # 학습 완료 메시지","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:06:30.571440Z","iopub.execute_input":"2025-07-17T15:06:30.572035Z","iopub.status.idle":"2025-07-17T15:06:30.579680Z","shell.execute_reply.started":"2025-07-17T15:06:30.572011Z","shell.execute_reply":"2025-07-17T15:06:30.578796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 모델 훈련\nEPOCHS_resnet18 = 7\nDEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu'\n\ntrain_cycle(resnet18, optimizer= optimizer, criterion= criterion, p_iter= 200, n_epochs= EPOCHS_resnet18)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:30:17.880617Z","iopub.execute_input":"2025-07-17T15:30:17.880880Z","iopub.status.idle":"2025-07-17T15:43:52.675136Z","shell.execute_reply.started":"2025-07-17T15:30:17.880863Z","shell.execute_reply":"2025-07-17T15:43:52.674440Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_fn(model, train_loader, val_loader, optimizer, criterion, device, n_epochs=7):\n    model.to(device)\n\n    for epoch in range(n_epochs):\n        model.train()\n        train_loss, train_correct = 0.0, 0\n\n        for images, labels in train_loader:\n            images, labels = images.to(device), labels.to(device)\n\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            train_loss += loss.item() * images.size(0)\n            train_correct += (outputs.argmax(1) == labels).sum().item()\n\n        train_loss /= len(train_loader.dataset)\n        train_acc = train_correct / len(train_loader.dataset)\n\n        model.eval()\n        val_loss, val_correct = 0.0, 0\n\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(device), labels.to(device)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n\n                val_loss += loss.item() * images.size(0)\n                val_correct += (outputs.argmax(1) == labels).sum().item()\n\n        val_loss /= len(val_loader.dataset)\n        val_acc = val_correct / len(val_loader.dataset)\n\n        print(f\"Epoch [{epoch+1}/{n_epochs}]\")\n        print(f\"  🟦 Train Loss: {train_loss:.4f}, Accuracy: {train_acc:.4f}\")\n        print(f\"  🟥 Val   Loss: {val_loss:.4f}, Accuracy: {val_acc:.4f}\")\n\n\n            # 🟢 성능이 좋아졌을 때 , early stop 기능 부분 \n    #     if val_loss < best_val_loss:\n    #         best_val_loss = val_loss\n    #         best_model_wts = copy.deepcopy(model.state_dict())\n    #         counter = 0\n    #         print(f\"✅ Improved! Saving model at epoch {epoch+1}\")\n    #     else:\n    #         counter += 1\n    #         print(f\"⚠️ No improvement ({counter}/{patience})\")\n    #         if counter >= patience:\n    #             print(\"🛑 Early stopping triggered!\")\n    #             break\n    \n    # # 🔄 가장 좋았던 가중치로 복원\n    # model.load_state_dict(best_model_wts)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:44:54.639813Z","iopub.execute_input":"2025-07-17T16:44:54.640461Z","iopub.status.idle":"2025-07-17T16:44:54.648180Z","shell.execute_reply.started":"2025-07-17T16:44:54.640442Z","shell.execute_reply":"2025-07-17T16:44:54.647420Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**🎯 그래서 이건 fine-tuning이야?**\n\n아직 아님. → 선택에 따라 달라져.\n\n🔹 모든 레이어가 학습 가능하게 두면 → Fine-tuning (전체 재학습)\n\n🔹 일부만 학습 가능하게 두면 → Partial fine-tuning\n\n🔹 모두 freeze, fc만 학습하면 → Feature extractor (transfer learning)","metadata":{}},{"cell_type":"code","source":"def test_model(model, test_data, criterion):\n    model.eval()   # 모델을 평가(evaluation) 모드로 설정 (Dropout, BatchNorm 등 비활성화)\n\n    acc_list = []   # 배치별 정확도 저장 리스트\n    loss_list = []  # 배치별 손실 저장 리스트\n    val_loss = 0    # 전체 검증 손실 누적 변수\n\n    with torch.no_grad():   # 평가 시에는 기울기 계산하지 않음 (메모리 절약 및 속도 향상) -> 모델이 이미 학습된 가중치를 그대로 사용해서 결과만 예측하면 돼.\n        for batch_no, data in tqdm(enumerate(test_data, 0)):   # 검증 데이터셋을 배치 단위로 반복\n            samples, labels = data['image'], data['label']     # 배치에서 이미지와 라벨 추출\n            samples = samples.to(DEVICE)   # GPU 또는 CPU 등 지정한 디바이스로 데이터 이동\n            labels = labels.to(DEVICE)     # 라벨도 같은 디바이스로 이동\n            \n            output = model(samples)         # 모델에 입력 넣어 예측값(output) 계산\n            loss = criterion(output, labels) # 손실 함수로 배치별 손실 계산\n            \n            val_loss += loss.item()         # 손실 값 누적 (float 값으로 저장) -> 전체 평가 손실 누적 및 평균 계산 목적\n\n            pred = torch.argmax(output, dim=1)  # 예측값 중 가장 높은 확률을 가진 클래스 선택\n            correct = pred.eq(labels)            # 예측과 실제 라벨 비교 (True/False tensor)\n            acc = torch.mean(correct.float())   # 배치 정확도 계산 (맞은 비율)\n            acc = acc.to('cpu')                  # 정확도를 CPU로 옮겨서 리스트에 저장\n            acc_list.append(acc)                 # 정확도 저장\n            loss = loss.to('cpu')                # 손실도 CPU로 옮김\n            loss_list.append(loss)               # 손실 저장\n\n    # 배치별 정확도와 손실의 평균값 출력\n    print(f'Mean acc = {np.mean(acc_list): .3f}. Mean loss = {np.mean(loss_list): .3f}')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:21:11.854030Z","iopub.execute_input":"2025-07-17T15:21:11.854274Z","iopub.status.idle":"2025-07-17T15:21:11.860765Z","shell.execute_reply.started":"2025-07-17T15:21:11.854253Z","shell.execute_reply":"2025-07-17T15:21:11.860011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:21:33.571587Z","iopub.execute_input":"2025-07-17T15:21:33.572262Z","iopub.status.idle":"2025-07-17T15:21:33.575472Z","shell.execute_reply.started":"2025-07-17T15:21:33.572241Z","shell.execute_reply":"2025-07-17T15:21:33.574798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import copy\nimport torch.optim as optim\nimport torch.nn as nn\n\nnum_folds = 3\nnum_epochs = 5\n\n# 예: resnet18 생성 함수 (가중치 초기화 포함)\ndef create_model():\n    model = torchvision.models.resnet18(pretrained=True)  # 또는 True 필요에 따라\n    # model.fc = nn.Linear(model.fc.in_features, num_classes)  # 출력 레이어 수정\n    # return model\n\n    # ResNet18의 마지막 fully connected layer의 입력 feature 수를 가져옴\n    num_ftrs = model.fc.in_features\n    \n    # ResNet18의 마지막 fully connected layer(fc)를 새로 정의함\n    # 원래는 1000개의 ImageNet 클래스를 예측하던 구조 → 지금은 5개 클래스 예측으로 변경\n    \n    model.fc = nn.Sequential(\n        nn.Linear(num_ftrs, 500),  # 먼저 500차원으로 줄이는 hidden layer 추가\n        nn.Linear(500, 5)          # 마지막 출력층: 5개 클래스 분류\n    )\n    return model\n\n# K-Fold 시작\nfor fold in range(num_folds):\n    print(f'\\n===== Fold {fold} =====')\n\n    # 데이터 분할 (기존 코드 참고)\n    train_data = train_df[train_df['fold'] != fold].reset_index(drop=True)\n    val_data   = train_df[train_df['fold'] == fold].reset_index(drop=True)\n    \n    X_train = train_data.drop(columns=['label'])\n    y_train = train_data[['label']]\n    X_val = val_data.drop(columns=['label'])\n    y_val = val_data[['label']]\n\n    train_dataset = DiseaseData(X_train, y_train, transform=train_transforms)\n    val_dataset   = DiseaseData(X_val, y_val, transform=test_transforms)\n    \n    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=2)\n    val_loader   = torch.utils.data.DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=2)\n\n    # **매 폴드마다 모델 새로 생성, 초기화**\n    model = create_model().to(device)\n    optimizer = optim.Adam(model.parameters(), lr=1e-4)\n    criterion = nn.CrossEntropyLoss()\n\n    best_val_loss = float('inf')\n    best_model_wts = copy.deepcopy(model.state_dict())\n    patience = 3\n    counter = 0\n\n    for epoch in range(num_epochs):\n        # train_fn 함수 내부를 epoch 단위로 나눠서 사용하거나,\n        # train_fn에 epoch 1씩 돌리도록 수정 가능\n        \n    # 훈련 함수 부분\n        model.train()\n        train_loss, train_correct = 0.0, 0\n        for images, labels in train_loader:\n            images, labels = images.to(device), labels.to(device)\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            train_loss += loss.item() * images.size(0)\n            train_correct += (outputs.argmax(1) == labels).sum().item()\n        train_loss /= len(train_loader.dataset)\n        train_acc = train_correct / len(train_loader.dataset)\n\n\n        # 평가 함수 부분\n        model.eval()\n        val_loss, val_correct = 0.0, 0\n        with torch.no_grad():\n            for images, labels in val_loader:\n                images, labels = images.to(device), labels.to(device)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n                val_loss += loss.item() * images.size(0)\n                val_correct += (outputs.argmax(1) == labels).sum().item()\n        val_loss /= len(val_loader.dataset)\n        val_acc = val_correct / len(val_loader.dataset)\n\n        print(f\"Epoch {epoch+1}/{num_epochs} - Train loss: {train_loss:.4f}, acc: {train_acc:.4f} | Val loss: {val_loss:.4f}, acc: {val_acc:.4f}\")\n\n        # Early Stopping & best model 저장\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            best_model_wts = copy.deepcopy(model.state_dict())\n            counter = 0\n        else: # 손실이 커지는 구간에서 멈추기\n            counter += 1\n            if counter >= patience:\n                print(\"Early stopping!\")\n                break\n\n    # 가장 좋았던 모델 가중치로 복원\n    model.load_state_dict(best_model_wts)\n\n    # 여기서 모델 저장하거나, 평가, 예측 등에 사용 가능\n    torch.save(model.state_dict(), f'model_fold{fold}.pth')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T16:55:39.346724Z","iopub.execute_input":"2025-07-17T16:55:39.347455Z","iopub.status.idle":"2025-07-17T17:31:32.358684Z","shell.execute_reply.started":"2025-07-17T16:55:39.347433Z","shell.execute_reply":"2025-07-17T17:31:32.357774Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for fold in range(3):\n    print(f'\\n==== Fold {fold} 테스트 중 ====')\n    \n    model = create_model().to(DEVICE)  # 새 모델 객체 생성\n    model.load_state_dict(torch.load(f'model_fold{fold}.pth'))  # 해당 fold에서 저장한 모델 로드\n    \n    test_model(model, testloader, criterion)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T17:47:10.459533Z","iopub.execute_input":"2025-07-17T17:47:10.459840Z","iopub.status.idle":"2025-07-17T17:48:38.847008Z","shell.execute_reply.started":"2025-07-17T17:47:10.459813Z","shell.execute_reply":"2025-07-17T17:48:38.846261Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\nprint('ResNet18:')\ntest_model(resnet18, testloader, criterion)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T15:43:52.676719Z","iopub.execute_input":"2025-07-17T15:43:52.677021Z","iopub.status.idle":"2025-07-17T15:44:24.450571Z","shell.execute_reply.started":"2025-07-17T15:43:52.676990Z","shell.execute_reply":"2025-07-17T15:44:24.449466Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**1. 기본**\n\n> 101it [00:29,  3.39it/s]\n> \n> **Mean acc =  0.804. Mean loss =  0.674**\n> \n> CPU times: user 3.83 s, sys: 1.63 s, total: 5.47 s\n> \n> Wall time: 29.9 s\n\n| 변경 사항             | 효과                                      |\n| ----------------- | --------------------------------------- |\n| `lr=0.009 → 1e-4` | 너무 빠른 학습 → 더 안정적이고 정밀한 파라미터 업데이트        |\n| `bs=32 → 64`      | gradient noise 감소 → 학습 안정성 증가, 성능 향상 가능 |\n\n**2. train_transforms 증강 -> 0.02정도 떨어짐**\n\n>ResNet18:\n>\n>101it [01:01,  1.64it/s]\n>\n>**Mean acc =  0.782. Mean loss =  0.690**\n>\n>CPU times: user 4.06 s, sys: 1.52 s, total: 5.58 s\n>\n>Wall time: 1min 1s\n\n\ntrain_transforms = transforms.Compose([\n    transforms.Resize((224,224)),\n    \n    # 좌우 반전 + 추가 증강\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.3),        # 위아래 뒤집기 추가\n    \n    transforms.RandomRotation(degrees=15),       # ±15도 회전\n    transforms.ColorJitter(\n        brightness=0.2,                           # 밝기 변화\n        contrast=0.2,                             # 대비 변화\n        saturation=0.2,                           # 채도 변화\n        hue=0.1                                  # 색조 변화\n    ),\n    \n    transforms.RandomAffine(\n        degrees=0,                               # 회전 없음 (이미 RandomRotation 썼으니)\n        translate=(0.1,0.1),                     # 이동 (가로세로 10% 내외)\n        scale=(0.8, 1.2),                        # 크기 변화\n        shear=10                                # 시계 방향으로 최대 10도 전단\n    ),\n\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.430, 0.496, 0.313],\n        std=[0.219, 0.224, 0.201]\n    )\n])\n\n**3. train_transforms 증강2 : 조금 좋아짐** \n\n> ResNet18:\n> \n>101it [00:31,  3.18it/s]\n> \n>**Mean acc =  0.808. Mean loss =  0.646**\n> \n>CPU times: user 3.85 s, sys: 1.66 s, total: 5.51 s\n> \n>Wall time: 31.8 s\n\ntrain_transforms = transforms.Compose([\ntransforms.Resize(256),\n\ntransforms.RandomCrop(224),\n\ntransforms.RandomHorizontalFlip(),\n\ntransforms.ColorJitter(0.1, 0.1, 0.1, 0.05), # 너무 과하지 않게\n\ntransforms.ToTensor(),\n\ntransforms.Normalize(\n    mean=[0.430, 0.496, 0.313],\n    std=[0.219, 0.224, 0.201]\n)\n])\n\n**4. kfold + early stop : 최고점, 가장 성능 좋음**\n\n> ==== Fold 2 테스트 중 ====\n> \n>101it [00:29,  3.46it/s]\n> \n>**Mean acc =  0.861. Mean loss =  0.401**","metadata":{}},{"cell_type":"markdown","source":"# Save models","metadata":{}},{"cell_type":"code","source":"READY_MODELS_PATH = 'saved_models/'\n\nos.makedirs(os.path.join('../working/', READY_MODELS_PATH))\ntorch.save(resnet18.state_dict(), os.path.join(READY_MODELS_PATH, 'resnet18.pt'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:42:05.970668Z","iopub.execute_input":"2025-07-16T06:42:05.971401Z","iopub.status.idle":"2025-07-16T06:42:06.057985Z","shell.execute_reply.started":"2025-07-16T06:42:05.971376Z","shell.execute_reply":"2025-07-16T06:42:06.057211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sub = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/sample_submission.csv')\n\nsub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T06:46:07.898001Z","iopub.execute_input":"2025-07-16T06:46:07.898293Z","iopub.status.idle":"2025-07-16T06:46:07.918675Z","shell.execute_reply.started":"2025-07-16T06:46:07.898274Z","shell.execute_reply":"2025-07-16T06:46:07.918107Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Predictions","metadata":{}},{"cell_type":"code","source":"[x for x in os.listdir('/kaggle/input/cassava-leaf-disease-classification') if 'test' in x]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T08:26:27.095061Z","iopub.execute_input":"2025-07-16T08:26:27.095828Z","iopub.status.idle":"2025-07-16T08:26:27.114171Z","shell.execute_reply.started":"2025-07-16T08:26:27.095804Z","shell.execute_reply":"2025-07-16T08:26:27.113484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(model, image_path, device='cuda'):\n    model.eval()\n    image = Image.open(image_path).convert('RGB')\n    input_tensor = test_transforms(image).unsqueeze(0).to(device)  # 배치 차원 추가 및 device 이동\n    \n    with torch.no_grad():\n        output = model(input_tensor)\n        pred = torch.argmax(output, dim=1).item()\n    return pred","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T08:36:05.523614Z","iopub.execute_input":"2025-07-16T08:36:05.524171Z","iopub.status.idle":"2025-07-16T08:36:05.528342Z","shell.execute_reply.started":"2025-07-16T08:36:05.524151Z","shell.execute_reply":"2025-07-16T08:36:05.527575Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict(resnet18, '/kaggle/input/cassava-leaf-disease-classification/test_images/2216849948.jpg')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T08:36:06.767964Z","iopub.execute_input":"2025-07-16T08:36:06.768489Z","iopub.status.idle":"2025-07-16T08:36:06.789527Z","shell.execute_reply.started":"2025-07-16T08:36:06.768465Z","shell.execute_reply":"2025-07-16T08:36:06.788936Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame([{\n    'image_id': '2216849948.jpg',\n    'label': predict(resnet18, '/kaggle/input/cassava-leaf-disease-classification/test_images/2216849948.jpg')\n}])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T08:38:44.621295Z","iopub.execute_input":"2025-07-16T08:38:44.622113Z","iopub.status.idle":"2025-07-16T08:38:44.639795Z","shell.execute_reply.started":"2025-07-16T08:38:44.622077Z","shell.execute_reply":"2025-07-16T08:38:44.639096Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T08:38:47.108167Z","iopub.execute_input":"2025-07-16T08:38:47.108837Z","iopub.status.idle":"2025-07-16T08:38:47.115572Z","shell.execute_reply.started":"2025-07-16T08:38:47.108813Z","shell.execute_reply":"2025-07-16T08:38:47.114779Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T08:39:09.504805Z","iopub.execute_input":"2025-07-16T08:39:09.505052Z","iopub.status.idle":"2025-07-16T08:39:09.517727Z","shell.execute_reply.started":"2025-07-16T08:39:09.505035Z","shell.execute_reply":"2025-07-16T08:39:09.516877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}