Русский
Русский
English
Статистика
Реклама

Data visualization

Визуализация данных в интерфейсе

09.04.2021 20:23:06 | Автор: admin

Меня зовут Илона, я Senior Experience Designer в EPAM. Я проектирую сложные интерфейсы для зарубежных заказчиков, выступаю с докладами, менторю дизайнеров. В свободное время преподаю проектирование интерфейсов в магистратуре Университета ИТМО и ведуТелеграм-канал о UX-дизайне.

В этой статье хочу немного поговорить об истории инфографики и о том, как с точки зрения дизайна визуализировать данные и эффективно отображать их в интерфейсе.


Множественные данные, которые, в принципе, можно считать Big Data, представляют из себя набор информации (цифры, процентные соотношения, статусы, параметры, текст и пр.), на основании которой пользователь может делать определённые выводы.

Любимые дизайнерамидашбордырезультат визуализации таких данных.

Dashboard by Ghulam RasoolDashboard by Ghulam Rasool

Но раскидать цветастые графики по экрануещё не значит хорошо визуализировать информацию.

Проектирование интерфейса, который визуализирует и отображает данные это отдельная сложная задача.
Как же подходить к проектированию такого интерфейса и сделать результат действительно полезным для пользователя?

В первую очередь вам понадобятся знания основ инфографики.

Инфографика одна из формкоммуникационного дизайна, которая помогает организовать и наглядно показать большие объёмы данных.

Инфографика зародилась в 18 веке с изобретением столбчатой, линейной и круговой диаграмм шотландским экономистом по имени Уильям Плейфей, которые он опубликовал в книгеCommercial and Political Atlas and Statistical Breviary.Его, по моему скромному мнению, гениальные наработки послужили базисом визуализации данных и до сих пор активно используются для отображения информации.

Первая столбчатая диаграмма за авторством Уильяма Плейфея, 1781Первая столбчатая диаграмма за авторством Уильяма Плейфея, 1781

Большое влияние на современную визуализацию данных оказал профессор Йельского университетаЭдвард Тафти, написавший несколько книг об отображении информации, в том числеThe Visual Display of Quantitative Information.Книга содержит множество примеров и я крайне рекоммендую её к прочтению, для расширения кругозора.

Книги Э. Тафти о данныхКниги Э. Тафти о данных

Тафти выступает за создание информационно насыщенных визуализаций, показывающих все доступные данные для микро/макрочтения. При беглом обзоре насыщенной данными визуализации (макрочтение) человек должен увидеть общую картину и тенденции, а при близком изучении (микрочтение) разглядеть каждый элемент и понять причины наблюдаемых тенденций.

Кроме того, Тафти пропагандирует аскетичную инфографику, где главный компонент данные, а не визуальная эстетика, ведь основная задача наглядно отобразить точную информацию и убрать всё лишнее.

Косметическое украшение никогда не компенсирует ошибки отображения данных, зато может их исказить.
Э. Тафти

Иллюстрация из книги Э. ТафтиИллюстрация из книги Э. Тафти

Способы визуализации информации

Для отображения данных используются базовые элементы изобразительного искусства:

  1. Точка индикатор положения данных. Может находиться на оси графика, точке окружности и т.д.

  2. Линия соединяет две точки и отображает тенденцию изменения данных.

  3. Цвет инструмент выделения качества данных (например, хорошо зеленый, плохо красный). Имейте в виду, что около 4% людей в силу физиологических или национальных причин могут трактовать значение цвета по-разному. Например, многие дальтоники видят и красный и зеленый цвета как коричневый.

  4. Пропорции инструмент отображения соотношения данных. Например, взглянув на два столбика в диаграмме, мы быстро определим, каких данных больше, исходя из высоты столбиков.

На основе этих простых элементов можно построить множество визуализаций. Перечислю лишь основные и самые распространённые типы в зависимости от целей отображения.

Инструменты визуализации

  • Крупный показатель для быстрого информирования без погружения в контекст.
    Примеры применения: сумма дохода за квартал, количество активных пользователей, класс энергоэффективности.

  • Круговая диаграмма, пончик отображение количества чего-либо от общей массы.
    Примеры применения: количество гостей/зарегестрированных/регулярных покупателей от общего числа пользователей интернет-магазина, отношение затрат на развлечения/здоровье/путешествия в онлайн-банке, процент любителей белого/красного/розового вина в винном бутике.

  • Солнечные лучи похожа на круговую диаграмму, но бонусом содержит иерархическую структуру (внешнее кольцо дочерние элементы внутреннего).
    Примеры применения: количество продаж за разные кварталы по месяцам, количество жильцов в субьектах по федерациям.

  • Вертикальная (столбчатая), горизонтальная диаграмма/гистограмма сравнение значений. Могут быть сгруппированными и сегментированными. Горизонтальный вариант лучше в случае длинных заголовков.
    Примеры применения: сравнение количества продаж разных товаров по месяцам, сравнение количества сотрудников разного уровня в компании, сравнение количества посетителей на разных сайтах.

  • Пулевая диаграмма разновидность столбчатой диаграммы для отображения нескольких слоев информации.
    Примеры применения: доходы за год по сравнению со средним значением, количество новых пользователей по сравнению с контрольными показателями.

  • Пузырьковая диаграмма сравнение значений в объеме.
    Примеры применения: пробег автомобилей в зависимости от года выпуска, объём продаж на продукты в зависимости от цены.

  • Линейный график, диаграмма с областями отображение перспективы изменения данных во времени и предсказания тенденций. Линейный график хорошо отображает несвязанные потоки данных, а диаграмма с областями подходит для отображения частей целого.
    Внимание! Полупрозрачные заливки внахлёст не очень хорошее решение, так как могут путать и создавать впечатление отдельности данных.
    Примеры применения: доходы и расходы в неделю за январь, количество активных пользователей в каждом месяце 2020 года.

  • Временной график линейное отображение событий.
    Примеры применения: отображение опыта работы, план выполнения проекта.

  • Диаграмма Ганта вариация временного графика для отображения нескольких слоев информации.
    Примеры применения: отображение опыта работы в разных областях, план выполнения разных частей проекта.

  • Картограмма отображение этнографической информации.
    Примеры применения: уровень опастности в разных странах по количеству заболевших COVID-19, количество людей в разных штатах.

  • Диаграмма Венна отображение пересечений, классов и общих черт.
    Примеры применения: виды реагентов и результат их взаимодействия, появление мультидисциплинарных специалистов на пересечениях профессий. Другие примеры и виды диаграмм можно посмотреть по этой ссылке.

  • Граф отображение связей и структуры данных.
    Примеры применения: связи сущностей в базе данных, связи в рабочем коллективе, визуализация нейросети.

  • Ящик с усами и Японские свечи два похожих, но совершенно разных графика, которые лучше не путать. Ящик с усами используется для отображенияраспределения вероятностейи применяется статистами в ответах на вопросы симметричны ли данные, насколько плотно они сгруппированы и т.д. Японские свечи (совмещение интервального и линейного графиков) больше применим акционерами и помогает следить за биржевыми котировками, ценами и пр.

  • Дерево выглядит совсем не как дерево, но отображает пропорции в иерархии. Саму иерархию визуализирует плохо, для этого больше подходит диаграмма отношений.

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

  • Блок-схема, дорожки бассейна визуализация процесса.
    Примеры применения: сценарий работы устройств на конвейере, процесс взаимодействия разных департаментов во время работы над проектом.

    Пример дорожки бассейна. Если убрать департаменты получится блок-схемаПример дорожки бассейна. Если убрать департаменты получится блок-схема
  • Таблица простое отображение большого объёма однородной информации для детального изучения. Убедитесь, что таблицы отображают только нужную информацию, достаточно читаемы и интерактивны. Отличный гайд по оформлению таблиц можно найтитут.

  • Комбинированная диаграмма гибрид любых диаграмм и подходов для сравнения различных наборов данных (аккуратнее с ней, легко перегрузить и сделать неинформативной).

Проектирование интерфейса

Теперь, когда мы преисполнились видами визуализации и готовы проектировать интерфейс, необходимо перейти кпотребностям пользователей,как и всегда в UX-дизайне.

Чаще всего у пользователей подобных систем множество задач, связанных с принятием решений на основе разнородных данных, которые должны отображаться компактно и, желательно, на одном экране. Очень важно определить, какая задача являетсяведущей. Именно данные по ведущей задаче должны быть наиболее заметны. График с этими данными может быть крупнее или выделен цветом, располагаться в левом верхнем углу (этот угол привлекает больше всего внимания у читающих слева направо).

В целом, можно разделить цели пользователей интерфейсов с визуализированными данными на два типа: анализактуального состоянияи анализстатистики.

Анализ актуального состояния

Анализом актуального состояния занимаются, к примеру, сотрудники АЭС, производства, аэропорта, тот самый админ в серверной.

Дашборд для анализа актуального состояния нагрузки порта Хобокен, Нью-ДжерсиДашборд для анализа актуального состояния нагрузки порта Хобокен, Нью-Джерси

Задача таких пользователей видеть актуальное состояние объектов и быстро принимать решения, так как от оперативности их действий зависит качество работы.

К примеру, человек, отвечающий за работу сложного станка, должен вовремя увидеть, что один из элементов неисправен, и быстро его отремонтировать или остановить работу устройства.

Приложение мониторинга на производстве от devvelaПриложение мониторинга на производстве от devvela

Данные, отображающие актуальное состояние, постоянно меняются и быстро устаревают. Из всего объёма данных, наибольший интерес всегда представляет последний, самый новый показатель. В визуализации таких данных помогают выделение главной информации цветом и размером, анимация прогресса или статуса.

Анализ статистики

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

Например, статистика продаж может обновляться раз в день или неделю, но эта информация позволяет менеджеру понять тенденции и предложить варианты улучшения показателей (акции устроить или рекламу с Нагиевым снять).

Дашборд состатистики продажДашборд состатистики продаж

В визуализации данных подобного рода крайне важно показать динамику и вид сверху. Часто требуется высокоинтерактивный интерфейс, в котором можно менять параметры, разрезы, временные рамки, скрывать и показывать слои данных. Хорошим решением для подобных сервисов будет автоматическое предсказание будущих показателей на основе предыдущих. Например, Анализ расходов Сбербанк Онлайн может предсказать, сколько вы потратите в следующем месяце, на основе ваших предыдущих трат.

