Сможете ли вы отличить собаку от кошки, или Как объяснить проблемы дата-сайентиста коллегам. Часть 3

от автора

В предыдущей части вы узнали, что качество модели Data Science в первую очередь зависит от исходных данных: растет, если у вас больше исходных переменных и уровней градации каждой из них, более равномерно распределены значения каждой из переменных; если у вас меньше пропущенных значений и они менее скоррелированы друг с другом. И наконец, если ваша модель распознает события из прошлого, а не предсказывает будущее.

В третьей, заключительной части статьи я дам ответы по рисункам из предыдущей части. И для дата-сайентистов приведу общий код Python, который использовался для получения всех представленных ниже изображений.

Код для изображений
# Импортируем модули from PIL import Image import numpy as np from tqdm import tqdm import os import pandas as pd import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec plt.rcParams['figure.figsize'] = (18,7)  def im_centralized(img):         """Обрезает рисунок в квадрат     по меньшей стороне"""              width, height = img.size      # рассчитываем точки для обрезки в квадрат      # по наименьшей стороне     if height > width:         left = 0         top = int((height - width) / 2)         right = width         bottom = width + int((height - width) / 2)     else:         left = int((width - height) / 2)         top = 0         right = height + int((width - height) / 2)         bottom = height      # возвращаем изображение, обрезанное в квадрат      # по меньшей стороне     return img.crop((left, top, right, bottom)) 

Рисунки, связанные с количеством переменных

Далее на рисунках-ответах розовым помечены те точки, которые на рисунках-загадках являются белыми. На гистограмме справа от рисунка красной вертикальной линией отмечен порог между теми точками, которые отображаются как белые, и теми, которые отображаются как черные.

Код для генерации рисунков
def show_images(im_path, share, nw_size):        """Получает путь к изображению, обрезает его в квадрат     по наименьшей стороне. Приводит к размеру     nw_size * nw_size точек.     share точек делает черными, остальные белыми.     Выводит двухцветное изображение, и рядом с ним -      гистограмму распределения цветов.     Также выводит серое изображение, красит точки,     оказавшиеся белыми, в розовый; рядом с изображением рисует      гистограмму распределения оттенков серого, а также порог,     разделяющий точки, изображенные на первом рисунке     белыми и черными"""          # загружаем изображение     im = Image.open(im_path)      # обрезаем в квадрат по меньшей стороне     im_croped = im_centralized(im)      # переводим в квадрат nw_size * nw_size точек     im_resized = im_croped.resize((nw_size, nw_size))          # конвертируем изображение в тона серого цвета      im_grey = im_resized.convert("L")          # переводим в массив numpy     im_grey = np.array(im_grey)      # выделяем три компоненты цвета     im_red = im_grey.copy()     im_green = im_grey.copy()     im_blue = im_grey.copy()      # значения переменных превращаем в одномерный массив     ravel_points = im_grey.copy().ravel()      # сортируем значения переменных     ravel_points.sort()      # находим порог, отличающий белых от черных     threshold = ravel_points[int(share * len(ravel_points))]      # формируем двухцветное изображение     im_2_colors = im_grey.copy()     im_2_colors[im_2_colors <= threshold] = 0     im_2_colors[im_2_colors > threshold] = 255      # формируем изображение в серых тонах,     # подсвечиваем белые точки (на двухцветном изображении)     # розовым цветом     im_red[im_red > threshold] = 255     im_green[im_green > threshold] = 190     im_blue[im_blue > threshold] = 190      # добавляем дополнительную ось     im_red = im_red[:,:,np.newaxis]     im_green = im_green[:,:,np.newaxis]     im_blue = im_blue[:,:,np.newaxis]      # формируем цветное изображение     im_marked = np.concatenate([im_red, im_green, im_blue],                                 axis=2) / 255      # получаем параметры гистограммы     hist_height, _ = np.histogram(im_grey.ravel(), 64)      # формируем фигуру     fig = plt.figure(figsize=(18,12))      # создаем сетку для двух графиков     gs = GridSpec(1, 3, figure=fig)     axs0 = fig.add_subplot(gs[0, :-1])     axs1 = fig.add_subplot(gs[0, -1])          # выводим изображение в серых тонах,     # подкрашенное розовым     axs0.imshow(im_marked)     # название изображения     axs0.set_title('Изображение', fontsize=16)     # шрифт меток оси     axs0.tick_params(labelsize=14)     # гистограмма для изображения в серых тонах     axs1.hist(im_grey.ravel(), 64, color='gray');     axs1.set_title('Распределение значений переменных',                     fontsize=16);     # рисуем порог     axs1.plot([threshold, threshold], [0, max(hist_height)], 'r',                label='Порог, разделяющий черные и белые точки')     # выводим легенду     axs1.legend(fontsize=14);       axs1.tick_params(labelsize=14)          # аналогично для двухцветного изображения     fig = plt.figure(figsize=(18,12))      gs = GridSpec(1, 3, figure=fig)     axs0 = fig.add_subplot(gs[0, :-1])     axs1 = fig.add_subplot(gs[0, -1])          axs0.imshow(im_2_colors, cmap='gray')     axs0.set_title('Изображение', fontsize=16)     axs0.tick_params(labelsize=14)     axs1.hist(im_2_colors.ravel(), 64, color='k');     axs1.set_title('Распределение значений переменных',                     fontsize=16);     axs1.tick_params(labelsize=14)   im_path = 'путь к файлу с изображением' # задаем размер рисунка NW_SIZE = 100  # задаем долю значений черного цвета SHARE = .95  # выводим изображение show_images(im_path, SHARE, NW_SIZE)

