Aplicación del modelo preentrenado VGG16 a recomendaciones basadas en imágenes del producto

Hoy quiero contarles sobre mi experiencia al usar una red neuronal para encontrar productos similares para un sistema de recomendación de una tienda en línea. Hablaré principalmente de aspectos técnicos. Decidí escribir este artículo sobre Habré porque cuando estaba empezando a hacer este proyecto, encontré una solución adecuada en Habré, pero resultó que ya estaba desactualizado y tuve que modificarlo. Entonces decidí actualizar el material para aquellos que necesitarán una solución similar.






Por separado, quiero decir que esta es mi primera experiencia de crear un proyecto más o menos serio en el campo de la ciencia de datos, por lo que si uno de los colegas con más experiencia ve lo que aún se puede mejorar, solo estaré encantado por el consejo. .





Comenzaré con un poco de antecedentes sobre por qué se eligió la lógica elegida de la tienda en línea, es decir, una recomendación basada en productos similares (y no métodos de filtrado colaborativo, por ejemplo). El caso es que este sistema de recomendación fue desarrollado para una tienda online que vende relojes y por tanto hasta el 90% de los usuarios que llegan al sitio no regresan. En general, la tarea era la siguiente: aumentar el número de páginas vistas de los usuarios que llegan a las páginas de productos específicos a través de la publicidad. Dichos usuarios vieron una página y abandonaron el sitio si el producto no les convenía.





Debo decir que en este proyecto no tuve la oportunidad de integrarme con el backend de una tienda online, una historia clásica para las pequeñas y medianas tiendas online. Era necesario confiar solo en el sistema, que haré aparte del sitio. Por lo tanto, como una solución visual en el sitio en sí, decidí crear un widget js emergente. Una línea agrega js al código html, comprende el título de la página a la que llegó el usuario y lo pasa al backend del servicio. Si el backend encuentra un producto en su base de datos de productos precargados, vuelve a buscar recomendaciones en la base de datos de productos preparada previamente y las devuelve a js, y luego js las muestra en el widget. Además, para reducir el impacto en la velocidad de carga, js crea un iframe, en el que hace todo el trabajo de mostrar el widget. Entre otras cosas,también le permite eliminar el problema con la intersección de las clases css del widget y el sitio.





, Data Science. , . , , - .





.





( , A/B-) - ; , , - .





. .





:





!pip install theano

%matplotlib inline
from keras.models import Sequential
from keras.layers.core import Flatten, Dense, Dropout
from keras.layers.convolutional import Convolution2D, MaxPooling2D, ZeroPadding2D
from keras.optimizers import SGD
import cv2, numpy as np
import os
import h5py
from matplotlib import pyplot as plt

from keras.applications import vgg16
from keras.applications import Xception
from keras.preprocessing.image import load_img,img_to_array
from keras.models import Model
from keras.applications.imagenet_utils import preprocess_input

from PIL import Image
import os
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import pandas as pd

import theano
theano.config.openmp = True
      
      



( , ):





import re
def sorted_alphanumeric(data):
    convert = lambda text: int(text) if text.isdigit() else text.lower()
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(data, key=alphanum_key)

dirlist = sorted_alphanumeric(os.listdir('images'))

r1 = []
r2 = []
for i,x in enumerate(dirlist):
    if x.endswith(".jpg"):
        r1.append((int(x[:-4]),i))
        r2.append((i,int(x[:-4])))

extid_to_intid_dict = dict(r1)
intid_to_extid_dict = dict(r2)
      
      



:





imgs_path = "images/"
imgs_model_width, imgs_model_height = 224, 224

nb_closest_images = 3 #    (  )
      
      



( ):





vgg_model = vgg16.VGG16(weights='imagenet')
      
      



( 1000 ImageNet - . 4096- , ).





— , .





:





feat_extractor = Model(inputs=vgg_model.input, outputs=vgg_model.get_layer("fc2").output)
      
      



, CNN . , :





feat_extractor.summary()
      
      



( , xml -, , , ; , ):





files = [imgs_path + x for x in os.listdir(imgs_path) if "jpg" in x]

print("number of images:",len(files))
      
      



:





import re

def atof(text):
    try:
        retval = float(text)
    except ValueError:
        retval = text
    return retval

def natural_keys(text):
    '''
    alist.sort(key=natural_keys) sorts in human order
    http://nedbatchelder.com/blog/200712/human_sorting.html
    (See Toothy's implementation in the comments)
    float regex comes from https://stackoverflow.com/a/12643073/190597
    '''
    return [ atof(c) for c in re.split(r'[+-]?([0-9]+(?:[.][0-9]*)?|[.][0-9]+)', text) ]

files.sort(key=natural_keys)
      
      



PIL :





original = load_img(files[1], target_size=(imgs_model_width, imgs_model_height))
plt.imshow(original)
plt.show()
print("image loaded successfully!")
      
      



PIL numpy array:

PIL - width, height, channel

Numpy - height, width, channel





numpy_image = img_to_array(original) #    
      
      



batch format.

expand_dims





, - batchsize, height, width, channels. , 0.





image_batch = np.expand_dims(numpy_image, axis=0) #   - (2-dims)
print('image batch size', image_batch.shape)
      
      



VGG:





processed_image = preprocess_input(image_batch.copy()) #    
      
      