Десять советов дизайнеру дашборда

  1. Начните проектирование с анализа потребностей пользователя. Определите цель пользователя и какую информацию он анализирует: актуальную или статистическую.

  2. Подберите способ визуализации данных исходя из того, что пользователь хочет оценить (сравнить объём данных, соотношение, увидеть иерархию и т.д.).

  3. Не перегружайте интерфейс эстетическими рюшами. В погоне за красивым UI и богатыми градиентами можно исказить восприятие данных.

  4. Группируйте разные данные в отдельные блоки.

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

  6. Не перегружайте один экран множеством разнородных блоков данных. Человек не очень хорошо справляется с объемом более 57 блоков.

  7. Используйте понятные заголовкидля всехданных и графиков.

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

  9. Будьте осторожны с комбинированием разнородных данных в один график. Лучше протестировать получившуюся визуализацию на понятность.

  10. Кастомизируемый интерфейс это замечательно. Но ещё лучше, когда система автоматически настроена должным образом и сама покрывает все потребности пользователя. В создании такого интерфейса поможет анализ пользовательских потребностей, интервью и наблюдения.

Не очень позитивный, но актуальный и информативный пример дашборда от Johns HopkinsНе очень позитивный, но актуальный и информативный пример дашборда от Johns Hopkins

Больше о проектировании интерфейсов и UX можно почитать в моём телеграм-канале Поясни за UX.

Подробнее..

3D teeth instance segmentation. В темноте, но не один

23.05.2021 14:09:40 | Автор: admin

3D сегментация зубов от поиска данных до конечного результата. Почти.

Дисклеймер

Данная статья не является обучающей в любом понимании этого термина и носит сугубо информативный характер. Автор статьи не несет ответственности за время, потраченное на ее чтение.

Об авторе

Добрый - всем, зовут Андрей(27). Постараюсь коротко. Почему программирование? По образованию - бакалавр электромеханик, профессию знаю. Отработал 2 года на должности инженера-энергетика в буровой компании вполне успешно, вместо повышения написал заявление - сгорел, да не по мне оказалось это всё. Нравится создавать, находить решения сложных задач, с ПК в обнимку с сознательных лет. Выбор очевиден. Вначале (полгода назад), всерьёз думал записаться на курсы от Я или подобные. Начитался отзывов, поговорил с участниками и понял что с получением информацией проблем нет. Так нашел сайт, там получил базу по Python и с ним уже начал свой путь (сейчас там постепенно изучаю всё, что связано с ML). Сразу заинтересовало машинное обучение, CV в частности. Придумал себе задачу и вот здесь (по мне, так отличный способ учиться).

1. Введение

В результате нескольких неудачных попыток, пришел к решению использовать 2 легковесные модели для получения желаемого результата. 1-ая сегментирует все зубы как [1, 0] категорию, а вторая делит их на категории[0, 8]. Но начнем по порядку.

2. Поиск и подготовка данных

Потратив не один вечер на поиск данных для работы, пришел в выводу что в свободном доступе челюсть в хорошем качестве и формате (*.stl, *.nrrd и т.д.) не получится. Лучшее, что мне попалось - это тестовый образец головы пациента после хирургической операции на челюсти в программе 3D Slicer.

Очевидно, мне не нужна голова целиком, поэтому обрезал исходник в той же программе до размера 163*112*120рх (в данном посте {x*y*z = ш-г-в} и 1рх - 0,5мм), оставив только зубы и сопутствующие челюстно-лицевые части.

Уже больше похоже на то что нужно, дальше - интереснее. Теперь нужно создать маски всех необходимых нам объектов. Для тех, кто уже работал с этим - "autothreshold" не то чтобы совсем не работает, просто лишнего много, думаю, исправление заняло бы столько же времени, сколько и разметка вручную(через маски).

- Пиксели(срезы слева)? - Вспоминаем размер изображения- Пиксели(срезы слева)? - Вспоминаем размер изображения

Размечал часов 12~14. И да, тот факт что я не сразу разметил каждый зуб как категорию стоил мне еще порядка 4 часов. В итоге у нас есть данные, с которыми у же можно работать.

Конечный вариант маски. Smooth 0.5. (сглаживание в обучении не использовалось)Конечный вариант маски. Smooth 0.5. (сглаживание в обучении не использовалось)

Должен добавить, даже на мой (без опыта) взгляд, этих данных очень мало для обучения и последующей полноценной работы нейронной сети. На данном этапе, единственное что пришло в голову, повернуть имеющиеся данные N-раз и соединить, random-crop использовать не стал.

Код подготовки данных
import nrrdimport torchimport torchvision.transforms as tfclass DataBuilder:    def __init__(self,                 data_path,                 list_of_categories,                 num_of_chunks: int = 0,                 augmentation_coeff: int = 0,                 num_of_classes: int = 0,                 normalise: bool = False,                 fit: bool = True,                 data_format: int = 0,                 save_data: bool = False                 ):        self.data_path = data_path        self.number_of_chunks = num_of_chunks        self.augmentation_coeff = augmentation_coeff        self.list_of_cats = list_of_categories        self.num_of_cls = num_of_classes        self.normalise = normalise        self.fit = fit        self.data_format = data_format        self.save_data = save_data    def forward(self):        data = self.get_data()        data = self.fit_data(data) if self.fit else data        data = self.pre_normalize(data) if self.normalise else data        data = self.data_augmentation(data, self.augmentation_coeff) if self.augmentation_coeff != 0 else data        data = self.new_chunks(data, self.number_of_chunks) if self.number_of_chunks != 0 else data        data = self.category_splitter(data, self.num_of_cls, self.list_of_cats) if self.num_of_cls != 0 else data        torch.save(data, self.data_path[-14:]+'.pt') if self.save_data else None        return torch.unsqueeze(data, 1)    def get_data(self):        if self.data_format == 0:            return torch.from_numpy(nrrd.read(self.data_path)[0])        elif self.data_format == 1:            return torch.load(self.data_path).cpu()        elif self.data_format == 2:            return torch.unsqueeze(self.data_path, 0).cpu()        else:            print('Available types are: "nrrd", "tensor" or "self.tensor(w/o load)"')    @staticmethod    def fit_data(some_data):        data = torch.movedim(some_data, (1, 0), (0, -1))        data_add_x = torch.nn.ZeroPad2d((5, 0, 0, 0))        data = data_add_x(data)        data = torch.movedim(data, -1, 0)        data_add_z = torch.nn.ZeroPad2d((0, 0, 8, 0))        return data_add_z(data)    @staticmethod    def pre_normalize(some_data):        min_d, max_d = torch.min(some_data), torch.max(some_data)        return (some_data - min_d) / (max_d - min_d)    @staticmethod    def data_augmentation(some_data, aug_n):        torch.manual_seed(17)        tr_data = []        for e in range(aug_n):            transform = tf.RandomRotation(degrees=(20*e, 20*e))            for image in some_data:                image = torch.unsqueeze(image, 0)                image = transform(image)                tr_data.append(image)        return tr_data    def new_chunks(self, some_data, n_ch):        data = torch.stack(some_data, 0) if self.augmentation_coeff != 0 else some_data        data = torch.squeeze(data, 1)        chunks = torch.chunk(data, n_ch, 0)        return torch.stack(chunks)    @staticmethod    def category_splitter(some_data, alpha, list_of_categories):        data, _ = torch.squeeze(some_data, 1).to(torch.int64), alpha        for i in list_of_categories:            data = torch.where(data < i, _, data)            _ += 1        return data - alpha

Имейте ввиду что это финальная версия кода подготовки данных для 3D U-net. Форвард:

  • Загружаем дату (в зависимости от типа).

  • Добавляем 0 по краям чтобы подогнать размер до 168*120*120 (вместо исходных 163*112*120). *пригодится дальше.

  • Нормализуем входящие данные в 0...1 (исходные ~-2000...16000).

  • Поворачиваем N-раз и соединяем.

  • Полученные данные режем на равные части чтобы забить память видеокарты по максимуму (в моем случае это 1, 1, 72, 120, 120).

  • Эта часть распределяет по категориям 28 имеющихся зубов и фон для облегчения обучения моделей (см. Введение):

    • одну категорию для 1-ой;

    • на 9 категорий (8+фон) для 2-ой.

Dataloader стандартный
import torch.utils.data as tudclass ToothDataset(tud.Dataset):    def __init__(self, images, masks):        self.images = images        self.masks = masks    def __len__(self): return len(self.images)    def __getitem__(self, index):        if self.masks is not None:            return self.images[index, :, :, :, :],\                    self.masks[index, :, :, :, :]        else:            return self.images[index, :, :, :, :]def get_loaders(images, masks,                batch_size: int = 1,                num_workers: int = 1,                pin_memory: bool = True):    train_ds = ToothDataset(images=images,                            masks=masks)    data_loader = tud.DataLoader(train_ds,                                 batch_size=batch_size,                                 shuffle=False,                                 num_workers=num_workers,                                 pin_memory=pin_memory)    return data_loader

На выходе имеем следующее:

Semantic

Instance

Predictions

Data

(27*, 1, 56*, 120,120)[0...1]

(27*, 1, 56*, 120,120) [0, 1]

(1, 1, 168, 120, 120)[0...1]

Masks

(27*, 1, 56*, 120,120)[0, 1]

(27*, 1, 56*, 120,120)[0, 8]

-

*эти размеры менялись, в зависимости от эксперимента, подробности - дальше.

3. Выбор и настройка моделей обучения

Цель работы - обучение. Поэтому взял наиболее простую и понятную для себя модель нейросети архитектуры U-Net. Код не выкладываю, можно посмотреть тут.

2D U-Net2D U-Net

Подробно рассказывать не буду, информации в достатке в сети. Метод оптимизации - Adam, функция расчета потерь Dice-loss(implement), спусков/подъемов 4, фильтры [64, 128, 256, 512] (знаю, много, об этом - позже). Обучал в среднем 60-80 epochs на эксперимент. Transfer learning не использовал.