Ответ к рисунку 3 — вертолет. 1 024 переменных, частота модального значения 95%:

Ответ к рисунку 4 — лошадь. 2 500 переменных, частота модального значения 95%:

Ответ к рисунку 5 — самолет. 5 000 переменных, частота модального значения 95%:

Ответ к рисунку 6 — автобус. 10 000 переменных, частота модального значения 95%:

Ответ к рисунку 7 — скоростной поезд. 20 000 переменных, частота модального значения 95%:

Ответ к рисунку 8 — велосипед. 40 000 переменных, частота модального значения 95%:

Ответ к рисунку 9 — слон. 100 000 переменных, частота модального значения 95%:

Ответ к рисунку 10 — рыба. 250 000 переменных, частота модального значения 95%:

Рисунки, связанные с частотой модального значения

Для их генерации использовался тот же код, что я для рисунков, связанных с числом переменных.

Ответ к рисунку 11 — лягушка. 10 000 переменных, частота модального значения 95%:

Ответ к рисунку 12 — парусный корабль. 10 000 переменных, частота модального значения 90%:

Ответ к рисунку 13 — автомобиль. 10 000 переменных, частота модального значения 80%:

Ответ к рис. 14 — тигр. 10 000 переменных, частота модального значения 70%.

Ответ к рис. 15 — трактор. 10 000 переменных, частота модального значения 60%:

Ответ к рисунку 16 — птица. 10 000 переменных, частота модального значения 50%:

Ответ к рисунку 17 — змея. 10 000 переменных, частота модального значения 95%:

Рисунок для демонстрации трех градаций переменных

Далее справа от рисунка я приведу гистограмму. На ней синей вертикальной чертой изображена граница между точками, раскрашенными черным и серым цветом. Красной вертикальной чертой – между раскрашенными серым и белым цветом.

Код для генерации изображений
def images_3_colors(im_path, nw_size):          """Получает путь к изображению, обрезает его в квадрат     по наименьшей стороне. Приводит к размеру     nw_size * nw_size точек.     Делает 1/3 точек черными, 1/3 серыми, 1/3 белыми.     Выводит трехцветное изображение, и рядом с ним     гистограмму распределения цветов.     """          # загружаем изображение     im = Image.open(im_path)      # обрезаем в квадрат по меньшей стороне     im_croped = im_centralized(im)      # переводим в квадрат nw_size * nw_size точек     im_resized = im_croped.resize((nw_size, nw_size))          # конвертируем изображение в тона серого цвета      im_grey = im_resized.convert("L")          # переводим в массив numpy     im_grey = np.array(im_grey)      # значения переменных превращаем в одномерный массив     ravel_points = im_grey.copy().ravel()      # сортируем значения переменных     ravel_points.sort()      # находим 2 порога, делящие градации цвета     # на 3 части     threshold1 = ravel_points[int(1/3 * len(ravel_points))]     threshold2 = ravel_points[int(2/3 * len(ravel_points))]               # формируем трехцветное изображение     im_3_colors = im_grey.copy()      im_3_colors[im_3_colors <= threshold1] = 0     im_3_colors[im_3_colors > threshold2] = 255     im_3_colors[(im_3_colors > 0)&(         im_3_colors <255)] = 127      # получаем параметры гистограммы     hist_height, _ = np.histogram(im_grey.ravel(), 64)               # формируем фигуру     fig = plt.figure(figsize=(18,12))      # создаем сетку для двух графиков     gs = GridSpec(1, 3, figure=fig)     axs0 = fig.add_subplot(gs[0, :-1])     axs1 = fig.add_subplot(gs[0, -1])          # выводим изображение в серых тонах     axs0.imshow(im_grey, cmap='gray')     # название изображения     axs0.set_title('Изображение', fontsize=16)     # шрифт меток оси     axs0.tick_params(labelsize=14)     # гистограмма для изображения в серых тонах     axs1.hist(im_grey.ravel(), 64, color='gray');     axs1.set_title('Распределение значений переменных',                     fontsize=16);     # рисуем пороги     axs1.plot([threshold1, threshold1], [0, max(hist_height)], 'b',                label='Порог, разделяющий черные и серые точки')     axs1.plot([threshold2, threshold2], [0, max(hist_height)], 'r',                label='Порог, разделяющий серые и белые точки')     # выводим легенду     axs1.legend(fontsize=14);       axs1.tick_params(labelsize=14)          # формируем фигуру     fig = plt.figure(figsize=(18,12))      # создаем сетку для двух графиков     gs = GridSpec(1, 3, figure=fig)     axs0 = fig.add_subplot(gs[0, :-1])     axs1 = fig.add_subplot(gs[0, -1])      # выводим трехцветное изображение     axs0.imshow(im_3_colors, cmap='gray')     # название изображения     axs0.set_title('Изображение', fontsize=16)     # шрифт меток оси     axs0.tick_params(labelsize=14)     # гистограмма для изображения в серых тонах     axs1.hist(im_3_colors.ravel(), 64, color='gray');     axs1.set_title('Распределение значений переменных',                     fontsize=16);     axs1.tick_params(labelsize=14) im_path = 'путь к файлу с изображением' # задаем размер изображения NW_SIZE = 100 # выводим изображение images_3_colors(im_path, NW_SIZE)

