diff --git a/Marinskiy/01. Data Collection.ipynb b/Marinskiy/01. Data Collection.ipynb new file mode 100644 index 0000000..07c5feb --- /dev/null +++ b/Marinskiy/01. Data Collection.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# iPhone Project" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*by Alexander Marinskiy*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 1. Data Collection" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For data collection, I decided to use the site avito.ru. Currently, over a million ads are published in the \"phones\" category, and each ad can contain up to 10 photos. Thus, the amount of dataset available is larger than we can theoretically process. Moreover, these are photos taken by the users themselves, which corresponds to the data on which the model will be tested." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1. Build functions to collect images" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# import libraries\n", + "import pandas as pd\n", + "import requests\n", + "import time\n", + "from bs4 import BeautifulSoup\n", + "import os\n", + "import urllib.request " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# function to get web page from url\n", + "def get_html(url):\n", + " \n", + " # set user agent\n", + " user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'\n", + "\n", + " # get web page from url\n", + " r = requests.get(url, headers={'User-Agent': user_agent})#, proxies=proxy)\n", + " \n", + " # return text of web page\n", + " return r.text" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# function to get links to all the product images on the page \n", + "def get_links_from_page(html):\n", + " \n", + " # create soup\n", + " soup = BeautifulSoup(html, 'lxml')\n", + "\n", + " # get links to all the images\n", + " images = [x['src'] for x in soup.findAll('img', {'class': 'large-picture-img'})]\n", + " \n", + " # filter only the images we need\n", + " product_photo = []\n", + " for i in images:\n", + " if i[:9] != 'https://w':\n", + " product_photo.append(i)\n", + "\n", + " # return list with links to images\n", + " return product_photo" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# function to get list of links to pictures of iphone\n", + "def get_all_links(name, base_url, n_pages=10, query=''):\n", + " \n", + " # Construct url adress. \n", + " # iPhones: https://www.avito.ru/rossiya/telefony/iphone?p=1&q=iphone+x\n", + " # Other: https://www.avito.ru/rossiya/telefony/alcatel?p=1\n", + " \n", + " # if downloading iphones we specify model\n", + " if query != '':\n", + " query = '&q=iphone+' + query\n", + " \n", + " # get links to images from all the pages\n", + " links_list = []\n", + " for i in range(1, n_pages+1):\n", + " url_gen = base_url + 'p=' + str(i) + query\n", + " print(url_gen)\n", + " page_html = get_html(url_gen)\n", + " links_list += get_links_from_page(page_html)\n", + " \n", + " # wait for 5 second in order to avoid block from avito\n", + " time.sleep(5)\n", + "\n", + " # save list of links to csv\n", + " df = pd.DataFrame()\n", + " df['links'] = links_list\n", + " df.to_csv(name+'.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get images\n", + "def get_images(subfolder, models):\n", + " for model in models[:]:\n", + " # create folder\n", + " if not os.path.exists('dataset/' + subfolder + '/' + model):\n", + " os.makedirs('dataset/' + subfolder + '/' + model)\n", + "\n", + " # read list of links\n", + " df = pd.read_csv(model+'.csv')\n", + "\n", + " # print info\n", + " print('Downloading ' + model + '. Total number of photos: ' + str(len(df['links'])))\n", + "\n", + " # getting photos\n", + " count = 0\n", + " for i in df['links']: \n", + " count+= 1\n", + " \n", + " # print information massege every 100 photos\n", + " if count % 100 == 0: \n", + " print(str(count) + ' done')\n", + "\n", + " # get the image\n", + " try:\n", + " urllib.request.urlretrieve(i, 'dataset/' + subfolder + '/' + model + '/' + str(count) + '.jpg')\n", + " except:\n", + " print('Skip photo ' + str(count) + ' due to error')\n", + "\n", + " # wait for 0.5 second to avoid ban\n", + " time.sleep(0.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2. Collect images of iPhones" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since we need to learn to recognize all the iPhone models that exist on the market, we will upload photos of these iPhones in equal proportions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# list of models\n", + "iphone_models = ['XR', 'XS', 'X', '8', '7', 'SE', '6S', '6', '5S', '5C', '5', '4S', '4', '3GS', '3G']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create lists of links for all te models\n", + "for model in iphone_models:\n", + " print('Getting links for model', model)\n", + " get_all_links(name=model, base_url='https://www.avito.ru/rossiya/telefony/iphone?', n_pages=12, query=model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_images('iphone', iphone_models)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 3. Get images of non-iphones" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since there are only 15 iPhone models, it was reasonable to upload the same number of photos for each model. In the case of non-iPhones, there are much more models, so another strategy was applied. I looked at the number of ads for each of the manufacturers on Avito and decided to upload photos in appropriate proportions. Thus, our dataset will reflect as closely as possible the conditions in which the model will be tested. The proportions have been saved in the non-iphones.xlsx file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# check what number of avito pages we need to download\n", + "df_non_iphones = pd.read_excel('non-iphones.xlsx')\n", + "df_non_iphones['n_pages'] = df_non_iphones['n_pages'].apply(int)\n", + "df_non_iphones['brand'] = df_non_iphones['brand'].apply(str)\n", + "df_non_iphones" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create lists of links for all te models\n", + "for i in range(len(df_non_iphones['brand'])):\n", + " print('Getting links for model', df_non_iphones['brand'][i])\n", + " get_all_links(name=df_non_iphones['brand'][i],\n", + " base_url='https://www.avito.ru/rossiya/telefony/' + df_non_iphones['brand'][i] + '?', \n", + " n_pages=df_non_iphones['n_pages'][i])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_images('other', other_models)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Thus, I managed to collect a balanced dataset from 50,000 images of iPhones and phones from other manufacturers. Then this dataset was merged with the dataset collected by Anton Anisimov. The model was trained in a combined dataset.\n", + "\n", + "It is important to note that more than 1,000,000 phone advertisements are available on Avito, and in each ad there are 2-4 photos. Thus, our dataset can be painlessly increased by a factor of over sixty, which could significantly improve the accuracy of the model, but also would require more computational resources and time for training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Marinskiy/02. Train model.ipynb b/Marinskiy/02. Train model.ipynb new file mode 100644 index 0000000..73437f0 --- /dev/null +++ b/Marinskiy/02. Train model.ipynb @@ -0,0 +1,619 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# iPhone Project" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*by Alexander Marinskiy*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 2. Training model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this assignment, I decided to use the Resnet18 architecture. It shows good accuracy and at the same time it trains reasonable time. By default, the Resnet18 architecture is not represented in keras, so this repository on the github was used: https://github.com/qubvel/classification_models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "# install the nessesary package\n", + "!pip install git+https://github.com/qubvel/classification_models.git\n", + "\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + } + ], + "source": [ + "# import libraries\n", + "import os\n", + "import warnings\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import keras\n", + "from keras.optimizers import Adam\n", + "from keras.layers import Dense\n", + "from keras.models import Model\n", + "from keras.models import load_model\n", + "from keras.preprocessing.image import ImageDataGenerator\n", + "from keras.preprocessing import image\n", + "from keras.callbacks import ModelCheckpoint\n", + "\n", + "from classification_models.resnet import ResNet18\n", + "\n", + "import tensorflow as tf\n", + "from tensorflow.contrib import lite\n", + "\n", + "from sklearn.metrics import average_precision_score\n", + "from sklearn.metrics import precision_recall_curve" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# ignore warnings\n", + "warnings.filterwarnings('ignore')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I decided to load pre-trained model because it might be a good starting point to train the model." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:From C:\\Users\\Alex\\Documents\\iphoneOrNot\\env\\lib\\site-packages\\tensorflow\\python\\framework\\op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\n", + "Instructions for updating:\n", + "Colocations handled automatically by placer.\n" + ] + } + ], + "source": [ + "# Load pre-tained Resnet18 model\n", + "model = ResNet18(input_shape=(224,224,3), weights='imagenet', include_top=True)\n", + "\n", + "# change the last layer\n", + "model.layers.pop()\n", + "last = model.layers[-1].output\n", + "x = Dense(2, activation=\"softmax\")(last)\n", + "model = Model(model.input, x)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# compile model with adam optimizer\n", + "model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configure ImageDataGenerator" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# set the batch size\n", + "batch_size = 32\n", + "\n", + "# define directories for train and test\n", + "train_dir = 'iphones_dataset_train'\n", + "validation_dir = 'iphones_dataset_val'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next step is to define ImageDataGenerators and add augmentation for training dataset. I used quite strong augmentation in order to avoid overfitting. For testing set I did't use augmentation, so that the test sample was as close as possible to the real data on which the model will work." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Set ImageDataGenerator for training set. I used strong augmentation + rescaling.\n", + "train_datagen = ImageDataGenerator(rescale = 1./255., \n", + " rotation_range = 90,\n", + " width_shift_range = 0.25,\n", + " height_shift_range = 0.25,\n", + " brightness_range = [0.3, 1.5], \n", + " shear_range = 0.4,\n", + " zoom_range = 0.2,\n", + " channel_shift_range = 20,\n", + " vertical_flip = True,\n", + " horizontal_flip = True)\n", + "\n", + "# Set ImageDataGenerator for testing set. Note that here I used just rescaling and didn't use any augmentation\n", + "test_datagen = ImageDataGenerator(rescale = 1.0/255.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is the example of augmented images:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's set flow_from_directory. The images are resized to 224*224 because it is the default size for Resnet18 architecture." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 50144 images belonging to 2 classes.\n", + "Found 2846 images belonging to 2 classes.\n" + ] + } + ], + "source": [ + "# Flow training images using train_datagen generator. \n", + "train_generator = train_datagen.flow_from_directory(train_dir,\n", + " batch_size = batch_size,\n", + " class_mode = 'categorical', \n", + " target_size = (224, 224)) \n", + "\n", + "# Flow validation images using test_datagen generator. Here I set turned off shuffling so that sklearn metriks worked correctly.\n", + "validation_generator = test_datagen.flow_from_directory(validation_dir,\n", + " batch_size = batch_size,\n", + " class_mode = 'categorical', \n", + " target_size = (224, 224),\n", + " shuffle=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set checkpoints" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Model training takes a lot of time, and the process can be interrupted for various technical reasons. To save the progress in training the model, I used checkpoints. I saved the model after each epoch when val_score improved." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# set checkpoint\n", + "filepath=\"resnet18-{epoch:02d}-{val_acc:.2f}.hdf5\"\n", + "checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max', save_weights_only=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Fit the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# fit the model using fit generator\n", + "model.fit_generator(\n", + " train_generator,\n", + " validation_data = validation_generator,\n", + " steps_per_epoch = train_generator.samples / batch_size,\n", + " epochs = 100,\n", + " validation_steps = validation_generator.samples / batch_size,\n", + " verbose = 1,\n", + " callbacks=[checkpoint])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compres the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It turned out that GitHub does not accept files larger than 100 MB. In order to meet this restriction, I decided to use Tensorflow Lite, which is usually used for mobile devices. First of all, I converted the model:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING:tensorflow:From C:\\Users\\Alex\\Documents\\iphoneOrNot\\env\\lib\\site-packages\\tensorflow\\python\\ops\\math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n", + "Instructions for updating:\n", + "Use tf.cast instead.\n", + "WARNING:tensorflow:From C:\\Users\\Alex\\Documents\\iphoneOrNot\\env\\lib\\site-packages\\tensorflow\\lite\\python\\lite.py:591: convert_variables_to_constants (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\n", + "Instructions for updating:\n", + "Use tf.compat.v1.graph_util.convert_variables_to_constants\n", + "WARNING:tensorflow:From C:\\Users\\Alex\\Documents\\iphoneOrNot\\env\\lib\\site-packages\\tensorflow\\python\\framework\\graph_util_impl.py:245: extract_sub_graph (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\n", + "Instructions for updating:\n", + "Use tf.compat.v1.graph_util.extract_sub_graph\n", + "INFO:tensorflow:Froze 100 variables.\n", + "INFO:tensorflow:Converted 100 variables to const ops.\n" + ] + }, + { + "data": { + "text/plain": [ + "46791096" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Converting keras model to tflite model\n", + "converter = lite.TFLiteConverter.from_keras_model_file('model.hdf5')\n", + "tflite_model = converter.convert()\n", + "open(\"converted_model.tflite\", \"wb\").write(tflite_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the **size of the model has decreased from 137 MB to 45 MB**, and such model can already be uploaded to the GitHab." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Quality check" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, I would like to assess how much the quality of the model has deteriorated in the process of such conversion. For this, the Average Precision metric will be used. This metric can be approximied as Area Under Precision-Recall Curve that will be used for the final evaluation of the project." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's get predictions with full model:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "# get prdictions with original model\n", + "model = load_model('model.hdf5')" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "89/88 [==============================] - 713s 8s/step\n" + ] + } + ], + "source": [ + "# get predictions\n", + "validation_generator.reset()\n", + "full_pred = model.predict_generator(validation_generator, \n", + " steps= validation_generator.samples / batch_size, \n", + " verbose=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's get predictions with compressed model:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "#create function for loading and rescaling images\n", + "\n", + "def load_image(img_path):\n", + "\n", + " img = image.load_img(img_path, target_size=(224, 224))\n", + " img_tensor = image.img_to_array(img) \n", + " img_tensor = np.expand_dims(img_tensor, axis=0) \n", + " img_tensor /= 255.\n", + " \n", + " return img_tensor" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "# Load TFLite model and allocate tensors.\n", + "interpreter = tf.lite.Interpreter(model_path=\"converted_model.tflite\")\n", + "interpreter.allocate_tensors()\n", + "\n", + "# Get input and output tensors.\n", + "input_details = interpreter.get_input_details()\n", + "output_details = interpreter.get_output_details()\n", + "\n", + "# Now lets walk through our validation set and get predictions for each photo\n", + "# I'm going to store predictions from compressed model in compr_pred\n", + "compr_pred = []\n", + "for subdir, dirs, files in os.walk(validation_dir):\n", + " for file in files:\n", + " input_data = load_image(os.path.join(subdir, file))\n", + " interpreter.set_tensor(input_details[0]['index'], input_data)\n", + " interpreter.invoke()\n", + " output_data = interpreter.get_tensor(output_details[0]['index'])\n", + " compr_pred.append(output_data[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's compare results:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Average precision-recall score for full model: 0.9942834919\n", + "Average precision-recall score for compressed model: 0.9942834953\n" + ] + } + ], + "source": [ + "# calculate avarage percision\n", + "average_precision_full = average_precision_score(validation_generator.classes, [x[1] for x in full_pred])\n", + "average_precision_compr = average_precision_score(validation_generator.classes, [x[1] for x in compr_pred])\n", + "\n", + "# print results\n", + "print('Average precision-recall score for full model: {0:0.10f}'.format(average_precision_full))\n", + "print('Average precision-recall score for compressed model: {0:0.10f}'.format(average_precision_compr))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**We can see that results almost did not change after compression**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, one more thing. Let's plot precision-recall curve to check what it looks like" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, '2-class Precision-Recall curve: AP=0.9943')" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# plot the precision-recall curve\n", + "precision, recall, _ = precision_recall_curve(validation_generator.classes, [x[1] for x in compr_pred])\n", + "plt.fill_between(recall, precision, alpha=0.2, color='b')\n", + "plt.xlabel('Recall')\n", + "plt.ylabel('Precision')\n", + "plt.ylim([0.0, 1.0])\n", + "plt.xlim([0.0, 1.0])\n", + "plt.title('2-class Precision-Recall curve: AP={0:0.4f}'.format(average_precision_compr))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All done :)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create requirements file" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! conda list -e > requirements.txt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Marinskiy/converted_model.tflite b/Marinskiy/converted_model.tflite new file mode 100644 index 0000000..a1e0032 Binary files /dev/null and b/Marinskiy/converted_model.tflite differ diff --git a/Marinskiy/images/augmentation.jpg b/Marinskiy/images/augmentation.jpg new file mode 100644 index 0000000..3a1831e Binary files /dev/null and b/Marinskiy/images/augmentation.jpg differ diff --git a/Marinskiy/non-iphones.xlsx b/Marinskiy/non-iphones.xlsx new file mode 100644 index 0000000..b267640 Binary files /dev/null and b/Marinskiy/non-iphones.xlsx differ diff --git a/Marinskiy/predict.py b/Marinskiy/predict.py new file mode 100644 index 0000000..69049dc --- /dev/null +++ b/Marinskiy/predict.py @@ -0,0 +1,78 @@ +# import libraries +import argparse +import pandas as pd +import numpy as np +import os +import tensorflow as tf +from keras.preprocessing import image + + +# create function for loading and rescaling images +def load_image(img_path): + img = image.load_img(img_path, target_size=(224, 224)) + img_tensor = image.img_to_array(img) + img_tensor = np.expand_dims(img_tensor, axis=0) + img_tensor /= 255. + return img_tensor + + +# create function for calculating probabilities +def predict_proba(model_path, in_folder, out_file): + + print('Start image processing...') + + # Load TFLite model and allocate tensors. + interpreter = tf.lite.Interpreter(model_path=model_path) + interpreter.allocate_tensors() + + # Get input and output tensors. + input_details = interpreter.get_input_details() + output_details = interpreter.get_output_details() + + # initialize variables + preds = [] + filenames = [] + count = 0 + + # Now lets walk through our validation set and get predictions for each photo + for subdir, dirs, files in os.walk(in_folder): + for file in files: + input_data = load_image(os.path.join(subdir, file)) + interpreter.set_tensor(input_details[0]['index'], input_data) + interpreter.invoke() + output_data = interpreter.get_tensor(output_details[0]['index']) + + # save results + preds.append(output_data[0]) + filenames.append(file) + + # print message every 100 images + count += 1 + if count % 100 == 0: + print('First', str(count), 'images done!') + + # create dataframe with filenames and iphone probabilities + out_df = pd.DataFrame() + out_df['image_name'] = filenames + out_df['iphone_probability'] = [pred[0] for pred in preds] + + # save dataframe with answer + out_df.to_csv(out_file, index=False) + + +if __name__ == '__main__': + # disable warnings + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + + # parse arguments + parser = argparse.ArgumentParser(description='Iphone detector') + parser.add_argument('--model', type=str, default='converted_model.tflite', help='path to model') + parser.add_argument('--input', type=str, default='test', help='path to folder with pictures') + parser.add_argument('--output', type=str, default='predictions.csv', help='path to file with model output') + args = parser.parse_args() + print("model= {0} input_data= {1} output_data= {2}".format(args.model, args.input, args.output)) + + # get predictions + predict_proba(args.model, args.input, args.output) + print() + print('Predictions file is created successfully!') diff --git a/Marinskiy/requirements.txt b/Marinskiy/requirements.txt new file mode 100644 index 0000000..debf75f --- /dev/null +++ b/Marinskiy/requirements.txt @@ -0,0 +1,137 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: win-64 +_tflow_select=2.3.0=mkl +absl-py=0.7.1=py36_0 +asn1crypto=0.24.0=py36_0 +astor=0.7.1=py36_0 +attrs=19.1.0=py36_1 +backcall=0.1.0=py36_0 +beautifulsoup4=4.7.1=py36_1 +blas=1.0=mkl +bleach=3.1.0=py36_0 +ca-certificates=2019.5.15=0 +certifi=2019.3.9=py36_0 +cffi=1.12.3=py36h7a1dbc1_0 +chardet=3.0.4=py36_1 +colorama=0.4.1=py36_0 +cryptography=2.6.1=py36h7a1dbc1_0 +cycler=0.10.0=py36h009560c_0 +decorator=4.4.0=py36_1 +defusedxml=0.6.0=py_0 +entrypoints=0.3=py36_0 +freetype=2.9.1=ha9979f8_1 +gast=0.2.2=py36_0 +grpcio=1.16.1=py36h351948d_1 +h5py=2.9.0=py36h5e291fa_0 +hdf5=1.10.4=h7ebc959_0 +icc_rt=2019.0.0=h0cc432a_1 +icu=58.2=ha66f8fd_1 +idna=2.8=py36_0 +image-classifiers=0.2.2=pypi_0 +intel-openmp=2019.3=203 +ipykernel=5.1.0=py36h39e3cac_0 +ipython=7.5.0=py36h39e3cac_0 +ipython_genutils=0.2.0=py36h3c5d0ee_0 +jedi=0.13.3=py36_0 +jinja2=2.10.1=py36_0 +joblib=0.13.2=py36_0 +jpeg=9b=hb83a4c4_2 +jsonschema=3.0.1=py36_0 +jupyter_client=5.2.4=py36_0 +jupyter_core=4.4.0=py36_0 +keras=2.2.2=0 +keras-applications=1.0.8=pypi_0 +keras-base=2.2.2=py36_0 +keras-preprocessing=1.1.0=pypi_0 +kiwisolver=1.1.0=py36ha925a31_0 +libiconv=1.15=h1df5818_7 +libmklml=2019.0.3=0 +libpng=1.6.37=h2a8f88b_0 +libprotobuf=3.7.1=h7bd577a_0 +libsodium=1.0.16=h9d3ae62_0 +libtiff=4.0.10=hb898794_2 +libxml2=2.9.9=h464c3ec_0 +libxslt=1.1.33=h579f668_0 +lxml=4.3.3=py36h1350720_0 +m2w64-gcc-libgfortran=5.3.0=6 +m2w64-gcc-libs=5.3.0=7 +m2w64-gcc-libs-core=5.3.0=7 +m2w64-gmp=6.1.0=2 +m2w64-libwinpthread-git=5.0.0.4634.697f757=2 +markdown=3.1=py36_0 +markupsafe=1.1.1=py36he774522_0 +matplotlib=3.0.3=py36hc8f65d3_0 +mistune=0.8.4=py36he774522_0 +mkl=2019.4=245 +mkl-service=2.0.2=py36he774522_0 +mkl_fft=1.0.12=py36h14836fe_0 +mkl_random=1.0.2=py36h343c172_0 +mock=3.0.5=pypi_0 +msys2-conda-epoch=20160418=1 +nbconvert=5.5.0=py_0 +nbformat=4.4.0=py36h3a5bc1b_0 +notebook=5.7.8=py36_0 +numpy=1.16.3=py36h19fb1c0_0 +numpy-base=1.16.3=py36hc3f5095_0 +olefile=0.46=py36_0 +openssl=1.1.1c=he774522_1 +pandas=0.24.2=py36ha925a31_0 +pandoc=2.2.3.2=0 +pandocfilters=1.4.2=py36_1 +parso=0.4.0=py_0 +pickleshare=0.7.5=py36_0 +pillow=6.0.0=py36hdc69c19_0 +pip=19.1.1=py36_0 +prometheus_client=0.6.0=py36_0 +prompt_toolkit=2.0.9=py36_0 +protobuf=3.7.1=py36h33f27b4_0 +pycparser=2.19=py36_0 +pygments=2.4.0=py_0 +pyopenssl=19.0.0=py36_0 +pyparsing=2.4.0=py_0 +pyqt=5.9.2=py36h6538335_2 +pyreadline=2.1=py36_1 +pyrsistent=0.14.11=py36he774522_0 +pysocks=1.7.0=py36_0 +python=3.6.8=h9f7ef89_7 +python-dateutil=2.8.0=py36_0 +pytz=2019.1=py_0 +pywinpty=0.5.5=py36_1000 +pyyaml=5.1=py36he774522_0 +pyzmq=18.0.0=py36ha925a31_0 +qt=5.9.7=vc14h73c81de_0 +requests=2.21.0=py36_0 +scikit-learn=0.21.2=py36h6288b17_0 +scipy=1.2.1=py36h29ff71c_0 +send2trash=1.5.0=py36_0 +setuptools=41.0.1=py36_0 +sip=4.19.8=py36h6538335_0 +six=1.12.0=py36_0 +soupsieve=1.8=py36_0 +sqlite=3.28.0=he774522_0 +tensorboard=1.13.1=pypi_0 +tensorflow=1.13.1=pypi_0 +tensorflow-estimator=1.13.0=pypi_0 +termcolor=1.1.0=py36_1 +terminado=0.8.2=py36_0 +testpath=0.4.2=py36_0 +tk=8.6.8=hfa6e2cd_0 +tornado=6.0.2=py36he774522_0 +traitlets=4.3.2=py36h096827d_0 +urllib3=1.24.2=py36_0 +vc=14.1=h0510ff6_4 +vs2015_runtime=14.15.26706=h3a45250_4 +wcwidth=0.1.7=py36h3d5aa90_0 +webencodings=0.5.1=py36_1 +werkzeug=0.15.2=py_0 +wheel=0.33.4=py36_0 +win_inet_pton=1.1.0=py36_0 +wincertstore=0.2=py36h7fe50ca_0 +winpty=0.4.3=4 +xlrd=1.2.0=py36_0 +xz=5.2.4=h2fa13f4_4 +yaml=0.1.7=hc54c509_2 +zeromq=4.3.1=h33f27b4_3 +zlib=1.2.11=h62dcd97_3 +zstd=1.3.7=h508b16e_0