model.summary()
model = UNet(dim=2, in_channels=1, out_channels=1, n_blocks=4, start_filters=64).to(device)print(summary(model, (1, 168, 120)))"""----------------------------------------------------------------        Layer (type)               Output Shape         Param #================================================================            Conv2d-1         [-1, 64, 168, 120]             640              ReLU-2         [-1, 64, 168, 120]               0       BatchNorm2d-3         [-1, 64, 168, 120]             128            Conv2d-4         [-1, 64, 168, 120]          36,928              ReLU-5         [-1, 64, 168, 120]               0       BatchNorm2d-6         [-1, 64, 168, 120]             128         MaxPool2d-7           [-1, 64, 84, 60]               0         DownBlock-8  [[-1, 64, 84, 60], [-1, 64, 168, 120]]  0            Conv2d-9          [-1, 128, 84, 60]          73,856             ReLU-10          [-1, 128, 84, 60]               0      BatchNorm2d-11          [-1, 128, 84, 60]             256           Conv2d-12          [-1, 128, 84, 60]         147,584             ReLU-13          [-1, 128, 84, 60]               0      BatchNorm2d-14          [-1, 128, 84, 60]             256        MaxPool2d-15          [-1, 128, 42, 30]               0        DownBlock-16  [[-1, 128, 42, 30], [-1, 128, 84, 60]]  0           Conv2d-17          [-1, 256, 42, 30]         295,168             ReLU-18          [-1, 256, 42, 30]               0      BatchNorm2d-19          [-1, 256, 42, 30]             512           Conv2d-20          [-1, 256, 42, 30]         590,080             ReLU-21          [-1, 256, 42, 30]               0      BatchNorm2d-22          [-1, 256, 42, 30]             512        MaxPool2d-23          [-1, 256, 21, 15]               0        DownBlock-24  [[-1, 256, 21, 15], [-1, 256, 42, 30]]  0           Conv2d-25          [-1, 512, 21, 15]       1,180,160             ReLU-26          [-1, 512, 21, 15]               0      BatchNorm2d-27          [-1, 512, 21, 15]           1,024           Conv2d-28          [-1, 512, 21, 15]       2,359,808             ReLU-29          [-1, 512, 21, 15]               0      BatchNorm2d-30          [-1, 512, 21, 15]           1,024        DownBlock-31  [[-1, 512, 21, 15], [-1, 512, 21, 15]]  0  ConvTranspose2d-32          [-1, 256, 42, 30]         524,544             ReLU-33          [-1, 256, 42, 30]               0      BatchNorm2d-34          [-1, 256, 42, 30]             512      Concatenate-35          [-1, 512, 42, 30]               0           Conv2d-36          [-1, 256, 42, 30]       1,179,904             ReLU-37          [-1, 256, 42, 30]               0      BatchNorm2d-38          [-1, 256, 42, 30]             512           Conv2d-39          [-1, 256, 42, 30]         590,080             ReLU-40          [-1, 256, 42, 30]               0      BatchNorm2d-41          [-1, 256, 42, 30]             512          UpBlock-42          [-1, 256, 42, 30]               0  ConvTranspose2d-43          [-1, 128, 84, 60]         131,200             ReLU-44          [-1, 128, 84, 60]               0      BatchNorm2d-45          [-1, 128, 84, 60]             256      Concatenate-46          [-1, 256, 84, 60]               0           Conv2d-47          [-1, 128, 84, 60]         295,040             ReLU-48          [-1, 128, 84, 60]               0      BatchNorm2d-49          [-1, 128, 84, 60]             256           Conv2d-50          [-1, 128, 84, 60]         147,584             ReLU-51          [-1, 128, 84, 60]               0      BatchNorm2d-52          [-1, 128, 84, 60]             256          UpBlock-53          [-1, 128, 84, 60]               0  ConvTranspose2d-54         [-1, 64, 168, 120]          32,832             ReLU-55         [-1, 64, 168, 120]               0      BatchNorm2d-56         [-1, 64, 168, 120]             128      Concatenate-57        [-1, 128, 168, 120]               0           Conv2d-58         [-1, 64, 168, 120]          73,792             ReLU-59         [-1, 64, 168, 120]               0      BatchNorm2d-60         [-1, 64, 168, 120]             128           Conv2d-61         [-1, 64, 168, 120]          36,928             ReLU-62         [-1, 64, 168, 120]               0      BatchNorm2d-63         [-1, 64, 168, 120]             128          UpBlock-64         [-1, 64, 168, 120]               0           Conv2d-65          [-1, 1, 168, 120]              65================================================================Total params: 7,702,721Trainable params: 7,702,721Non-trainable params: 0----------------------------------------------------------------Input size (MB): 0.08Forward/backward pass size (MB): 7434.08Params size (MB): 29.38Estimated Total Size (MB): 7463.54"""
Эксп.12D U-Net, подача изображений покадрово, плоскость [x, z]Эксп.12D U-Net, подача изображений покадрово, плоскость [x, z]

Определенно, это - зубы. Только кроме зубов есть много всего, нам ненужного. Подробнее о трансформации numpy - *.stl в Главе 6. Посмотрим ещё раз на фактический размер и качество изображений, которые попадают на вход нейросети:

Слева на право:1. Не видно[x, y]. 2. Немного лучше[x, z]. 3.Ещё лучше[y, z]Слева на право:1. Не видно[x, y]. 2. Немного лучше[x, z]. 3.Ещё лучше[y, z]

Если сам не видишь на 100% где там начался зуб а где нет, то как тогда эту работу выполнит нейросеть? Как минимум, необходимо изменить плоскость подачи изображения.

Проведя не один день разбираясь в том, как можно улучшить сложившуюся ситуацию, пришел к тому, что можно составлять каскад и сетей, поочередно обрабатывающих изображение, аналогично работе фильтров грубой и тонкой очистки.

Эксп.2Каскад 2-ух 2D U-Net, подача изображений покадрово, плоскость [y, z]Эксп.2Каскад 2-ух 2D U-Net, подача изображений покадрово, плоскость [y, z]

Прогресс виден, однако вместе с помехами пропадают и части зубов, дальнейшее обучение тому подтверждение:

Эксп.3Каскад 2-ух 2D U-Net, подача изображений покадрово плоскость [y, z]с увеличением времени обучения на 50%Эксп.3Каскад 2-ух 2D U-Net, подача изображений покадрово плоскость [y, z]с увеличением времени обучения на 50%

Ввиду последних событий было принято решение о переходе на 3D архитектуру нейронной сети. Переподготовил входные данные, а именно разделил на части размером (24*, 120, 120). Почему так? - изначально большая модель обучения (~22млн. параметров). Моя видеокарта(1063gtx) не могла физически вместить больше.

24*

Это размер глубины. Был подобран так чтобы:

  • количество данных(1512, 120, 120) делится нацело на это число - получается 63;

  • в свою очередь получившийся batch size (24, 120, 120) - максимум, вмещающийся в память видеокарты с текущими параметрами сети;

  • само это число (24) делилось на количество спусков/подъемов так же нацело (имеется в виду соответствие выражению 24/2/2/2=3 и 3*2*2*2=24, где количество делений/умножений на 2 соответствует количеству спусков/подъемов минус 1);

  • то же самое не только для глубины данных, но и длинны и ширины. Подробнее в .summary()

model.summary()
model = UNet(dim=3, in_channels=1, out_channels=1, n_blocks=4, start_filters=64).to(device)print(summary(model, (1, 24, 120, 120)))"""  ----------------------------------------------------------------        Layer (type)               Output Shape         Param #================================================================            Conv3d-1     [-1, 64, 24, 120, 120]             1,792              ReLU-2     [-1, 64, 24, 120, 120]                 0       BatchNorm3d-3     [-1, 64, 24, 120, 120]               128            Conv3d-4     [-1, 64, 24, 120, 120]           110,656              ReLU-5     [-1, 64, 24, 120, 120]                 0       BatchNorm3d-6     [-1, 64, 24, 120, 120]               128         MaxPool3d-7        [-1, 64, 12, 60, 60]                0         DownBlock-8  [[-1, 64, 12, 60, 60], [-1, 64, 24, 120, 120]]               0            Conv3d-9       [-1, 128, 12, 60, 60]          221,312             ReLU-10       [-1, 128, 12, 60, 60]                0      BatchNorm3d-11       [-1, 128, 12, 60, 60]              256           Conv3d-12       [-1, 128, 12, 60, 60]          442,496             ReLU-13       [-1, 128, 12, 60, 60]                0      BatchNorm3d-14       [-1, 128, 12, 60, 60]              256        MaxPool3d-15       [-1, 128, 6, 30, 30]                 0        DownBlock-16  [[-1, 128, 6, 30, 30], [-1, 128, 12, 60, 60]]               0           Conv3d-17       [-1, 256, 6, 30, 30]           884,992             ReLU-18       [-1, 256, 6, 30, 30]                 0      BatchNorm3d-19       [-1, 256, 6, 30, 30]               512           Conv3d-20       [-1, 256, 6, 30, 30]         1,769,728             ReLU-21       [-1, 256, 6, 30, 30]                 0      BatchNorm3d-22       [-1, 256, 6, 30, 30]               512        MaxPool3d-23       [-1, 256, 3, 15, 15]                 0        DownBlock-24  [[-1, 256, 3, 15, 15], [-1, 256, 6, 30, 30]]               0           Conv3d-25       [-1, 512, 3, 15, 15]         3,539,456             ReLU-26       [-1, 512, 3, 15, 15]                 0      BatchNorm3d-27       [-1, 512, 3, 15, 15]             1,024           Conv3d-28       [-1, 512, 3, 15, 15]         7,078,400             ReLU-29       [-1, 512, 3, 15, 15]                 0      BatchNorm3d-30       [-1, 512, 3, 15, 15]             1,024        DownBlock-31  [[-1, 512, 3, 15, 15], [-1, 512, 3, 15, 15]]               0  ConvTranspose3d-32       [-1, 256, 6, 30, 30]         1,048,832             ReLU-33       [-1, 256, 6, 30, 30]                 0      BatchNorm3d-34       [-1, 256, 6, 30, 30]               512      Concatenate-35       [-1, 512, 6, 30, 30]                 0           Conv3d-36       [-1, 256, 6, 30, 30]         3,539,200             ReLU-37       [-1, 256, 6, 30, 30]                 0      BatchNorm3d-38       [-1, 256, 6, 30, 30]               512           Conv3d-39       [-1, 256, 6, 30, 30]         1,769,728             ReLU-40       [-1, 256, 6, 30, 30]                 0      BatchNorm3d-41       [-1, 256, 6, 30, 30]               512          UpBlock-42       [-1, 256, 6, 30, 30]                 0  ConvTranspose3d-43       [-1, 128, 12, 60, 60]          262,272             ReLU-44       [-1, 128, 12, 60, 60]                0      BatchNorm3d-45       [-1, 128, 12, 60, 60]              256      Concatenate-46       [-1, 256, 12, 60, 60]                0           Conv3d-47       [-1, 128, 12, 60, 60]          884,864             ReLU-48       [-1, 128, 12, 60, 60]                0      BatchNorm3d-49       [-1, 128, 12, 60, 60]              256           Conv3d-50       [-1, 128, 12, 60, 60]          442,496             ReLU-51       [-1, 128, 12, 60, 60]                0      BatchNorm3d-52       [-1, 128, 12, 60, 60]              256          UpBlock-53       [-1, 128, 12, 60, 60]                0  ConvTranspose3d-54       [-1, 64, 24, 120, 120]          65,600             ReLU-55       [-1, 64, 24, 120, 120]               0      BatchNorm3d-56       [-1, 64, 24, 120, 120]             128      Concatenate-57      [-1, 128, 24, 120, 120]               0           Conv3d-58       [-1, 64, 24, 120, 120]         221,248             ReLU-59       [-1, 64, 24, 120, 120]               0      BatchNorm3d-60       [-1, 64, 24, 120, 120]             128           Conv3d-61       [-1, 64, 24, 120, 120]         110,656             ReLU-62       [-1, 64, 24, 120, 120]               0      BatchNorm3d-63       [-1, 64, 24, 120, 120]             128          UpBlock-64       [-1, 64, 24, 120, 120]               0           Conv3d-65        [-1, 1, 24, 120, 120]              65================================================================Total params: 22,400,321Trainable params: 22,400,321Non-trainable params: 0----------------------------------------------------------------Input size (MB): 0.61Forward/backward pass size (MB): 15974.12Params size (MB): 85.45Estimated Total Size (MB): 16060.18----------------------------------------------------------------"""
Эксп.43D U-Net, подача объемом, плоскость [y, z],время*0,38Эксп.43D U-Net, подача объемом, плоскость [y, z],время*0,38