Ответ к рисунку 18 — овца. 10 000 переменных, три градации с частотой по 33% каждая:

Рисунки с некоррелированными пропусками переменных

Справа от рисунка ниже по-прежнему представлена гистограмма. Самый высокий столбик на ней — число пропусков, замененных средним фоном рисунка. Чем больше пропусков, тем выше этот столбик по сравнению со всеми остальными.

Код для генерации изображений
def show_noised(im_path, share, nw_size):     """Изображение по адресу im_path обрезает     в квадрат по меньшей стороне, переводит      в градации серого, а затем удаляет из него      долю точек, равную share, путем замены их      истинного цвета на средний цвет всего рисунка     Слева выводит зашумленное изображение,      справа - гистограмму распределения цветов"""              im = Image.open(im_path)          # обрезаем в квадрат по наименьшей стороне     im_croped = im_centralized(im)          # переводим в квадрат nw_size * nw_size точек     im_resized = im_croped.resize((nw_size, nw_size))      # конвертируем файл в тона серого     im_grey = im_resized.convert("L")              # конвертируем файл в numpy     im_grey = np.array(im_grey)      # формируем файл шума того же размера,     # что и текущий     noise = np.random.rand(         im_grey.shape[0], im_grey.shape[1])      # формируем зашумленный файл     im_noised = im_grey.copy()      # для точек, которым соответствуют значения     # в случайном файле, превосходящие 1 - share, заменяем      # истинный цвет на цвет фона     im_noised[noise > 1 - share] = im_grey.mean()      # создаем фигуру     fig = plt.figure(figsize=(18,12))      # создаем сетку для двух графиков     gs = GridSpec(1, 3, figure=fig)     axs0 = fig.add_subplot(gs[0, :-1])     axs1 = fig.add_subplot(gs[0, -1])      # выводим изображение в серых тонах,     # подкрашенное розовым     axs0.imshow(im_noised, cmap='gray')     # название изображения     axs0.set_title('Изображение', fontsize=16)     # шрифт меток оси     axs0.tick_params(labelsize=14)     # гистограмма для изображения в серых тонах     axs1.hist(im_noised.ravel(), 64, color='gray');     axs1.set_title('Распределение значений переменных',                     fontsize=16);      axs1.tick_params(labelsize=14)  im_path = 'путь к файлу с изображением' # задаем размер рисунка NW_SIZE = 100  # задаем долю шума SHARE = .5  # выводим изображение show_noised(im_path, SHARE, NW_SIZE)

Ответ к рисунку 19 — велосипедистка. 10 000 переменных, доля нескоррелированных пропусков 20%:

Ответ к рисунку 20 — обезьяна. 10 000 переменных, доля нескоррелированных пропусков 40%:

Ответ к рисунку 21 — стиральная машина. 10 000 переменных, доля нескоррелированных пропусков 50%:

Ответ к рисунку 22 — лев. 10 000 переменных, доля нескоррелированных пропусков 60%:

Ответ к рис. 23 — танк. 10 000 переменных, доля нескоррелированных пропусков 70%:

