{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"## Dataset - https://www.kaggle.com/competitions/dogs-vs-cats-redux-kernels-edition/data","metadata":{"id":"PfSCI03_7Vye","execution":{"iopub.status.busy":"2023-07-30T13:55:37.748815Z","iopub.execute_input":"2023-07-30T13:55:37.753690Z","iopub.status.idle":"2023-07-30T13:55:37.760963Z","shell.execute_reply.started":"2023-07-30T13:55:37.753601Z","shell.execute_reply":"2023-07-30T13:55:37.759693Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Набор данных\nПапка \"train\" содержит 25 000 изображений собак и кошек. Каждое изображение в этой папке имеет метку как часть имени файла. Тестовая папка содержит 12 500 изображений, названных в соответствии с числовым идентификатором.\nДля каждого изображения в тестовом наборе мы должны предсказать вероятность того, что изображение является собакой (1 = собака, 0 = кошка).","metadata":{"id":"LuZslXdSKBu7"}},{"cell_type":"markdown","source":"## Метод решения задачи классификации\n\nДля решения поставленной задачи классификации котов и собак мы будем использовать предобученную модель на базе ResNet-50 с заменой последнего слоя.","metadata":{"id":"gOLRG8x9KYS2"}},{"cell_type":"markdown","source":"### Загрузка библиотек","metadata":{"id":"TLCxhE4HKtsu"}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nfrom tqdm import tqdm\n\n%matplotlib inline\nfrom matplotlib import pyplot as plt\n\nimport cv2\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras.applications import resnet50\nfrom tensorflow.keras.optimizers import RMSprop\n\n\nfrom keras import Sequential\nfrom keras.layers import Dense, Conv2D, MaxPooling2D, Flatten, BatchNormalization, Dropout","metadata":{"id":"zpwwG3qm8gzJ","execution":{"iopub.status.busy":"2023-07-30T13:55:26.517398Z","iopub.execute_input":"2023-07-30T13:55:26.518111Z","iopub.status.idle":"2023-07-30T13:55:37.740991Z","shell.execute_reply.started":"2023-07-30T13:55:26.518065Z","shell.execute_reply":"2023-07-30T13:55:37.739036Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### \"Распаковка\" файлов\n","metadata":{"id":"SpcCGGdUK9Nt"}},{"cell_type":"code","source":"import zipfile\n\nzip_files = ['test', 'train']\n\nfor zip_file in zip_files:\n    with zipfile.ZipFile(\"../input/dogs-vs-cats-redux-kernels-edition/{}.zip\".format(zip_file),\"r\") as z:\n        z.extractall(\".\")\n        print(\"{} unzipped\".format(zip_file))","metadata":{"execution":{"iopub.status.busy":"2023-07-30T13:55:37.762816Z","iopub.execute_input":"2023-07-30T13:55:37.763240Z","iopub.status.idle":"2023-07-30T13:55:54.529863Z","shell.execute_reply.started":"2023-07-30T13:55:37.763200Z","shell.execute_reply":"2023-07-30T13:55:54.528698Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Функции загрузки и предобработки данных\n\nДалее будут реализованы 3 функции:\n\n- **load_image** - функция загрузки и предобработки изображения под единый размер, а затем препроцессинг с использованием RESNET50 (изображение преобразуется из RGB в BGR, затем каждый цветовой канал центрируется по нулю относительно набора данных ImageNet, скалирование не проводится);\n\n- **fit_generator** - функция-генератор загрузки трэйновых данных, в процессе которой определятся батчи, а также происходит своего рода OHE, когда изображениям с названием dog присваивается \"1\", а изображениям с \"cat\" - 0.\n\n- **predict_generator** - функция-генератор загрузки тестовых данных.","metadata":{"id":"KxPknc-HLEvC"}},{"cell_type":"code","source":"from random import shuffle\nfrom glob import glob\n\nIMG_SIZE = (224, 224)\n\ntrain_files = glob('/kaggle/working/train/*.jpg')\ntest_files = glob('/kaggle/working/test/*.jpg')\n\ndef load_image(path, target_size=IMG_SIZE):\n    img = cv2.imread(path)[...,::-1]\n    img = cv2.resize(img, target_size)\n    return resnet50.preprocess_input(img)\n\ndef fit_generator(files, batch_size=64):\n    batch_size = min(batch_size, len(files))\n    while True:\n        shuffle(files)\n        for k in range(len(files) // batch_size):\n            i = k * batch_size\n            j = i + batch_size\n            if j > len(files):\n                j = - j % len(files)\n            x = np.array([load_image(path) for path in files[i:j]])\n            y = np.array([1. if os.path.basename(path).startswith('dog') else 0.\n                          for path in files[i:j]])\n            yield (x, y)\n\ndef predict_generator(files):\n    while True:\n        for path in files:\n            yield np.array([load_image(path)])","metadata":{"id":"FLX04YhG9V5N","execution":{"iopub.status.busy":"2023-07-30T13:55:54.533050Z","iopub.execute_input":"2023-07-30T13:55:54.533450Z","iopub.status.idle":"2023-07-30T13:55:54.695626Z","shell.execute_reply.started":"2023-07-30T13:55:54.533411Z","shell.execute_reply":"2023-07-30T13:55:54.694530Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Визуализация датасета","metadata":{"id":"-gHhIsjqPab1"}},{"cell_type":"code","source":"fig = plt.figure(figsize=(16, 8))\nfor i, path in enumerate(train_files[:10], 1):\n    subplot = fig.add_subplot(2, 5, i)\n    subplot.set_title('%s' % path.split('/')[-1])\n    img = cv2.imread(path)[...,::-1]\n    img = cv2.resize(img, IMG_SIZE)\n    plt.imshow(img)","metadata":{"id":"jgKKJ3l0F-TM","outputId":"7f382b7e-c800-46c5-d8c6-9b249e490450","execution":{"iopub.status.busy":"2023-07-30T13:55:54.697231Z","iopub.execute_input":"2023-07-30T13:55:54.697857Z","iopub.status.idle":"2023-07-30T13:55:57.461387Z","shell.execute_reply.started":"2023-07-30T13:55:54.697819Z","shell.execute_reply":"2023-07-30T13:55:57.459913Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Загружаем предобученную модель\n\nЗа основу возьмем RESNET50. Данная модель создавалась для решения проблемы затухающих градиентов.","metadata":{"id":"_WseilgZQtwg"}},{"cell_type":"code","source":"base_model = resnet50.ResNet50(weights='imagenet',\n                               include_top=False,\n                               pooling='max',\n                               input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3))","metadata":{"id":"0QxCctsLPqB3","execution":{"iopub.status.busy":"2023-07-30T13:55:57.462694Z","iopub.execute_input":"2023-07-30T13:55:57.463051Z","iopub.status.idle":"2023-07-30T13:56:06.017493Z","shell.execute_reply.started":"2023-07-30T13:55:57.463019Z","shell.execute_reply":"2023-07-30T13:56:06.016311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"base_model.summary()","metadata":{"id":"htcoiAa0Vqm8","outputId":"05d49479-6583-49b9-816f-85e6daa5655c","execution":{"iopub.status.busy":"2023-07-30T13:56:06.019079Z","iopub.execute_input":"2023-07-30T13:56:06.019507Z","iopub.status.idle":"2023-07-30T13:56:06.443144Z","shell.execute_reply.started":"2023-07-30T13:56:06.019468Z","shell.execute_reply":"2023-07-30T13:56:06.442268Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Вносим изменения в модель RESNET50\n\nДобавляем в нее полносвязный слой с бинарным выходом. В качестве функции активации поставим сигмоиду и добавим L1-регуляризатор для управления переобучением модели.","metadata":{"id":"WQpopAY317N_"}},{"cell_type":"code","source":"for layer in base_model.layers:\n    layer.trainable = False\n\nx = base_model.layers[-5].output\nx = tf.keras.layers.Flatten()(x)\nx = tf.keras.layers.Dense(1,\n                          activation='sigmoid',\n                          kernel_regularizer=tf.keras.regularizers.l1(0.001))(x)\n\nmodel = tf.keras.Model(inputs=base_model.input, outputs=x, name='cats_vs_dogs')","metadata":{"id":"q6tYCK-3XDU8","execution":{"iopub.status.busy":"2023-07-30T13:56:06.444362Z","iopub.execute_input":"2023-07-30T13:56:06.444786Z","iopub.status.idle":"2023-07-30T13:56:06.573744Z","shell.execute_reply.started":"2023-07-30T13:56:06.444746Z","shell.execute_reply":"2023-07-30T13:56:06.572802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.summary()","metadata":{"id":"0gobEY1MXgw6","outputId":"d1b97ef8-d98a-42a9-ae5c-6a2243f6bcd6","execution":{"iopub.status.busy":"2023-07-30T13:56:06.574989Z","iopub.execute_input":"2023-07-30T13:56:06.575380Z","iopub.status.idle":"2023-07-30T13:56:07.049723Z","shell.execute_reply.started":"2023-07-30T13:56:06.575343Z","shell.execute_reply":"2023-07-30T13:56:07.048825Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Компиляция модели\n\nВ качестве оптимизация возьмем \"Adam\", потери будем считать logloss'ом.","metadata":{"id":"twV5lLmK3Kr1"}},{"cell_type":"code","source":"model.compile(optimizer='Adam',\n              loss='binary_crossentropy',\n              metrics='accuracy')","metadata":{"id":"txwiTkXOXd8I","execution":{"iopub.status.busy":"2023-07-30T13:56:07.055449Z","iopub.execute_input":"2023-07-30T13:56:07.055876Z","iopub.status.idle":"2023-07-30T13:56:07.119496Z","shell.execute_reply.started":"2023-07-30T13:56:07.055837Z","shell.execute_reply":"2023-07-30T13:56:07.118565Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"val_samples = 5000\n\nshuffle(train_files)\nvalidation_data = next(fit_generator(train_files[:val_samples], val_samples))\ntrain_data = fit_generator(train_files[val_samples:])","metadata":{"id":"Ixwaf2AIWWch","execution":{"iopub.status.busy":"2023-07-30T13:56:07.120767Z","iopub.execute_input":"2023-07-30T13:56:07.121143Z","iopub.status.idle":"2023-07-30T13:56:38.367699Z","shell.execute_reply.started":"2023-07-30T13:56:07.121107Z","shell.execute_reply":"2023-07-30T13:56:38.366616Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Переда началом обучения модели пропишем коллбэки, чтобы получать информацию о внутреннем состоянии модели в процессе обучения.","metadata":{"id":"JZLc0k_y4lgO"}},{"cell_type":"code","source":"reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss',\n                                                 factor=0.2,\n                                                 patience=3,\n                                                 verbose=0,\n                                                 mode='auto')\n\ncheckpointer = tf.keras.callbacks.ModelCheckpoint('cats_dogs_resnet50.hdf5',\n                                                monitor='val_loss',\n                                                save_best_only=True,\n                                                save_weights_only=False,\n                                                period=4,\n                                                verbose=1,\n                                                mode='auto')\n\n\nhistory = model.fit(train_data,\n                    steps_per_epoch=10,\n                    epochs=20,\n                    validation_data=validation_data,\n                    callbacks=[reduce_lr, checkpointer])","metadata":{"id":"sZMN8OJRXqrQ","outputId":"25eea557-0ba1-4e9c-983d-a788dbcea878","execution":{"iopub.status.busy":"2023-07-30T13:56:38.369257Z","iopub.execute_input":"2023-07-30T13:56:38.369663Z","iopub.status.idle":"2023-07-30T14:05:04.547980Z","shell.execute_reply.started":"2023-07-30T13:56:38.369605Z","shell.execute_reply":"2023-07-30T14:05:04.546833Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Построим график точности и потерь во время обучения.","metadata":{"id":"SdWNFaFtYLdr"}},{"cell_type":"code","source":"def plot_accuracy_and_loss(train_model):\n    hist = train_model.history\n    acc = hist['accuracy']\n    val_acc = hist['val_accuracy']\n    loss = hist['loss']\n    val_loss = hist['val_loss']\n    epochs = range(len(acc))\n    f, ax = plt.subplots(1,2, figsize=(16,4))\n    ax[0].plot(epochs, acc, 'g', label='Training accuracy', marker='D', mec='b', ms='3')\n    ax[0].plot(epochs, val_acc, 'r', label='Validation accuracy', marker='o', mec='yellow', ms='3')\n    ax[0].set_title('Training and validation accuracy')\n    ax[0].legend()\n    ax[1].plot(epochs, loss, 'g', label='Training loss', marker='D', mec='b', ms='3')\n    ax[1].plot(epochs, val_loss, 'r', label='Validation loss', marker='o', mec='yellow', ms='3')\n    ax[1].set_title('Training and validation loss')\n    ax[1].legend()\n    plt.show()\nplot_accuracy_and_loss(history)","metadata":{"id":"IulSckAmNoZo","outputId":"2f3dad33-ce3a-4961-8ec1-a13a164cec2b","execution":{"iopub.status.busy":"2023-07-30T14:05:04.550409Z","iopub.execute_input":"2023-07-30T14:05:04.550864Z","iopub.status.idle":"2023-07-30T14:05:05.181503Z","shell.execute_reply.started":"2023-07-30T14:05:04.550823Z","shell.execute_reply":"2023-07-30T14:05:05.180430Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('Validation loss:', min(history.history['val_loss']))\nprint('Validation accuracy:', max(history.history['val_accuracy']))","metadata":{"id":"PMOUm2zxYp5G","outputId":"560b42d9-6ad3-4583-8191-252a0c3b0a40","execution":{"iopub.status.busy":"2023-07-30T14:05:05.182982Z","iopub.execute_input":"2023-07-30T14:05:05.183861Z","iopub.status.idle":"2023-07-30T14:05:05.190409Z","shell.execute_reply.started":"2023-07-30T14:05:05.183821Z","shell.execute_reply":"2023-07-30T14:05:05.189381Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Предсказания на проверочной выборке","metadata":{"id":"zR5-QZ3uDPfV"}},{"cell_type":"code","source":"test_pred = model.predict(\n    predict_generator(test_files), steps=len(test_files))\n\npredicted_class_indices = np.argmax(test_pred, axis = 1)","metadata":{"id":"D2P1WYNMDAbJ","outputId":"df2c2cce-91b2-4495-a18e-1e8d39546651","execution":{"iopub.status.busy":"2023-07-30T14:13:38.150813Z","iopub.execute_input":"2023-07-30T14:13:38.151307Z","iopub.status.idle":"2023-07-30T14:16:11.376082Z","shell.execute_reply.started":"2023-07-30T14:13:38.151266Z","shell.execute_reply":"2023-07-30T14:16:11.374954Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = plt.figure(figsize=(16, 8))\nfor i, (path, score) in enumerate(zip(test_files[:10], test_pred[:10]), 1):\n    subplot = fig.add_subplot(2, 5, i)\n    if score  > 0.5:\n          str_label='Dog'\n    elif score < 0.5:\n          str_label='Cat'\n    subplot.set_title(\"Label: {}\".format(str_label))\n    img = cv2.imread(path)[...,::-1]\n    img = cv2.resize(img, IMG_SIZE)\n    subplot.imshow(img)","metadata":{"id":"VtGAYBgPDNIx","outputId":"1d6f5b87-5965-47ce-8580-f146ed35ae17","execution":{"iopub.status.busy":"2023-07-30T14:08:28.220930Z","iopub.execute_input":"2023-07-30T14:08:28.221919Z","iopub.status.idle":"2023-07-30T14:08:31.067197Z","shell.execute_reply.started":"2023-07-30T14:08:28.221881Z","shell.execute_reply":"2023-07-30T14:08:31.065990Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import re\n\nwith open('sample_submission.csv', 'w') as dst:\n    dst.write('id,label\\n')\n    for path, score in zip(test_files, test_pred):\n        dst.write('%s,%.1f\\n' % (re.search('(\\d+).jpg$', path).group(1), score))","metadata":{"id":"NajPU6pAnNf4","execution":{"iopub.status.busy":"2023-07-30T14:08:31.068974Z","iopub.execute_input":"2023-07-30T14:08:31.070079Z","iopub.status.idle":"2023-07-30T14:08:31.185482Z","shell.execute_reply.started":"2023-07-30T14:08:31.070037Z","shell.execute_reply":"2023-07-30T14:08:31.184360Z"},"trusted":true},"execution_count":null,"outputs":[]}]}