{"cells":[{"metadata":{},"cell_type":"markdown","source":"# **Cassava Competition**\n이 competition은 열대에 서식하는 카사바 나무에 발병하는 질병 4개와 건강한 상태, 즉 5가지의 class들을 분류하는 classification 문제입니다. 주어지는 이미지는 모두 600x800 사이즈이고, test set은 공개되지 않습니다. 각 질병들의 특징 등은 아래의 EDA를 참고합니다. https://www.kaggle.com/ihelon/cassava-leaf-disease-exploratory-data-analysis\n\n이 코드는 이 베이스라인 코드로부터 여러 방법들을 통하여 리더보드 점수를 향상한 코드입니다. https://www.kaggle.com/vkehfdl1/for-korean-cassava\n\n주석에 자세한 설명을 기재하였습니다.\n\n질문은 언제든지 자유롭게 해주시면 됩니다. tensorflow, numpy, pandas, scikit-learn은 공식 문서를 참고하면 더욱 정확한 정보를 빠르게 찾을 수 있습니다.\n\n* Tensorflow - https://www.tensorflow.org/api_docs/python/tf\n* numpy - https://numpy.org/doc/1.19/\n* pandas - https://pandas.pydata.org/pandasdocs/stable/reference/index.html\n* scikit-learn - https://scikit-learn.org/stable/modules/classes.html"},{"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\nimport seaborn as sns\nfrom tqdm import tqdm\nfrom sklearn.utils import shuffle\nfrom sklearn.utils import class_weight\nfrom sklearn.preprocessing import minmax_scale\nimport random\nimport cv2\nfrom imgaug import augmenters as iaa\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, ReduceLROnPlateau, EarlyStopping\nfrom tensorflow.keras.experimental import CosineDecay\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.applications import EfficientNetB4\nfrom tensorflow.keras.layers.experimental.preprocessing import RandomCrop,CenterCrop, RandomRotation\n\n##추가\nimport tensorflow_addons as tfa\nfrom tensorflow.keras.optimizers import Adam\nfrom sklearn.model_selection import StratifiedKFold\nimport imgaug as ia\nimport imgaug.augmenters as iaa\nimport tensorflow_datasets as tfds\nimport json\nfrom tensorflow.keras.activations import softmax\nfrom tensorflow.keras.losses import CategoricalCrossentropy","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# **데이터 전처리 (이미지 로드)**"},{"metadata":{},"cell_type":"markdown","source":"여기서 2019 데이터셋과 2020 데이터셋을 합친 데이터를 사용합니다. https://www.kaggle.com/tahsin/cassava-leaf-disease-merged"},{"metadata":{"trusted":true},"cell_type":"code","source":"training_folder = '../input/cassava-leaf-disease-merged/train/' #훈련할 이미지들이 있는 폴더\nsamples_df = pd.read_csv('../input/cassava-leaf-disease-merged/merged.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 = samples_df.sort_values(by='source') #2019년 대회의 데이터와 이 대회의 데이터로 분류","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n아래의 코드는 500x500의 크기보다 더 작은 사진들을 제거하기 위해서 그러한 사진들을 찾는 코드입니다. \n아래 small_list의 값과 동일하니 꼭 실행할 필요가 없습니다.\n\"\"\"\nsmall_list = []\nfor i in range (4940):\n    img = np.array(Image.open(samples_df.filepath.iloc[i])).shape\n    if (img[0] < 500 or img[1] < 500):\n        print(img)\n        small_list.append(samples_df.iloc[i].name)\nprint(small_list)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"small_list = [23219, 22850, 22832, 21743, 21982, 21932, 21419, 21697, 22125, 22036, 22308, 25510, 25270, 25319, 26274, 26301, 25771, 25964, 25988, 25898, 25882, 24104, 24897, 25080, 24959, 24592, 24496]\n#이미지 사이즈가 500x500보다 작은 사진들","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples_df = samples_df.drop(small_list, axis=0) #이미지 사이즈가 500x500보다 작은 사진들을 버림","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples_df = samples_df.drop(['source'],axis=1) #필요없어진 source를 버림","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples_df = shuffle(samples_df, random_state=42) #데이터를 무작위로 섞음","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples_df.groupby('label').label.count() #각 label들의 개수를 출력","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples_df.head() #samples_df의 앞부분을 예시로 출력","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n각 label들의 개수가 서로 많이 다르기 때문에, stratifiedKFold를 사용하여 train 데이터와 validation 데이터를 나누어줍니다.\n여기서는 4 fold를 사용했으며, 한 번에 한 fold만을 훈련합니다. (kaggle 노트북은 한 번에 연속 9시간까지만 사용할 수 있기 때문입니다)\n팀을 이루거나 여러 계정을 이루어 각각 돌려서 합치는 방법을 추천드립니다. \n참고 : https://sgmath.tistory.com/61\n\"\"\"\nfinal_train_index = list() #어떤 train 데이터를 고를지 저장하는 list를 정의\nfinal_test_index = list() #어떤 test 데이터를 고를지 저장하는 list를 정의\nskf = StratifiedKFold(n_splits=4, random_state=42) #4 fold를 하는 stratifiedKFold를 선언 (validation 25%)\nfor train_index, test_index in skf.split(samples_df.filepath, samples_df.label): #stratifiedKFold를 작동하는 반복문\n    print(\"TRAIN:\", train_index.shape, \"TEST:\", test_index.shape) #각 fold에 해당하는 데이터를 출력\n    final_train_index.append(train_index) #final_train_index에 각 fold에 해당하는 데이터를 입력\n    final_test_index.append(test_index) #final_test_index에 각 fold에 해당하는 데이터를 입력","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n0으로 되어 있는 곳의 숫자를 바꾸는 것으로 몇 번째 fold의 데이터로 훈련과 validation을 할 것인지 결정할 수 있습니다. \n한 번 훈련 후 숫자를 바꾸어 총 4번 (fold의 개수) 훈련하면 됩니다.\n\"\"\"\ntraining_df = samples_df.iloc[final_train_index[0]] #train 데이터를 만듬\nvalidation_df = samples_df.iloc[final_test_index[0]] #validation 데이터를 만듬","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 8 # 배치 사이즈를 설정\nimage_size = 500 # 이미지의 크기를 설정\ninput_shape = (500, 500, 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 데이터를 불러옴\n\n#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":"\"\"\"\nimgaug를 사용해서 heavy한 augmentation을 해주는 코드. 실행하면 오히려 성능이 나빠질 수 있습니다. \n\"\"\"\n\n\"\"\"augmenter = iaa.Sequential([\n    iaa.Fliplr(0.5),\n    iaa.Flipud(0.5),\n    iaa.CoarseDropout((0,0.03), size_percent=(0.02,0.25)),\n    iaa.Cutout(nb_iterations=(0,2), size=0.2, fill_mode=\"gaussian\", fill_per_channel=True),\n    iaa.GaussianBlur(sigma=(0,0.5)),\n    iaa.MultiplyBrightness((0.75,1.25)),\n    iaa.GammaContrast((0.75,1.25)),\n    iaa.PiecewiseAffine(scale=(0,0.03)),\n    \n], random_order=True)\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\nimgaug를 tensorflow dataset에 적용시키는 함수\n\"\"\"\n\n\"\"\"def augment_fn():\n    def augment(images, labels):\n        img_dtype = images.dtype\n        img_shape = tf.shape(images)\n        images = tf.numpy_function(augmenter.augment_images,\n                                   [images],\n                                   img_dtype)\n        images = tf.reshape(images, shape = img_shape)\n        return images, labels\n    return augment\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\nimgaug로 augmentation한 이미지를 적용시켜주는 코드입니다\n\"\"\"\n#training_data_batches = training_data_batches.map(augment_fn())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\nimgaug를 적용한 이미지를 출력해 볼 수 있는 코드입니다.\n\"\"\"\n\"\"\"def view_image(ds):\n    image, label = next(iter(ds)) # extract 1 batch from the dataset\n    image = image.numpy()\n    label = label.numpy()\n\n    fig = plt.figure(figsize=(22, 22))\n    for i in range(8):\n        ax = fig.add_subplot(2, 4, i+1, xticks=[], yticks=[])\n        ax.imshow(image[i])\n        ax.set_title(f\"Label: {label[i]}\")\nview_image(training_data_batches)\"\"\"","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위의 숨겨진 코드를 열어보면 이 코드에 강력한 augmentation인 imgaug를 작동시킬 수 있는 코드가 있습니다. \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        layers.experimental.preprocessing.RandomContrast((0.2,0.2))#이미지를 대비를 랜덤으로 조금씩 바꿈\n    ]\n)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# **모델 만들기 및 학습**"},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\n이 베이스라인에서는 transfer learning을 사용합니다. 미리 훈련되어 있는 이미지용 모델을 불러와서 그 모델의 뒤쪽에 나만의 모델을 추가한 뒤 학습하는 방식입니다.\n직접 수많은 레이어의 모델을 디자인하는 것은 어렵기 때문에 이러한 방법을 사용합니다.\n여기서는 구글의 EfficientNetB4를 사용합니다. 이 모델에 대한 자세한 내용은 https://arxiv.org/pdf/1905.11946.pdf 이 논문을 참고하세요.\n\n주의!! imagenet 가중치 값을 다운받기 위하여 우측 상단 |< 표시를 누르고 setting에서 Internet을 켜줘야합니다.\n\"\"\"\nefficientnet = EfficientNetB4(weights=\"imagenet\", #이미지넷 가중치 값을 불러와 적용\n                              include_top=False, \n                              input_shape=input_shape, \n                              drop_connect_rate=dropout_rate)#efficientnetB4 모델을 로드\nefficientnet.trainable=True # efficientnetb4의 학습을 허용.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = 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 = 18#에폭 수를 설정\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 방법을 사용\n\ncallbacks = [ModelCheckpoint(filepath='efficientnetb4_2019dataset.h5', monitor='val_loss', save_best_only=True),#가장 validation loss가 낮은 에폭의 모델을 .h5 파일로 저장 \n            EarlyStopping(monitor='val_loss', patience = 3, verbose=1)]#정해진 에폭이 되기 전에 3번의 에폭동한 validation loss가 향상되지 않으면 학습을 종료\n\nmodel.compile(loss=\"sparse_categorical_crossentropy\", optimizer=tfa.optimizers.Lookahead(Adam(cosine_decay)), metrics=[\"accuracy\"]) #loss는 sparse_categorical_crossentropy, optimizer는 Adam을 사용. 각 에폭당 정확도를 통해 모델의 성능을 모니터링함 ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# **여러가지 loss**\n위에서 쓴 categorical crossentropy 뿐만 아니라 여러가지 loss를 사용해 볼 수도 있습니다. 이 대회의 사진들은 해당하는 병이 잘못 입력이 되어있는 것들이 많습니다. 이것을 noisy labels라고 부릅니다. 이러한 상황에서 사용할 수 있는 여러가지 방법들이 소개되어 있습니다. \n1. symmetric cross entropy --> 논문 : https://arxiv.org/abs/1908.06112  코드 : https://www.kaggle.com/c/cassava-leaf-disease-classification/discussion/208324\n2. bi-tempered loss --> https://www.kaggle.com/c/cassava-leaf-disease-classification/discussion/209773\n3. taylor cross entropy loss --> https://www.kaggle.com/c/cassava-leaf-disease-classification/discussion/209782"},{"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":"# **Submission**\n\nTTA 및 실제 데이터 submission은 이곳에서 보실 수 있습니다! \nhttps://www.kaggle.com/vkehfdl1/for-korean-lb-0-895-submission\n\n**도움이 되었다면 업보트 upvote 한 번 씩 부탁드립니다! 감사합니다!**"}],"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}