( ):





img_features = feat_extractor.predict(processed_image)
      
      



:





print("features successfully extracted!")
print("number of image features:",img_features.size)
img_features
      
      



, — .





importedImages = []

for f in files:
    filename = f
    original = load_img(filename, target_size=(224, 224))
    numpy_image = img_to_array(original)
    image_batch = np.expand_dims(numpy_image, axis=0)
    
    importedImages.append(image_batch)
    
images = np.vstack(importedImages)

processed_imgs = preprocess_input(images.copy())
      
      



:





imgs_features = feat_extractor.predict(processed_imgs)

print("features successfully extracted!")
imgs_features.shape
      
      



:





cosSimilarities = cosine_similarity(imgs_features)
      
      



pandas dataframe:





columns_name = re.findall(r'[0-9]+', str(files))

cos_similarities_df = pd.DataFrame(cosSimilarities, columns=files, index=files)
cos_similarities_df.head()
      
      



. 6000 SKU. 6000 * 6000. float 0 1 8 , . , 430 ( 130 ). . , - GitHub, . GitHub 100 ( ). , - . :) - - - . :





cos_similarities_df_2.round(2) # cos_similarities_df_2 -     ,   
      
      



, . float. pandas float float16 - .





int:





cos_similarities_df_2.apply(lambda x: x * 100)

cos_similarities_df_2.apply(lambda x: x.astype(np.uint8))
      
      



31 . .





h5:





cos_similarities_df_2.to_hdf('storage/cos_similarities.h5', 'data')
      
      



40 . , -, GitHub, -, :)





, , :





import re

# function to retrieve the most similar products for a given one

def retrieve_most_similar_products(given_img):

    print("-----------------------------------------------------------------------")
    print("original product:")
    original = load_img(given_img, target_size=(imgs_model_width, imgs_model_height))
    original_img = int(re.findall(r'[0-9]+', given_img)[0])
    print((df_items_2.iloc[[original_img]]['name'].iat[0], df_items_2.iloc[[original_img]]['pricer_uah'].iat[0], df_items_2.iloc[[original_img]]['url'].iat[0]))
   
    plt.imshow(original)
    plt.show()

    print("-----------------------------------------------------------------------")
    print("most similar products:")

    closest_imgs = cos_similarities_df[given_img].sort_values(ascending=False)[1:nb_closest_images+1].index
    closest_imgs_scores = cos_similarities_df[given_img].sort_values(ascending=False)[1:nb_closest_images+1]

    for i in range(0,len(closest_imgs)):
        original = load_img(closest_imgs[i], target_size=(imgs_model_width, imgs_model_height))
        item = int(re.findall(r'[0-9]+', closest_imgs[i])[0])
        print(item)
        print((df_items_2.iloc[[item]]['name'].iat[0], df_items_2.iloc[[item]]['pricer_uah'].iat[0], df_items_2.iloc[[item]]['url'].iat[0]))
        plt.imshow(original)
        plt.show()
        print("similarity score : ",closest_imgs_scores[i])

kbr = '' #    
find_rec = int(df_items_2.index[df_items_2['name'] == kbr].tolist()[0]) # df_items_2    ,     
print(find_rec)

retrieve_most_similar_products(files[find_rec])
      
      



:)





.





, - :





, :





import os

if not os.path.exists('storage'):
    os.makedirs('storage')

if not os.path.exists('images'):
    os.makedirs('images')
      
      



, xml - .





, , :





# importing required modules
import urllib.request

image_counter = 0

error_list = []

#        
def image_from_df(row):
    global image_counter
    
    item_id = image_counter
    
    filename = f'images/{item_id}.jpg'
    image_url = f'{row.image}'

    try:
      conn = urllib.request.urlopen(image_url)
       
    except urllib.error.HTTPError as e:

      # Return code error (e.g. 404, 501, ...)
      error_list.append(item_id)

    except urllib.error.URLError as e:

      # Not an HTTP-specific error (e.g. connection refused)
      
      print('URLError: {}'.format(e.reason))


    else:

      # 200
      urllib.request.urlretrieve(image_url, filename)
      image_counter += 1
      
      



xml, :





df_items_2.apply(lambda row: image_from_df(row), axis=1)
      
      



, . . . , xml , . , , , , , .





for i in error_list:

  df_items_2.drop(df_items_2.index[i], inplace = True)
  df_items_2.reset_index(drop=True, inplace = True) 

print(f'   : {error_list}')
print(len(error_list))
      
      



, . , - ! )





, - )





P.S. , VGG - VGG19. , .





P.S.S , : , Senior JavaScript Developer ( js CORS-); , Senior Python Developer Senior Engineer ( Docker CI/CD pipeline); SkillFactory, SkillFactory Accelerator ( , Data Science ); (, A/B- ); (otro mentor que ayudó con la comprensión de los problemas de PNL y, en particular, el trabajo de los magnates a la hora de crear chat bots (otro proyecto en el que trabajé como parte del acelerador y del que puedo hablar un poco más adelante); este es Emil Maggeramov (mentor, quien en general supervisó mi progreso en el acelerador para la creación de este proyecto); estos son los compañeros de clase Valery Kuryshev y Georgy Bregman (llamados regularmente una vez a la semana y compartieron la experiencia adquirida durante la semana).








All Articles