С учетом сокращенного на ~60% времени обучения(25 epochs) результат меня устроил, продолжаем.

Эксп.53D U-Net, подача объемом, плоскость [y, z], 65 epochs ~ 1,5 часа Эксп.53D U-Net, подача объемом, плоскость [y, z], 65 epochs ~ 1,5 часа

Особых потерь в искомых зонах не заметил. Решил продолжать, однако результат дальнейшего обучения мы уже где то видели(эксп.3) - значительное уменьшение искомых зон и появление артефактов:

Эксп.63D U-Net, подача объемом, плоскость [x, z], 105 epochs ~ 2,1 часа Эксп.63D U-Net, подача объемом, плоскость [x, z], 105 epochs ~ 2,1 часа

"Научный" перебор параметров в течении недели принес результат. Уменьшил количество параметров сети до ~400к (от первоначальных ~22м) путем уменьшения фильтра [18, 32, 64, 128] и спуска/подъема до 3. Изменил метод оптимизации на RSMProp. Уменьшение количества параметров нейросети позволило увеличить объем входных данных в три раза (1, 1, 72*, 120, 120). Посмотрим результат?

model.summary()
model = UNet(dim=3, in_channels=1, out_channels=1, n_blocks=3, start_filters=18).to(device)print(summary(model, (1, 1, 72, 120, 120)))"""----------------------------------------------------------------        Layer (type)               Output Shape         Param #================================================================            Conv3d-1     [-1, 18, 72, 120, 120]             504              ReLU-2     [-1, 18, 72, 120, 120]               0       BatchNorm3d-3     [-1, 18, 72, 120, 120]              36            Conv3d-4     [-1, 18, 72, 120, 120]           8,766              ReLU-5     [-1, 18, 72, 120, 120]               0       BatchNorm3d-6     [-1, 18, 72, 120, 120]              36         MaxPool3d-7       [-1, 18, 36, 60, 60]               0         DownBlock-8  [[-1, 18, 36, 60, 60], [-1, 18, 24, 120, 120]]               0            Conv3d-9       [-1, 36, 36, 60, 60]          17,532             ReLU-10       [-1, 36, 36, 60, 60]               0      BatchNorm3d-11       [-1, 36, 36, 60, 60]              72           Conv3d-12       [-1, 36, 36, 60, 60]          35,028             ReLU-13       [-1, 36, 36, 60, 60]               0      BatchNorm3d-14       [-1, 36, 36, 60, 60]              72        MaxPool3d-15        [-1, 36, 18, 30, 30]              0        DownBlock-16  [[-1, 36, 18, 30, 30], [-1, 36, 36, 60, 60]]               0           Conv3d-17        [-1, 72, 18, 30, 30]         70,056             ReLU-18        [-1, 72, 18, 30, 30]              0      BatchNorm3d-19        [-1, 72, 18, 30, 30]            144           Conv3d-20        [-1, 72, 18, 30, 30]        140,040             ReLU-21        [-1, 72, 18, 30, 30]              0      BatchNorm3d-22        [-1, 72, 18, 30, 30]            144        DownBlock-23  [[-1, 72, 18, 30, 30], [-1, 72, 18, 30, 30]]               0  ConvTranspose3d-24       [-1, 36, 36, 60, 60]          20,772             ReLU-25       [-1, 36, 36, 60, 60]               0      BatchNorm3d-26       [-1, 36, 36, 60, 60]              72      Concatenate-27       [-1, 72, 36, 60, 60]               0           Conv3d-28       [-1, 36, 36, 60, 60]          70,020             ReLU-29       [-1, 36, 36, 60, 60]               0      BatchNorm3d-30       [-1, 36, 36, 60, 60]              72           Conv3d-31       [-1, 36, 36, 60, 60]          35,028             ReLU-32       [-1, 36, 36, 60, 60]               0      BatchNorm3d-33       [-1, 36, 36, 60, 60]              72          UpBlock-34       [-1, 36, 36, 60, 60]               0  ConvTranspose3d-35     [-1, 18, 72, 120, 120]           5,202             ReLU-36     [-1, 18, 72, 120, 120]               0      BatchNorm3d-37     [-1, 18, 72, 120, 120]              36      Concatenate-38     [-1, 36, 72, 120, 120]               0           Conv3d-39     [-1, 18, 72, 120, 120]          17,514             ReLU-40     [-1, 18, 72, 120, 120]               0      BatchNorm3d-41     [-1, 18, 72, 120, 120]              36           Conv3d-42     [-1, 18, 72, 120, 120]           8,766             ReLU-43     [-1, 18, 72, 120, 120]               0      BatchNorm3d-44     [-1, 18, 72, 120, 120]              36          UpBlock-45     [-1, 18, 72, 120, 120]               0           Conv3d-46      [-1, 1, 72, 120, 120]              19================================================================Total params: 430,075Trainable params: 430,075Non-trainable params: 0----------------------------------------------------------------Input size (MB): 1.32Forward/backward pass size (MB): 5744.38Params size (MB): 1.64Estimated Total Size (MB): 5747.34----------------------------------------------------------------"""
72*

Некоторые из вас подумают, исходные данные (168, 120, 120), а часть (72, 120, 120). Назревает вопрос, как делить. Всё просто, во 2 главе мы увеличивали размер наших данных и затем делили их на части, соответствующие объему памяти видеокарты. Я увеличил данные в 9 раз (1512, 120, 120) т.е. повернул на 9 различных углов относительно одной оси, а затем разделил на 21(batch size) часть по (72, 120, 120). Так же 72 соответствует всем условиям, описанным в 24*(выше).

Эксп.73D U-Net, подача объемом, плоскость [x, z],Маска (слева) и готовая сегментация (справа),оптимизированные параметры сети,время обучения(65 epochs) ~ 14мин.Эксп.73D U-Net, подача объемом, плоскость [x, z],Маска (слева) и готовая сегментация (справа),оптимизированные параметры сети,время обучения(65 epochs) ~ 14мин.

Результат вполне удовлетворительный, есть недочеты (вроде "похудевших" зубов). Возможно, исправим их в другом посте. Для этапа semantic segmentation я думаю мы сделали достаточно, теперь необходимо задать категории.

О размере подаваемых данных

Первоначальная идея при переходе на 3D архитектуру была в том чтобы делить данные не слайсами (как в данном посте) (1512, 120, 120) --> 21*(1, 72, 120, 120), а кубиками ~х*(30, 30, 30) или около того (результат этой попытки не был сохранен оп понятным причинам). Опытным путем понял 2 вещи: чем большими порциями ты подаешь 3-х мерные объекты, тем лучше результат(для моего конкретного случая); и нужно больше изучать теорию того, с чем работаешь.

О времени обучения и размере модели

Параметры сети подобраны так, что обучение 1 epochs на моей "старушке" занимает ~13сек, а размер конечной модели не превышает 2мб (прошлая>80мб). Время рабочего цикла примерно равно 1 epochs. Однако стоит понимать, это обучение и работа на данных достаточно маленького размера.

Для разделения на категории пришлось немного повозиться с функцией расчета ошибки и визуализацией данных. Первоначально поставил себе задачу разделить на 8 категорий + фон. О loss function и визуализации поговорим подробнее.

Код training loop
import torchfrom tqdm import tqdmfrom _loss_f import LossFunctionclass TrainFunction:    def __init__(self,                 data_loader,                 device_for_training,                 model_name,                 model_name_pretrained,                 model,                 optimizer,                 scale,                 learning_rate: int = 1e-2,                 num_epochs: int = 1,                 transfer_learning: bool = False,                 binary_loss_f: bool = True                 ):        self.data_loader = data_loader        self.device = device_for_training        self.model_name_pretrained = model_name_pretrained        self.semantic_binary = binary_loss_f        self.num_epochs = num_epochs        self.model_name = model_name        self.transfer = transfer_learning        self.optimizer = optimizer        self.learning_rate = learning_rate        self.model = model        self.scale = scale    def forward(self):        print('Running on the:', torch.cuda.get_device_name(self.device))        self.model.load_state_dict(torch.load(self.model_name_pretrained)) if self.transfer else None        optimizer = self.optimizer(self.model.parameters(), lr=self.learning_rate)        for epoch in range(self.num_epochs):            self.train_loop(self.data_loader, self.model, optimizer, self.scale, epoch)            torch.save(self.model.state_dict(), 'models/' + self.model_name+str(epoch+1)                       + '_epoch.pth') if (epoch + 1) % 10 == 0 else None    def train_loop(self, loader, model, optimizer, scales, i):        loop, epoch_loss = tqdm(loader), 0        loop.set_description('Epoch %i' % (self.num_epochs - i))        for batch_idx, (data, targets) in enumerate(loop):            data, targets = data.to(device=self.device, dtype=torch.float), \                            targets.to(device=self.device, dtype=torch.long)            optimizer.zero_grad()            *тут секрет*            with torch.cuda.amp.autocast():                predictions = model(data)                loss = LossFunction(predictions, targets,                                    device_for_training=self.device,                                    semantic_binary=self.semantic_binary                                    ).forward()            scales.scale(loss).backward()            scales.step(optimizer)            scales.update()            epoch_loss += (1 - loss.item())*100            loop.set_postfix(loss=loss.item())        print('Epoch-acc', round(epoch_loss / (batch_idx+1), 2))

4. Функция расчета ошибки

Мне в целом понравилось как проявляет себя Dice-loss в сегментации, только 'проблема' в том что он работает с форматом данных [0, 1]. Однако, если предварительно разделить данные на категории (а так же привести к формату [0, 1]), и пропускать пары (имеется ввиду "предсказание" и "маска" только одной категории) в стандартную Dice-loss функцию, то это может сработать.