Ответ к рисунку 24 — чашка кофе. 10 000 переменных, доля нескоррелированных пропусков 80%:

Ответ к рисунку 25 — чайник. 10 000 переменных, доля нескоррелированных пропусков 90%:

Рисунки со скоррелированными пропусками переменных

Слева показан зашумленный рисунок, справа — матрица шума. Они имеют одинаковый размер. Матрица шума накладывается на рисунок. Там, где в матрице шума белые точки, на рисунке оригинальное изображение заменено серым цветом. Там, где в матрице шума черные точки, сохранено оригинальное изображение.

Код для генерации изображений
def show_correlated_noise(im_path, nw_size, only_left=True):     """Обрезает рисунок в квадрат по наименьшему     размеру, конвертирует в градации серого цвета,     если only_left=True выводит только пикселизированное     изображение.     Если only_left=False, то функция     выводит слева исходный рисунок с сеткой,      соответствующей числу пикселей справа,     а справа - пикселизированное изображение"""                    # загружаем изображение     im = Image.open(im_path)      # обрезаем в квадрат по наименьшей стороне     im_croped = im_centralized(im)      # конвертируем изображение в тона серого цвета      im_grey = im_croped.convert("L")      # переводим в квадрат nw_size * nw_size точек     im_resized = im_grey.resize((nw_size, nw_size))          # конвертируем файл в numpy     im_grey1 = np.array(im_resized)      # формируем файл шума того же размера,     # что и изображение     noise = np.random.rand(         im_grey1.shape[0], im_grey1.shape[1])          # среднее значение      # цвета всего серому изображению     gray_mean = im_grey1.mean()            # формируем файл со скоррелированным шумом     noise_correlated = 0 * noise              # вероятность шума изменяется вдоль     # ширины рисунка по логистическому      # закону: слева почти все точки зашумлены,     # справа шума почти нет     for i in tqdm(range(im_grey1.shape[1])):         # noise_column - одномерный вектор         # для одновременного вычисления степени зашумленности         # всех точек с одинаковой координатой по ширине          # копируем нескоррелированный шум          # с данной координатой по ширине         noise_column = noise[:, i].copy()          # в зависимости от координаты по ширине с помощью         # логистической кривой устанавливаем точки, в которых          # шум равен 1: чем левее, тем таких точек больше          noise_column[noise_column < 1 / (             1 + np.exp((i / im_grey1.shape[1] - .5)/.05)) ] = 1          # формируем очередной столбец для скоррелированного шума         noise_correlated[:, i] = noise_column                     # формируем зашумленное изображение         im_grey_noised = im_grey1.copy()         im_grey_noised[noise_correlated == 1] = gray_mean                            if only_left == True:         # создаем фигуру         fig = plt.figure(figsize=(8, 8))         plt.imshow(im_grey1, cmap='gray');                  plt.title('Исходное изображение'.format(             nw_size, nw_size),                        fontsize=16);          plt.tick_params(labelsize=14)                  else:          # создаем фигуру         fig = plt.figure(figsize=(18, 9))          # создаем сетку для двух изображений         gs = GridSpec(1, 2, figure=fig)         axs0 = fig.add_subplot(gs[0, 0])         axs1 = fig.add_subplot(gs[0, 1])          # выводим зашумленное изображение в серых тонах         axs0.imshow(im_grey_noised, cmap='gray')          # название изображения         axs0.set_title('Зашумленное изображение', fontsize=16)         # шрифт меток оси         axs0.tick_params(labelsize=14)                  # выводим файл скоррелированного шума         axs1.imshow(noise_correlated == 1, cmap='gray');          axs1.set_title('Шум: зашумленные точки показаны белым цветом'.format(             nw_size, nw_size),                         fontsize=16);          axs1.tick_params(labelsize=14)   im_path = 'путь к файлу с изображением' # задаем размер рисунка NW_SIZE = 100  # выводим зашумленное изображение show_correlated_noise(im_path, NW_SIZE, only_left=False)  # выводим исходное изображение show_correlated_noise(im_path, NW_SIZE, only_left=True)

Ответ к рисунку 26 — две девушки. 10 000 переменных, доля скоррелированных пропусков 50%:

Ответ к рисунку 27 — две девушки. 1 000 000 переменных, доля скоррелированных пропусков 50%:

Вот и всё!

В заключение пожелаем представителям бизнес-подразделений снабжать дата-сайентистов качественными данными, а дата-сайентистам — строить на них модели с точностью 100 и более процентов (ну это я погорячился :)). Для удобства — ссылки на первую и вторую часть этой статьи.


ссылка на оригинал статьи https://habr.com/ru/articles/750810/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *