В предыдущей части вы узнали, что качество модели 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/
Добавить комментарий