Код categorical_dice_loss
import torchclass LossFunction:    def __init__(self,                 prediction,                 target,                 device_for_training,                 semantic_binary: bool = True,                 ):        self.prediction = prediction        self.device = device_for_training        self.target = target        self.semantic_binary = semantic_binary    def forward(self):        if self.semantic_binary:            return self.dice_loss(self.prediction, self.target)        return self.categorical_dice_loss(self.prediction, self.target)    @staticmethod    def dice_loss(predictions, targets, alpha=1e-5):        intersection = 2. * (predictions * targets).sum()        denomination = (torch.square(predictions) + torch.square(targets)).sum()        dice_loss = 1 - torch.mean((intersection + alpha) / (denomination + alpha))        return dice_loss    def categorical_dice_loss(self, prediction, target):        pr, tr = self.prepare_for_multiclass_loss_f(prediction, target)        target_categories, losses = torch.unique(tr).tolist(), 0        for num_category in target_categories:            categorical_target = torch.where(tr == num_category, 1, 0)            categorical_prediction = pr[num_category][:][:][:]            losses += self.dice_loss(categorical_prediction, categorical_target).to(self.device)        return losses / len(target_categories)    @staticmethod    def prepare_for_multiclass_loss_f(prediction, target):        prediction_prepared = torch.squeeze(prediction, 0)        target_prepared = torch.squeeze(target, 0)        target_prepared = torch.squeeze(target_prepared, 0)        return prediction_prepared, target_prepared

Тут просто, но всё равно объясню "categorical_dice_loss":

  • подготовка данных (убираем ненужные в данном расчете измерения);

  • получения списка категорий, которые содержит каждый batch масок;

  • для каждой категории берем "прогноз" и "маску" соответствующих категорий, приводим значения к формату [0, 1] и пропускаем через стандартную Dice-loss;

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

Так же, думаю, помог бы перевод данных к one-hot формату, но только не в момент формирования основного дата сета (раздует в размере), а непосредственно перед расчетом ошибки, но я не проверял. Кто в курсе, напишите, пожалуйста, буду рад. Результат работы данной функции будет в Главе(5).

5. Визуализация данных

Так и хочется добавить "..как отдельный вид искусства". Начну с того что прочитать *.nrrd оказалось самым простым.

Код
import nrrd# читает в numpyread = nrrd.read(data_path) data, meta_data = read[0], read[1]print(data.shape, np.max(data), np.min(data), meta_data, sep="\n")(163, 112, 120)14982-2254  OrderedDict([('type', 'short'), ('dimension', 3), ('space', 'left-posterior-superior'), ('sizes', array([163, 112, 120])), ('space directions', array([[-0.5,  0. ,  0. ],       [ 0. , -0.5,  0. ],       [ 0. ,  0. ,  0.5]])), ('kinds', ['domain', 'domain', 'domain']), ('endian', 'little'), ('encoding', 'gzip'), ('space origin', array([131.57200623,  80.7661972 ,  32.29940033]))])

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

Неправильный путь

Иными словами, чтобы сделать куб нам необходимо 8 вершин и 12 треугольных поверхностей. В этом и состояла первая идея (до применения специальных библиотек) - заменить все пиксели (числа в 3-х мерной матрице) на такие кубики. Код я не сохранил, но смысл прост, рисуем куб на месте "пикселя" со сдвигом -1 по трем направлениям, потом следующий и т.д.

Выглядит это так же бредово, как и звучитВыглядит это так же бредово, как и звучит

Отрицательный результат - тоже результат, продолжаем. На этом этапе я уже понял, что без сторонних библиотек мне не обойтись. Первой попыткой в была пара Skimage и Stl.

from skimage.measure import marching_cubesimport nrrdimport numpy as npfrom stl import meshpath = 'some_path.nrrd'data = nrrd.read(path)[0]def three_d_creator(some_data):    vertices, faces, volume, _ = marching_cubes(some_data)    cube = mesh.Mesh(np.full(faces.shape[0], volume.shape[0], dtype=mesh.Mesh.dtype))    for i, f in enumerate(faces):        for j in range(3):            cube.vectors[i][j] = vertices[f[j]]    cube.save('name.stl')    return cubestl = three_d_creator(datas)

Пользовался этим способом, но иногда файлы "ломались" в процессе сохранения и не открывались. А на те, которые открывались, ругался встроенный в Win 10 3D Builder и постоянно пытался там что-то исправить. Так же еще придется "прикрутить" к коду модуль для просмотра 3D объектов без их сохранения. Решение "из коробки" дальше.

На момент написания статью пользуюсь v3do. Коротко, быстро, удобно и можно сразу осмотреть модель.

Код перевода npy в stl и вывода объекта на дисплей
from vedo import Volume, show, writeprediction = 'some_data_path.npy'def show_save(data, save=False):    data_multiclass = Volume(data, c='Set2', alpha=(0.1, 1), alphaUnit=0.87, mode=1)    data_multiclass.addScalarBar3D(nlabels=9)    show([(data_multiclass, "Multiclass teeth segmentation prediction")], bg='black', N=1, axes=1).close()    write(data_multiclass.isosurface(), 'some_name_.stl') if save else None    show_save(prediction, save=True)

Названия функций говорят сами за себя.

Пришло время увидеть конечный результат всего вышесказанного. Томить не буду:

model.summary()
model = UNet(dim=3, in_channels=1, out_channels=9, n_blocks=3, start_filters=9).to(device)print(summary(model, (1, 168*, 120, 120)))    """----------------------------------------------------------------        Layer (type)               Output Shape         Param #================================================================            Conv3d-1      [-1, 9, 168, 120, 120]            252              ReLU-2      [-1, 9, 168, 120, 120]              0       BatchNorm3d-3      [-1, 9, 168, 120, 120]             18            Conv3d-4      [-1, 9, 168, 120, 120]          2,196              ReLU-5      [-1, 9, 168, 120, 120]              0       BatchNorm3d-6      [-1, 9, 168, 120, 120]             18         MaxPool3d-7        [-1, 9, 84, 60, 60]               0         DownBlock-8  [[-1, 9, 84, 60, 60], [-1, 9, 168, 120, 120]]               0            Conv3d-9       [-1, 18, 84, 60, 60]           4,392             ReLU-10       [-1, 18, 84, 60, 60]               0      BatchNorm3d-11       [-1, 18, 84, 60, 60]              36           Conv3d-12       [-1, 18, 84, 60, 60]           8,766             ReLU-13       [-1, 18, 84, 60, 60]               0      BatchNorm3d-14       [-1, 18, 84, 60, 60]              36        MaxPool3d-15       [-1, 18, 42, 30, 30]               0        DownBlock-16  [[-1, 18, 18, 42, 30], [-1, 18, 84, 60, 60]]               0           Conv3d-17       [-1, 36, 42, 30, 30]          17,532             ReLU-18       [-1, 36, 42, 30, 30]               0      BatchNorm3d-19       [-1, 36, 42, 30, 30]              72           Conv3d-20       [-1, 36, 42, 30, 30]          35,028             ReLU-21       [-1, 36, 42, 30, 30]               0      BatchNorm3d-22       [-1, 36, 42, 30, 30]              72        DownBlock-23  [[-1, 36, 42, 30, 30], [-1, 36, 42, 30, 30]]               0  ConvTranspose3d-24       [-1, 18, 84, 60, 60]           5,202             ReLU-25       [-1, 18, 84, 60, 60]               0      BatchNorm3d-26       [-1, 18, 84, 60, 60]              36      Concatenate-27       [-1, 36, 84, 60, 60]               0           Conv3d-28       [-1, 18, 84, 60, 60]          17,514             ReLU-29       [-1, 18, 84, 60, 60]               0      BatchNorm3d-30       [-1, 18, 84, 60, 60]              36           Conv3d-31       [-1, 18, 84, 60, 60]           8,766             ReLU-32       [-1, 18, 84, 60, 60]               0      BatchNorm3d-33       [-1, 18, 84, 60, 60]              36          UpBlock-34       [-1, 18, 84, 60, 60]               0  ConvTranspose3d-35      [-1, 9, 168, 120, 120]          1,305             ReLU-36      [-1, 9, 168, 120, 120]              0      BatchNorm3d-37      [-1, 9, 168, 120, 120]             18      Concatenate-38     [-1, 18, 168, 120, 120]              0           Conv3d-39      [-1, 9, 168, 120, 120]          4,383             ReLU-40      [-1, 9, 168, 120, 120]              0      BatchNorm3d-41      [-1, 9, 168, 120, 120]             18           Conv3d-42      [-1, 9, 168, 120, 120]          2,196             ReLU-43      [-1, 9, 168, 120, 120]              0      BatchNorm3d-44      [-1, 9, 168, 120, 120]             18          UpBlock-45      [-1, 9, 168, 120, 120]              0           Conv3d-46      [-1, 9, 168, 120, 120]             90================================================================Total params: 108,036Trainable params: 108,036Non-trainable params: 0----------------------------------------------------------------Input size (MB): 3.96Forward/backward pass size (MB): 12170.30Params size (MB): 0.41Estimated Total Size (MB): 12174.66----------------------------------------------------------------    """

*Ввиду ещё большего уменьшения параметров сети(фильтр[9, 18, 36, 72]), удалось уместить объект в память видеокарты целиком - 9*(168, 120, 120)

6. After words

Думал, что закончил, а оказалось - только начал. Тут еще есть над чем поработать. Мне, в целом, 2 этап не нравится, хоть он и работает. Зачем заново переопределять каждый пиксель, когда мне нужен целый регион? А если, образно, есть 28 разделенных регионов, зачем мне пытаться определить их все, не проще ли определить один зуб и завязать это всё на "условный" ориентированный/неориентированный граф? Или вместо U-net использовать GCNN и вместо Pytorch - Pytorch3D? Пятна, думаю, можно убрать с помощью выравнивания данных внутри bounding box(ведь один зуб может принадлежать только 1 категории). Но, возможно, это вопросы для следующей публикации.

Прототип (набросок)
Тот самый "условный граф"
Пример неориентированного графа на 28 категорий с "разделителями"Пример неориентированного графа на 28 категорий с "разделителями"

Отдельное спасибо моей жене - Алёне, за особую поддержку во время этого "погружения в темноту".

Благодарю всех за внимание. Конструктивная критика и предложения, как исправлений, так и новых проектов - приветствуются.

Подробнее..

День открытых данных 2021. Онлайн

01.03.2021 20:22:05 | Автор: admin

image


1-6 марта приглашаем на мероприятия, приуроченные к Международному Дню открытых данных 2021.


Это крупнейшее ежегодное международное событие, которое помогает продвигать концепцию открытых данных среди органов государственной власти, бизнес-корпораций, некоммерческих организаций и гражданского общества. Организатором Дня открытых данных в России выступает АНО Информационная культура.


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


  • 1 марта, мастер-класс Вскрываем декларации. Как при помощи регулярных выражений привести Wordовскую табличку к пригодной для анализа форме. Доступна видеозапись.
  • 2 марта, мастер-класс О чем говорят депутаты Госдумы? Анализ текстовых данных на Python.
  • 3 марта, мастер-классы по работе с геопространственными данными и картами для новичков и профи.
  • 4 марта, мастер-класс по поиску открытых данных от DataMasters.
  • 5 марта, мастер-класс Российская официальная статистика: как сделать работу с данными удобнее, а данные понятнее?.
  • 5 марта, мастер-класс Визуализация данных в ObservableHQ.

