Визуализация портфолио (на данном рисунке, чем краснее область, тем больше фото в этой области, имеющих нулевое количество скачиваний)
Коротко о задаче: есть фото-портфолио с большим количеством фото, хотим узнать список популярных и не популярных тем.
Для примера рассмотрим портфолио автора с iStock (рассмотрим только фото с возрастом <= 2 лет; около 5000 фото).
Информацию о фото (количество загрузок, дату загрузки, ключевые слова, имя автора, ...) можно узнать по ссылке: rest.istockphoto.com/api/v1/assets/516188033
import requests
from bs4 import BeautifulSoup
import wget
import json
import sys
import time
author_name = "urbancow"
author_id = 300612
page_count = 93
path_to_save = "/home/forcesh/Downloads/Projects/iStock/urbancow/"
for page in range(1, page_count):
search_url = 'http://www.istockphoto.com/ru/portfolio/'+author_name
data = {"facets": {"pageNumber": page, "perPage": 100, "abstractType": ["photos", "illustrations"],
"order": "bestMatch", "portfolioID": [author_id],
"additionalAudio": "false"}}
r = requests.post(search_url, json.dumps(data))
soup = BeautifulSoup(r.text, "lxml")
scope = soup.select("div.results-container")
search_list = scope[0].select("section.search-list")
figure_list = search_list[0].select("figure")
for figure in figure_list:
href = figure.select("a")[0].get("href")
gm_id = href.rsplit("gm", 1)[1].split('-')[0]
rest_url = "https://rest.istockphoto.com/api/v1/assets/"+gm_id
wget.download(rest_url, out = path_to_save + gm_id)
#iStock is sensitive to frequent requests
time.sleep(0.5)
import json
import requests
import wget
import time
author_name = 'urbancow'
portfolio_path = '/home/forcesh/Downloads/Projects/iStock/'
path_to_gm_ids_list = portfolio_path + author_name + '/gm_ids_list.txt'
gm_ids = open(path_to_gm_ids_list,'r')
rest_info_file = open(portfolio_path + author_name + '/rest_info.csv','a')
rest_info_file.write('gm_id,downloads,keywordsn')
for gm_id in gm_ids:
gm_id = gm_id.replace('n','')
rest_page = open(portfolio_path + author_name + '/' + gm_id,'r').read()
parsed_string = json.loads(rest_page)
downloads_count = parsed_string['downloadsCount']
info = gm_id + ','
info += str(downloads_count)
info += ','.join( parsed_string['keywords'])
rest_info_file.write(info)
thumbnails_preview_url = parsed_string['thumbnails']['previewUrl']
wget.download(thumbnails_preview_url, out = portfolio_path + author_name+'/images/'+gm_id+'.jpg')
print thumbnails_preview_url
time.sleep(0.5)
Признаки, по которым будут разбиваться данные, будем получать при помощи модели inceptionV3. Но будем использовать не названия объектов, которые находит нейронка, а выходные сигналы с предпоследнего слоя (2048 признаков).
# coding: utf-8
from keras_inception_v3.inception_v3 import InceptionV3, preprocess_input
from keras.preprocessing import image
import numpy as np
from keras_inception_v3.imagenet_utils import decode_predictions
import json
import os
import keras
from multiprocessing import Pool
from multiprocessing.dummy import Pool as ThreadPool
import time
import h5py
import keras.backend as K
import tensorflow as tf
import nltk
from nltk.stem import WordNetLemmatizer
def preprocess_image(image_path):
img = image.load_img(image_path, target_size=(299, 299))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
return x
def preprocess_image_batch(images):
batch = np.zeros((len(images), 299, 299, 3), dtype=np.float32)
for i, image_path in enumerate(images):
batch[i] = preprocess_image(image_path)
return batch
def get_descriptor(i):
offset = i%num_file_items
file_id = i/num_file_items
f = open("descriptors/descriptors_inception_v3_pool3_%d.bin"%file_id, "rb")
f.seek(8*2048*offset, os.SEEK_SET)
desc = np.fromfile(f, dtype=np.double, count=2048)
f.close()
return desc
model = InceptionV3(include_top=False, weights=None)
weights_file = h5py.File('/home/snapper/.keras/models/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5')
weight_value_tuples = []
for layer in model.layers:
layer_name = layer.name
if layer_name in weights_file and len(weights_file[layer_name]) != 0:
g = weights_file[layer_name]
weight_names = [n.decode('utf8') for n in g.attrs['weight_names']]
weight_values = [g[weight_name] for weight_name in weight_names]
symbolic_weights = layer.weights
weight_value_tuples += zip(symbolic_weights, weight_values)
K.batch_set_value(weight_value_tuples)
batch = preprocess_image_batch(['/home/snapper/120549251.jpg'])
p = model.predict(batch)
images = ['/home/snapper/120549251.jpg' for i in range(100)]
images = np.array(images)
num_file_items = 1024*4
descriptors = np.zeros([num_file_items, 2048], dtype=np.double)
step = 64
m = len(images)
for i in range(0, m, step):
data_end_index = min(i % num_file_items + step, i % num_file_items+min(i+step, m)-i)
data_indexes = np.arange(i % num_file_items, data_end_index)
batch_indexes = np.arange(i, min(i+step, m))
batch = images[batch_indexes]
X = preprocess_image_batch(batch)
p = model.predict(X)
descriptors[data_indexes] = p
if i%num_file_items+step >= num_file_items or i+step>= m:
print 'saving to', 'descriptors_inception_v3_pool3_%d.bin'%(i/num_file_items)
descriptors[:data_end_index].tofile('descriptors/descriptors_inception_v3_pool3_%d.bin'%(i/num_file_items))
np.allclose(get_descriptor(0), p[0])
Ссылка на обученную InceptionV3
Далее будем использовать t-distributed Stochastic Neighbor Embedding (t-sne) — инcтрумент для визуализации высокоразмерных данных. Разработчики sklearn в документации рекомендуют предварительно использовать другие методы для сокращения размерности (PCA для плотных данных либо TruncatedSVD для разряжённых данных). Сжать размерность, например, до 50 и уже затем использовать t-sne.
В нашем случае данные разряжённые, поэтому выбираем TruncatedSVD и сжимаем размерность до 50. Затем сжимаем данные до двухмерного пространства. И всё, можно отрисовывать картинки.
Для того, чтобы выделить на построенном изображении популярные и не популярные темы, будем использовать gaussian_kde из sklearn (функция для оценки плотности). Посчитаем м.о.(mean) и с.о.(sd) скачиваний и выделим 4 группы фото:
- фото, у которых количество скачиваний = 0
- фото, у которых количество скачиваний > 0 и <= mean + sd
- фото, у которых количество скачиваний > mean + sd и <= mean + 2 * sd
- фото, у которых количество скачиваний > mean + 2 * sd
Далее будем строить 4 картинки для каждой группы фото. Градиентом от синего к зелёному и от зелёного к красному выполняется визуализация плотности точек(фото) в данной области. Чем ближе цвет к красному, тем больше точек в данной области принадлежит к рассматриваемой группе.
# coding: utf-8
import numpy as np
import pandas as pd
import sys
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import json
import os
import cv2
from sklearn import manifold, decomposition
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from scipy.stats import gaussian_kde
import gc
num_file_items = 1024*4
def get_descriptor( directory, i ):
offset = i%num_file_items
file_id = i/num_file_items
f = open( directory + "descriptors_inception_v3_pool3_%d.bin"%file_id, "rb" )
f.seek(8*2048*offset, os.SEEK_SET)
desc = np.fromfile(f, dtype=np.double, count=2048)
f.close()
return desc
def imscatter(x, y, image, ax=None, label=False):
label=label==True
im = OffsetImage(image)
x, y = np.atleast_1d(x, y)
artists = []
for x0, y0 in zip(x, y):
ab = AnnotationBbox(im, (x0, y0), xycoords='data', frameon=label)
artists.append(ax.add_artist(ab))
ax.update_datalim(np.column_stack([x, y]))
ax.autoscale()
return artists
name = 'urbancow'
portfolio_dir = '/home/traineeship/portfolio/'
images_directory = portfolio_dir + name + '/images/'
descriptors_directory = portfolio_dir + name + '/descriptors/'
#order number of gm_id
with open( descriptors_directory + 'descriptors_map.json' ) as data_file:
descriptors_map = json.load( data_file )
#gm_id,downloads,keywords
rest_info = pd.read_csv( portfolio_dir + name + '/rest_info.csv' )
gm_ids = rest_info['gm_id']
downloads = dict( zip( list( map( str, rest_info['gm_id'] ) ), rest_info['downloads'] ) )
X = np.matrix( [ get_descriptor( descriptors_directory, descriptors_map[str(gm_id)]) for gm_id in gm_ids ] ) #descriptors
y = np.array( [ downloads[str(gm_id)] for gm_id in gm_ids ] ) #downloads count
sd_downloads = y.std()
mean_downloads = y.mean()
X_list = list()
y_list = list()
gm_ids_list = list()
part_size = 10000
if (len(X) > part_size):
batches_count = len(X) / part_size + 1
index = 0
for i in range(batches_count - 1):
if (i < batches_count - 2):
X_list.append(X[index:index+part_size])
y_list.append(y[index:index+part_size])
gm_ids_list.append(gm_ids[index:index+part_size])
elif (i == batches_count - 2):
if (len(X[index+part_size:]) < 1000):
X_list.append(X[index:])
y_list.append(y[index:])
gm_ids_list.append(gm_ids[index:])
batches_count -= 1
else:
X_list.append(X[index:index+part_size])
y_list.append(y[index:index+part_size])
gm_ids_list.append(gm_ids[index:index+part_size])
index += part_size
X_list.append(X[index:])
y_list.append(y[index:])
gm_ids_list.append(gm_ids[index:])
index += part_size
else:
X_list.append(X)
y_list.append(y)
gm_ids_list.append(gm_ids)
print len(X_list), len(X)
del X
del y
del gm_ids
del rest_info
del descriptors_map
del downloads
gc.collect()
for ii in range(len(X_list)):
X = X_list[ii]
y = y_list[ii]
gm_ids = gm_ids_list[ii]
#TruncatedSVD due to sparse data
X = decomposition.TruncatedSVD(n_components=50).fit_transform(X)
X = manifold.TSNE().fit_transform(X)
fig, ax = plt.subplots()
scale_factor=15
fig.set_size_inches(16*scale_factor, 9*scale_factor, forward=True)
for i, gm_id in enumerate( gm_ids ):
image_path = images_directory + str(gm_id) + '.jpg'
try:
image=cv2.imread(image_path)
b,g,r = cv2.split(image) # get b,g,r
image = cv2.merge([r,g,b]) # switch it to rgb
image=cv2.resize(image, (80, 80))
except Exception as ex:
size = 80, 80, 3
image = np.zeros(size, dtype=np.uint8)
pass
x1=X[i, 0]
x2=X[i, 1]
imscatter(x1, x2, image, ax)
ax.plot(x1, x2)
for idx in range(4):
if (idx == 0):
x1=X[y == 0][:,0]
x2=X[y == 0][:,1]
elif (idx == 1):
x1=X[(y > 0) & (y <= mean_downloads + sd_downloads)][:,0]
x2=X[(y > 0) & (y <= mean_downloads + sd_downloads)][:,1]
elif (idx == 2):
x1=X[(y > mean_downloads + sd_downloads) & (y <= mean_downloads + 2 * sd_downloads)][:,0]
x2=X[(y > mean_downloads + sd_downloads) & (y <= mean_downloads + 2 * sd_downloads)][:,1]
elif (idx == 3):
x1=X[y > mean_downloads + 2 * sd_downloads][:,0]
x2=X[y > mean_downloads + 2 * sd_downloads][:,1]
xy = np.vstack([x1,x2])
kde = gaussian_kde(xy)#simple density estimation
z = kde(xy)
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
xedges = np.linspace(xmin, xmax, 700)
yedges = np.linspace(ymin, ymax, 700)
xx, yy = np.meshgrid(xedges, yedges)
gridpoints = np.array([xx.ravel(), yy.ravel()])
zz = np.reshape(kde(gridpoints), xx.shape)
im = ax.imshow(zz, cmap='jet', interpolation='nearest', origin='lower', extent=[xmin, xmax, ymin, ymax])
ax.grid()
suffix_name = str(idx) + '_tsne_part'+str(ii)+'.png'
fig.savefig('vism/'+name+'/descriptors/'+name+'_'+ suffix_name, dpi=100, bbox_inches='tight')
fig.clf()
ax.cla()
Построились следующие картинки (размер картинок около 50 МБ, разрешение порядка 10к x 10к; GPicView неплохо их открывает):
- количество скачиваний равно 0
- количество скачиваний > 0 и <= mean + sd
- количество скачиваний > mean + sd и <= mean + 2 * sd
- количество скачиваний > mean + 2 * sd
Получилось, что у автора следующие популярные темы:
- фото улиц сверху
- места с большим скоплением народа (метрополитен, ...);
- достопримечательности
- школьники
- внутри супермаркетов с детьми
и не популярные:
- пейзаж; природа
P.S. Специально для тех, кто не хочет заниматься сбором данных и вычислением признаков, но хочет посмотреть, как визуализируются данные, выкладываю портфолио другого автора и вычисленные признаки.
Автор: forcesh