В предыдущей части вы узнали, что качество модели 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 и более процентов (ну это я погорячился :)). Для удобства — ссылки на первую и вторую часть этой статьи.