6 марта пройдет онлайн-конференция День открытых данных.


В центре внимания вопросы о том, что происходит с открытостью в России и мире и как использовать данные для эффективного решения конкретных проблем и задач общества. В дискуссиях примут участие не только российские эксперты, но и представители крупнейших международных проектов, продвигающих ценности и идеологию открытых данных: Global Data Barometer, The Humanitarian Data Exchange.


В программе дискуссии и выступления:


  • Дискуссия. Бизнес на открытости: зачем заниматься открытым кодом и открытыми данными
  • Дискуссия. Как инструменты оценки влияют на открытость государства?
  • Дискуссия. Доступность данных о госфинансах
  • Дискуссия. Данные переписи населения 2021: приватность vs. польза для общества
  • Выступления. Что происходит с тематикой открытости в мире?

Программа и регистрация: opendataday.ru/msk. Трансляция будет доступна и бесплатна для всех желающих.


Подробнее..

Из песочницы Формат таблиц в pandas

03.10.2020 16:08:05 | Автор: admin

Если вы пока ещё не знаете как транслировать данные напрямую заказчику в подсознание или, на худой конец, текст сообщения в slack, вам пригодится информация о том, как сделать процесс интерпретации таблиц более быстрым и комфортным.


Например, в excel для этого используется условное форматирование и спарклайны. А в этой статье мы посмотрим как визуализировать данные с помощью Python и библиотеки pandas: будем использовать свойства DataFrame.style и Options and settings.


Настраиваем базовую визуализацию


Импортируем библиотеки: pandas для работы с данными и seaborn для загрузки классического набора данных penguins:


import pandas as pdimport seaborn as sns

С помощью pd.set_option настроим вывод так чтобы:


  • количество строк в таблице было не больше 5;
  • текст в ячейке отражался полностью вне зависимости от длины (это удобно, если в ячейке длинный заголовок или URL, которые хочется посмотреть);
  • все числа отражались с двумя знаками после запятой;

pd.set_option('max_rows', 5)pd.set_option('display.max_colwidth', None)pd.set_option('display.float_format', '{:.2f}'.format)

Прочитаем и посмотрим датафрейм.


penguins = sns.load_dataset(penguins)penguins

image


Если нужно вернуть настройки к дефолтным, используем pd.reset_option. Например, так, если хотим обновить все настройки разом:


pd.reset_option('all')

Полный список свойств set_option.


Настраиваем отображение данных в таблицах


Формат чисел, пропуски и регистр


У датафреймов в pandas есть свойство DataFrame.style, которое меняет отображение содержимого ячеек по условию для строк или столбцов.


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


(penguins .head(5) .style .format('{:.1f}', na_rep='-') .format({'species': lambda x:x.lower(),          'island': lambda x:x.lower(),          'sex': lambda x: '-' if pd.isna(x) else x.lower()         }))

image


У нас тут всё про пингвинов, но в данные о ценах, можно добавить знак перед числом таким образом:


(df.style.format({'price': '{:.2f}'}))

Дальше больше!


Выделение цветом (минимум, максимум, пропуски)


Функции для поиска минимального и максимального значений не работают с текстовыми полями, поэтому заранее выделим столбцы, для которых они будут применяться, в отдельный список. Этот список будем передавать в параметр subset.


numeric_columns = ['bill_length_mm',                   'bill_depth_mm',                   'flipper_length_mm',                   'body_mass_g']

Подсветим минимум, максимум и пустые ячейки и выведем первые 5 строк датафрейма.


(penguins .head(5) .style .format('{:.1f}', na_rep='-') .format({'species': lambda x:x.lower(),          'island': lambda x:x.lower(),          'sex': lambda x: '-' if pd.isna(x) else x.lower()         }) .highlight_null(null_color='lightgrey') .highlight_max(color='yellowgreen', subset=numeric_columns) .highlight_min(color='coral', subset=numeric_columns))

image


Наглядно видно, что в этих 5ти строках самый длинный клюв у пингвина в строке с индексом 2 и у него (неё!) же самые длинные плавники и самый маленький вес.


Усложним ещё немного: посмотрим на разброс длины плавников пингвинов-девочек вида Adelie.


Bar chart в таблице


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


adelie_female = (penguins[(penguins['species'] == 'Adelie') &                           (penguins['sex'] == 'FEMALE')]                 .copy()                )adelie_female['flipper_l_var'] = ((adelie_female['flipper_length_mm']-                                                  adelie_female['flipper_length_mm'].mean()).round())

К форматированию числовых значений, пропусков и регистра добавляем формат для столбца 'flipper_l_var'. Задаём:


  • группу столбцов (subset), для которых будем строить график;
  • выравнивание (align): mid так как мы ожидаем, что значения будут как положительные, так и отрицательные. Подробнее про другие параметры выравнивания можно посмотреть тут;
  • цвет (color). В нашем случае 2 цвета: для отрицательных и положительных значений;
  • границы (vmin, vmax).

Отдельно с помощью set_properties пропишем, что значения в столбце 'flipper_l_var' должны стоять в центре ячейки.


(adelie_female .head(5) .style .format('{:.1f}', na_rep='-') .format({'species': lambda x:x.lower(),          'island': lambda x:x.lower(),          'sex': lambda x: '-' if pd.isna(x) else x.lower()         }) .bar(subset=['flipper_l_var'],      align='mid',      color=['coral', 'yellowgreen'],      vmin=adelie_female['flipper_l_var'].min(),      vmax=adelie_female['flipper_l_var'].max()     ) .set_properties(**{'text-align': 'center'}, subset='flipper_l_var'))

image


Heatmap в таблице


Иногда очень удобно подсветить все значения в таблице с помощью градиента. Например, когда нужно сравнить сгруппированные данные.


Посчитаем количество пингвинов разных видов и средние значения массы, длин плавников и клюва в зависимости от вида.


species_stat=(penguins             .groupby('species')             .agg(penguins_count=('species','count'),                  mean_bill_length=('bill_length_mm', 'mean'),                  mean_bill_depth=('bill_depth_mm', 'mean'),                  mean_flipper_length=('flipper_length_mm', 'mean'),                  mean_body_mass=('body_mass_g', 'mean'),                 )             )

image


О трех видах пингвинов можно сделать выводы и по этой таблице, но если значений становится чуть больше, хочется сразу заняться чем-то более полезным, чем разглядывать ряды чисел.


Исправим это. Потому что, ну что может быть полезнее и веселее разглядывания чисел?! И если вы думаете по-другому, я не знаю, зачем вы дочитали до этого момента.


(species_stat .T .style .format("{:.1f}") .background_gradient(cmap='Blues', axis=1))

image


Транспонируем таблицу так нагляднее сравнение между видами и применяем метод background_gradient со следующими параметрами:


  • цветовая карта(cmap): Blues. Это одна из дефолтных карт;
  • сравнение по строкам (axis=1).

Вывод


Форматирование таблиц в pandas с помощью DataFrame.style и Options and settings упрощает жизнь, ну или как минимум улучшает читабельность кода и отчетов. Но обработку типов данных, пропусков и регистра лучше, конечно, проводить осознанно ещё до этапа визуализации.


Дополнительно можно разобраться с:


Подробнее..
Категории: Python , Data analysis , Data visualization , Pandas

Перевод Топ 6 библиотек Python для визуализации какую и когда лучше использовать?

20.05.2021 20:12:40 | Автор: admin

Перевод подготовлен в рамках курса "Machine Learning. Basic".

Всех желающих приглашаем на открытый онлайн-интенсив Data Science это проще, чем кажется. Поговорим об истории и основных вехах в развитии ИИ, вы узнаете, какие задачи решает DS и чем занимается ML. И уже на первом занятии вы сможете научить компьютер определять, что изображено на картинке. А именно, вы попробуете обучить свою первую модель машинного обучения для решения задачи классификации изображений. Поверьте, это проще, чем кажется!


Не знаете, какой инструмент визуализации использовать? В этой статье мы подробно расскажем о плюсах и минусах каждой библиотеки.

Это руководство было дополнено несколькими подробными примерами. Вы также можете отслеживать актуальные версии этой статьи здесь.

Мотивация

Если вы только собираетесь начать работу с визуализацией в Python, количество библиотек и решений вас определенно поразит:

  • Matplotlib

  • Seaborn

  • Plotly

  • Bokeh

  • Altair

  • Folium

Но какую из этих библиотек лучше выбрать для визуализации DataFrame? Некоторые библиотеки имеют больше преимуществ для использования в некоторых конкретных случаях. В этой статье приведены плюсы и минусы каждой из них. Прочитав эту статью, вы будете разбираться в функционале каждой библиотеки и будете способны подбирать для ваших потребностей оптимальную.

Мы будем использовать один и тот же набор данных, на примере которого будем рассматривать каждую библиотеку, уделяя особое внимание нескольким показателям:

Интерактивность

Хотите ли вы, чтобы ваша визуализация была интерактивной?

Визуализация в некоторых библиотеках, таких как Matplotlib, является простым статичным изображением, что хорошо подходит для объяснения концепций (в документе, на слайдах или в презентации).

Другие библиотеки, такие как Altair, Bokeh и Plotly, позволяют создавать интерактивные графики, которые пользователи могут изучать, взаимодействуя с ними.

Синтаксис и гибкость

Чем отличается синтаксис каждой библиотеки? Библиотеки низкого уровня, такие как Matplotlib, позволяют делать все, что вы захотите, но за счет более сложного API. Некоторые библиотеки, такие как Altair, очень декларативны, что упрощает построение графиков по вашим данным.

Тип данных и визуализации

Приходилось ли вам сталкиваться в работе с нестандартными юзкейсами, например, с географическим графиком, включающим большой набор данных или с типом графика, который поддерживается только определенной библиотекой?

Данные

Чтобы было проще сравнивать библиотеки, здесь представлены реальные данные с Github из этой статьи:

I Scraped more than 1k Top Machine Learning Github Profiles and this is what I Found

В статью включены визуализации из каждой библиотеки с помощью Datapane, который представляет собой Python фреймворк и API для публикации и совместного использования Python-отчетов. Больше реальных примеров вы можете найти в пользовательских отчетах в галереи Datapane.

Вы можете скачать файл csv здесь, либо получите данные напрямую из Datapane Blob.

import datapane as dpdp.Blob.get(name='github_data', owner='khuyentran1401').download_df()

Не забудьте залогиниться со своим токеном авторизации в Datapane, если вы хотите использовать Blob. Это займет менее минуты.

