{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\n\nfrom scipy.ndimage.measurements import center_of_mass\nfrom tqdm.notebook import tqdm\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\nfrom multiprocessing import cpu_count\nfrom PIL import Image\n\nimport os\nimport glob\nimport imageio\nimport cv2\nimport gc\nimport joblib\nimport json","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"IMG_HEIGHT = 600\nIMG_WIDTH = 800\nIMG_SIZE = 600\nN_CHANNELS = 3\nCHUNK_SIZE = 1024\nN_VAL_IMGS = 128 * 8","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"IMG_DIR = '/kaggle/input/cassava-leaf-disease-classification/train_images/'\n\ntrain = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/train.csv')\n# add image file paths to train DataFrame\ntrain['file_path'] = train['image_id'].apply(lambda image_id: f'{IMG_DIR}{image_id}')\n\nN_IMGS = len(train)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# read label to disease map and add disease to train DataFrame\nwith open('/kaggle/input/cassava-leaf-disease-classification/label_num_to_disease_map.json') as f:\n    label_map = json.load(f)\n    \ntrain['disease'] = train['label'].apply(str).apply(label_map.get)\ntrain['label+disease'] = train[['label', 'disease']].apply(lambda row: f'{row[\"label\"]} ({row[\"disease\"]})', axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Show train DataFrame head\npd.options.display.max_colwidth = 99\ndisplay(train.head())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Class Inbalance Visualization\n\nThe above graph clearly shows the dataset is unbalanced. The dataset consists 61% of label number 3."},{"metadata":{"trusted":true},"cell_type":"code","source":"display(train['label+disease'].value_counts())\n\n\nlabel_counts = train['label'].value_counts()\nprint(f'Label 3 contributes to {int(label_counts.loc[3] / label_counts.sum() * 100)}%')\n\nplt.figure(figsize=(20,6))\ntrain['label+disease'].value_counts().sort_index().plot(kind='bar')\nplt.xticks(size=14, rotation=15)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Split train in train and val df"},{"metadata":{},"cell_type":"markdown","source":"The dataset is split in 5 stratified folds, meaning the class inbalance is preserved and equal in all folds. Using 5 folds means each fold will consist of 80% training data and 20% validation data, resulting in each sample being used exactly once over all folds."},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_fold_data():\n    # split in train test\n    LABELS = train['label'].values\n    SKF = StratifiedKFold(n_splits=5, random_state=42, shuffle=True)\n    fold_data = dict()\n    for idx, (train_fold_idxs, val_fold_idxs) in enumerate(SKF.split(np.arange(N_IMGS), LABELS)):\n        fold_data[f'fold_{idx}'] = {\n            'train': {\n                'file_paths': train.loc[train_fold_idxs, 'file_path'].values,\n                'labels': train.loc[train_fold_idxs, 'label'].values,\n                'image_ids': train.loc[train_fold_idxs, 'image_id'].values,\n            },\n            'val': {\n                'file_paths': train.loc[val_fold_idxs, 'file_path'].values,\n                'labels': train.loc[val_fold_idxs, 'label'].values,\n                'image_ids': train.loc[val_fold_idxs, 'image_id'].values,\n            },\n        }\n        \n    return fold_data\n\nfold_data = get_fold_data()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# show number of train and validation images per fold and the label count in the validation set\nfor fold, (k, v) in enumerate(fold_data.items()):\n    print(f'--- FOLD {fold} ---')\n    print(f'length train: {len(v[\"train\"][\"labels\"])}, length validation: {len(v[\"val\"][\"labels\"])}')\n    print(f'validation label count: {pd.Series(v[\"val\"][\"labels\"]).value_counts().to_dict()}')\n    print()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Show Training Images"},{"metadata":{"trusted":true},"cell_type":"code","source":"def show_train_images(rows, cols):\n    fig, axes = plt.subplots(nrows=rows, ncols=cols, figsize=(cols*6, rows*6))\n    for idx, fp, label, disease in train.loc[:rows*cols-1, ['file_path', 'label', 'disease']].itertuples(name=None):\n        img = imageio.imread(fp)\n        axes[idx // cols, idx % cols].imshow(img)\n        axes[idx // cols, idx % cols].set_title(f'Label: {label}, disease: {disease}')\n            \nshow_train_images(5,4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def split_in_chunks(data):\n    return [data[i:i + CHUNK_SIZE] for i in range(0, len(data), CHUNK_SIZE)]\n\n# split fold file paths and labels in chunks for TFRecords\nfold_data_chunks = dict()\nfor fold_k, fold_v in fold_data.items():\n    fold_data_chunks[fold_k] = dict()\n    \n    # Every fold consists of a train and val dictionary consisting of a list with the file paths, labels and image_ids\n    fold_data_chunks[fold_k] = {\n        'train': {\n            'file_paths': split_in_chunks(fold_v['train']['file_paths']),\n            'labels': split_in_chunks(fold_v['train']['labels']),\n            'image_ids': split_in_chunks(fold_v['train']['image_ids']),\n        },\n        'val': {\n            'file_paths': split_in_chunks(fold_v['val']['file_paths']),\n            'labels': split_in_chunks(fold_v['val']['labels']),\n            'image_ids': split_in_chunks(fold_v['val']['image_ids']),\n        }\n    }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check for correct split of images, as shown every images occurs 5 times in 5 folds and once in the validation set\ndef occurances():\n    res_all = []\n    res_val = []\n    for fold_v in fold_data_chunks.values():\n        for k in ['train', 'val']:\n            for chunk in fold_v[k]['file_paths']:\n                for fp in chunk:\n                    res_all.append(fp)\n\n        for chunk in fold_v['val']['file_paths']:\n            for fp in chunk:\n                res_val.append(fp)\n\n    s_all = pd.Series(res_all).value_counts()\n    s_val = pd.Series(res_val).value_counts()\n    print(f's_all min: {s_all.min()}, s_all max: {s_all.max()}')\n    print(f's_val min: {s_val.min()}, s_val max: {s_val.max()}')\n    \noccurances()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Make TFRecords\n\nCreate TFRecords consisting of JPEGS for image augmentation purposed, as the JPEGS are sized 800\\*600. A random 600\\*600 square can be selected from the JPEG, using a slightly different image every epoch. Using TFRecords instead of individual JPEGS is also more efficient, as a TFRecord consists oof 1024 images and can be read at once, this reduces the amount of disk reads significantly."},{"metadata":{"trusted":true},"cell_type":"code","source":"def process_img(file_path):\n    img = tf.io.read_file(file_path).numpy()\n    \n    return img","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_tfrecords(fold_data_chunks):\n    for fold_k, fold_v in fold_data_chunks.items():\n        print('*'*10, fold_k.upper(), '*'*10)\n        # Try to make output folder\n        try:\n            os.makedirs(f'./{fold_k}/val')\n            os.makedirs(f'./{fold_k}/train')\n        except:\n            print(f'folders already created')\n\n        for k, v in fold_v.items():\n            # make TFRecords for each chunk\n            data = zip(v['file_paths'], v['labels'], v['image_ids'])\n            for idx, (file_paths_chunk, labels_chunk, image_ids_chunk) in tqdm(enumerate(data), total=len(v['labels'])):\n                # read the images in parallel using joblib\n                jobs = [joblib.delayed(process_img)(fp) for fp in file_paths_chunk]\n                processed_images_chunk = joblib.Parallel(n_jobs=cpu_count(), verbose=0)(jobs)\n                \n                # write the raw JPEGS to a TFRecord, including the label and image_id\n                with tf.io.TFRecordWriter(f'./{fold_k}/{k}/batch_{idx}.tfrecords') as file_writer:\n                    for image, label, image_id in zip(processed_images_chunk, labels_chunk, image_ids_chunk):\n                        record_bytes = tf.train.Example(features=tf.train.Features(feature={\n                            'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),\n                            'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),\n                            'image_id': tf.train.Feature(bytes_list=tf.train.BytesList(value=[str.encode(image_id)])),\n                        })).SerializeToString()\n                        file_writer.write(record_bytes)\n    \n\nmake_tfrecords(fold_data_chunks)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Check TFRecords\n\nQuick check to see if everything went OK. Show the first train and validation batch"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check spectograms\ndef decode_tfrecord(record_bytes):\n    features = tf.io.parse_single_example(record_bytes, {\n        'image': tf.io.FixedLenFeature([], tf.string),\n        'label': tf.io.FixedLenFeature([], tf.int64),\n        'image_id': tf.io.FixedLenFeature([], tf.string),\n    })\n\n    image = tf.io.decode_jpeg(features['image'])    \n    image = tf.cast(image, tf.float32)\n    image = image / 255\n    \n    label = tf.cast(features['label'], tf.int32)\n    \n    image_id = features['image_id']\n    \n    return image, label, image_id\n\ndef show_tfrecords(file_path):\n    rows = 4\n    cols = 3\n    fig, ax = plt.subplots(rows, cols, figsize=(cols*6, rows*6))\n    tfrecord = tf.data.TFRecordDataset(file_path)\n    for idx, (image, label, image_id) in enumerate(tfrecord.map(decode_tfrecord)):\n        image = tf.cast(image * 255, tf.uint8)\n        image = tf.squeeze(image)\n        row, col = idx // cols, idx % cols\n        ax[row, col].imshow(image)\n        ax[row, col].title.set_text(f'Label {label}, image_id: {image_id.numpy().decode()}')\n        if idx == rows * cols - 1:\n            break\n    plt.show()\n\nprint('TRAIN BATCH')\nshow_tfrecords(f'./fold_0/train/batch_0.tfrecords')\nprint('VAL BATCH')\nshow_tfrecords(f'./fold_0/val/batch_0.tfrecords')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}