{"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":"markdown","source":"# Imports","metadata":{"_uuid":"fe76d1d1ded592430e7548feacfa38dc42f085d9"}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport random\nimport os\nfrom keras.preprocessing.image import ImageDataGenerator, load_img\nfrom keras.utils import to_categorical\nfrom sklearn.model_selection import train_test_split","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-01-15T09:56:45.972152Z","iopub.execute_input":"2022-01-15T09:56:45.973024Z","iopub.status.idle":"2022-01-15T09:56:45.979693Z","shell.execute_reply.started":"2022-01-15T09:56:45.972612Z","shell.execute_reply":"2022-01-15T09:56:45.978007Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"IMAGE_SIZE=(128, 128)\nIMAGE_CHANNELS=3","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:56:45.981752Z","iopub.execute_input":"2022-01-15T09:56:45.982522Z","iopub.status.idle":"2022-01-15T09:56:45.992203Z","shell.execute_reply.started":"2022-01-15T09:56:45.982441Z","shell.execute_reply":"2022-01-15T09:56:45.990648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Determine categories for every single train file\n* 1 means **BAD** coffee, 2 means **GOOD** coffee\n* DataFrame creation\n* DataFrame => Two-dimensional, size-mutable, potentially heterogeneous tabular data.","metadata":{}},{"cell_type":"code","source":"files = os.listdir(\"../input/coffee-images/coffees/train/\")\ncategories = []\nfor file in files:\n    category = file.split('.')[0]\n    if category == 'bad':\n        categories.append(1)\n    else:\n        categories.append(0)\n\ndf = pd.DataFrame({\n    'file': files,\n    'category': categories\n})","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","execution":{"iopub.status.busy":"2022-01-15T09:56:45.993541Z","iopub.execute_input":"2022-01-15T09:56:45.995811Z","iopub.status.idle":"2022-01-15T09:56:46.011316Z","shell.execute_reply.started":"2022-01-15T09:56:45.993852Z","shell.execute_reply":"2022-01-15T09:56:46.009911Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Total count of BAD and GOOD coffees","metadata":{"_uuid":"a999484fc35b73373fafe2253ae9db7ff46fdb90"}},{"cell_type":"code","source":"df['category'].value_counts().plot.bar()","metadata":{"_uuid":"fa26f0bc7a6d835a24989790b20f3c6f32946f45","execution":{"iopub.status.busy":"2022-01-15T09:56:46.015152Z","iopub.execute_input":"2022-01-15T09:56:46.015403Z","iopub.status.idle":"2022-01-15T09:56:46.284914Z","shell.execute_reply.started":"2022-01-15T09:56:46.015348Z","shell.execute_reply":"2022-01-15T09:56:46.283761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* **Sequential model** => for cases, where we send exactly one input tensor and expect exactly one output tensor\n* **conv2D** => 2D convolutional layer which creates convolutional kernel\n* **activation function relu** => applies the rectified linear unit activation function\n* **BatchNormalization** => a transformation that hold the mean output close to 0 and the output standard deviation close to 1\n* **MaxPooling2D** => downsamples the input\n* **Dropout** => randomly sets input units to 0 with a frequency of rate at each step during training time, which helps prevent overfitting","metadata":{}},{"cell_type":"code","source":"from keras.models import Sequential\nfrom keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, Activation, BatchNormalization\n\nmodel = Sequential()\n\nmodel.add(Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, IMAGE_CHANNELS)))\nmodel.add(BatchNormalization())\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\nmodel.add(Dropout(0.25))\n\nmodel.add(Conv2D(64, (3, 3), activation='relu'))\nmodel.add(BatchNormalization())\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\nmodel.add(Dropout(0.25))\n\nmodel.add(Conv2D(128, (3, 3), activation='relu'))\nmodel.add(BatchNormalization())\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\nmodel.add(Dropout(0.25))\n\nmodel.add(Flatten())\nmodel.add(Dense(512, activation='relu'))\nmodel.add(BatchNormalization())\nmodel.add(Dropout(0.5))\nmodel.add(Dense(2, activation='softmax')) # 2 because we have just 2 classes\n\nmodel.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])\n\nmodel.summary()","metadata":{"_uuid":"8c9f833c1441b657c779844912d0b8028218d454","execution":{"iopub.status.busy":"2022-01-15T09:56:46.286450Z","iopub.execute_input":"2022-01-15T09:56:46.293466Z","iopub.status.idle":"2022-01-15T09:56:46.920116Z","shell.execute_reply.started":"2022-01-15T09:56:46.293380Z","shell.execute_reply":"2022-01-15T09:56:46.916670Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare the data\nConvert nums to strings - 1 to \"Good\" and 1 to \"Bad\" because of use of \"categorical\" class_mode, which requires categories as a strings","metadata":{}},{"cell_type":"code","source":"df[\"category\"] = df[\"category\"].replace({0: 'good', 1: 'bad'}) ","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:56:46.921344Z","iopub.execute_input":"2022-01-15T09:56:46.921615Z","iopub.status.idle":"2022-01-15T09:56:46.932524Z","shell.execute_reply.started":"2022-01-15T09:56:46.921553Z","shell.execute_reply":"2022-01-15T09:56:46.931205Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df, validate_df = train_test_split(df, test_size=0.30, random_state=42)\ntrain_df = train_df.reset_index(drop=True)\nvalidate_df = validate_df.reset_index(drop=True)","metadata":{"_uuid":"4eeb7af8dcf02c4ef5ca744c8305c51a2f5cedef","execution":{"iopub.status.busy":"2022-01-15T09:56:46.935433Z","iopub.execute_input":"2022-01-15T09:56:46.935762Z","iopub.status.idle":"2022-01-15T09:56:46.946763Z","shell.execute_reply.started":"2022-01-15T09:56:46.935695Z","shell.execute_reply":"2022-01-15T09:56:46.945716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Total count of train and validation data","metadata":{}},{"cell_type":"code","source":"total_train = train_df.shape[0]\ntotal_validate = validate_df.shape[0]\nbatch_size=15","metadata":{"_uuid":"ae3dec0361f0443132d0309d3b883ee80070cf9f","execution":{"iopub.status.busy":"2022-01-15T09:56:46.948947Z","iopub.execute_input":"2022-01-15T09:56:46.949647Z","iopub.status.idle":"2022-01-15T09:56:46.957660Z","shell.execute_reply.started":"2022-01-15T09:56:46.949329Z","shell.execute_reply":"2022-01-15T09:56:46.956226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training generator\n**ImageDataGenerator** generates batches of tensor image data with real-time data augmentation.\n****\n**train_generator** - train dataframe, path to train images, x_col = coffee image, y_col = good or bad coffee","metadata":{}},{"cell_type":"code","source":"train_datagen = ImageDataGenerator(\n    rotation_range=15,\n    rescale=1./255,\n    shear_range=0.1,\n    zoom_range=0.2,\n    horizontal_flip=True,\n    width_shift_range=0.1,\n    height_shift_range=0.1\n)\n\ntrain_generator = train_datagen.flow_from_dataframe(\n    train_df,\n    \"../input/coffee-images/coffees/train/\",\n    x_col='file',\n    y_col='category',\n    target_size=IMAGE_SIZE,\n    class_mode='categorical',\n    batch_size=batch_size\n)","metadata":{"_uuid":"4d1c7818703a8a4bac5c036fdea45972aa9e5e9e","execution":{"iopub.status.busy":"2022-01-15T09:56:46.959746Z","iopub.execute_input":"2022-01-15T09:56:46.960309Z","iopub.status.idle":"2022-01-15T09:56:47.126176Z","shell.execute_reply.started":"2022-01-15T09:56:46.960058Z","shell.execute_reply":"2022-01-15T09:56:47.125195Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Validation generator\nSimilar to train_generator","metadata":{}},{"cell_type":"code","source":"validation_datagen = ImageDataGenerator(rescale=1./255)\nvalidation_generator = validation_datagen.flow_from_dataframe(\n    validate_df,\n    \"../input/coffee-images/coffees/train/\",\n    x_col='file',\n    y_col='category',\n    target_size=IMAGE_SIZE,\n    class_mode='categorical',\n    batch_size=batch_size\n)","metadata":{"_uuid":"7925e16bcacc89f4484fb6fe47e54d6420af732e","execution":{"iopub.status.busy":"2022-01-15T09:56:47.127377Z","iopub.execute_input":"2022-01-15T09:56:47.127893Z","iopub.status.idle":"2022-01-15T09:56:47.203763Z","shell.execute_reply.started":"2022-01-15T09:56:47.127825Z","shell.execute_reply":"2022-01-15T09:56:47.202662Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# fit_generator Model","metadata":{"_uuid":"5cd8df64e794ed17de326b613a9819e7da977a0e"}},{"cell_type":"code","source":"epochs=80\nhistory = model.fit_generator(\n    train_generator,\n    epochs=epochs,\n    validation_data=validation_generator,\n    validation_steps=total_validate//batch_size,\n    steps_per_epoch=total_train//batch_size\n)","metadata":{"_uuid":"0836a4cc8aa0abf603e0f96573c0c4ff383ad56b","execution":{"iopub.status.busy":"2022-01-15T09:56:47.205339Z","iopub.execute_input":"2022-01-15T09:56:47.205919Z","iopub.status.idle":"2022-01-15T09:58:27.743776Z","shell.execute_reply.started":"2022-01-15T09:56:47.205750Z","shell.execute_reply":"2022-01-15T09:58:27.742801Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save Model","metadata":{"_uuid":"aa1fbc4081ae0de2993188b2bf658a0be5bc0687"}},{"cell_type":"code","source":"model.save_weights(\"model.h5\")","metadata":{"_uuid":"67575a4decdaf79a915d23151626b784ffa82642","execution":{"iopub.status.busy":"2022-01-15T09:58:27.748326Z","iopub.execute_input":"2022-01-15T09:58:27.748951Z","iopub.status.idle":"2022-01-15T09:58:28.295203Z","shell.execute_reply.started":"2022-01-15T09:58:27.748708Z","shell.execute_reply":"2022-01-15T09:58:28.293914Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training visualization","metadata":{"_uuid":"1b76c0a9040bc0babf0a453e567e41e22f8a1e0e"}},{"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 12))\nax1.plot(history.history['loss'], color='b', label=\"Training loss\")\nax1.plot(history.history['val_loss'], color='r', label=\"validation loss\")\nax1.set_xticks(np.arange(1, epochs, 1))\nax1.set_yticks(np.arange(0, 1, 0.1))\n\nax2.plot(history.history['acc'], color='b', label=\"Training accuracy\")\nax2.plot(history.history['val_acc'], color='r',label=\"Validation accuracy\")\nax2.set_xticks(np.arange(1, epochs, 1))\n\nlegend = plt.legend(loc='best', shadow=True)\nplt.tight_layout()\nplt.show()","metadata":{"_uuid":"79055f2dc3e2abb47bea758e0464c86ca42ab431","execution":{"iopub.status.busy":"2022-01-15T09:58:28.299447Z","iopub.execute_input":"2022-01-15T09:58:28.299861Z","iopub.status.idle":"2022-01-15T09:58:31.978686Z","shell.execute_reply.started":"2022-01-15T09:58:28.299781Z","shell.execute_reply":"2022-01-15T09:58:31.977763Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Load test images","metadata":{}},{"cell_type":"code","source":"test_files = os.listdir(\"../input/coffee-images/coffees/test/\")\ntest_df = pd.DataFrame({\n    'file': test_files\n})\nnb_samples = test_df.shape[0]","metadata":{"_uuid":"c35e70d3e1e4834dbbf840fa0ea08c049bfcd915","execution":{"iopub.status.busy":"2022-01-15T09:58:31.980233Z","iopub.execute_input":"2022-01-15T09:58:31.980893Z","iopub.status.idle":"2022-01-15T09:58:31.988207Z","shell.execute_reply.started":"2022-01-15T09:58:31.980832Z","shell.execute_reply":"2022-01-15T09:58:31.986917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create test_generator\nSimilar to train_generator, but y_col is None, because we don't know the category of the image yet. Then shuffle all test images.","metadata":{}},{"cell_type":"code","source":"test_gen = ImageDataGenerator(rescale=1./255)\ntest_generator = test_gen.flow_from_dataframe(\n    test_df, \n    \"../input/coffee-images/coffees/test/\", \n    x_col='file',\n    y_col=None,\n    class_mode=None,\n    target_size=IMAGE_SIZE,\n    batch_size=batch_size,\n    shuffle=True\n)","metadata":{"_uuid":"52249ec1c35fb1be3adef386be245de3794e55aa","execution":{"iopub.status.busy":"2022-01-15T09:58:31.989920Z","iopub.execute_input":"2022-01-15T09:58:31.990603Z","iopub.status.idle":"2022-01-15T09:58:32.156333Z","shell.execute_reply.started":"2022-01-15T09:58:31.990540Z","shell.execute_reply":"2022-01-15T09:58:32.155276Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Image prediction","metadata":{"_uuid":"2fa580afca2931ec5ce374e732d8c1789d03f2ed"}},{"cell_type":"code","source":"predict = model.predict_generator(test_generator, steps=np.ceil(nb_samples/batch_size))","metadata":{"_uuid":"4782eb23fa7d003f0e2415d995894017edb2d896","execution":{"iopub.status.busy":"2022-01-15T09:58:32.157846Z","iopub.execute_input":"2022-01-15T09:58:32.158246Z","iopub.status.idle":"2022-01-15T09:58:33.009598Z","shell.execute_reply.started":"2022-01-15T09:58:32.158124Z","shell.execute_reply":"2022-01-15T09:58:33.008517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_df['category'] = np.argmax(predict, axis=-1)","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:58:33.010783Z","iopub.execute_input":"2022-01-15T09:58:33.011083Z","iopub.status.idle":"2022-01-15T09:58:33.017839Z","shell.execute_reply.started":"2022-01-15T09:58:33.011029Z","shell.execute_reply":"2022-01-15T09:58:33.016796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_map = dict((v,k) for k,v in train_generator.class_indices.items())\ntest_df['category'] = test_df['category'].replace(label_map)","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:58:33.019395Z","iopub.execute_input":"2022-01-15T09:58:33.023485Z","iopub.status.idle":"2022-01-15T09:58:33.035651Z","shell.execute_reply.started":"2022-01-15T09:58:33.023422Z","shell.execute_reply":"2022-01-15T09:58:33.033911Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_df['category'] = test_df['category'].replace({ 'bad': 1, 'good': 0 })","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:58:33.040974Z","iopub.execute_input":"2022-01-15T09:58:33.041352Z","iopub.status.idle":"2022-01-15T09:58:33.051844Z","shell.execute_reply.started":"2022-01-15T09:58:33.041286Z","shell.execute_reply":"2022-01-15T09:58:33.050560Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Total count of classified good and bad coffees","metadata":{"_uuid":"b00add65fe765529e637c3a9904d710bb7eff1d8"}},{"cell_type":"code","source":"from sklearn import metrics\n\ntest_df['category'].value_counts().plot.bar()","metadata":{"_uuid":"d0bf6dd5ff344092fa0121f70bdd60fa5a40e29c","execution":{"iopub.status.busy":"2022-01-15T09:58:33.053878Z","iopub.execute_input":"2022-01-15T09:58:33.054515Z","iopub.status.idle":"2022-01-15T09:58:33.384003Z","shell.execute_reply.started":"2022-01-15T09:58:33.054248Z","shell.execute_reply":"2022-01-15T09:58:33.382785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['category'].value_counts().plot.bar()","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:58:33.388678Z","iopub.execute_input":"2022-01-15T09:58:33.393159Z","iopub.status.idle":"2022-01-15T09:58:33.694931Z","shell.execute_reply.started":"2022-01-15T09:58:33.393066Z","shell.execute_reply":"2022-01-15T09:58:33.693750Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import seaborn as sn\n\ndata = {'y_Actual':    df[\"category\"],\n        'y_Predicted': test_df[\"category\"]\n        }\n\ndf = pd.DataFrame(data, columns=['y_Actual','y_Predicted'])\nconfusion_matrix = pd.crosstab(df['y_Actual'], df['y_Predicted'], rownames=['Actual'], colnames=['Predicted'])\n\nsn.heatmap(confusion_matrix, annot=True)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-15T09:58:33.761868Z","iopub.execute_input":"2022-01-15T09:58:33.762239Z","iopub.status.idle":"2022-01-15T09:58:34.197378Z","shell.execute_reply.started":"2022-01-15T09:58:33.762185Z","shell.execute_reply":"2022-01-15T09:58:34.196239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predicted result images\n* Create and visualize plot of every image\n* Create and visualize plot with 99 classified images with description\n* Convert category from nums to strings so it is readable","metadata":{"_uuid":"ce72a83f80d6e012b12b82c8ee3365d671a3b307"}},{"cell_type":"code","source":"sample_test = test_df.head(99)\nsample_test.head()\nplt.figure(figsize=(12, 24))\ngood_coffee_counter = 0\nbad_coffee_counter = 0\nfor index, row in sample_test.iterrows():\n    filename = row['file']\n    category = row['category']\n    img = load_img(\"../input/coffee-images/coffees/test/\" + filename, target_size=IMAGE_SIZE)\n    plt.subplot(10, 10, index+1)\n    plt.imshow(img)\n    if category == 0:\n        category = 'GOOD'\n        good_coffee_counter += 1\n    else: \n        category = 'BAD'\n        bad_coffee_counter += 1\n    plt.xlabel(\"{}\".format(category) + \" - \" + filename[:7])\nprint(\"Total coffees: \" + str(good_coffee_counter+bad_coffee_counter))\nprint(\"Found: \" + str(good_coffee_counter) + \" perfect coffees!\")\nplt.tight_layout()\nplt.show()","metadata":{"_uuid":"98b41dc83075e6297137fb45bf703c313dd4ae28","execution":{"iopub.status.busy":"2022-01-15T09:58:34.198962Z","iopub.execute_input":"2022-01-15T09:58:34.199524Z","iopub.status.idle":"2022-01-15T09:58:51.250654Z","shell.execute_reply.started":"2022-01-15T09:58:34.199441Z","shell.execute_reply":"2022-01-15T09:58:51.249419Z"},"trusted":true},"execution_count":null,"outputs":[]}]}