Matplotlib

Matplotlib, вероятно, является самой популярной библиотекой Python для визуализации данных. Все, кто интересуется data science, наверняка хоть раз сталкивались с Matplotlib.

Плюсы

  1. Четко отображены свойства данных

При анализе данных возможность быстро посмотреть распределение может быть очень полезной.

Например, если я хочу быстро посмотреть распределение топ 100 пользователей с наибольшим количеством подписчиков, обычно Matplotlib мне будет вполне достаточно:

import matplotlib.pyplot as plttop_followers = new_profile.sort_values(by='followers', axis=0, ascending=False)[:100]fig = plt.figure()plt.bar(top_followers.user_name,       top_followers.followers)

Даже что-то вроде этого:

fig = plt.figure()plt.text(0.6, 0.7, "learning", size=40, rotation=20.,         ha="center", va="center",         bbox=dict(boxstyle="round",                   ec=(1., 0.5, 0.5),                   fc=(1., 0.8, 0.8),                   )         )plt.text(0.55, 0.6, "machine", size=40, rotation=-25.,         ha="right", va="top",         bbox=dict(boxstyle="square",                   ec=(1., 0.5, 0.5),                   fc=(1., 0.8, 0.8),                   )         )plt.show()

Минусы

Matplotlib может создать любой график, но с его помощью может быть сложно построить или подогнать сложные графики, чтобы они выглядели презентабельно.

Несмотря на то, что график достаточно хорошо подходит для визуализации распределений, если вы хотите презентовать его публике, вам нужно будет откорректировать оси X и Y, что потребует больших усилий, потому что Matplotlib имеет чрезвычайно низкоуровневый интерфейс.

correlation = new_profile.corr()fig, ax = plt.subplots()im = plt.imshow(correlation)ax.set_xticklabels(correlation.columns)ax.set_yticklabels(correlation.columns)plt.setp(ax.get_xticklabels(), rotation=45, ha="right",         rotation_mode="anchor")

Вывод: с помощью Matplotlib можно создать что угодно, но для сложных графиков может потребоваться гораздо больше кода, чем другим библиотекам.

Seaborn

Seaborn - это библиотека Python для визуализации данных, построенная на базе Matplotlib. Она более высокоуровневая, что упрощает ее использование.

Плюсы

  1. Меньше кода

Предоставляет интерфейс более высокого уровня для построения похожих графиков. Другими словами, seaborn обычно строит графики, аналогичные matplotlib, но с меньшим количеством кода и более красивым дизайном.

Мы используем те же данные, что и раньше, чтобы построить аналогичный график пользовательской активности.

correlation = new_profile.corr()sns.heatmap(correlation, annot=True)

Мы получаем лучший график пользовательской активности без возни x и y!

2. Делает стандартные графики красивее

Многие люди выбирают seaborn для создания широко используемых графиков, таких как столбчатые и прямоугольные диаграммы, расчетные графики, гистограммы и т. д., но не только потому, что это потребует меньше кода, они еще и визуально приятнее. Как видно на примере выше, цвета выглядят лучше, чем цвета по умолчанию в Matplotlib.

sns.set(style="darkgrid")titanic = sns.load_dataset("titanic")ax = sns.countplot(x="class", data=titanic)

Минусы

Seaborn более ограничен и не имеет такой широкой коллекции графиков, как matplotlib.

Вывод: Seaborn это версия Matplotlib более высокого уровня. Несмотря на то, что коллекция графиков не настолько большая, как в Matplotlib, созданные с помощью seaborn широко используемые графики (например, столбчатая диаграмма, прямоугольная диаграмма, график пользовательской активности и т. д.), при меньшем количестве кода будет выглядеть визуально приятнее.

Plotly

Python библиотека Plotly упрощает создание интерактивных графиков типографского качества. Он также может создавать диаграммы, аналогичные Matplotlib и seaborn, такие как линейные графики, точечные диаграммы, диаграммы с областями, столбчатые диаграммы и т. д.

Плюсы

  1. Похож на R

Если вы поклонник графиков в R и вам не хватает его функционала при переходе на Python, Plotly даст вам такое же качество графиков с использованием Python!

Мой любимая версия - Plotly Express, потому что с ней можно легко и быстро создавать отличные графики одной строчкой в Python.

import plotly.express as pxfig = px.scatter(new_profile[:100],          x='followers',          y='total_stars',          color='forks',          size='contribution')fig.show()

2. Простота создания интерактивных графиков

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

Помните столбчатую диаграмму, которую мы показывали ранее в matplotlib? Давайте посмотрим, как она получится с помощью Plotly

import plotly.express as pxtop_followers = new_profile.sort_values(by='followers', axis=0, ascending=False)[:100]fig = px.bar(top_followers,              x='user_name',              y='followers',            )fig.show()

Примерно за столько же строк кода мы создали интерактивный график, на котором можно навести указатель мыши на каждый столбец, чтобы увидеть, кому он принадлежит и сколько подписчиков у этого пользователя. Это означает, что пользователь вашей визуализации может изучить ее самостоятельно.

3. Легко делать сложные графики

С помощью Plotly достаточно легко создавать сложные графики.

Например, если мы хотим создать карту для визуализации местоположения пользователей GitHub, мы можем найти широту и долготу их расположения как показано здесь, а затем использовать эти данные чтобы отметить местоположение пользователей уже на карте:

import plotly.express as pximport datapane as dplocation_df = dp.Blob.get(name='location_df', owner='khuyentran1401').download_df()m = px.scatter_geo(location_df, lat='latitude', lon='longitude',                 color='total_stars', size='forks',                 hover_data=['user_name','followers'],                 title='Locations of Top Users')m.show()

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

Вывод: Plotly отлично подходит для создания интерактивных и качественных графиков при помощи всего нескольких строк кода.

Altair

Altair - это библиотека Python декларативной статистической визуализации, которая основана на vega-lite, что идеально подходит для графиков, требующих большого количества статистических преобразований.

Плюсы

1. Простая грамматика визуализации

Грамматика, используемая для визуализации, невероятно проста для понимания. Необходимо только обозначить связи между столбцами данных и каналами их преобразования, а остальная часть построения графиков обрабатывается автоматически. Это звучит довольно абстрактно, но имеет решающее значение, когда вы работаете с данными, и делает визуализацию информации очень быстрой и интуитивно понятной.

Например, для данных о Титанике выше мы хотели бы подсчитать количество людей в каждом классе. Все, что нам нужно, это использовать count() в y_axis

import seaborn as snsimport altair as alt titanic = sns.load_dataset("titanic")alt.Chart(titanic).mark_bar().encode(    alt.X('class'),    y='count()')

2. Простота преобразования данных

Altair также упрощает преобразование данных при создании диаграммы.

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

hireable = alt.Chart(titanic).mark_bar().encode(    x='sex:N',    y='mean_age:Q').transform_aggregate(    mean_age='mean(age)',    groupby=['sex'])hireable

Логика здесь состоит в том, чтобы использовать transform_aggregate() для взятия среднего значения возраста (mean(age)) каждого пола (groupby=['sex']) и сохранить его в переменной mean_age). За ось Y мы берем переменную.

Мы также можем убедиться, что класс - это номинальные данные (категорийные данные в произвольном порядке), используя :N, или что mean_age - это количественные данные (меры значений, такие как числа), используя :Q.

Полный список преобразований данных можно найти здесь.

3. Связывание нескольких графиков

Altair также позволяет создавать впечатляющие связи между графиками, например, с возможностью использовать выбор интервала для фильтрации содержимого прикрепленной гистограммы.

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

brush = alt.selection(type='interval')points = alt.Chart(titanic).mark_point().encode(    x='age:Q',    y='fare:Q',    color=alt.condition(brush, 'class:N', alt.value('lightgray'))).add_selection(    brush)bars = alt.Chart(titanic).mark_bar().encode(    y='class:N',    color='class:N',    x = 'count(class):Q').transform_filter(    brush)points & bars

Когда мы перетаскиваем мышь, чтобы выбрать интервал на корреляционной диаграмме, мы можем наблюдать изменения на гистограмме ниже. В сочетании с преобразованиями и вычислениями, сделанными ранее, это означает, что вы можете создавать несколько чрезвычайно интерактивных графиков, которые выполняют вычисления на лету - даже не требуя работающего сервера Python!

Минусы

Если вы не задаете пользовательский стиль, простые диаграммы, такие как, например, столбчатые, не будут оформлены стилистически так же хорошо, как в seaborn или Plotly. Altair также не рекомендует использовать наборы данных с более чем 5000 экземплярами и рекомендует вместо этого агрегировать данные перед визуализацией.

Вывод: Altair идеально подходит для создания сложных графиков для отображения статистики. Altair не может обрабатывать данные, превышающие 5000 экземпляров, и некоторые простые диаграммы в нем уступают по стилю Plotly или Seaborn.

Bokeh

Bokeh - это интерактивная библиотека для визуализации, предназначенная для презентации данных в браузерах.

Плюсы

  1. Интерактивная версия Matplotlib

Если мы будем будем составлять топы интерактивных библиотек для визуализации, Bokeh, вероятно, займет первое место в категории сходства с Matplotlib.

Matplotlib позволяет создать любой график, так как эта библиотека предназначена для визуализации на достаточно низком уровне. Bokeh можно использовать как с высокоуровневым, так и низкоуровневым интерфейсом; таким образом, она способна создавать множество сложных графиков, которые создает Matplotlib, но с меньшим количеством строк кода и более высоким разрешением.

Например, круговой график Matplotlib,

import matplotlib.pyplot as pltfig, ax = plt.subplots()x = [1, 2, 3, 4, 5]y = [2, 5, 8, 2, 7]for x,y in zip(x,y):     ax.add_patch(plt.Circle((x, y), 0.5, edgecolor = "#f03b20",facecolor='#9ebcda', alpha=0.8))#Use adjustable='box-forced' to make the plot area square-shaped as well.ax.set_aspect('equal', adjustable='datalim')ax.set_xbound(3, 4)ax.plot()   #Causes an autoscale update.plt.show()

который, в Bokeh, может быть создан с лучшим разрешением и функциональностью:

from bokeh.io import output_file, showfrom bokeh.models import Circlefrom bokeh.plotting import figurereset_output()output_notebook()plot = figure(plot_width=400, plot_height=400, tools="tap", title="Select a circle")renderer = plot.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=50)selected_circle = Circle(fill_alpha=1, fill_color="firebrick", line_color=None)nonselected_circle = Circle(fill_alpha=0.2, fill_color="blue", line_color="firebrick")renderer.selection_glyph = selected_circlerenderer.nonselection_glyph = nonselected_circleshow(plot)

2. Связь между графиками

В Bokeh также можно достаточно просто связывать графики. Изменение, примененное к одному графику, будет применено к другому графику с этой же переменной.

