{"cells":[{"metadata":{},"cell_type":"markdown","source":"If you are not Korean, please check out this notebook. https://www.kaggle.com/vkehfdl1/cassava-baseline-with-detailed-explanation"},{"metadata":{},"cell_type":"markdown","source":"# **Cassava Competition Baseline**\n\n이 competition은 열대에 서식하는 카사바 나무에 발병하는 질병 4개와 건강한 상태, 즉 5가지의 class들을 분류하는 classification 문제입니다. 주어지는 이미지는 모두 600x800 사이즈이고, test set은 공개되지 않습니다. \n각 질병들의 특징 등은 아래의 EDA를 참고합니다.\nhttps://www.kaggle.com/ihelon/cassava-leaf-disease-exploratory-data-analysis\n\n이 베이스라인 코드는 다음 커널을 참고하여 재구성하였습니다.\nhttps://www.kaggle.com/frlemarchand/efficientnet-aug-tf-keras-for-cassava-diseases\n\n주석에 자세한 설명을 기재하였습니다.\n\n질문은 언제든지 자유롭게 해주시면 됩니다. tensorflow, numpy, pandas, scikit-learn은 공식 문서를 참고하면 더욱 정확한 정보를 빠르게 찾을 수 있습니다.\n* Tensorflow - https://www.tensorflow.org/api_docs/python/tf\n* numpy - https://numpy.org/doc/1.19/\n* pandas - https://pandas.pydata.org/pandas-docs/stable/reference/index.html\n* scikit-learn - https://scikit-learn.org/stable/modules/classes.html"},{"metadata":{},"cell_type":"markdown","source":"# 라이브러리 import"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nfrom PIL import Image\nimport os\nimport matplotlib.pyplot as plt\nfrom sklearn.utils import shuffle\nfrom sklearn.utils import class_weight\nfrom sklearn.preprocessing import minmax_scale\nimport random\nimport cv2\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential, Model\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.layers import Dense, Dropout, Activation, Input, BatchNormalization, GlobalAveragePooling2D\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\nfrom tensorflow.keras.experimental import CosineDecay\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.layers.experimental.preprocessing import RandomCrop,CenterCrop, RandomRotation\nfrom tensorflow.keras.optimizers import Adam","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 데이터 전처리 (이미지 로드)"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"training_folder = '../input/cassava-leaf-disease-classification/train_images/' #훈련할 이미지들이 있는 폴더\nsamples_df = pd.read_csv('../input/cassava-leaf-disease-classification/train.csv') #훈련할 이미지의 이름 및 각 label 데이터 로드\nsamples_df[\"filepath\"] = training_folder+samples_df[\"image_id\"] #사진을 불러오기 쉽도록 폴더와 이미지의 이름을 합쳐 경로를 생성\nsamples_df = samples_df.drop(['image_id'],axis=1) #필요없는 이미지 이름을 모두 버림","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples_df = shuffle(samples_df, random_state=42) #데이터를 무작위로 섞음\ntrain_size = int(len(samples_df)*0.8) # 훈련에 사용할 데이터의 크기를 지정\ntraining_df = samples_df[:train_size] # 훈련 데이터셋을 만들어줌\nvalidation_df = samples_df[train_size:] # validation 데이터셋을 만들어줌","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 8 # 배치 사이즈를 설정\nimage_size = 512 # 이미지의 크기를 설정\ninput_shape = (image_size, image_size, 3) #이미지의 사이즈 정의 (컬러 이미지이기 때문에 한 화소당 3개의 데이터가 필요)\ndropout_rate = 0.4 #드롭아웃 비율 정의\nclasses_to_predict = sorted(training_df.label.unique()) #예측해야 하는 클래스 수 정의, 여기서는 5개","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\ntrain 데이터와 validation 데이터를 텐서플로우 Dataset으로 정의합니다.\n텐서플로우 Dataset는 동적으로 데이터를 불러와, 너무 많은 데이터가 메모리에 쓰여지는 일을 방지하여 퍼포먼스가 향상됩니다.\n더 자세한 내용은 아래의 링크를 참조하세요.\nhttps://www.tensorflow.org/guide/data_performance?hl=ko\n\"\"\"\ntraining_data = tf.data.Dataset.from_tensor_slices((training_df.filepath.values, training_df.label.values))\nvalidation_data = tf.data.Dataset.from_tensor_slices((validation_df.filepath.values, validation_df.label.values))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image_and_label_from_path(image_path, label): #이미지 데이터를 불러와 텐서 (array와 비슷한 형태)로 변환하는 함수\n    img = tf.io.read_file(image_path) #이미지 경로의 파일을 읽음\n    img = tf.image.decode_jpeg(img, channels=3) #이미지를 array 데이터로 변환하여 저장\n    img = tf.image.random_crop(img, size=[image_size,image_size,3]) # 이미지를 랜덤으로 원하는 사이즈로 잘라줌. 중앙만 자르고 싶다면 central_crop 사용.\n    return img, label\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE #메모리 동적 할당을 위한 AUTOTUNE\ntraining_data = training_data.map(load_image_and_label_from_path, num_parallel_calls=AUTOTUNE) #train 데이터를 불러옴\nvalidation_data = validation_data.map(load_image_and_label_from_path,num_parallel_calls=AUTOTUNE) #validation 데이터를 불러옴","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#train 및 validation 데이터를 훈련하기 좋게 batch로 자름\ntraining_data_batches = training_data.shuffle(buffer_size=1000).batch(batch_size).prefetch(buffer_size=AUTOTUNE)\nvalidation_data_batches = validation_data.shuffle(buffer_size=1000).batch(batch_size).prefetch(buffer_size=AUTOTUNE)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n이미지를 Augmentation 해주는 레이어를 만들어줍니다. 모델을 만들 때 augmentation layer을 넣으면 자동으로 이미지를 다양하게 변환하여 줍니다.\n더 많은 augmentation을 적용해보고 싶으면 https://www.tensorflow.org/api_docs/python/tf/keras/layers/experimental/preprocessing 이 링크를 참조하세요.\n또한, imgaug, albumentation과 같은 강력한 augmentation 라이브러리도 살펴보세요. \n\"\"\"\ndata_augmentation_layers = tf.keras.Sequential(\n    [\n        layers.experimental.preprocessing.RandomFlip(\"horizontal_and_vertical\"), #랜덤으로 이미지를 좌우로 뒤집어줌.\n        layers.experimental.preprocessing.RandomRotation(0.25), #이미지를 좌우로 25% 이내로 랜덤으로 돌립니다. \n        layers.experimental.preprocessing.RandomZoom((-0.2, 0)), #이미지를 0~20%만큼 랜덤으로 축소합니다.\n        \n    ]\n)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 모델 만들기 및 학습"},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n이 베이스라인에서는 transfer learning을 사용합니다. 미리 훈련되어 있는 이미지용 모델을 불러와서 그 모델의 뒤쪽에 나만의 모델을 추가한 뒤 학습하는 방식입니다.\n직접 수많은 레이어의 모델을 디자인하는 것은 어렵기 때문에 이러한 방법을 사용합니다.\n여기서는 구글의 EfficientNetB0를 사용합니다. 이 모델에 대한 자세한 내용은 https://arxiv.org/pdf/1905.11946.pdf 이 논문을 참고하세요.\n\n주의!! imagenet 가중치 값을 다운받기 위하여 우측 상단 |< 표시를 누르고 setting에서 Internet을 켜줘야합니다.\n\"\"\"\nefficientnet = EfficientNetB0(weights=\"imagenet\", #이미지넷 가중치 값을 불러와 적용\n                              include_top=False, \n                              input_shape=input_shape, \n                              drop_connect_rate=dropout_rate) #efficientnetB0 모델을 로드\nefficientnet.trainable=True # efficientnetb0의 학습을 허용. 만약 False로 지정할 시에 정확도는 떨어지지만 학습 속도가 매우 빨라짐.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n자신만의 CNN 모델을 직접 만들어 보아도 괜찮습니다.\n\"\"\"\nmodel = Sequential() #새 Sequential 모델을 만듬 \nmodel.add(Input(shape=input_shape)) #인풋을 이미지 사이즈로 설정\nmodel.add(data_augmentation_layers) #이미지 augumentation 레이어 추가\nmodel.add(efficientnet) # efficientnetb0 추가\nmodel.add(layers.GlobalAveragePooling2D()) # 풀링 레이어를 추가\nmodel.add(layers.Dropout(dropout_rate)) # 드롭아웃 레이어를 추가\nmodel.add(Dense(len(classes_to_predict), activation=\"softmax\")) #마지막 덴스 레이어를 추가. 예측할 클래스의 개수만큼이 아웃풋이 된다. \nmodel.summary() #모델 확인","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"epochs = 30 #에폭 수를 설정합니다.\ndecay_steps = int(round(len(training_df)/batch_size))*epochs\ncosine_decay = CosineDecay(initial_learning_rate=1e-4, decay_steps=decay_steps, alpha=0.3) #learning rate를 에폭이 지날수록 점점 줄여나가는 cosine decay 방법을 사용합니다. \ncallbacks = [ModelCheckpoint(filepath='mymodel.h5', monitor='val_loss', save_best_only=True), #가장 validation loss가 낮은 에폭의 모델을 .h5 파일로 저장합니다. \n            EarlyStopping(monitor='val_loss', patience = 5, verbose=1)] #정해진 에폭이 되기 전에 5번의 에폭동한 validation loss가 향상되지 않으면 학습을 종료합니다. \n\nmodel.compile(loss=\"sparse_categorical_crossentropy\", optimizer=Adam(cosine_decay), metrics=[\"accuracy\"]) #loss는 sparse_categorical_crossentropy, optimizer는 Adam을 사용합니다. 각 에폭당 정확도를 통해 모델의 성능을 모니터링합니다, ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit(training_data_batches, #모델을 학습합니다. \n                  epochs = epochs, \n                  validation_data=validation_data_batches,\n                  callbacks=callbacks)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 실제 Test 데이터 예측"},{"metadata":{},"cell_type":"markdown","source":"이 아래부터의 내용은 실제 submission 할 때 필요한 내용입니다. kaggle에서 submission을 할 때 모든 노트북이 다시 돌아가서 훈련을 다시 하게 되므로, .h5로 저장된 파일을 불러와서 사용하여 시간을 크게 절약할 수 있습니다. 이를 위한 내용은 아래와 같습니다. \n\n1. 저장된 .h5 파일을 output -> /kaggle/working 에서 찾아 오른쪽의 점 세개를 눌러 download를 클릭하여 다운로드 합니다.\n2. kaggle Data 탭에 들어가 New Dataset을 클릭합니다.\n3. Dataset 이름을 입력하고 .h5 파일을 찾아 업로드한 후, Create 버튼을 눌러 데이터셋을 만듭니다.\n4. submission을 위한 새로운 notebook을 만들고, 우측 상단의 Add data를 누른 후 Your Dataset으로 간 후, 방금 만든 데이터셋에서 파란색 Add 버튼을 눌러 notebook에 추가해줍니다.\n5. model = tf.keras.models.load_model('파일경로') 코드를 이용하여 모델을 가져옵니다. \n참고 : 우측의 Data 탭에서 Copy File path라고 나오는 작은 버튼을 누르면 파일 경로를 쉽게 복사할 수 있습니다. "},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\nmodel = tf.keras.models.load_model('파일경로')\n\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#위의 train 데이터를 로드한 방법과 비슷하게 test 데이터도 로드해줍니다.\ntest_folder = '../input/cassava-leaf-disease-classification/test_images/' \nsubmission_df = pd.DataFrame(columns={\"image_id\",\"label\",\"filepath\"})\nsubmission_df[\"image_id\"] =  os.listdir(test_folder) #자동으로 테스트 폴더 안의 이미지 이름들을 image_id에 입력함\nsubmission_df[\"label\"] = 0\nsubmission_df[\"filepath\"] = test_folder+submission_df['image_id']\nsubmission_df = submission_df.drop(['image_id'],axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image(image_path): #이미지 데이터를 불러와 텐서 (array와 비슷한 형태)로 변환하는 함수\n    img = tf.io.read_file(image_path) #이미지 경로의 파일을 읽음\n    img = tf.image.decode_jpeg(img, channels=3) #이미지를 array 데이터로 변환하여 저장\n    img = tf.image.random_crop(img, size=[image_size,image_size,3]) # 이미지를 랜덤으로 원하는 사이즈로 잘라줌. 중앙만 자르고 싶다면 central_crop 사용.\n    img = np.reshape(img, [-1,512,512,3]) #이미지를 3차원에서 4차원 텐서로 변환\n    return img","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def test_predict(filepath):\n    local_image = load_image(filepath) #함수를 통하여 이미지를 불러옴\n    predictions = model.predict(local_image) #각 클래스의 확률을 훈련한 모델으로 예측\n    final_prediction = np.argmax(predictions) #가장 확률이 높은 클래스를 최종 예측 결과로 도출\n    return final_prediction #예측결과 반환","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def predictions_over_image(filepath):\n    predictions = [] #에측값을 저장하는 리스트\n    for path in filepath:   \n        predictions.append(test_predict(path)) # 각 사진마다 예측값을 저장한다\n    return predictions #예측값 변환","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df[\"label\"] = predictions_over_image(submission_df[\"filepath\"]) # 모델로 예측한 값을 submission DataFrame에 입력","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df.to_csv(\"submission.csv\", index=False) # submission csv 파일 생성","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 점수 향상을 위해 시도해볼 수 있는 것들\n\n1. 더 큰 모델 사용 (EfflicientNetB4 등)\n2. 더 다양한 augmentation 사용 (Dropout, Cutmix, Mixup 등)\n3. TTA (Test Time Augmentation) 사용    https://inspaceai.github.io/2019/12/20/Test_Time_Augmentation_Review/\n4. Stratified Kfold 사용 : 데이터가 한 클래스로 치우쳐져 있기 때문에 개수에 맞춰서 train 데이터와 validation 데이터를 나누면 좋다.\n5. 새로운 Callbacks 전략 사용\n6. Symmetric Cross Entropy와 같은 다른 loss fuction 사용    https://arxiv.org/abs/1908.06112\n7. Lookahead나 RAdam 등 다른 optimizer 사용\n8. 외부 데이터 셋 활용    https://www.kaggle.com/tahsin/cassava-leaf-disease-merged\n9. 다양한 Ensemble 기법 활용 : Bagging, voting, stacking 등\n10. 새로운 model 디자인"},{"metadata":{},"cell_type":"markdown","source":"**이 노트북이 도움이 되었다면 업보트 한 번 씩 부탁드립니다. 감사합니다!**"}],"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}