Например, если мы создаем 3 графика рядом и хотим наблюдать их взаимосвязь, мы можем связанное закрашивание

from bokeh.layouts import gridplot, rowfrom bokeh.models import ColumnDataSourcereset_output()output_notebook()source = ColumnDataSource(new_profile)TOOLS = "box_select,lasso_select,help"TOOLTIPS = [('user', '@user_name'),            ('followers', '@followers'),            ('following', '@following'),            ('forks', '@forks'),             ('contribution', '@contribution')]s1 = figure(tooltips=TOOLTIPS, plot_width=300, plot_height=300, title=None, tools=TOOLS)s1.circle(x='followers', y='following', source=source)s2 = figure(tooltips=TOOLTIPS, plot_width=300, plot_height=300, title=None, tools=TOOLS)s2.circle(x='followers', y='forks', source=source)s3 = figure(tooltips=TOOLTIPS, plot_width=300, plot_height=300, title=None, tools=TOOLS)s3.circle(x='followers', y='contribution', source=source)p = gridplot([[s1,s2,s3]])show(p)

Минусы

Поскольку Bokeh - это библиотека, которая имеет интерфейс среднего уровня, она часто требует меньше кода, чем Matplotlib, но требует больше кода для создания того же графика, чем Seaborn, Altair или Plotly.

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

Если мы не добавим ширину столбцов графика, то он будет выглядеть так:

from bokeh.transform import factor_cmapfrom bokeh.palettes import Spectral6p = figure(x_range=list(titanic_groupby['class']))p.vbar(x='class', top='survived', source = titanic_groupby,      fill_color=factor_cmap('class', palette=Spectral6, factors=list(titanic_groupby['class'])      ))show(p)

Таким образом, нам нужно вручную настраивать параметры, чтобы сделать график более красивым:

from bokeh.transform import factor_cmapfrom bokeh.palettes import Spectral6p = figure(x_range=list(titanic_groupby['class']))p.vbar(x='class', top='survived', width=0.9, source = titanic_groupby,      fill_color=factor_cmap('class', palette=Spectral6, factors=list(titanic_groupby['class'])      ))show(p)

Если вы хотите создать красивую столбчатую диаграмму, используя меньшее количеством кода, то для вас это может быть недостатком Bokeh по сравнению с другими библиотеками

Вывод: Bokeh - единственная библиотека, чей интерфейс варьируется от низкого до высокого, что позволяет легко создавать как универсальные, так и сложные графики. Однако цена этого заключается в том, что для создания графиков с качеством, аналогичным другим библиотекам, обычно требуется больше кода.

Folium

Folium позволяет легко визуализировать данные на интерактивной встраиваемой карте. В библиотеке есть несколько встроенных тайлсетов из OpenStreetMap, Mapbox и Stamen

Плюсы

  1. Очень легко создавать карты с маркерами

Несмотря на то, что Plotly, Altair и Bokeh также позволяют нам создавать карты, Folium использует открытую уличную карту, что-то близкое к Google Map, с помощью минимального количества кода

Помните, как мы создавали карту для визуализации местоположения пользователей Github с помощью Plotly? Мы могли бы сделать карту еще лучше с помощью Folium:

import folium# Load datalocation_df = dp.Blob.get(name='location_df', owner='khuyentran1401').download_df() # Save latitudes, longitudes, and locations' names in a listlats = location_df['latitude']lons = location_df['longitude']names = location_df['location']# Create a map with an initial locationm = folium.Map(location=[lats[0], lons[0]])for lat, lon, name in zip(lats, lons, names):      # Create marker with other locations    folium.Marker(location=[lat, lon],                  popup= name,                  icon=folium.Icon(color='green')).add_to(m)    m

Живой вариант карты можно посмотреть в оригинале: https://towardsdatascience.com/top-6-python-libraries-for-visualization-which-one-to-use-fe43381cd658

2. Добавление потенциального местоположения

Если мы хотим добавить потенциальные местоположения других пользователей, Folium упрощает это, позволяя пользователям добавлять маркеры:

# Code to generate map here#....# Enable adding more locations in the mapm = m.add_child(folium.ClickForMarker(popup='Potential Location'))

Живой вариант карты можно посмотреть в оригинале: https://towardsdatascience.com/top-6-python-libraries-for-visualization-which-one-to-use-fe43381cd658

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

3. Плагины

У Folium есть ряд плагинов, которые вы можете использовать со своей картой, в том числе плагин для Altair. Что, если мы хотим увидеть карту пользовательской активности общего количества звездных пользователей Github в мире, чтобы определить, где находится большое количество пользователей Github с большим количеством звезд? Карта пользовательской активности в плагинах Folium позволяет вам это сделать:

from folium.plugins import HeatMapm = folium.Map(location=[lats[0], lons[0]])HeatMap(data=location_df[['latitude', 'longitude', 'total_stars']]).add_to(m)

Живой вариант карты можно посмотреть в оригинале: https://towardsdatascience.com/top-6-python-libraries-for-visualization-which-one-to-use-fe43381cd658

Уменьшите масштаб, чтобы увидеть полное отображение пользовательской активности на карте.

Вывод: Folium позволяет создавать интерактивную карту в несколько строк кода. Он дает вам ощущения близкие к использованию Google Map.

Заключение

Поздравляем! Вы только что узнали о шести различных инструментах визуализации. Я надеюсь, что эта статья даст вам представление о возможностях каждой библиотеки и когда их лучше использовать. Освоение ключевых функций каждой библиотеки позволит вам быстрее определять нужную библиотеку для конкретной работы по мере необходимости.

Если вы все еще не знаете, какую библиотеку использовать для ваших данных, просто выберите ту, которая вам больше нравится. Затем, если код слишком громоздкий или график не так хорош, как вы думаете, просто попробуйте другую библиотеку!

Не стесняйтесь форкать и использовать код для этой статьи из этого репозитория на Github.

Мне нравится писать об основных концепциях data science и пробовать различные алгоритмы и инструменты анализа данных. Вы можете связаться со мной в LinkedIn и Twitter.

Отметьте этот репозиторий, если хотите изучить код всех статей, которые я писал. Следите за мной на Medium, чтобы быть в курсе моих последних статей по data science.


Узнать подробнее о курсе "Machine Learning. Basic"

Смотреть онлайн-интенсив Data Science это проще, чем кажется

Подробнее..

Перевод Минимизируем наложение лейблов в интерактивных визуализациях

28.07.2020 14:05:45 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса Промышленный ML на больших данных. Интересно развиваться в данном направлении? Смотрите записи трансляций бесплатных онлайн-мероприятий: День Открытых Дверей, Вывод ML моделей в промышленную среду на примере онлайн-рекомендаций.





Визуализация новых случаев заражения COVID-19 по дням для каждого штата США без алгоритма позиционирования лейблов


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


Моя визуализация 91-DIVOC пандемии COVID-19 позволяет пользователям получить самые последние данные о коронавирусе из Университета Джона Хопкинса с помощью интерактивной визуализации, построенной на библиотеке d3.js. Поскольку визуализация использует данные, которые обновляются несколько раз в день, а пользователи имеют возможность изучать данные и создавать более миллиона различных визуализаций, все должно рендериться программно.


Есть множество ограничений, которые часто встречаются, когда речь заходит о комбинировании линейных графов, поэтому сделать лейблы читаемыми бывает совсем непросто:


  • Линий может быть много, и они все могут заканчиваться в одной точке.
  • Некоторые линии важнее, чем другие. В визуализации 91-DIVOC пользователь может выделить одну или несколько стран, тогда линия станет темнее и толще, а лейбл увеличится в размерах.
  • Большинство пользователей используют мобильные устройства, что ограничивает вычислительные ресурсы.

Без использования алгоритма позиционирования лейблов, визуализация новых случаев COVID-19 выводит несколько нечитаемых выделенных лейблов и еще более нечитаемых невыделенных.


Популярный подход: Силовые алгоритмы визуализации графов



Этапы моделирования силового алгоритма размещения лейблов на трех лейблах. На третьем этапе лейблы Mississippi и Virginia наконец разделились.


Распространенный подход к решению проблемы позиционирования лейблов это компонент force в d3.js, который реализует force-directed граф. Force-directed граф это модель, основанная на физике, в которой у всех элементов есть сила притяжения и отталкивания относительно других элементов. Если говорить об алгоритмах позиционирования лейблов, то у каждого элемента появляется небольшая сила отталкивания от других элементов, в результате чего симуляция отталкивает элементы друг от друга, когда это возможно, создавая читаемые лейблы. Когда в результате моделирования симуляция достигает стабильного конечного состояния, итог получается довольно хорошим, поэтому такой подход признан вполне рабочим.


К сожалению, force-directed графы медленные. Время работы force-directed алгоритмов растет кубически по отношению к входному значению, O(n). То есть области, в которых много элементов, требовали большого количества вычислительной мощности. В более ранних экспериментах с силовыми алгоритмами больше времени было потрачено на построение force-directed графа, чем на обработку и визуализацию. Пришло время для нового решения.


Быстрое решение: Render или Nudge


Меня постигла неудача с force-directed графами, которые оказались слишком медленными. Нужно было, чтобы время выполнения любого решения было минимальным. Дабы сэкономить время на позиционирование одного лейбла, размещение каждого из них сводилось к решению render или nudge:


  1. Если в данный момент в области не отображается ни один лейбл, отрендерите его. Такое решение сработает незамедлительно, а местоположение не будет изменено.
  2. Если другой лейбл, который был отрендерен раньше, теперь перекрывается новым лейблом, подтолкните (nudge) его, чтобы попробовать найти позицию получше.


Стратегия позиционирования лейблов Render или Nudge, показанная на примере трех меток, где подталкивается лейбл Mississippi


В процессе подталкивания лейбла, он сдвигается на высоту лейбла выше или ниже предполагаемого положения. Если подходящего места не найдено, то лейбл будет отображаться в исходной позиции, создавая наложение. (Смещение лейбла дальше предполагаемого места часто приводит к визуальному разрыву между данными и лейблом.)


Если лейблы просматриваются единожды, то алгоритм выполняется за линейное время O(n) и будет отнесен к жадным алгоритмам позиционирования лейблов.


Результат


После реализации алгоритма, результаты по скорости работы алгоритма были ошеломляющими. Ниже вы видите, что все выделенные лейблы читаемы, да и многие другие из 40+ невыделенных лейблов, тоже читаемы.



Визуализация количества заражений COVID-19 в день для каждого штата США с позиционированием лейблов по стратегии render или nudge


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


(Кстати, мою визуализацию с этим алгоритмом позиционирования лейблов вы можете посмотреть тут: 91-DIVOC #01: An interactive visualization of the exponential spread of COVID-19)




Узнать подробнее о курсе Промышленный ML на больших данных



Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru