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

Data engineering

Изучаем YELP с помощью Neo4j, python

12.05.2021 16:20:09 | Автор: admin

YELP зарубежная сеть, которая помогает людям находить местные предприятия и услуги, основываясь на отзывах, предпочтениях и рекомендациях. В текущей статей будет проведен определенный ее анализ с использованием платформы Neo4j, относящаяся к графовым СУБД, а также язык python.
Что посмотрим:
как работать с Neo4j и объемными датасетами на примере YELP;
чем может быть полезен YELP dataset;
частично: какие особенности в новых версиях Neo4j и почему книга Графовые алгоритмы 2019 года от O'REILLY уже устарела.


Что такое YELP и yelp dataset.


Сеть YELP на текущий момент охватывает 30 стран, РФ пока не входит в их число. Русский язык сетью не поддерживается. Сама сеть содержит достаточно объемное количество сведений о различного рода предприятиях, а также отзывах о них. Также yelp можно смело назвать социальной сетью, так как в ней имеются данные о пользователях, оставлявших отзывы. Никаких персональных данных там нет, только имена. Тем не менее пользователи образуют сообщества, группы или же могут быть в дальнейшем в эти группы и сообщества объединены по различным признакам. Например по количеству звезд (stars), которые поставили той точке (ресторану, заправке и т.п.), которую посетили.
Сама себя YELP описывает следующим образом:
-8,635,403 отзывов
-160,585 предприятий
-200,000 картинок
-8 мегаполисов
1,162,119 рекомендаций от 2,189,457 пользователей
Более 1.2 миллиона бизнес-атрибутики: часы работы, парковка, доступность и т.п.

С 2013 года Yelp регулярно проводит конкурс Yelp Dataset, призывая всех желающих
исследовать и изучать открытый набор данных Yelp.
Сам датасет доступен по ссылке
Датасет достаточно объемный и после распаковки представляет из себя 5 файлов формата json:


Все бы ничего, да вот только YELP выкладывает сырые (raw), необработанные данные и, чтобы начать с ними работать, потребуется предобработка.

Установка и быстрая настройка Neo4j.


Для анализа будет использоваться Neo4j, используем возможности графовой СУБД и их незамысловатый язык cypher для работы с датасетом.
О Neo4j как графовой СУБД неоднократно писали на Habrе (здесь и здесь статьи для начинающих), поэтому повторно представлять ее нет смысла.
Для того, чтобы начать работу с платформой, необходимо скачать desktop версию (около 500Mb) либо поработать в online песочнице. На момент написания статьи доступна Neo4j Enterprise 4.2.6 for Developers, а также иные, более ранние версии для установки.
Далее будет использоваться вариант работа в desktop версии в среде Windows (версии 4.2.5, 4.2.1).
Не смотря на то, что самая свежая версия 4.2.6, лучше ее пока не устанавливать, так как для нее еще не актуализированы все плагины, использующиеся в neo4j. Достаточно будет предыдущей версии 4.2.5.
После установки скачанного пакета, необходимо будет:
создать новую локальную БД, указав пользователя neo4j и пароль 123 (почему именно их, объясню ниже),
картинка


установить плагины, которые понадобятся APOC, Graph Data Science Library.
картинка


проверить, запускается ли БД и открывается ли браузер при нажатии на кнопку старт.
картинка




*- включить offline режим, чтобы БД истово не пыталась предлагать новые версии.
картинка



Загружаем данные в Neo4j.


Если с установкой Neo4j все прошло гладко, можно двигаться дальше и тут есть два пути.

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

и итоговой схемой:


Чтобы пройти первый путь, лучше ознакомиться сперва со статьей на medium.
*Большое человеческое спасибо за это TRAN Ngoc Thach.
И воспользоваться готовым jupyter notebookом (адаптирован мною под windows) ссылка.
Процесс импорта не из простых и занимает достаточно продолжительное время

Проблем с памятью при этом не возникает даже при наличии всего лишь 8Гб Ram, так как используется пакетный импорт.
Однако потребуется создать swap файл размером на 10Гб, так как при проверке импортированных данных jupyter крашится, об этом моменте есть упоминание в вышеуказанной тетрадке jupyter.

Второй путь самый быстрый и был обнаружен случайно. Он подразумевает копирование уже готовой БД neo4j в существующую БД neo4j напрямую. Из минусов (пока обнаруженных) нельзя произвести backup БД средствами Neo4j (neo4j-admin dump --database=neo4j --to=D:\neo4j\neo4j.dump). Однако, это может быть связано с различиями в версиях в версии 4.2.1 была скопирована БД от версии 4.2.5.
Как реализуется этот метод:
открыть вкладку Manage БД, куда будет произведен импорт
картинка




перейти в папку с БД и скопировать туда папку data, перезаписав возможные совпадения.
картинка


При этом сама БД, куда произведено копирование не должна быть запущена.
перезапустить Neo4j.
И вот здесь пригодятся логин-пароль, которые ранее были использованы (neo4j,123) для избежания конфликтов.
После старта скопированной БД будет доступна БД c yelp-датасетом:


Смотрим YELP.


Изучать YELP можно как из Neo4j браузера, так и отправляя запросы в БД из того же jupyter notebook.
Благодаря тому, что БД графовая, в браузере будет сопровождать приятная наглядная картинка, на которой эти графы и будут отображаться.
Приступая к ознакомлению с YELP необходимо оговориться, что в БД будут только 3 страны US,KG и CA:

Посмотреть схему БД можно написав запрос на языке cypher в браузере neo4j:
CALL db.schema.visualization()

И вот здесь, если мы пошли по пути импорта БД путем прямого копирования (второй путь) нас ждет совсем иная картинка:

На работоспособность БД это не влияет.
Однако будем ориентироваться на оригинальную схему


Как читать эту схему? Выглядит все следующим образом. Вершина User имеет связь сама с собой типа FRIENDS, а также связь WROTE с вершиной Review. Rewiew в свою очередь имеет связь REVIEWS с Business и так далее. Посмотреть на это можно наглядно после нажатия на одной из вершин (node labels), например на User:

БД выберет любых 25 пользователей и покажет их:

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

Это удобно и неудобно одновременно. С одной стороны о пользователе можно посмотреть всю информацию одним кликом, но в то же время этим кликом нельзя убрать лишнее.
Но здесь нет ничего страшного, можно по id найти этого пользователя и только всех его друзей:
MATCH p=(:User {user_id:"u-CFWELen3aWMSiLAa_9VANw"}) -[r:FRIENDS]->() RETURN p LIMIT 25


Точно так же можно посмотреть какие отзывы написал данный человек:

YELP хранит отзывы аж от 2010 года! Сомнительная полезность, но тем не менее.
Чтобы почитать эти отзывы необходимо переключиться в вид текста, нажав на А

Посмотрим на место, о котором писала Sandy 10 лет назад и найдем его на yelp.com
Такое место действительно существует www.yelp.com/biz/cafe-sushi-cambridge,
а вот и сама Sandy co своим отзывом www.yelp.com/biz/cafe-sushi-cambridge?q=I%20was%20really%20excited
картинка



Запросы на python из jupyter notebook.


Здесь будут частично использованы сведения из упомянутой свободно распространяемой книги Графовые алгоритмы 2019 года от O'REILLY. Частично, потому как синтаксис из книги во многих местах устарел.
База, с которой мы будем работать должна быть запущена, при этом сам neo4j браузер запускать нет необходимости.
Импорт библиотек:
from neo4j import GraphDatabaseimport pandas as pdfrom tabulate import tabulateimport matplotlibmatplotlib.use('TkAgg')import matplotlib.pyplot as plt

Подключение к БД:
driver = GraphDatabase.driver("bolt://localhost", auth=("neo4j", "123"))

Подсчитаем количество вершин для каждой метки в БД:
result = {"label": [], "count": []}with driver.session() as session:    labels = [row["label"] for row in session.run("CALL db.labels()")]    for label in labels:        query = f"MATCH (:`{label}`) RETURN count(*) as count"        count = session.run(query).single()["count"]        result["label"].append(label)        result["count"].append(count)df = pd.DataFrame(data=result)print(tabulate(df.sort_values("count"), headers='keys',tablefmt='psql', showindex=False))

На выходе:
+----------+---------+
| label | count |
|----------+---------|
| Country | 3 |
| Area | 15 |
| City | 355 |
| Category | 1330 |
| Business | 160585 |
| User | 2189457 |
| Review | 8635403 |
+----------+---------+
Похоже на правду, в нашей базе 3 страны, как мы увидели ранее через neo4j браузер.
А этот код подсчитает количество связей (ребер):
result = {"relType": [], "count": []}with driver.session() as session:    rel_types = [row["relationshipType"] for row in session.run    ("CALL db.relationshipTypes()")]    for rel_type in rel_types:        query = f"MATCH ()-[:`{rel_type}`]->() RETURN count(*) as count"        count = session.run(query).single()["count"]        result["relType"].append(rel_type)        result["count"].append(count)df = pd.DataFrame(data=result)print(tabulate(df.sort_values("count"), headers='keys',tablefmt='psql', showindex=False))

Выход:
+-------------+---------+
| relType | count |
|-------------+---------|
| IN_COUNTRY | 15 |
| IN_AREA | 355 |
| IN_CITY | 160585 |
| IN_CATEGORY | 708884 |
| REVIEWS | 8635403 |
| WROTE | 8635403 |
| FRIENDS | 8985774 |
+-------------+---------+
Думаю, принцип понятен. В завершение напишем запрос и визуализируем его.
Top 10 отелей Ванкувера с наибольшим количеством отзывов
# Find the 10 hotels with the most reviewsquery = """MATCH (review:Review)-[:REVIEWS]->(business:Business),      (business)-[:IN_CATEGORY]->(category:Category {category_id: $category}),      (business)-[:IN_CITY]->(:City {name: $city})RETURN business.name AS business, collect(review.stars) AS allReviewsORDER BY size(allReviews) DESCLIMIT 10"""#MATCH (review:Review)-[:REVIEWS]->(business:Business),#(business)-[:IN_CATEGORY]->(category:Category {category_id: "Hotels"}),#(business)-[:IN_CITY]->(:City {name: "Vancouver"})#RETURN business.name AS business, collect(review.stars) AS allReviews#ORDER BY size(allReviews) DESC#LIMIT 10fig = plt.figure()fig.set_size_inches(10.5, 14.5)fig.subplots_adjust(hspace=0.4, wspace=0.4)with driver.session() as session:    params = { "city": "Vancouver", "category": "Hotels"}    result = session.run(query, params)    for index, row in enumerate(result):                business = row["business"]        stars = pd.Series(row["allReviews"])        #print(dir(stars))        total = stars.count()        #s = pd.concat([pd.Series(x['A']) for x in data]).astype(float)        s = pd.concat([pd.Series(row['allReviews'])]).astype(float)        average_stars = s.mean().round(2)        # Calculate the star distribution        stars_histogram = stars.value_counts().sort_index()        stars_histogram /= float(stars_histogram.sum())        # Plot a bar chart showing the distribution of star ratings        ax = fig.add_subplot(5, 2, index+1)        stars_histogram.plot(kind="bar", legend=None, color="darkblue",                             title=f"{business}\nAve:{average_stars}, Total: {total}")                                    #print(business)        #print(stars)plt.tight_layout()plt.show()


Результат должен получиться следующий

Ось X представляет рейтинг отеля в звездах, а ось Y общий процент каждого рейтинга.

Чем может быть полезен YELP dataset

.
Из плюсов можно выделить следующие:
достаточно богатое информационное поле по содержательной составляющей. В частности можно просто насобирать отзывы со звездами 1.0 или 5.0 и заспамить какой-либо бизнес. Гм. Немного не в ту сторону, но вектор понятен;
датасет объемен, что создает дополнительные приятные трудности в плане тестирования производительности различных платформ по анализу данных;
представленные данные имеют определенную ретроспективу и в принципе возможно понять, как менялось предприятие, исходя из отзывов о нем;
данные можно использовать как ориентиры по предприятиям, учитывая, что имеются адреса;
пользователи в датасете зачастую образуют интересные взаимосвязанные структуры, которые можно брать как есть, не формируя пользователей в искусственную соц. сеть и не собирая данную сеть из иных существующих соц. сетей.
Из минусов:
всего лишь три страны представлены из 30-ти и есть подозрение, что и то не полностью,
отзывы хранятся по 10 лет, что может искажать и зачастую портить характеристику существующего бизнеса,
о пользователях мало данных, они обезличены, поэтому, рекомендательные системы на базе датасета будут явно хромать,
в связях FRIENDS используются направленные графы, то есть Аня дружит -> Петей. Получается, что Петя не дружит с Аней. Это решается программно, но все равно это неудобно.
датасет выкладывается сырой и требуется значительные усилия для его предобработки.

Несколько слов об особенностях новых версий Neo4j


Neo4j динамично обновляется и новая версия интерфейса, используемого в 4.2.6 не совсем удобна, на мой взгляд. В частности не хватает наглядности в части сведений о количестве нод и связей в БД, что было в предыдущих версиях. Кроме того, интерфейс перемещения по вкладкам при работе с БД был изменен и к нему тоже необходимо привыкнуть.
Главная неприятность в обновлениях интеграция графовых алгоритмов в плагин Graph Data Science Library. Ранее они именовались neo4j-graph-algorithms
После интеграции многие алгоритмы значительно изменили синтаксис. По этой причине, изучение книги Графовые алгоритмы 2019 года от O'REILLY может быть затруднено.

Обработанная БД yelp для neo4j для прямого копирования и последующего анализа будет выложена позднее.
Подробнее..

5 условий зарождения искуственного интеллекта в индустрии

28.05.2021 16:10:14 | Автор: admin


Революция искусственного интеллекта не грядёт, она уже здесь и требует от компаний переосмысления бизнес-процессов, чтобы максимально использовать возможности искусственного интеллекта (ИИ) для расширения человеческих способностей.

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

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

1. Единая команда с общим мышлением





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

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

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

2. Переход к новой культуре технологических и бизнес-процессов





В ходе ряда исследований последних лет учёные выяснили, что при совершении одной и той же ошибки в прогнозах люди скорее перестают доверять алгоритму, чем человеку [1].
Да, люди склонны больше доверять себе подобным, потому что знают, как мы устроены, потому что примерно понимают логику поведения друг друга и легко могут представить себя на месте другого человека, спроецировать ситуацию.
Когда менеджеров первой линейки и среднего звена спросили, что побудило бы их доверять советам системы, 60 процентов выбрали вариант Чёткое понимание того, как работает система и как она генерирует совет, 55 процентов Система с проверенной репутацией, и 49 Система, которая объясняет свою логику [2].
Перед компаниями, которые берут курс на цифровизацию и переход на новый уровень построения технологических и бизнес-процессов за счёт внедрения систем ИИ, стоит сложная лидерская задача сформировать корпоративную культуру, способствующую пониманию целей, этапов, способов их проектирования и внедрения. Достичь этой цели непросто, поскольку многие люди, особенно те, кому непосредственно придётся взаимодействовать с ИИ, часто обеспокоены, что в конечном счёте машины могут занять их место, а они останутся ненужными и без собственного ремесла.
В рабочей среде необходимо сформировать понимание, что искусственный интеллект позволит не отвлекаться на отдельные задачи и направлен не на замену сотрудников, а на расширение их возможностей, перевод функционала на новый уровень, облегчение их работы и возможность сосредоточиться не на рутинных процедурах, а на вещах, по-настоящему нуждающихся в человеческом интеллекте.
Команда разработки, со своей стороны, должна освоить язык индустрии, максимально глубоко погрузиться в производственные и технологические процессы.
Крайне важно, чтобы люди, которые будут непосредственно пользоваться ИИ, понимали основные принципы его устройства и поведения, могли вносить коррективы в результаты его работы и чувствовали себя активными участниками разработки, чтобы у них было ощущение прозрачности и контроля системы. В идеале, конечно, системы ИИ необходимо проектировать так, чтобы они объясняли свои решения и помогали людям сохранять определенную автономию в принятии решения.

3. Экспериментирование с ИИ





Несколько раз в нашей практике бывало такое, что производственные бригады, которые работали с нашим сервисом, не выполняли его рекомендации или пытались его обмануть, потому что боялись получить нагоняй от своих начальников за возможное снижение показателей эффективности производства и повышенные производственные затраты (например, повышенный расход электроэнергии).
На этапах горячего тестирования системы ИИ важно создать максимально доверительную обстановку внутри объединённой команды, важно дать понять экспериментаторам, что отрицательный результат это тоже результат и порой он бывает даже более ценным, чем положительный. Тут необходимо быть максимально честными и не утаивать истинное положение дел. Где-то это сравнимо с приёмом у врача. У пациента не всегда бывает желание рассказывать обо всех своих симптомах и отклонениях по здоровью, он утаивает некоторые, а впоследствии лечение становится гораздо более длительным, дорогостоящим и сложным.
Соль в том, чтобы стать немножко стартапом и научиться быстро экспериментировать с цифровизацией в стиле стартапов. Их обычное правило: если получается, идём вперёд, если нет, пробуем новую идею. Каждый такой стартап это многоступенчатый процесс проработки и развития гипотезы от рождения, через проверку и превращение в рабочее решение, до получения бизнес-эффекта. Причем сотрудники, которые занимаются одной гипотезой, должны сопровождать ее от начала до конца [2].
Основной метрикой развития гипотезы должен стать бизнес-эффект, для которого важно построить модель расчета в самом начале проекта, при этом на каждом шаге данная модель актуализируется. Очевидные вначале источники эффекта для гипотезы могут оказаться бесперспективными, но по ходу реализации могут появиться новые идеи, и результат будет достигнут за счет них.

4. Важность налаженной и полной поставки данных





Прежде всего следует отметить, что качество данных играет важнейшую роль в системах искусственного интеллекта. По существу, данные это топливо для искусственного интеллекта. Больше всего времени и усилий уходит на сбор и изучение адекватного массива данных для формирования и апробации гипотез. Опыт говорит, что примерно 80 процентов времени реализации ИИ-инициатив занимает извлечение, доставка и предобработка данных, конструирование признаков, а не составление самих алгоритмов. Нужно объяснять бизнес-заказчику, насколько важно собрать качественный датасет и почему столько времени уходит на его создание.
По нашему опыту, на начальных стадиях проекта данных не будет совсем или их объем будет ограничен как с точки зрения глубины хранения, так и со стороны дискретности, они будут с большим количеством пропусков и общим низким уровнем качества. И с этим придется работать: придумывать, как строить модели, оперирующие неполными данными, как оптимально интерпретировать некачественные.
В нашей практике также встречались случаи, когда технологи или производственники убеждали нас в бесполезности целого ряда технологических данных, которые, по их мнению и опыту, никоим образом не могут влиять на формирование оценки или рекомендации системы ИИ. Однако на практике оказывалось, что именно эти, на первый взгляд, нецелевые данные, помогали ИИ улавливать самую тонкую зависимость технологического процесса, и прогностическая сила модели увеличивалась именно за счёт них.
Поэтому важно с самого начала проекта готовить полноценную площадку для беспрепятственной и бесперебойной доставки данных, они должны быть обширными, разнообразными, качественными и полезными. Не выстраивать цепочки для доставки отдельных данных для проверки конкретной гипотезы, а наладить процесс, который поможет быстро конфигурировать требуемый набор данных и будет иметь достаточный потенциал для наращивания объёма доставляемой информации.

5. Забег на длинную дистанцию





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

Заключение


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

Литература



  1. Человек+машина. Новые принципы работы в эпоху искусственного интеллекта / Пол Доэрти, Джеймс Уилсон; пер.с англ. Олега Сивченко, Натальи Яцюк; [науч. ред. М. Григорьева, А. Кучма, А. Епишев, Е. Кученева]. М.: Манн, Иванов и Фербер, 2019. 304 с.
  2. Индустрия Х.0. Преимущества цифровых технологий для производства / Эрик Шеффер: Пер. с англ. М.: Издательская группа Точка, 2019.-320 с.
Подробнее..

Моя эволюция интерфейсов систем диспетчеризации

30.05.2021 20:12:29 | Автор: admin

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

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

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

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

Лонгрид!

2014

Главное окно с мнемосхемой на сенсорной панеле управления 7 дюймовГлавное окно с мнемосхемой на сенсорной панеле управления 7 дюймовОсновное меню с настройками на сенсорной панеле управления 7 дюймовОсновное меню с настройками на сенсорной панеле управления 7 дюймовОкно диспетчеризации, можно открыть на любом устройстве под управлением WindowsОкно диспетчеризации, можно открыть на любом устройстве под управлением Windows

Это индивидуальный тепловой пункт одного из павильонов ВДНХ в Москве, первый самостоятельный объект, где можно было проявить творческий подход. В помещении ИТП на шкафу управления установлена 7 дюймовая сенсорная панель для локального управления и облачная система диспетчеризации. Несмотря на то, что в этом интерфейсе довольно все криво и сыро, здесь есть много элементов и правил, которым мы придерживаемся до сих пор.

С самого первого объекта я начал применять темный интерфейс. У специализированных программ (AutoCad, Photoshop), был темно-серый интерфейс, в нем было комфортно работать долгое время и я решил придерживаться их идеологии. К тому же, помещение ИТП было без освещения и очень темным, делать яркий светлый фон на панеле просто издевательство над эксплуатацией. Хотя сейчас у нас в портфолио есть кейсы со светлой темой, все равно, на сегодняшний день предпочтение отдается именно темной.

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

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

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

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

2015

Приточно-вытяжная установка с гликолевым рекуператором. Панель оператора 10 дюймов.Приточно-вытяжная установка с гликолевым рекуператором. Панель оператора 10 дюймов.

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

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

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

2016

Станция ВЗУ коттеджного поселка. Панель оператора 10 дюймов.Станция ВЗУ коттеджного поселка. Панель оператора 10 дюймов.

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

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

Структура меню не изменилась, изменился немного дизайн. Добавились иконки в пунктах меню. Иконки декоративные, какой-то пользы они не несут, скорее просто иконки ради иконок. Здесь они крупные, выполнены в стиле Flat, занимают прилично место на экране. В дальнейшем от иконок мы не отказывались, они стали меньше и аккуратнее и лучше вписываются в общую стилистику. Стоит применять иконки или нет в пунктах меню ответить однозначно сложно. Думаю, что так же как и с 3Д картинками, не под все пункты меню можно подобрать подходящие по стилю и смыслу иконки. Если есть возможность добавить аккуратные иконки, не уменьшая полезную площадь подписей, то лучше их добавить, они разбавят табличную структуру меню.

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

2017

Производственно-складской комплекс, МО, обзорная схема, 27 дюймовПроизводственно-складской комплекс, МО, обзорная схема, 27 дюймовПроизводственно-складской комплекс, МО, под экран планшета и ноутбукаПроизводственно-складской комплекс, МО, под экран планшета и ноутбука

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

2018

Это крупный логистический центр в Московской Области, оснащенный разными системами, за работой которых нужно следить, управлять и анализировать. Этот клиент дал мощный толчок в развитие, в том числе и с точки зрения пользовательского интерфейса, мы по сей день проводим здесь много работ, применяя новые алгоритмы управления и аналитики. К этому объекту я еще буду возвращаться в этой статье, но пока о том, как он повлиял на разработку интерфейса.

Основное отличие подхода к разработке этого интерфейса от предыдущих, это то, как рисуется вся статика. До этого интерфейс собирался по частям в среде разработки для панелей и в Scada системе, это очень усложняет процесс, особенно, выравнивание объектов. Здесь же вся статика делается в графическом редакторе и подгружается одной картинкой PNG на экран. Сверху накладываются только переменные и анимация. Это сразу убивает несколько зайцев: выравнивать и делать отступы можно с большой точностью, скорость загрузки страниц увеличивается, так как нужно меньше информации подгружать из памяти, уменьшается нагрузка на процессор.

С точки зрения дизайна, тут появилось много нового. Появился не стандартный шрифт GothamPro, цвета теперь не рандомные, а подобраны из палитр Material Design, появились кратные отступы и сетка. Объекты теперь выровнены все относительны друг друга. Поля индикации и ввода значения уже стали существенно различаться, к этому быстро привыкаешь и пользоваться становится удобно.

Что осталось от прежних интерфейсов? Структура меню, она не поменялась, иконки стали меньше и аккуратнее, теперь они гармонично вписались в общую стилистику. Иконки на главном окне с мнемосхемой остались прежние, но сама мнемосхема слегка упростилась и стала чуть более плоской. Это был последний год использования этих иконок и 3Д элементов в целом.

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

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

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

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

Что касается главного экрана, то здесь реализован "карточный" интерфейс. Смысл его в том, чтобы структурировать информацию по системам, на каждую сделать свою карточку и разместить ее на схеме здания. Если систем много, можно разделить их по нескольким экранам, если не много, то можно все показать на одном экране и отделить разные схемы цветами. На карточке нужно отображать только самые основные параметры, которые влияют на работоспособность установки и которые можно сравнивать между собой. Для вентиляции наиболее важными параметрами являются: статус работы, наличие аварий, режим зима/лето, температура канала, помещения и обратной воды. Дополнительно стараемся отображать обороты вентилятора, процент открытия клапана и задание уставки температуры. Эти значения позволяют быстро оценить стабильность работы вентсистем и теплоснабжения установок, не переходя к схемам каждой системы отдельно. Очень удобно на одном экране сравнивать параметры работы разных систем. Если, например, наблюдается недогрев по многим системам, значит есть проблемы с теплоснабжением из котельной.

У нас есть объекты, которые имеют один этаж и там удобно все карточки систем размещать на планировке здания, привязывая к их реальному местоположению на плане. Есть объекты в 4-5 этажей, и здесь становится сложнее структурировать карточки. Можно разделить экран на 4 равных части и на каждом отобразить свой этаж с системами. Можно сделать 4 экрана, на каждом свой этаж с нанесенным на нем системами, но здесь могут быть нюансы, как правило, большая часть систем располагается на -1 этаже и на последнем или кровле, что приводит к сильному дисбалансу. Мы пробовали разные варианты, расскажу об этом чуть дальше в статье.

2019

Приточно-вытяжная установка, панель оператора 7 дюймовПриточно-вытяжная установка, панель оператора 7 дюймовМенюшка, панель 7 дюймовМенюшка, панель 7 дюймов

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

Здесь мы опять решили пересмотреть дизайн интерфейса и внести что-то новое. Наконец-то полностью ушли от 3Д, теперь у нас все плоское. Идея была уйти от 3Д, тем самым убрать лишние не информативные элементы со схемы, освободить ценное место и структурировать информацию не по графическим элементам, а по карточкам. Раньше, чтобы показать информацию о теплоснабжение установки (работа насоса, клапана, температура обратной воды) нужно было нарисовать целый узел с трубами, насосом, перемычками и прочим, это занимает много места и несет мало информации. Теперь вся эта информация находится в карточке "нагрев", точно так же структурированы и другие карточки систем. Такая структура дает унификацию интерфейса, если появится система, например, с дополнительным увлажнением или ступенью нагрева, то мы просто добавим новую карточку с нужной информацией, при этом интерфейс будет выглядеть одинаково преемственно на разных устройствах и разрешениях. Точно так же легко унифицируются и различные системы, вентиляция, отопление, освещение, энергетика, водоснабжение и так далее, все это легко приводится к одному стандарту и упрощает взаимодействие пользователя.

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

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

  1. Освобождаем интерфейс от всего лишнего, уменьшаем количество статических элементов, убираем все 3Д. Структурируем содержание по карточкам или вкладкам, оставляем больше места для динамических значение.

  2. Наводим порядок в цветах для удобства взаимодействия пользователя с интерфейсом. Оттенки темного цвета используются для фона, полей и заголовков, серым обозначаются подписи и весь текст в целом. Белый это переменные и все динамические значения. Яркий синий это цвет акцента, им обозначаются все элементы, с которыми пользователь может взаимодействовать, кнопки и уставки. И стандартные цвета: зеленый, желтый и красный, отвечают за состояние и индикацию.

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

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

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

Здесь очень удачно планировка ТЦ вписалась в разрешение Scada системы примерно 1920 х 980 рх., это позволило расположить максимум полезной информации с привязкой к физическому местоположению. Так как систем много (около 150 штук) то лучший вариант разбить их по венткамерам, где находятся щиты управления, отобразить системы как название и подкрашивать его в зависимости от состояния. Серый выведен из эксплуатации, белый стоянка, зеленая работает, желтая требует обслуживания, красный авария. Дальше архитектура строится так: при клике на венткамеру открывается табличный вид связанных систем с их основными параметрами. При клике на название системы уже откроется мнемосхема со всеми доступными параметрами и настройками.

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

2020

Приточно-вытяжная установка с увлажнением, монитор 25 дюймовПриточно-вытяжная установка с увлажнением, монитор 25 дюймовПриточно-вытяжная установка с увлажнением, монитор 25 дюймовПриточно-вытяжная установка с увлажнением, монитор 25 дюймовОбзорная схема системы диспетчеризации, медицинский центр, монитор 23 дюймаОбзорная схема системы диспетчеризации, медицинский центр, монитор 23 дюймаОбзорная схема системы диспетчеризации ТРЦ Рассвет, Москва, монитор 23 дюймаОбзорная схема системы диспетчеризации ТРЦ Рассвет, Москва, монитор 23 дюймаМнемосхема приточно-вытяжной установки.Мнемосхема приточно-вытяжной установки.

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

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

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

Если вы дочитали до этого места, то скорее всего обратили внимание, что впервые появился светлый интерфейс =). Появилась очень интересная идея сделать универсальный интерфейс с темной и светлой темой и переключать его либо автоматически от времени суток, либо вручную. Реализовали эту идею так: подложки на 95 процентов прозрачные с небольшим оттенком серого, статические элементы имеют цвета не сильно контрастирующие на темном и светлом фоне, переменные имеют подобранный серый цвет, больше контрастирующий на темном и светлом фоне. По сути, мы только меняем цвет фона с белого на темно серый, а все элементы остаются без изменения. С точки зрения работы Scada системы процесс максимально простой, подмена одного фона на другой, без замены других элементов, это не перегружает процесс работы и отрисовки. У такого решения есть минус, сложно подобрать цвета одинаково правильно отображающиеся тут и там, от этого переменные немного теряются на общем фоне. Есть вариант вместе с фоном менять скриптами и цвета переменных и других элементов, но не понятно насколько это может загрузить процесс отрисовки.

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

2021

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

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

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

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

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

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

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

2022

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

Надеюсь, вы нашли в статье что-то полезное для себя. Буду рад, если оставите свою критику и комментарии, напишите, какой интерфейс больше понравился вам и почему.

Подробнее..

Как работать с иерархической структурой классов

22.04.2021 14:23:53 | Автор: admin

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

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

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

Это звучит понятно в школьном учебнике, но в контексте машинного обучения возникает множество вопросов:

  • Как с этим всем обращаться?

  • Какие классы предсказывать?

  • Сколько моделей тренировать для решения задачи?

  • Как работать с данными?

  • Как вносить изменения в иерархию классов и как реагировать на эти изменения с точки зрения модели?

Все эти проблемы мы разберем на примере задачи классификации, которую мы решаем в Контуре.

Постановка задачи

Мы работаем с чеками. В каждом чеке может встретиться много разных товаров, которые можно сгруппировать множеством разных способов. На данный момент нам интересно группировать эти товары с помощью дерева категорий, которое мы будем называть KPC (Khajiit Product Classification). Это здоровенное дерево, состоящее из 683 категорий.

Для этих категорий у нас есть Golden Set, наш размеченный набор данных (штрихкод категория KPC) для обучения. В датасете почти три миллиона записей и мы постоянно работаем над его дополнением и улучшением разметки.

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

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

Разметка данных

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

Так появились статусы категорий. Они помогают понять, в каком состоянии категория прямо сейчас. Благодаря статусам мы можем отдельно обрабатывать только что появившиеся категории или категории, которые скоро будут удалены. На текущий момент мы выделяем четыре статуса категорий:

  • Активный.

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

  • Архивный категория, которую решено было удалить, но товары из категории еще не переразметили.

  • Удаленный.

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

Также стоит упомянуть две отдельные группы категорий:

  • свалка (например Одежда (свалка)) группа для логического удаления некоторых товаров, которые невозможно категоризировать. Например, у нас есть товар Полное тестирование Тест 2 10шт, у которого из какого-то источника данных стоит категория Одежда. Наш аналитик данных понимает, что по факту категории у такого товара нет, поэтому такой товар отправляется в свалку, которая при обучении не рассматривается.

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

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

Добавление категории

Добавить категорию мы можем в любое время, но для того, чтобы товары начали в неё попадать, необходимо:

  • Добавить категорию в KPC.

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

  • Переобучить модель, чтобы она научилась классифицировать товары в новую категорию.

Удаление категории

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

Для удаления категории необходимо:

  • Перевести категорию в статус Архивная.

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

  • Заменить удаляемую категорию у товаров в Golden Set.

  • Указать дочерним категориям новую родительскую категорию или её отсутствие (если дочерняя категория должна стать категорией верхнего уровня).

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

  • Перевести категорию в статус Удаленная.

Разбиение категории

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

  • Обновить категории в Golden Set, чтобы отнести товары к новым категориям.

  • Переобучить модель, чтобы она научилась классифицировать товары в новые категории.

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

Обучение модели

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

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

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

Алгоритм разрешения конфликтов

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

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

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

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

Future/Active на схеме это статусы категорий Запланированная/Активная, а present/NOT present in GS представлена ли категория в Golden set.

Еще один вопрос, который важно разобрать что мы хотим делать с Запланированными категориями? И что мы хотим делать с их детьми?

Есть несколько вариантов. Мы можем:

  • Использовать эти категории в классификации.

  • Не классифицировать и выбросить категории из GS.

  • Переразмечать эти категории в категорию-родителя.

  • Переразмечать эти товары в категорию другое/другие (например Молочные продукты, сыры и яйцо (другое))

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

  1. Убрать удаленные, редко встречающиеся (меньше 10 товаров в golden set) и те категории, у которых в названии есть свалка.

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

  3. Смаппить запланированные категории в sibling-категорию с другое/другие в названии, если такая есть.

  4. Удалить из Golden Set категории, у которых есть категории-потомки с товарами в Golden Set. Здесь происходит то самое разрешение конфликтов.

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

Валидация модели

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

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

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

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

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

'errors' - sum of errors of confusing two labels,
'label_1_confuse' - count(true=label_1, pred=label_2) / 'errors',
'label_2_confuse' - count(true=label_2, pred=label_1) / 'errors',
'fraction_of_error_to_label_1' - count(true=label_1, pred=label_2) / total_label_1,
'fraction_of_error_to_label_2' - count(true=label_2, pred=label_1) / total_label_2,
'fraction_of_all_errors' - 'errors' / total_errors,
'fraction_of_all_errors_cumulative'

Выводы

  1. Для удобной итеративной работы с деревом категорий полезно ввести статусы категорий. Такие статусы позволят обрабатывать категории в разном состоянии разными способами.

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

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

Подробнее..

Первые шаги в BI-аналитике. Роль Data Engineering

01.05.2021 10:11:55 | Автор: admin

Добрый день, уважаемые читатели! Материал носит теоретический характер и адресован исключительно начинающим аналитикам, которые впервые столкнулись с BI-аналитикой.

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

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

В таком случае происходит следующее: сбор, обработка и анализ данных происходит силами единственного инструмента самой BI-платформой. При этом данные предварительно никак не очищаются, не проходят компоновки. Забор информации идет из первичных источников без участия промежуточного хранилища. Результаты такого подхода можно легко лицезреть на тематических форумах. Если постараться обобщить все вопросы касательно BI-инструментов, то в топ-3 попадут, наверное, следующие: как загрузить в систему плохо структурированные данные, как по ним рассчитать требуемые метрики, что делать, если отчет работает очень медленно. Что удивительно, на этих форумах вы практически не найдете обсуждений ETL-инструментов, описания опыта применения хранилищ данных, лучших практик программирования и запросов SQL. Более того, я неоднократно сталкивался с тем, что опытные BI-аналитики не очень лестно отзывались о применении R/Python/Scala, мотивируя это тем, что все проблемы можно решить только силами BI-платформы. Вместе с тем всем понятно, что грамотный дата инжиниринг позволяет закрывать массу проблем при построении BI-отчетности.

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

Data BI Самый простой вариант. Именно с него начинается прототипирование управленческих панелей. В роли источника данных часто выступает отдельный (-ые) статичный файл (csv, txt, xlsx и т. д.).

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

Минусы. Далеко не изо всех источников можно забрать информацию напрямую (пример, прикладные решения на платформе 1С). Если массивы плохо структурированы, то это потребует много дополнительных шагов по их обработке. Качество данных никак не проверяется (проблема дубликатов, пустых строк, некорректного написания значений и т. д.). При большом количестве строк заметно замедляется работа самой BI-платформы, вплоть до полной невозможности перестраивать графики и диаграммы. Нет возможности составить расписание на обновление исходников.

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

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

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

Data ETL DB BI Частичная автоматизация. В качестве ETL-инструмента может выступать как программный продукт с графическим интерфейсом, так и код написанный на R/Python/Scala и т. д. Все данные проходят предварительный предпроцессинг. Структура наполняемых таблиц прописывается заранее.

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

Минусы. Аналитик должен уверенно владеть ETL-инструментом и языком запросов SQL. Процесс разработки и тестирования скриптов требует времени. Если источников информации много, то затрудняется синхронизация получения информации.

Для иллюстрации этого варианта я решил написать простейшие скрипты. В рамках игрушечного примера я использую SQLite. Это позволит прикрепить базу данных к публикации, чтобы каждый желающий мог попрактиковаться в написании скриптов (архив). Датасет для разбора это E-Commerce Data с сайта Kaggle.

# импорт библиотекimport pandas as pd# опции отображенияpd.set_option('display.max_columns', 10)pd.set_option('display.expand_frame_repr', False)path_dataset = 'dataset/ecommerce_data.csv'# Предварительная обработка датасетаdef func_main(path_dataset: str):    # Считываем датасет    df = pd.read_csv(path_dataset, sep=',')    # Приводим названия столбцов датасета к нижнему регистру    list_col = list(map(str.lower, df.columns))    df.columns = list_col    # Избавляемся от времени и трансформируем строку-дату в правильный формат    df['invoicedate'] = df['invoicedate'].apply(lambda x: x.split(' ')[0])    df['invoicedate'] = pd.to_datetime(df['invoicedate'], format='%m/%d/%Y')    # Рассчитываем сумму покупки по каждому товару    df['amount'] = df['quantity'] * df['unitprice']    # Удаляем ненужные для дальнейшего анализа столбцы    df_result = df.drop(['invoiceno', 'quantity', 'unitprice', 'customerid'], axis=1)    # Задаем порядок вывода столбцов для визуального контроля результата    df_result = df_result[['invoicedate', 'country', 'stockcode', 'description', 'amount']]    return df_result# Таблица Продажиdef func_sale():    tbl = func_main(path_dataset)    df_sale = tbl.groupby(['invoicedate', 'country', 'stockcode'])['amount'].sum().reset_index()    return df_sale# Таблица Страныdef func_country():    tbl = func_main(path_dataset)    df_country = pd.DataFrame(sorted(pd.unique(tbl['country'])), columns=['country'])    return df_country# Таблица Товарыdef func_product():    tbl = func_main(path_dataset)    df_product = tbl[['stockcode','description']].\        drop_duplicates(subset=['stockcode'], keep='first').reset_index(drop=True)    return df_product

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

# импорт библиотекimport pandas as pdimport sqlite3 as sqfrom etl1 import func_country,func_product,func_salecon = sq.connect('sale.db')cur = con.cursor()## Таблица Страны# cur.executescript('''DROP TABLE IF EXISTS country;#                     CREATE TABLE IF NOT EXISTS country (#                     country_id INTEGER PRIMARY KEY AUTOINCREMENT,#                     country TEXT NOT NULL UNIQUE);''')# func_country().to_sql('country',con,index=False,if_exists='append')## Таблица Товары# cur.executescript('''DROP TABLE IF EXISTS product;#                     CREATE TABLE IF NOT EXISTS product (#                     product_id INTEGER PRIMARY KEY AUTOINCREMENT,#                     stockcode TEXT NOT NULL UNIQUE,#                     description TEXT);''')# func_product().to_sql('product',con,index=False,if_exists='append')## Таблица Продажи (основная)# cur.executescript('''DROP TABLE IF EXISTS sale;#                     CREATE TABLE IF NOT EXISTS sale (#                     sale_id INTEGER PRIMARY KEY AUTOINCREMENT,#                     invoicedate TEXT NOT NULL,#                     country_id INTEGER NOT NULL,#                     product_id INTEGER NOT NULL,#                     amount REAL NOT NULL,#                     FOREIGN KEY(country_id)  REFERENCES country(country_id),#                     FOREIGN KEY(product_id)  REFERENCES product(product_id));''')## Таблица Продажи (временная)# cur.executescript('''DROP TABLE IF EXISTS sale_data_lake;#                     CREATE TABLE IF NOT EXISTS sale_data_lake (#                     sale_id INTEGER PRIMARY KEY AUTOINCREMENT,#                     invoicedate TEXT NOT NULL,#                     country TEXT NOT NULL,#                     stockcode TEXT NOT NULL,#                     amount REAL NOT NULL);''')# func_sale().to_sql('sale_data_lake',con,index=False,if_exists='append')## Перегружаем данные из вспомогательной таблицы (sale_data_lake) в основную (sale)# cur.executescript('''INSERT INTO sale (invoicedate, country_id, product_id, amount)#                     SELECT  sdl.invoicedate, c.country_id, pr.product_id, sdl.amount#                     FROM sale_data_lake as sdl LEFT JOIN country as c ON sdl.country = c.country#                     LEFT JOIN product as pr ON sdl.stockcode = pr.stockcode#                     ''')## Очищаем вспомогательную таблицу# cur.executescript('''DELETE FROM sale_data_lake''')def select(sql):  return pd.read_sql(sql,con)sql = '''select *        from (select s.invoicedate,                      c.country,                      pr.description,                      round(s.amount,1) as amount               from sale as s left join country as c on s.country_id = c.country_id                            left join product as pr on s.product_id = pr.product_id)'''print(select(sql))cur.close()con.close()

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

В заключении нам остается лишь выполнить тестовый запрос SQL, чтобы проверить корректность всех операций. Если все сделано правильно, запускаем BI-платформу.

Так как BI-инструмент не может из коробки напрямую подключиться к SQLite напишем простейший скрипт на Python.

import pandas as pdimport sqlite3 as sqcon = sq.connect('C:/Users/Pavel/PycharmProjects/test/sale.db')cur = con.cursor()def select(sql):  return pd.read_sql(sql,con)sql = '''select *        from (select s.invoicedate,                      c.country,                      pr.description,                      replace(round(s.amount,1),'.',',') as amount               from sale as s left join country as c on s.country_id = c.country_id                            left join product as pr on s.product_id = pr.product_id)'''tbl = select(sql)print(tbl)

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

Data Workflow management platform + ETL DB BI Полная автоматизация. Оркестратор скриптов берет на себя контроль за своевременным выполнением всех вспомогательных процессов в системе.

Плюсы. Возможность оптимально настроить время сбора данных из разрозненных источников. Можно мониторить ошибки и перезапускать упавшие задачи.

Минусы. Усложнение инфраструктуры. Рост требований к квалификации аналитика BI. Необходимо осваивать дополнительные инструменты или языки программирования.

Data Workflow management platform + ELT Data Lake Workflow management platform + ETL DB BI Самый сложный вариант, где информация проходит двухступенчатый конвейер: сначала это неструктурированные данные (Data Lake), а затем уже хранилище (DB), где информация отсортирована и преобразована к требуемому виду.

Плюсы. Возможность разнести во времени сбор информации и ее обработку. Если на этапе проектирования таблиц учтены не все требования, возможно обращение за дополнительными данными в Data Lake.

Минусы. Аналогичны предыдущему варианту. Если выбранная платформа Data Lake платная, как следствие рост расходов на аналитику компании.

Краткие выводы.

  • Построение BI-аналитики без даты инжиниринга возможно лишь на старте.

  • Если аналитик BI работает в единственном числе и система постоянно усложняется, то он обязан подменять собой сразу несколько специалистов.

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

На этом все. Всем здоровья, удачи и профессиональных успехов!

Подробнее..

Запросить 100 серверов нельзя оптимизировать код. Ставим запятую

15.06.2021 20:16:32 | Автор: admin

Можно выделить ряд алгоритмов, которые являются базовыми и лежат в основе практически каждой строчки программ, написанных на языках высокого уровня. Хорошо иметь под руками классический многотомный труд Дональда Кнута "The Art of Computer Programming", там детально разобраны многие базовые алгоритмы. Но прочесть и усвоить все задача, требующая много усилий и времени, которая должна как-то быть мотивирована.


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


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


Является продолжением серии предыдущих публикаций.


Введение


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


Допустим, что для алгоритмической отработки необходимо подготовить тестовый датасет, содержащий три колонки:


  • case_id уникальный идентификатор кейса/прецедента;
  • record журнальная запись события в кейсе;
  • start время регистрации.

Используемые библиотеки


library(tidyverse)library(data.table)library(rTRNG)

Наиболее интересной задачей является генерация временных меток. События должны идти последовательно во времени для каждого кейса в отдельности. Сначала подготовим простую "рыбу". В частном случае мы возьмем для демонстрации малое число кейсов. В продуктиве их может быть 10^5-10^n, что определяется задачами.


Пример кода
# определим число кейсовnn <- 100# создаем первичный набор кейсовrecords <- c("first", "one and a half", "second", "third", "fourth",              "fifth", "sixth")# готовим два варианта для экспериментовdf <- tibble(case_id = 1:nn, recs = list(records)) %>%  unnest(recs)dt <- as.data.table(df)[, case_id := as.numeric(case_id)]# указание ключа приводит к физической сортировке данныхsetkey(dt, case_id)head(df, 10)

  # A tibble: 10 x 2     case_id recs                 <int> <chr>            1       1 first            2       1 one and a half   3       1 second           4       1 third            5       1 fourth           6       1 fifth            7       1 sixth            8       2 first            9       2 one and a half  10       2 second  

Теперь приступим к интересному блоку генерации временных меток. Для простоты задачи сведем ее к распределению долей в интервале [0; 1] в рамках каждого кейса. Перевод в реальный unixtimestamp оставим за пределами, это неинтересно. Варианты с явными циклами также за пределами. Времена исполнения приведены на условном компьютере, главное, что выполняется все на одном.


Создание одной временнОй метки


Вариант 1. Прямолинейный


Этот вариант предлагается в большинстве случаев. А что, все просто и понятно.


Пример кода
f1 <- function(df) {  df %>%    group_by(case_id) %>%    mutate(t_idx = sort(runif(n(), 0, 1))) %>%    ungroup()}

Получаем такие условные показатели. Наверное, неплохо. Но не забываем, что тут всего 100 кейсов.


  median `itr/sec` mem_alloc 15.38ms      63.2   284.9KB

Подумаем, что можно улучшить?


Вариант 1+1/2. Прямолинейный + быстрый генератор чисел


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


Пример кода
f1_5 <- function(df) {  df %>%    group_by(case_id) %>%    mutate(t_idx = sort(runif_trng(n(), 0, 1))) %>%    ungroup()}

  median `itr/sec` mem_alloc 29.34ms      29.5   284.9KB

На малых объемах не получили никакого выигрыша. Это все? Конечно же нет. Мы знаем, что tidyverse медленнее data.table, попробуем применить его. Но здесь мы попробуем применить первую хитрость отсортировать вектор времен по индексам, а потом его переприсвоить.


Вариант 2. Однопроходный, через индексы data.table


Пример кода
f2 <- function(dt) {  # здесь полагаемся на то, что мы заранее отсортировали уже по `case_id``  # формируем случайные числа и сортируем их по кейсам  vec <- dt[, t_idx := runif_trng(.N, 0, 1)][order(case_id, t_idx), t_idx]  # возвращаем сортированный   dt[, t_idx := vec]}

Получается вполне неплохо, ускорение раз в 15-20 и памяти требуется почти в три раза меньше.


  median `itr/sec` mem_alloc   1.69ms     554.      109KB 

Останавливаемся? А почему да?


Вариант 3. Однопроходный, через композитный индекс


На самом деле, как только мы сваливаемся в цикл, явный, или через by, мы резко просаживаемся в производительности. Попробуем сделать все за один проход. Идея следующая сделать композитный индекс, который позволил бы нам отсортировать все события одним махом. Используем трюк. Поскольку у нас внутри кейса все временные метки будут в диапазоне [0; 1], то мы можем разделить индекс на две части. Целочисленная часть будет содержать case_id, дробная часть временнУю долю. Однократная сортировка одного такого индекса сохранит принадлежность строчек case_id, при этом мы одним махом отсортируем значения внутри каждого кейса


Пример кода
f3 <- function(dt) {  # делаем трюк, формируем композитный индекс из case_id, который является монотонным, и смещением по времени  # поскольку случайные числа генерятся в диапазоне [0, 1], мы их утаскиваем в дробную часть (за запятую)  # сначала просто генерируем случайные числа от 0 до 1 для каждой записи отдельно   # и масштабируем одним вектором  dt[, t_idx := sort(case_id + runif_trng(.N, 0, 1, parallelGrain = 10000L)) - case_id]}

Запускаем и получаем выигрыш еще в 2 раза против предыдущего варианта, как по времени, так и по памяти.


  median `itr/sec` mem_alloc 826.7us    1013.     54.3KB

Вариант 3+1/2. Однопроходный, через композитный индекс, используем set


Останавливаемся? Можно и остановиться, хотя поле для сжатия еще есть. Дело в том, что при таких малых временах исполнения накладные расходы на NSE становятся весьма ощутимыми. Если использовать прямые функции, то можно получить куда лучший результат.


Пример кода
f3_5 <- function(dt) {  set(dt, j = "t_idx",       value = sort(dt$case_id + runif(nrow(dt), 0, 1)) - dt$case_id)}

Ускорение еще в 5 раз, памяти потребляем в 4 раза меньше


  median `itr/sec` mem_alloc 161.5us    5519.     16.3KB

Промежуточное подведение итогов


Соберем все вместе.


Тестируем
bench::mark(  f1(df),  f1_5(df),  f2(dt),  f3(dt),  f3_5(dt),  check = FALSE)

  expression      min   median `itr/sec` mem_alloc  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>1 f1(df)       14.3ms  15.38ms      63.2   284.9KB2 f1_5(df)    24.43ms  29.34ms      29.5   284.9KB3 f2(dt)       1.55ms   1.69ms     554.      109KB4 f3(dt)        722us  826.7us    1013.     54.3KB5 f3_5(dt)    142.5us  161.5us    5519.     16.3KB

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


Создание временнОй метки начала записи и окончания


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


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


Вариант 1. Прямолинейный


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


Пример кода
# Cоздание ЧЕТРЕХ колонок -- case_id, record, start, finish# Все как в предыдущем, только для каждого записи finish > start # и для двух последовательных записей 1, 2 в одном кейсе start_2 > finish_1 dt[, t_idx := NULL] # очистим хвосты предыдущего упражненияf1 <- function(df) {  df %>%    group_by(case_id) %>%    mutate(ts_idx = sort(runif(n(), 0, 1))) %>%    ungroup() %>%    # еще раз пройдемся генератором, используя время начала следующей записи как границу    # чтобы избежать NaN при переходе между кейсами (в случае max < min),     # принудительно выставим порог 1 в таких переходах, NA в последней строке тоже заменим на 1    mutate(tf_idx = {lead(ts_idx, default = 1) %>% if_else(. > ts_idx, ., 1)}) %>%    mutate(tf_idx = map2_dbl(ts_idx, tf_idx, ~runif(1, .x, .y)))}

В целом меньше секунды, но, очевидно, что это ОЧЕНЬ далеко от оптимальности.


  median `itr/sec` mem_alloc  28.16ms      30.7    2.06MB 

Вариант 2. Однопроходный, через композитный индекс и матрицы


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


Пример кода
f2 <- function(dt){  dt[, c("ts_idx", "tf_idx") := {    # используем принцип vector recycling    x <- case_id + runif(2 * .N, 0, 1);    m <- matrix(sort(x), ncol = 2, byrow = TRUE) - case_id;    list(m[, 1], m[, 2])  }]}

В легкую сократили время и память почти в 30 раз! Да и код стал существенно проще и прямолинейнее.


  median `itr/sec` mem_alloc   1.04ms     733.    74.38KB 

Вариант 2+1/2. Однопроходный, через композитный индекс, матрицы и set


Пример кода
f2_5 <- function(dt){  x <- dt$case_id + runif(2 * nrow(dt), 0, 1)  m <- matrix(sort(x), ncol = 2, byrow = TRUE) - dt$case_id  set(dt, j = "ts_idx", value = m[, 1])  set(dt, j = "tf_idx", value = m[, 2])}

Перфекционизм в действии. Еще в 4 раза ускорили.


  median `itr/sec` mem_alloc  278.1us    2781.    57.55KB 

Промежуточное подведение итогов


Соберем все вместе.


Тестируем
bench::mark(  f1(df),  f2(dt),  f2_5(dt),  check = FALSE)

  median `itr/sec` mem_alloc  28.16ms      30.7    2.06MB   1.04ms     733.    74.38KB  278.1us    2781.    57.55KB 

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


Заключение


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


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


Предыдущая публикация Оценка структуры кредитного портфеля с помощью R.

Подробнее..

Проблемы мониторинга дата-пайплайнов и как я их решал

16.06.2021 00:20:01 | Автор: admin

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

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

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

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

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

Код - запускается следующий в цепочке пайплайн, происходят преобразования, рисуются графики и т.п.

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

Вот примеры:

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

  • Пайплайн стучится в источник с надеждой получить свежие данные, которые появляются в API источника с неопределенной задержкой (привет Apple), пайплан может успешно завершиться как скачав отчет, так и получив сообщение о том, что отчета еще нет. Тут, конечно, можно делать бесконечные ретраи внутри, либо просто ронять пайплайн, но тогда не особо очевидно будет, по какой причине он упал - что-то пошло не так, или данные в источнике еще не подтянулись.

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

ETL как он естьETL как он есть

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

Чего не хватает во встроенных мониторингах систем работы с данными:

  • Бизнес не может просто посмотреть в модный мониторинг типа того же Airflow или ELK и понять, можно или нельзя доверять данным, актуальность состояния данных непрозрачна.

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

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

Все это превращается в такие вот проблемы:

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

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

  3. Статистика, если и собирается, то собирается по техническим проблемам и нельзя понять, насколько эти технические проблемы повлияли на бизнес.

Концепция

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

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

Почему вообще вебхуки?

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

После того, как мы начали отсылать из процессов события, нужно их проанализировать и получить ответы на всякие важные вопросы, например (все числа абстрактны):

  • запустилась ли наша задача 10 раз за последний день?

  • не превышает ли количество падений (определяем падение, если полученное значение > 0, например) 15% от всех запусков за сегодня?

  • нет ли процессов, которые длятся больше 20 минут?

  • не прошло ли больше часа с момента последнего успешного завершения?

  • стартовало ли событие по планировщику в нужное время?

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

Реализация

Я начал с дашборда, дизайн - не моя профессия, так что просто взял за основу дашборд, показывающий состояние крон-джобов на сайте Nomadlist, у меня получилось как-то так:

Дашборд состояния серверов Sensorpad средствами SensorpadДашборд состояния серверов Sensorpad средствами Sensorpad

Каждый монитор на этом дашборде - это комбинация метрик, которые должны быть понятны и бизнесу, и инженерам.

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


Для инженера тут все понятно:

  • скрипт отрабатывает быстро (еще бы, простая крон-джоба);

  • монитор вполне живой, 25 минут назад обновился;

  • места еще с запасом (цифра 53 в левом нижнем углу - это последнее принятое значение);

Для людей из бизнеса тут тоже все просто:

  • монитор зеленый;

  • статус прописан в первой же строчке;

  • никакой лишней информации;

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

И насколько просто такое настроить?

  1. Создаем вебхук в самом сервисе, они там называются сенсорами, по аналогии со всякими штуками из физического мира.

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

    df -h |grep vda1 | awk  '{ print $5 }'| sed 's/.$//' | xargs -I '{}' curl -G "https://sensorpad.link/<уникальный ID>?value={}" > /dev/null 2>&1
    
  3. Присоединяем к этому вебхуку монитор, называем его: количество свободного места (но можно еще и другие, например, то, что события уходят по графику означает, что сервер не упал)

  4. Настраиваем правила, по которым монитор меняет свой статус.

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

  6. Добавляем монитор на один или несколько дашбордов.

А можно поподробнее?

Для вебхуков я пока что сделал саму простую имплементацию:

  • базовый вебхук, который будет нужен для 80% проектов;

  • cron-вебхук, который ожидает события в заданное через cron-синтаксис время;

  • chain-вебхук, который умеет отслеживать события от процессов, соединенных в цепочки;

главное в нашем деле - не усложнять интерфейсыглавное в нашем деле - не усложнять интерфейсы

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

Догфудинг в действииДогфудинг в действии

Дальше создаем тот самый монитор - квадратик, меняющий статус и цвет.

Можно даже иконку выбратьМожно даже иконку выбрать

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

Теперь, собственно то, из-за чего я и написал эту балалайку: правила и гибкая логика.

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

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

На скриншоте выше видно уже созданные правила, но я покажу как они создаются.

Например правило, которое можно сформулировать так: "установи статус Warning, если за последний день было больше 5 джоб, которые работали дольше 10 секунд".

А вот какие вообще можно выбирать проверки в каждом из пунктов:

И какие реальные кейсы можно покрыть этими правилами?

У каждого свои кейсы. Дата-инженерия вообще весьма специфичное для каждой компании направление. Если у вас есть дата-пайплайны или cron jobs, сервис оповестит вас, если (все цифры, разумеется, конфигурируемы):

  • Cron job, Airflow DAG или любой другой процесс не запустился по расписанию;

  • 20% задач одного и того же пайплайна за день не отработали как надо;

  • связанная задача в пайплайне не запустилась через 2 минуты после окончания родительской задачи;

  • интервал между запусками двух задач меньше 1 минуты (похоже, у нас две конкурентные джобы);

  • с момента последнего успешного завершения пайплайна прошло 2 часа (а данные должны считаться каждый час);

  • время работы пайплайна уже целых 20 минут (а должен был отработать за 5, что-то подвисло).

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

А теперь - статистика!

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

Немного полезных и не очень графиковНемного полезных и не очень графиков

Я подумываю допилить возможность шарить эти странички по секретной ссылке за пределы аккаунта, тогда такую страницу можно будет использовать не хуже любой status page.

Вот такой концепт. Чего не хватает?


Sensorpad - проект выходного дня, который делался в свободное от основной работы время, без тонны инвестиций да еще и в одно лицо, именно поэтому мне так важно мнение комьюнити: как вам? Что добавить, что улучшить, продолжать ли развивать?

Потыкайте его вживую, заодно зацените, какой я у мамы дизайнер лендингов: https://sensorpad.io

Подробнее..

Звездные войны или подробный гайд по dplyr

04.05.2021 18:23:50 | Автор: admin

Сегодня, 4 мая, в день Звездных войн мы подготовили для Вас подробный гайд по основным функциям библиотеки dplyr. Почему именно в день Звездных войн? А потому что разбирать мы все будем на примере датасета starwars.

Ну что, начнем!

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

Кстати говоря, а Вы знаете, почему день Звездных войн отмечается именно 4 мая? Все очень просто - знаменитая фраза May the fource be with you крайне созвучна с May, the 4th, т.е. 4 мая :)

Знакомство с датасетом

Для начала, давайте подключим библиотеку dplyr. Делается это с помощью функции library.

Вместе с этой библиотекой нам становится доступен и датасет starwars. Давайте выведем первые несколько строк на экран и посмотрим, какую информацию он в себе хранит.

  1. name - имя или прозвище героя вселенной Звездных войн. Например, Оби-Ван Кеноби.

  2. height - высота персонажа

  3. mass - масса персонажа

  4. hair_color - цвет волос

  5. skin_color - цвет кожи

  6. eye_color - цвет глаз

  7. birth_year - год рождения (до битвы на Явине)

  8. sex - биологический пол (есть существа без пола и гермафродиты)

  9. gender - поведенческий пол персонажа (например, на какой пол запрограммированы дроиды)

  10. homeworld - из какой вселенной существо

  11. species - биологический вид

  12. films - список фильмов, в которых появилось существо

  13. vehicles - список транспорта, которым существо управляло

  14. starships - список космических кораблей, которыми существо управляло

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

Знакомство с dplyr

dplyr - это один из основных пакетов семейства tidyverse. Его ближайший аналог в Python - библиотека Pandas. Библиотека dplyr служит для манипуляции с данными: фильтрация, сортировка, создание вычислимых столбцов и так далее.

По своему функционалу библиотека dplyr очень похожа на стандартный синтаксис SQL. Немного ранее мы вместе с Алексеем Селезневым из Netpeak делали карточки: сравнение глаголов dplyr и операторов SQL. Вы можете посмотреть их здесь.

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

  1. Каждая переменная находится в отдельном столбце

  2. Каждое измерение находится в отдельной строке

  3. Каждое значение находится в отдельной ячейке

Кстати говоря, наш датасет starwars не совсем соответствует этим правилам. Нам это не помешает, но сможете ли Вы найти, в чем именно несоответствие? ;)

Если Вы хотите поподробней познакомиться с tidy data, рекомендуем нашу статью.

Итак, вернемся к глаголам dplyr. Пакет dplyr позволяет делать несколько основных операций:

  • Отбор столбцов

  • Фильтрация строк

  • Сортировка строк

  • Группировка

  • Агрегирование

  • Создание вычислимых столбцов

  • Объединение таблиц

Давайте переходить к практике - хватит теории!

Отбор столбцов

Давайте начнем с самого простого - как выбрать только определенные столбцы из таблицы? Очень просто - с помощью функции select.

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

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

  • contains: название столбца содержит

  • ends_with: название столбца заканчивается на

  • matches: название столбца соответствует регулярному выражению

  • num_range: поиск занумерованных столбцов, например, V1, V2, V3...

  • one_of: название столбца соответствует одному из вариантов

  • starts_with: название столбца начинается с

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

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

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

Обратите внимание, все наши запросы возвращали таблицу tibble. А что, если мы хотим отобрать столбец и сразу начать работать с ним, как с вектором? Мало кто знает, но для этого в dplyr есть специальная функция pull. Она возвращает не таблицу, как остальные глаголы dplyr, а вектор.

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

Фильтрация строк

Фильтрация строк по значениям - это аналог привычного оператора WHERE в SQL. В синтаксисе dplyr же для этого используется глагол filter (как неожиданно, правда?).

Использовать filter очень просто - задаем некое логическое выражение и функция вернет только те строки, для которых это выражение True. Например:

Вы можете комбинировать несколько условий с помощью & и |:

Логические выражения Вы можете конструировать не только с помощью >/<, но и с помощью других логических операторов:

  • >=

  • <=

  • is.na

  • !is.na

  • %in%

  • !

Например:

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

Другая функция, sample_n, отбирает n случайных строк.

Функция slice же, например, позволяет отбирать строки по их индексу:

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

Итак, с фильтрацией строк мы тоже разобрались, переходим к следующему разделу.

Сортировка строк

За сортировку строк в SQL отвечает оператор ORDER BY. В dplyr для этого существует функция arrange.

Синтаксис максимально прост - достаточно указать столбец, по которому выполняется сортировка.

Чтобы отсортировать строки по убыванию, достаточно добавить функцию desc.

А если отсортировать нужно по нескольким столбцам? Легко, просто указываем их названия :)

Кстати говоря, с arrange Вы можете также использовать вспомогательные глаголы, которые мы обсуждали в блоке с select. Для этого нужно использовать функцию across. Например:

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

Группировка и агрегатные функции

Группировка - базовая операция, которая необходима для расчета различных характеристик - средних значений, медиан, сумм, количества строк в группе и так далее. В SQL для этого используется оператор GROUP BY и агрегатные функции sum, min, max и так далее. В dplyr же все то же самое :) Ну, почти.

Давайте для начала сгруппируем наши строки по полю eye_color:

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

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

Обратите внимание, здесь мы дополнительно воспользовались функцией drop_na из пакета tidyr, чтобы удалить строки, в которых есть пропуски. Сделали мы это, чтобы при расчете наших максимальных/минимальных/других агрегатных значений не вылезали значения NA.

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

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

  • n_distinct - считает количество уникальных элементов в группе

  • last - возвращает последнее значение в группе

  • nth - возвращает n-ое значение из группы

  • quantile - возвращает заданную квантиль

  • IQR - межквартильный размах, inter-quartile range

  • mad - медианное абсолютное отклонение, median absolute deviation

  • sd - стандартное отклонение

  • var - вариация

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

Продвинутая группировка

А как Вам такая задача - рассчитать все те же самые характеристики, что и в прошлый раз, но сразу для нескольких столбцов? Например, мы делали для mass, а теперь давайте сделаем для mass и height.

Без проблем - достаточно применить уже известную функцию across.

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

Итак, на этом закончим раздел группировки и перейдем к вычислимым столбцам.

Вычислимые столбцы

Создание вычислимого столбца - стандартная задача. Например, есть столбец A, столбец B, а мы хотим вывести столбец A/B. Или другой вариант - мы хотим проранжировать строки нашей таблицы. Все это можно сделать с помощью функции mutate.

Для начала базовая вещь - создадим вычислимый столбец отношения веса к росту.

А если нам нужно применить одну и ту же функцию к нескольким столбцам? Без проблем - снова нас выручит across. Например, давайте умножим на 10 все столбцы с численным типом данных. При этом мы не будем создавать новые столбцы - мы модифицируем старые.

Видно, что в столбцах с весом, ростом и возрастом все значения умножились на 10.

Давайте и с текстом немного поработаем - ко всем строковым значениям добавим суффикс _new. Для этого нам понадобится библиотека stringr все из того же семейства tidyverse.

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

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

Видно, что в конец таблицы добавился новый столбец rnk с рангами для каждой строки.

Таких функций, на самом деле, масса. Вот некоторые из них:

  • lag

  • lead

  • cumsum

  • dense_rank

  • ntile

  • row_number

  • case_when

  • coalesce

Это, пожалуй, самые популярные и все они на 100% перекликаются с SQL. Приведем несколько примеров.

Ну что, давайте переходить к последнему пункту - к объединению таблиц.

Объединение таблиц

Пожалуй, последняя необходимая для полноценной работы с датафреймами операция - объединение таблиц. Операция объединения в dplyr тесно связана с джоинами в SQL. Вот перечень основных функций:

  • left_join

  • right_join

  • inner_join

  • full_join

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

Обратите внимание, мы воспользовались функцией rename и переименовали поле name. Сделали мы это намеренно, чтобы показать работу аргумента by (аналог ON в SQL) для связи столбцов при джоине.

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

Если сделать full_join аналогичным образом, то строк в итоговой таблице будет 87, т.к. в таблице starwars их 87.

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

1 вариант:

Если название столбцов позволяет, то Вы можете убрать аргумент by из джоина. Тогда соединяться таблицы будут по всем столбцам, у которых совпадает названием. В нашем случае этот вариант подходит - названия одинаковые и в результат добавится только один столбец - new_name.

2 вариант:

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

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

  • bind_rows - помещает одну таблицу под другой

  • bind_cols - ставит одну таблицу справа от другой

  • intersect - находит пересекающиеся строки

  • setdiff - разность таблиц, т.е. строки из первой таблицы, которых нет во второй

  • union - возвращает строки, которые есть в любой из таблиц (дубликаты исключаются)

  • union_all - аналогично union, но оставляет дуликаты

Эпилог

Мы с Вами рассмотрели все основные операции библиотеки dplyr и почти все основные функции. Все остальное - практика, практика и еще раз практика. Если у Вас будут вопросы - рады будем помочь и ответить в комментариях :)

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

А, и совсем забыли. May the fource be with you!

P.S. Здесь Вы можете найти официальную шпаргалку по всем функциям dplyr. Все удобно и компактно собрано в одном месте :)

Подробнее..

Перевод Как построить систему распознавания лиц с помощью Elasticsearch и Python

13.05.2021 18:23:09 | Автор: admin

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


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

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

Краткое описание основ

Вам нужно освежить информацию? Давайте вкратце рассмотрим несколько основных понятий.

Распознавание лиц

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

  • Обнаружение лица: Идентификация человеческих лиц на цифровых изображениях.

  • Кодирование данных о лице: Преобразование черт лица в цифровое представление

  • Сопоставление лиц: Поиск и сравнение черт лица

Мы рассмотрим каждый этап на нашем примере.

128-мерный вектор

Черты лица могут быть преобразованы в набор цифровой информации для хранения и анализа.

Векторный тип данных

Elasticsearch предлагает тип данных dense_vector для хранения плотных векторов плавающих значений. Максимальное количество элементов в векторе не должно превышать 2048, что вполне достаточно для хранения репрезентаций черт лица.

Теперь давайте реализуем все эти концепции.

Все, что вам нужно

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

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

Установите Python и библиотеки Python

Ubuntu 20 и другие версии Debian Linux поставляются с установленным Python 3. Если у вас не такой пакет, загрузите и установите Python по этой ссылке.

Чтобы убедиться, что ваша версия является самой свежей, вы можете выполнить следующую команду:

sudo apt update sudo apt upgrade

Убедитесь, что версия Python - 3.x:

python3 -V

Установите pip3 для управления библиотеками Python:

sudo apt install -y python3-pip

Установите cmake, необходимый для работы библиотеки face_recognition:

pip3 install CMake

Добавьте папку cmake bin в каталог $PATH:

export PATH=$CMake_bin_folder:$PATH

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

pip3 install dlib pip3 install numpy pip3 install face_recognition  pip3 install elasticsearch

Обнаружение и кодирование информации о лице из изображения

Используя библиотеку face_recognition, мы можем обнаружить лица на изображении и преобразовать черты лица в 128-мерный вектор.

Создайте файл getVectorFromPicture.py:

touch getVectorFromPicture.py

Дополните файл следующим сценарием:

import face_recognition import numpy as np import sys image = face_recognition.load_image_file("$PATH_TO_IMAGE") # detect the faces from the images  face_locations = face_recognition.face_locations(image) # encode the 128-dimension face encoding for each face in the image face_encodings = face_recognition.face_encodings(image, face_locations) # Display the 128-dimension for each face detected for face_encoding in face_encodings:       print("Face found ==>  ", face_encoding.tolist())

Давайте выполним getVectorFromPicture.py, чтобы получить репрезентацию черт лица для изображений основателей компании Elastic. В сценарии необходимо изменить переменную $PATH_TO_IMAGE, чтобы задать имя файла изображения.

Теперь мы можем сохранить представление черт лица в Elasticsearch.

Сначала создадим индекс с отображением, содержащим поле с типом dense_vector:

# Store the face 128-dimension in Elasticsearch ## Create the mapping curl -XPUT "http://localhost:9200/faces" -H 'Content-Type: application/json' -d' {   "mappings" : {       "properties" : {         "face_name" : {           "type" : "keyword"         },         "face_encoding" : {           "type" : "dense_vector",           "dims" : 128         }       }     } }'

Нам необходимо создать один документ для каждой репрезентации лица, это можно сделать с помощью Index API:

## Index the face feature representationcurl -XPOST "http://localhost:9200/faces/_doc" -H 'Content-Type: application/json' -d'{  "face_name": "name",  "face_encoding": [     -0.14664565,     0.07806452,     0.03944433,     ...     ...     ...     -0.03167224,     -0.13942884  ]}'

Сопоставление лиц

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

Создайте файл recognizeFaces.py:

touch recognizeFaces.py

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

Импортируйте библиотеки:

import face_recognition import numpy as np from elasticsearch import Elasticsearch import sys

Добавьте следующий раздел для подключения к Elasticsearch:

# Connect to Elasticsearch cluster from elasticsearch import Elasticsearch es = Elasticsearch( cloud_id="cluster-1:dXMa5Fx...",      http_auth=("elastic", "<password>"), )

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

i=0 for face_encoding in face_encodings:         i += 1         print("Face",i)         response = es.search(         index="faces",         body={          "size": 1,          "_source": "face_name",         "query": {         "script_score": {                  "query" : {                      "match_all": {}                  },          "script": {                 "source": "cosineSimilarity(params.query_vector, 'face_encoding')",                  "params": {                  "query_vector":face_encoding.tolist()                 }            }           }          }         }         )

Предположим, что значение меньше 0,93 считается неизвестным лицом:

for hit in response['hits']['hits']:                 #double score=float(hit['_score'])                 if (float(hit['_score']) > 0.93):                     print("==> This face  match with ", hit['_source']['face_name'], ",the score is" ,hit['_score'])                 else:                         print("==> Unknown face")

Давайте выполним наш скрипт:

Скрипт смог обнаружить все лица с совпадением результатов более 0,93.

Сделайте еще один шаг вперед с расширенным поиском

Распознавание лиц и поиск можно объединить для расширенных сценариев использования. Вы можете использовать Elasticsearch для создания более сложных запросов, таких как geo-queries, query-dsl-bool-query и search-aggregations.

Например, следующий запрос применяет поиск cosineSimilarity к определенному местоположению в радиусе 200 км:

GET /_search {   "query": {     "script_score": {       "query": {     "bool": {       "must": {         "match_all": {}       },       "filter": {         "geo_distance": {           "distance": "200km",           "pin.location": {             "lat": 40,             "lon": -70           }         }       }     }   },        "script": {                 "source": "cosineSimilarity(params.query_vector, 'face_encoding')",                  "params": {                  "query_vector":[                         -0.14664565,                       0.07806452,                       0.03944433,                       ...                       ...                       ...                       -0.03167224,                       -0.13942884                    ]                 }            }     }   } }

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

Заключение

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

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


Узнать больше об экспресс-курсе Централизованные системы логирования Elastic stack.

Подробнее..

Простыми словами о простых линейных функциях

06.06.2021 14:21:06 | Автор: admin
Случайный лес (в буквальном смысле, сфотографировал с телефона)

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


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


Для начала есть смысл взглянуть на сами данные. Если вы отобразите массив данных с несколькими миллионами объектов, то человек не сможет всё это осмысленно прочитать. Придётся придумывать способ понятно описать его. Частая ошибка не изучив набор данных использовать среднее значение или медиану. Подобный подход чреват серьёзным искажением реальной картины.


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


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


Это гистограмма распределения

В итоге мы видим распределение случайной величины. Регулируя число интервалов можно добиться адекватного представления. Теперь нам нужен способ увидеть взаимосвязь этой случайной величины с другой. Отобразим наблюдения в виде точек на плоскости, где по оси X будет score, а по Y наш единственный предиктор (признак). И вот перед вами появляется график:


Взаимосвязь

На нём показано, что обе переменных сильно коррелируют между собой, следовательно, делаем предположение о линейной зависимости (score=a+bx). А теперь самая суть, но очень кратко: простая линейная регрессия это произведение коэффициента и признака, к которым добавляется смещение. Вот и всё. Степень линейной взаимосвязи вычисляют следующим образом:


Взаимосвязь

Не все точки выстроились на одну линию, что говорит нам о небольшом влиянии неизвестного фактора. Это я специально сделал, для красоты. Подобрать коэффициенты можно с помощью готовых решений, допустим, scikit-learn. Так, например, класс LinearRegression после обучения (fit) позволяет получить смещение (intercept) и массив коэффициентов (coef). Подставляем значения в формулу и проверяем результат с помощью метрик mean_absolute_error и median_absolute_error. Собственно, это и есть решение нашей задачи. В случае множества признаков суть не меняется: под капотом всё устроено аналогичным образом (смещение + скалярное произведение вектора признаков и соответствующих коэффициентов).


А теперь превратим эту функцию в классификатор, который называется логистическая регрессия. Под капотом это обычная линейная регрессия, только результат её работы передают в сигмоиду. Далее выполняется проверка если результат больше 0.5, то класс 1, иначе класс 0. По сути, это формула гиперплоскости, разделяющей классы.


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

Подробнее..

Перевод Как использовать конструкцию SELECT FROM UNNEST для анализа параметров в повторяющихся записях Google BigQuery

27.04.2021 10:04:26 | Автор: admin

В предыдущей статье я показал вам, как использовать функцию UNNEST в BigQuery для анализа параметров событий в данных Google Analytics для Firebase.

Мы использовали функцию UNNEST, потому что обычно параметры события хранятся как повторяющаяся запись (repeated record), которую вы можете рассматривать как массив, напоминающий JSON структуру. Вы можете разбить этот массив, и поместить каждый отдельный его элемент (параметр события) в новую строку, а затем скопировать исходную строку для каждого из этих отдельных параметров. Более понятно будет если посмотреть следующую анимацию:

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

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

Если вы посмотрите на событие level_complete_quickplay, то найдёте два интересующие нас параметра: параметр value, который сообщает нам окончательный счет игры (то есть сколько ходов потребовалось пользователю для прохождения игры), и параметр board, в котором хранится размер игрового поля.

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

Если мы просто будем использовать функцию UNNEST:

SELECT event_name, event_timestamp, user_pseudo_id, paramFROM `firebase-public-project.analytics_153293282.events_20181003`,UNNEST(event_params) AS paramWHERE event_name = "level_complete_quickplay"AND (param.key = "value" OR param.key = "board")

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

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

SELECT   MAX(if(param.key = "value", param.value.int_value, NULL)) AS score,  MAX(if(param.key = "board", param.value.string_value, NULL)) AS board_typeFROM (  SELECT event_name, event_timestamp, user_pseudo_id, param  FROM `firebase-public-project.analytics_153293282.events_20181003`,  UNNEST(event_params) AS param  WHERE event_name = "level_complete_quickplay"  AND (param.key = "value" OR param.key = "board")) GROUP BY user_pseudo_id, event_timestamp

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

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

SELECT FROM UNNEST в помощь!

К счастью, есть другой вариант решения, конструкция SELECT FROM UNNEST.

Используя конструкцию SELECT FROM UNNEST, вы говорите: Я хочу применить функцию UNNEST к повторяющейся записи в ее собственной маленькой временной таблице. Далее выбрать одну строку из неё и поместить ее в наши результаты, так же как если бы это было любое другое значение .

Объяснение приведённое выше звучит сложновато, давайте рассмотрим пример.

Первое, что я собираюсь сделать, это удалить в исходном варианте решения оператор UNNEST, чтобы просмотреть все события прохождения игры.

SELECT event_name, event_timestamp, user_pseudo_idFROM `firebase-public-project.analytics_153293282.events_20181003`WHERE event_name = "level_complete_quickplay"

Затем я запрашиваю столбец value.int_value из развёрнутого массива event_params, где поле key равно value.

SELECT event_name, event_timestamp, user_pseudo_id,   (SELECT value.int_value FROM UNNEST(event_params)     WHERE key = "value") AS scoreFROM `firebase-public-project.analytics_153293282.events_20181003`WHERE event_name = "level_complete_quickplay"

В итоге происходит что-то вроде того, что показывает приведённая ниже анимация. Сначала мы разбиваем массив event_params в его собственную небольшую временную таблицу, фильтруя одну запись, по условию key = "value", а затем забираем полеvalue.int_value.

После чего вы получаете следующий результат:

При использовании конструкции SELECT FROM UNNEST, следует помнить о трех важных моментах:

Во-первых, вы должны убедиться, что вы поместили вызов SELECT FROM UNNEST в круглые скобки. Если этого не сделать, BigQuery выдаст ошибку, т.к. вы используете два оператора SELECT в рамках одного запроса.

Во-вторых, похоже на то, что описанный приём будет работать, только если вы запрашиваете не более одного значения из каждого вызова SELECT FROM UNNEST.

В-третьих, вы можете использовать SELECT FROM UNNEST несколько раз в одном и том же операторе SELECT! Итак, теперь мы можем взять наш предыдущий запрос и добавить в него второй вызовSELECT FROM UNNEST, чтобы получить параметры board и value рядом, в одной строке.

SELECT event_name, event_timestamp, user_pseudo_id,    (SELECT value.int_value FROM UNNEST(event_params)     WHERE key = "value") AS score,  (SELECT value.string_value FROM UNNEST(event_params)     WHERE key = "board") AS board_sizeFROM `firebase-public-project.analytics_153293282.events_20181003`WHERE event_name = "level_complete_quickplay"

Приведённый выше запрос вернёт ожидаемый нами результат:

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

SELECT AVG(score) AS average, STDDEV(score) as std_dev, board_sizeFROM (    SELECT event_name, event_timestamp, user_pseudo_id,   (SELECT value.int_value FROM UNNEST(event_params)     WHERE key = "value") AS score,  (SELECT value.string_value FROM UNNEST(event_params)     WHERE key = "board") AS board_size  FROM `firebase-public-project.analytics_153293282.events_20181003`  WHERE event_name = "level_complete_quickplay") GROUP BY board_size

Анализируем параметры событий вместе со свойствами пользователей!

SELECT FROM UNNEST также можно использовать для совместного анализа параметров событий со свойствами пользователей.

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

Допустим, мы хотим выяснить, есть ли какая-либо корреляция между тем, сколько шагов пользователю изначально дано, и сколько дополнительных шагов он делает во время события use extra steps. Для этого нам необходимо проанализировать параметр value вместе с пользовательским свойством initial_extra_steps. Опять же, эти параметры объеденены в повторяющиеся записи, как нам их правильно развернуть?

Теоретически это можно сделать, объединив два вызова UNNEST.

SELECT event_name, event_timestamp, user_pseudo_id,   param.value.int_value AS moves_used,  userprop.value.string_value AS initial_extra_stepsFROM `firebase-public-project.analytics_153293282.events_20181003`,UNNEST (event_params) as param,UNNEST (user_properties) as userpropWHERE event_name = "spend_virtual_currency"AND param.key = "value"AND userprop.key = "initial_extra_steps"

Это могло бы сработать, ход выполнения такого запроса будет следующим: сначала мы развернём spend_virtual_currency в отдельные строки, а затем то же самое повторим для каждой из этих строк для каждого записываемого нами свойства пользователя. Это означает, что мы, по сути, умножаем каждую строку в большом наборе данных запроса на (количество параметров * количество свойств пользователя). Очень быстро такой запрос может стать достаточно дорогой операцией!

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

Вновь нам на помощь приходит SELECT FROM UNNEST. Сначала я получу значение нашего параметра value, как в нашем первом примере.

SELECT event_name, event_timestamp, user_pseudo_id,   (SELECT value.int_value FROM UNNEST(event_params)     WHERE key = "value") AS steps_usedFROM `firebase-public-project.analytics_153293282.events_20181003`WHERE event_name = "spend_virtual_currency"

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

SELECT event_name, event_timestamp, user_pseudo_id,   (SELECT value.int_value FROM UNNEST(event_params)     WHERE key = "value") AS steps_used,  CAST(    (SELECT value.string_value FROM UNNEST(user_properties)      WHERE key = "initial_extra_steps")    AS int64) AS initial_stepsFROM `firebase-public-project.analytics_153293282.events_20181003`WHERE event_name = "spend_virtual_currency"

Теперь у меня есть все необходимые данные в одной строке!

Этот код не только более эффективен, но и прост для понимания.

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

SELECT AVG(steps_used) AS average_steps_used, initial_steps FROM (  SELECT event_name, event_timestamp, user_pseudo_id,     (SELECT value.int_value FROM UNNEST(event_params)       WHERE key = "value") AS steps_used,    CAST(      (SELECT value.string_value FROM UNNEST(user_properties)        WHERE key = "initial_extra_steps")      AS int64) AS initial_steps  FROM `firebase-public-project.analytics_153293282.events_20181003`  WHERE event_name = "spend_virtual_currency") WHERE initial_steps IS NOT NULLGROUP BY initial_stepsORDER BY initial_steps

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

SELECT CORR(steps_used, initial_steps) AS correlation FROM (SELECT event_name, event_timestamp, user_pseudo_id,   (SELECT value.int_value FROM UNNEST(event_params)     WHERE key = "value") AS steps_used,  CAST(    (SELECT value.string_value FROM UNNEST(user_properties)      WHERE key = "initial_extra_steps")    AS int64) AS initial_stepsFROM `firebase-public-project.analytics_153293282.events_20181003`WHERE event_name = "spend_virtual_currency") WHERE initial_steps IS NOT NULL

Итак, вы можете использовать UNNEST и SELECT FROM UNNEST для быстрой обработки повторяющихся записей, которые Google Analytics для Firebase любит использовать в своей схеме BigQuery. И, как оказалось, это те же самые повторяющиеся записи, которые отображаются в данных Crashlytics и Cloud Messaging. Поэтому я рекомендую потратить время на то, чтобы привыкнуть к этим методам, т.к. они заметно облегчат работу со сложными структурами данных.

Подробнее..
Категории: Sql , Big data , Data mining , Api , Data engineering , Unnest , Bigquery

Курсы валют и аналитика использование обменных курсов в Хранилище Данных

19.05.2021 16:06:46 | Автор: admin

Привет! На связи Артемий Analytics Engineer из Wheely.

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

Покажу как этот вопрос решается с помощью современных подходов на примере кейса Wheely:

  • Расширение списка базовых валют

  • Регулярное обновление и получения актуальных курсов

  • Обеспечение корректности исторических показателей

  • Максимальное удобство и простота использования в аналитических инструментах

Велком под кат для разбора решения проблемы учета мультивалютных метрик и показателей: Open Exchange Rate, Airflow, Redshift Spectrum, dbt.


Новые требования к сервису валютных курсов

В качестве legacy-источника использовался веб-сервис ЦБ РФ. Однако с изменяющимися требованиями и расширением зон присутствия компании его стало недостаточно. Например, по причине отсутствия котировки AED (дирхам ОАЭ). Для кого-то могут быть актуальны курсы криптовалют BTC, ETH, которые в веб-сервисе ЦБ РФ тоже отсутствуют.

Новые требования можно суммировать следующим образом:

  • Поддержка расширенного набора базовых валют, которые отсутствуют в API ЦБ РФ

  • Получение самых актуальных котировок, включая внутридневные курсы

  • Минимизация трансформаций данных вне Хранилища Данных (лучше если их вообще нет)

Матрица новых требований к работе с курсами валютМатрица новых требований к работе с курсами валют

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

  • Интеграция нового API для уже использующихся курсов

  • Добавление новых базовых валют в выгрузку

  • Получение ретроспективных (исторических) данных по новым валютам за прошлые периоды

  • Архивирование курсов из legacy-источника

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

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

Интеграция с новым поставщиком данных

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

Минимальный необходимый план Developer включает в себя:

  • 10.000 запросов ежемесячно (более чем достаточно)

  • Ежечасные внутридневные обновления курсов

  • Широкий набор базовых валют, включая криптовалюты

Доступные методы API:

Для получения актуальных курсов валют воспользуемся API endpoint /latest.json

Простой запрос-ответ может выглядеть следующим образом:

Установка на расписание в Airflow

Для регулярного получения актуальных курсов валют я воспользуюсь инструментом Airflow. Apache Airflow де-факто стандарт в области оркестрации данных, data engineering и управления пайплайнами.

Смысловая составляющая графа задачи (DAG):

  • Сделать запрос к API

  • Сохранить полученный ответ (например, в виде уникального ключа на S3)

  • Уведомить в Slack в случае ошибки

Конфигурация DAG:

  • Базовые валюты (base currency), от которых отсчитываем курсы

  • Синхронизация расписание запусков с расчетом витрин в Хранилище Данных

  • Токен доступа к сервису

Самый простой DAG состоит из одного таска с вызовом простого shell-скрипта:

TS=`date +"%Y-%m-%d-%H-%M-%S-%Z"` curl -H "Authorization: Token $OXR_TOKEN" \ "https://openexchangerates.org/api/historical/$BUSINESS_DT.json?base=$BASE_CURRENCY&symbols=$SYMBOLS" \ | aws s3 cp - s3://$BUCKET/$BUCKET_PATH/$BUSINESS_DT-$BASE_CURRENCY-$TS.json

Вот как выглядит результат регулярной работы скрипта в S3:

Сегодня в штатном режиме выполняется около 25 обращений к сервису в сутки, статистика выглядит следующим образом:

Выгрузка истории по новым валютам

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

К сожалению, план Developer не включает обращения к API endpoint /time-series.json, и только ради этой разовой задачи не имеет смысла делать upgrade на более дорогостоящую версию.

Воспользуемся методом /historical/*.json и простым опросом API в цикле для формирования исторической выгрузки:

#!/bin/bash d=2011-01-01while [ "$d" != 2021-02-19 ]; do echo $d curl -H "Authorization: Token $TOKEN" "https://openexchangerates.org/api/historical/$d.json?base=AED&symbols=AED,GBP,EUR,RUB,USD" > ./export/$d.json d=$(date -j -v +1d -f "%Y-%m-%d" $d +%Y-%m-%d)done

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

Архивирование исторических курсов валют

Вся история обменных курсов полученная из legacy-источника ЦБ РФ до даты X (перехода на новый сервис-провайдер) подлежит архивированию в неизменном виде.

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

Для этого я выполню выгрузку накопленных значений обменных курсов за весь исторический период в Data Lake. Более детально, я произведу:

  • Трансформацию legacy pivot-таблицы в двумерную

  • Запись в колоночный формат PARQUET в AWS S3

Формирование архива в S3 в формате PARQUET
CREATE EXTERNAL TABLE spectrum.currencies_cbrfSTORED AS PARQUETLOCATION 's3://<BUCKET>/dwh/currencies_cbrf/' ASWITH base AS (   SELECT 'EUR' AS base_currency   UNION ALL   SELECT 'GBP'   UNION ALL   SELECT 'RUB'   UNION ALL   SELECT 'USD')SELECT   "day" AS business_dt   ,b.base_currency   ,CASE b.base_currency       WHEN 'EUR' THEN 1       WHEN 'GBP' THEN gbp_to_eur       WHEN 'RUB' THEN rub_to_eur       WHEN 'USD' THEN usd_to_eur       ELSE NULL     END AS eur   ,CASE b.base_currency       WHEN 'EUR' THEN eur_to_gbp       WHEN 'GBP' THEN 1       WHEN 'RUB' THEN rub_to_gbp       WHEN 'USD' THEN usd_to_gbp       ELSE NULL     END AS gbp   ,CASE b.base_currency       WHEN 'EUR' THEN eur_to_rub       WHEN 'GBP' THEN gbp_to_rub       WHEN 'RUB' THEN 1       WHEN 'USD' THEN usd_to_rub       ELSE NULL     END AS rub   ,CASE b.base_currency       WHEN 'EUR' THEN eur_to_usd       WHEN 'GBP' THEN gbp_to_usd       WHEN 'RUB' THEN rub_to_usd       WHEN 'USD' THEN 1       ELSE NULL     END AS usd     FROM ext.currencies c   CROSS JOIN base b;

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

Доступ к данным из DWH через S3 External Table

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

Оптимальное решение создание внешних таблиц EXTERNAL TABLE, которые обеспечивают SQL-доступ к данным, хранящимся в S3. При этом нам доступно чтение полуструктурированных данных в формате JSON, бинарных данных в форматах AVRO, ORC, PARQUET и другие опции. Продукт имеет название Redshift Spectrum и тесно связан с SQL-движком Amazon Athena, который имеет много общего с Presto.

CREATE EXTERNAL TABLE IF NOT EXISTS spectrum.currencies_oxr (   "timestamp" bigint   , base varchar(3)   , rates struct<aed:float8, eur:float8, gbp:float8, rub:float8, usd:float8>)ROW format serde 'org.openx.data.jsonserde.JsonSerDe'LOCATION 's3://<BUCKET>/dwh/currencies/';

Обратите внимание на обращение ко вложенному документу rates с помощью создания типа данных struct.

Теперь добавим к этой задаче секретную силу dbt. Модуль dbt-external-tables позволяет автоматизировать создание EXTERNAL TABLES и зарегистрировать их в качестве источников данных:

   - name: external     schema: spectrum     tags: ["spectrum"]     loader: S3     description: "External data stored in S3 accessed vith Redshift Spectrum"     tables:       - name: currencies_oxr         description: "Currency Exchange Rates fetched from OXR API https://openexchangerates.org"         freshness:           error_after: {count: 15, period: hour}         loaded_at_field: timestamp 'epoch' + "timestamp" * interval '1 second'         external:           location: "s3://<BUCKET>/dwh/currencies/"           row_format: "serde 'org.openx.data.jsonserde.JsonSerDe'"         columns:           - name: timestamp             data_type: bigint           - name: base             data_type: varchar(3)           - name: rates             data_type: struct<aed:float8, eur:float8, gbp:float8, rub:float8, usd:float8>

Немаловажным элементом является проверка своевременности данных source freshness test на курсы валют. Тем самым мы будем постоянно держать руку на пульсе поступления актуальных данных в Хранилище. Очень важно рассчитывать все финансовые метрики корректно и в срок, а без актуальных значений курсов задачу решить невозможно.

В случае отставания данных более 15 часов без свежих обменных курсов мы тут же получаем уведомление в Slack.

Для прозрачности и простоты пользователей объединим исторические данные (архив) и постоянно поступающие актуальные курсы (новый API) в одну модель currencies:

Объединение исторических и новых данных в единый справочник
{{   config(       materialized='table',       dist='all',       sort=["business_dt", "base_currency"]   )}} with cbrf as (  select      business_dt   , null as business_ts   , base_currency   , aed   , eur   , gbp   , rub   , usd  from {{ source('external', 'currencies_cbrf') }} where business_dt <= '2021-02-18' ), oxr_all as (    select      (timestamp 'epoch' + o."timestamp" * interval '1 second')::date as business_dt   , (timestamp 'epoch' + o."timestamp" * interval '1 second') as business_ts   , o.base as base_currency   , o.rates.aed::decimal(10,4) as aed   , o.rates.eur::decimal(10,4) as eur   , o.rates.gbp::decimal(10,4) as gbp   , o.rates.rub::decimal(10,4) as rub   , o.rates.usd::decimal(10,4) as usd   , row_number() over (partition by base_currency, business_dt order by business_ts desc) as rn    from {{ source('external', 'currencies_oxr') }} as o   where business_dt > '2021-02-18' ), oxr as (  select      business_dt   , business_ts   , base_currency   , aed   , eur   , gbp   , rub   , usd  from {{ ref('stg_currencies_oxr_all') }} where rn = 1 ), united as (  select      business_dt   , business_ts   , base_currency   , aed   , eur   , gbp   , rub   , usd  from cbrf  union all  select      business_dt   , business_ts   , base_currency   , aed   , eur   , gbp   , rub   , usd  from oxr ) select    business_dt , business_ts , base_currency , aed , eur , gbp , rub , usd from united

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

Использование курсов в моделировании данных

В целом, работа с курсами валют для аналитиков и инженеров, которые развивают Хранилище Данных не изменилась и осталась весьма простой. Все детали использования нового API, обращения к внешним полу-структурированным документам JSON в S3, объединению с архивными данными скрыты . В своих трансформациях достаточно сделать простой джоин на таблицу с курсами валют:

   select        -- price_details       , r.currency       , {{ convert_currency('price', 'currency') }}       , {{ convert_currency('discount', 'currency') }}       , {{ convert_currency('insurance', 'currency') }}       , {{ convert_currency('tips', 'currency') }}       , {{ convert_currency('parking', 'currency') }}       , {{ convert_currency('toll_road', 'currency') }}    from {{ ref('requests') }} r       left join {{ ref('stg_currencies') }} currencies on r.completed_dt_utc = currencies.business_dt           and r.currency = currencies.base_currency

Сами метрики конвертируются при помощи простого макроса, который на вход принимает колонку с исходной суммой и колонку с исходным кодом валюты:

-- currency conversion macro{% macro convert_currency(convert_column, currency_code_column) -%}      ( {{ convert_column }} * aed )::decimal(18,4) as {{ convert_column }}_aed   , ( {{ convert_column }} * eur )::decimal(18,4) as {{ convert_column }}_eur   , ( {{ convert_column }} * gbp )::decimal(18,4) as {{ convert_column }}_gbp   , ( {{ convert_column }} * rub )::decimal(18,4) as {{ convert_column }}_rub   , ( {{ convert_column }} * usd )::decimal(18,4) as {{ convert_column }}_usd {%- endmacro %}

Практико-ориентированное развитие

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

В конце мая состоится юбилейный запуск курса Data Engineer в ОТУС, в котором я принимаю участие в роли преподавателя.

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

  • Data Architecture

  • Data Lake

  • Data Warehouse

  • NoSQL / NewSQL

  • MLOps

Детально с программой можно ознакомиться на лендинге курса.

Также я делюсь своими авторскими заметками и планами в телеграм-канале Technology Enthusiast.

Благодарю за внимание.

Подробнее..

Перевод 5 вещей о наблюдаемости данных, которые должен знать каждый дата-инженер

26.05.2021 14:12:47 | Автор: admin

Как быть уверенным в своих рабочих процессах, конвейер за конвейером

В преддверии старта онлайн-курса "Data Engineer" подготовили перевод материала.


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

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

Джесси Андерсон, управляющий директор Big Data Institute и автор книги Команды инженерии данных: создание успешных Big Data команд и продуктов, и Барр Мозес, соучредитель и генеральный директор Monte Carlo, делятся всем, что вам нужно знать, чтобы начать работу на этом новом уровне стека данных.

Инжиниринг данных (Data Engineering) часто называют водопроводом data science - обычно, имея в виду способ, которым инженеры по обработке данных обеспечивают правильное функционирование всех конвейеров и рабочих процессов, а также правильные данные, поступающие в нужных направлениях к нужным заинтересованным сторонам. Но большинство дата-инженеров, с которыми я разговариваю, имеют одно вполне конкретное мнение о водопроводчиках: вы звоните им только тогда, когда что-то идет не так.

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

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

Slack в середине встречи от лида по маркетингу: рентабельность инвестиций в мою кампанию в этом месяце невысока. Я думаю, что что-то не так с данными атрибуции.

Сообщение, которое вы никогда не получите: данные в этом отчете идеальны. Так держать!

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

Один из способов выбраться из это порочного круга ночных писем - наблюдаемость данных (Data Observability).

#1. Что такое наблюдаемость данных и почему это важно

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

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

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

#2. Как DevOps заложил наблюдаемость данных

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

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

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

  • Свежесть (Freshness) показывает, насколько актуальны ваши таблицы данных.

  • Распределение (Distribution) сообщает вам, попадают ли ваши данные в ожидаемый диапазон.

  • Объем (Volume ) предполагает понимание полноты ваших таблиц данных и состояния ваших источников данных.

  • Схема (Schema) позволяет понять, кто и когда вносит изменения в таблицы данных.

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

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

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

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

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

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

#4. Наблюдаемость данных - это больше, чем просто тщательное тестирование и мониторинг

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

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

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

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

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

#5. Когда дело доходит до ваших данных, иметь в основном плохие данные хуже, чем их вообще не иметь

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

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

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


- Узнать подробнее о курсе "Data Engineer"

Подробнее..

Как победить букмекеров с помощью ИИ опыт студентов магистратуры Наука о данных

06.05.2021 14:05:14 | Автор: admin

Привет, Хабр! Сегодня хотим представить вам проект студентов магистратуры Наука о данных НИТУ МИСиС и Zavtra.Online (подразделении SkillFactory по работе с университетами) созданный на внутреннем хакатоне, который прошел в марте. Команда поделится решением выбранной задачи предсказание победителя-бойца турнира UFC. Задача отличалась от прочих тем, что после написания модели из неё можно сделать целый продукт, оформив модель в приложение, готовое к использованию конечными пользователями, например теми, кто захочет обыграть букмекеров.


Гипотеза и её проверка

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

  • физические параметры бойца (его рост, вес, размах рук и ног);

  • возраст бойца (всё-таки со временем физически мы слабеем);

  • разница в опыте соперников (вряд ли какой-то новичок без опыта одолеет Хабиба);

  • характер поведения бойца на ринге (чаще обороняется или чаще нападает);

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

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

Принцип разделения прост: мы случайным образом разбиваем свои данные примерно на 70 %, используемых для обучения модели, и 30 % для тестирования модели. Однако есть нюанс... Для предсказания результата боя UFC важно, чтобы тренировочная выборка была раньше тестовой во временном периоде.

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

Разработка проекта

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

Примерно 90 % времени мы потратили на обработку данных и остальные 10 % на обучение модели. Пайплайн подготовки данных верхнеуровнево выглядел следующим образом: мы очистили датасет от пропусков и выбросов и обогатили его новыми признаками.

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

Сбор данных и статистика

Ментор предоставил нам спарсенные данные по истории Боёв UFC и статистику по бойцам. Данные и Jupyter Notebook с бейзлайном модели можно найти по этой ссылке на Kaggle. В связи с тем что сроки у нас были ограничены, мы решили не заниматься дополнительным парсингом фичей, а уделить большое внимание обработке имеющихся данных и генерации новых признаков.

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

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

Предобработка данных

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

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

Вторым шагом был сбор фичей с кумулятивной статистикой по всем предыдущим боям для каждого из соперников и генерации из получившейся накопленной суммы новых важных признаков, таких как серия побед, сумма побед нокаутами, сумма чистых побед, точность ударов, среднее время боя, KO/TKO и т. д. Это перечень важных показателей по уже проведённым боям, которые обычно публикуются на сайте UFC до начала боя. Затем мы посчитали разницу по физическим характеристикам бойцов, удалили коррелированные между собой величины и законсервировали данные в pkl-файл.

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

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

Изначально мы не учли наличие временного ряда, и наша модель тренировалась на данных из будущего перемешанного с прошлым, то есть мы взяли временной ряд и случайным образом поделили данные на тренировочные и тестовые (80/20). Тренировочные для тренировки модели, тестовые для проверки правильности уже тренированной модели.

Процент точности прогноза победы варьировался от 75 до 82%! Но какая разница, если обучение было неправильным?.. Модель не должна смотреть в будущее, как это было у нас. Решить эту проблему удалось достаточно просто: мы вернули даты для нашего датасета и поделили на тренировочные и тестовые по датам: данные до 2018 года взяли за тренировочные, данные после 2018 года за тестовые, и вуаля, точность упала в среднем на 5 %, однако теперь мы в ней хотя бы уверены.

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

Посмотреть на модель можно на GitHub.

Для того чтобы модель предсказала победителя на новых данных, нужно эти данные обработать таким же образом, как мы это делали в нашем ноутбуке (Jupyter Notebook) DeepOverfitting-DataPreparing, после этого просто подать эти строчки данных для двух бойцов в predict функцию нашей модели и получить предсказание, либо 0, либо 1, 0 победил 2 боец, 1 победил первый боец.

Итоги

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

Поэтому мы наметили следующий план:

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

  2. Проверим нашу текущую модель на предстоящих боях.

  3. Попробуем применить нейронные сети и глубокое обучение.

  4. Разработаем приложение, которое будет в удобном виде показывать, сколько и куда ставить.

  5. Станем кем-то кроме букмекера, кто заработает на ставках. И, конечно, будем писать на Хабре про дальнейшее развитие проекта. Букмекеры, берегитесь, мы идём за вами.

Состав команды работавшей над проектов:

  • Евгения Шикина (г. Видное)

  • Оксана Евсеева (г. Барселона)

  • Максим Щиколодков (г. Москва)

  • Михаил Стриженов (г. Москва)

  • Лев Неганов (г. Москва)

  • Кирилл Плотников (г. Екатеринбург)

Узнать больше про магистратуру можно на сайтеdata.misis.ruи вTelegram канале.

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

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

Другие профессии и курсы
Подробнее..

Чтобы потолка не стало, а крышу не снесло о чем новый подкаст ВТБ

08.06.2021 22:04:34 | Автор: admin

Привет, Хабр! Команда ВТБ запустила серию подкастов о передовых решениях финтеха Деньги любят техно. Журналист, технологический обозреватель Марина Эфендиева будет обсуждать с экспертами банка, рынка, учеными и бизнесменами перспективы и сложности финтеха: внедрения технологий на основе Big Data, машинного обучения, искусственного интеллекта, вопросы кибербезопасности и защиты данных, перспективные технологические специальности, голосовых помощников и многое другое.

В первом выпускезаместитель президента-председателя правления ВТБ Вадим Кулик и директор Физтех-школы прикладной математики и информатики д.ф.-м.н. Андрей Райгородский обсуждают, почему банки вРоссии так любятData science, можно ли стать дата-сайнтистом за три месяцаигде учиться, чтобысоздатьуспешную карьеру. Под катом основные темы этой беседы и ссылка на сам подкаст.

Откуда взялся банковскийData Science

Тривиальный, но важный вопрос: почему именно банковский Data Science сегодня занимает передовые позиции?

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

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

Поэтому ВТБ уделяет такое внимание подходу Data Fusion, который предполагает объединение, обезличивание данных из разных источников и их обработку. По этому поводу недавно прошлабольшая конференция.

Хорошей иллюстрацией применения данного подхода может служить СП ВТБ и РостелекомаПлатформа больших данных, которое уже предоставляет рынку продукты на основе Big Data для увеличения эффективности и развития бизнеса.

Data Science за 3 месяца без SMS и регистрации

Андрей Райгородский ответил на ещё один очень важный вопрос: можно ли стать дата сайентистом за 3 месяца, как сейчас предлагают многие онлайн-платформы. Он также рассказал о том, какова сейчас потребность в этих специалистах.

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

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

МФТИ (Московский физико-технический институт) лидер этого направления в России фокусируется на фундаментальном обучении и готовит кадры для будущего. При этом есть и специальные нишевые программы например,Школа глубокого обучения, которая заработала в онлайн-формате ещё до того, когда это стало ковидным мейнстримом.

Главной особенностью МФТИ можно считать взаимодействие прикладного и фундаментального. В наши дни это связка между коммерческой индустрией, которая формирует запрос, и академической наукой, которая даёт фундаментальные математические решения. Отличный пример такого симбиоза созданная в начале 2021 года лаборатория ВТБ при МФТИ.

Резюме

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

А вот и сам подкаст:

Подробнее..

Перевод Перспективные архитектуры для современных инфраструктур данных

04.05.2021 18:23:50 | Автор: admin

На сегодняшний день базы данных класса Massive Parallel Processing это отраслевой стандарт для хранения Больших Данных и решения разнообразных аналитических задач на их основе.

Сценарии использования mpp-баз разнообразны: они могут быть и рабочей лошадкой корпоративного BI, и инструментом централизации данных из сотен источников в одном DWH, и даже использоваться для горячего взаимодействия с ML-моделями, работающими в продуктивном окружении.

Данный класс технологий необходимый элемент в инструментарии современного Data Engineer.

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

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


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

Многие из самых быстрорастущих инфраструктурных стартапов сегодня создают продукты для управления данными. Эти системы позволяют принимать решения на основе данных (аналитические системы) и управлять продуктами на основе данных, в том числе с помощью машинного обучения (оперативные системы). Они варьируются от конвейеров, по которым передаются данные, до решений для их хранения, SQL-движков, которые анализируют данные, дашбордов для мониторинга, которые упрощают понимание данных от библиотек машинного обучения и data science до автоматизированных конвейеров данных, каталогов данных и т.д.

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

Инфраструктура данных включает

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

Стремительный рост рынка инфраструктуры данных

Одной из основных причин, из-за которых был составлен этот доклад, является стремительный рост инфраструктуры данных за последние несколько лет. По данным Gartner, расходы на инфраструктуру данных достигли в 2019 году рекордного показателя в 66 миллиардов долларов, что составляет 24% и эта цифра растет всех расходов на программное обеспечение для инфраструктуры. По данным Pitchbook, 30 крупнейших стартапов по созданию инфраструктуры данных за последние 5 лет привлекли более 8 миллиардов долларов венчурного капитала на общую сумму 35 миллиардов долларов.

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

Примечание: Любые инвестиции или портфельные компании, упомянутые или описанные в этой презентации, не являются репрезентативными для всего объема инвестиций во все инвестиционные каналы, управляемые a16z, и нет никаких гарантий, что эти инвестиции будут прибыльными или что другие инвестиции, сделанные в будущем, будут иметь аналогичные характеристики или результаты. Список инвестиций, сделанных фондами под управлением a16z, доступен здесь: https://a16z.com/investments/.

Гонка за данными также отражается на рынке труда. Аналитики данных, инженеры по обработке данных и инженеры по машинному обучению возглавили список самых быстрорастущих специальностей Linkedin в 2019 году. По данным NewVantage Partners 60% компаний из списка Fortune 1000 имеют директоров по обработке и анализу данных, по сравнению с 12% в 2012 году, и согласно исследованию роста и прибыльности McKinsey эти компании значительно опережают своих коллег.

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

Унифицированная архитектура инфраструктуры данных

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

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

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

Unified Architecture for Data Infrastructure

Унифицированная архитектура для инфраструктуры данных

Примечание: Исключает транзакционные системы (OLTP), обработку логов и SaaS-приложения для аналитики. Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

Столбцы диаграммы определены следующим образом:

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

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

Аналитика, AI/ML и грядущая конвергенция?

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

Вокруг этих вариантов использования выросли две параллельные экосистемы. Основу аналитической экосистемы составляют хранилища данных (data warehouse). Большинство хранилищ данных хранят данные в структурированном формате и предназначены для быстрого и простого получения выводов на основе обработки основных бизнес-метрик, обычно с помощью SQL (хотя Python становится все более популярным). Озеро данных (data lake) является основой оперативной экосистемы. Сохраняя данные в необработанном виде, он обеспечивает гибкость, масштабируемость и производительность, необходимые для специализированных приложений и более сложных задач обработки данных. Озера данных работают на широком спектре языков, включая Java/Scala, Python, R и SQL.

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

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

Архитектурные сдвиги

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

Новые возможности

Формируется набор новых возможностей обработки данных, которые нуждаются в новых наборах инструментов и базовых систем. Многие из этих трендов создают новые категории технологий (и рынки) с нуля.

Схемы построения современной инфраструктуры данных

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

Здесь мы предоставим обзор трех обобщенных схем. Мы начнем схемы современной бизнес-аналитики, которая фокусируется на облачных хранилищах данных и аналитических вариантах использования. Во второй схеме мы рассматриваем мультимодальную обработку данных, охватывая как аналитические, так и оперативные варианты использования, построенные на основе озера данных. В окончательной схеме мы подробно рассмотрим оперативные системы и новые компоненты AI и ML стека.

Три обобщенных схемы

Схема 1: современная бизнес-аналитика

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Основные варианты использования включают отчеты, дашборды и специальный анализ, в основном с использованием SQL (и некоторого количества Python) для анализа структурированных данных.

К сильным сторонам этой модели относятся низкие первоначальные инвестиции, скорость и простота начала работы, а также широкая доступность кадров. Этот план менее приемлем для команд, у которых есть более сложные потребности в данных, включая обширную data science, машинное обучение или приложения для потоковой передачи / с низкой задержкой.

Схема 2: мультимодальная обработка данных

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Сценарии использования включают в себя как бизнес-аналитику, так и более продвинутые функции, включая оперативный AI/ML, аналитику, чувствительную к потоковой передаче / задержке, крупномасштабные преобразования данных и обработку различных типов данных (включая текст, изображения и видео) с использованием целого набора языков (Java/Scala, Python, SQL).

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

Схема 3: Искусственный интеллект и машинное обучение.

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

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

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

Смотря в будущее

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

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


Перевод подготовлен в рамках онлайн-курса "Data Engineer".

Смотреть вебинар Введение в MPP-базы данных на примере ClickHouse.

Подробнее..

Из таксиста в дата саентисты (перекатиться в 37 лет). Часть 2

12.05.2021 14:11:23 | Автор: admin

Часть 1. "4 месяца борьбы за место DS джуна" - тут: http://personeltest.ru/aways/habr.com/ru/post/536014/

ТАКСИ

- "А у вас тоже свой бизнес, а такси так, для души?" - пошутил пассажир на заднем сидении.

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

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

- "Нет, к счастью, никакого бизнеса у меня нет."

На самом деле, историй из прошлого про бизнес у меня более, чем достаточно. Но это закрытая страница. Мог бы завести разговор, что не так давно решил рискнуть и начал учиться Data Science. Но не стал. За время поездки я вряд ли объясню обычному человеку, что значат эти два слова. Да, честно говоря, тогда и сам еще не до конца понимал.

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

К этому времени уже 4 месяца я сидел за рулем одновременно в Яндекс.такси и Убере, катаясь по 12-14 часов в сутки 5-6 дней в неделю.

- "Такси, как наркотик. Я вот 12 лет назад пришел подработать, и до сих пор здесь", - сказал как-то управляющий моего первого таксопарка. Очень обнадеживающе.

Прогрессивная шкала. Ежедневная погоня за максимальной ставкой. Абсолютно бесполезная техподдержка. Пьяные, надменные, иногда вончие пассажиры. Бесконечные пробки. Говорили, что кто-то умудрялся даже по 50-60т.р. в месяц зарабатывать. Но это - один перерыв на 15 минут за 12 часов, педаль в пол, постоянные нарушения. И дежурная чекушка для снятия стресса, как только сдал ключи сменщику.

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

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

Было, конечно, в этой работе и что-то хорошее. Иногда попадались интересные люди: вертолетчик-ликвидатор пожара на ЧАЭС, директор областной школы для детей с задержкой в развитии с амбициями регионального депутата, участник команды Уральские пельмени, техник-водитель в арктической экспедиции.

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

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

Для начала я пересел на свою машину. На парковой или арендной первые 6-8 часов ты, по сути, отбиваешь аренду, потом уже работаешь на себя. На своей можно хоть что-то заработать даже за 4 часа. Время важнее. Число смен сократил до 3-4х в неделю, чтоб оставался необходимый минимум денег на жизнь. Если ответственно относиться к бюджету и отложить на неопределенный срок все, что не горит, вполне можно прожить и на 20т.р. в месяц. Если есть ради чего (должен отметить, что без поддержки близких было бы куда тяжелее). Остальное время - учеба.

Тут наверно многие скажут:

- "Ага, ага, как же. Что за развод? Какой-то тупой таксист затащил вышмат и залез в технологичную сферу, рассказывай..."

Ок, я не платные курсы, поэтому не буду убеждать, что так может каждый. Вероятно, не каждый. Тут уж надо себя адекватно оценивать. Тем более, если сейчас ты - таксист :)

Вообще, я учился в одной из лучших физ-мат школ Екатеринбурга, потом поступил на мат-мех. На 3м курсе пошёл на вторую вышку. Еще через год вместе с братом открыл компанию, став партнером СКБ Контура. И вобщем-то неплохо развернулся, продавая и обслуживая контуровский софт. Даже немного поруководил партнерской сетью Контура в Москве. Так что IT для меня - это не дальний космос. Да и софт скилы какие-никакие есть.

Но в жизни всякое бывает. Случилась долгая черная полоса (хотя это совсем другая история). А мат-мех был 15 лет назад, опыта в разработке - ноль, и мне уже стукнуло 37. Успех мероприятия был совсем не очевиден.

С марта 2020 началась самоизоляциия, и из такси я ушел в учебу на 100%, самоизолировавшись в саду, где регулярно поливал морковку, редис и даже вырастил полную теплицу огурцов, чтобы было чем отвлечься в перерывах.

- "Куда уходишь то?" - поинтересовался управляющий моего последнего таксопарка.

- "Учиться делать беспилотное такси", - подколол я.

- "Надо бы сломать тебе пальцы, пока не поздно", - задумчиво парировал он.

На том и попрощались.

Когда на меня нападает прокрастинация, я вспоминаю это "прекрасное" время и понимаю, что не хочу больше в него возвращаться НИКОГДА. И сразу хочется учиться и работать.

УЧЕБА

В первой части меня спрашивали, где и как конкретно я учился. Отвечаю.

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

1. STEPIK

Программирование на Python: https://stepik.org/course/67

Введение в Linux: https://stepik.org/course/73

Нейронные сети: https://stepik.org/course/401

Нейронные сети и компьютерное зрение: https://stepik.org/course/50352

Практикум по математике и Python: https://stepik.org/course/3356

Линейная алгебра: https://stepik.org/course/2461

Python: основы и применение: https://stepik.org/course/512

Теория вероятностей: https://stepik.org/course/3089

Что-то пройдено полностью, что-то в достаточном для понимания объеме.

2. COURSERA

Легендарная специализация "Машинное обучение и анализ данных": https://www.coursera.org/specializations/machine-learning-data-analysis

Первые 4 курса - must have любому, кто хочет вкатиться в тему. 5й и 6й курс - тут уже по желанию. На мой взгляд, за практикой лучше сразу идти на kaggle.com и/или на собесы, решать тестовые задания.

3. ODS

Открытый курс машинного обучения: http://personeltest.ru/aways/habr.com/ru/company/ods/blog/322626/

Это как конспект с подробными лекциями. Как только нужно копнуть поглубже или освежить какую-то тему - иду сюда.

4. Ну и какой-нибудь из известных онлайн-транажеров по SQL.

Я тут занимался: https://learndb.ru

5. Вся моя практика - это 1я часть истории, с сентября 2020 до января 2021: http://personeltest.ru/aways/habr.com/ru/post/536014/

JUNIOR DATA SCIENTIST

Три недели назад я узнал, что успешно прошел испытательный срок в питерской компании ADRIVER (группа компаний Internest). Собственно, этого момента и ждал, чтобы написать продолжение. А то было бы забавно... "Всем привет. Я стал дата саентистом, но не вытащил"...

Думаю, AdRiver это лучший вариант куда я мог попасть, с учетом моего прошлого опыта и пути развития.

За три месяца работы я уже успел поработать над несколькими задачами.

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

Задача - оценить вероятность клика в каждом конкретном случае и, исходя из прогноза, предложить свою максимальную ставку. Короче говоря, то же, что я делал руками, настраивая тергетированную рекламу ВКонтакте, только теперь Data Science, Big Data и вот это вот всё.

Компания существует много лет, команда отличная (в этом я уже убедился, даже пожаловаться не на что), клиенты - в плюсе. Даже странно, что до работы здесь я никогда про нее не слышал, хотя наши технологии используют серьезные бренды и рекламные агентства, вроде Ламоды, МВидео, Digital BBDO и т.д.

Но есть одно маленькое "но".

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

Собственно, я и делаю. И тут уже такой простор для творчества, что можно только позавидовать самому себе. Штатно в задачах "click prediction" используется градиентный бустинг.

Пришлось погрузиться в тонкости XGBOOST, CatBoost, форматы данных libsvm, написание скриптов для сбора статистик из файлов на 30-80млн. строк и фильтрации 400тыс. признаков, формирования всяких выборок и т.д. Погонять модели, поискать гиперпараметры для обновленных моделей. (А тут, знаете, модель в полном фарше может и сутки учиться). Короче, реальный Data Science - это вам не Титаник на Kaggle).

Сейчас начал изучать Hadoop и Java, чтобы мог сам вытаскивать данные для моделей. Дальше - больше. У нас есть баннеры, площадки, посадочные страницы. Это все кладезь информации, которая должна помочь улучшить модели в условиях надвигающегося дефицита. Здравствуйте, нейронки. Bert для выдергивания фичей из текста. CV-нейросетки. Данных - тьма! Скучно не будет. Что-то уже начал проверять.

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

Нужно было разобрать действующий в компании алгоритм визуальных рекомендаций(читай ResNet на Keras, который я в глаза не видел). Разобраться и оценить перспективы модели CLIP, выложенной за неделю до начала моей работы в компании. Если есть смысл - поменять ResNet на CLIP.

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

На выходе получилась модель, выдирающая признаки с картинок и их описаний в виде определенного вида векторов. А по этим векторам можно уже и расстояние оценить. Русские части описаний картинок предварительно переводятся на английский другой нейросеткой(CLIP-то на английский заточен). И если кому-то на Ламоде приглянулось модное платье, модель готова предложить 10 похожих по стилю, бренду, цвету и фасону. Визуально выглядит очень круто, как само по себе, так и по сравнению с ResNet50.

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

Подводя итог, все хорошо! Даже не верится. Но потом вспоминаешь, сколько сил и нервов ушло. И тогда вполне верится.

ЧБД (ЧТО БЛО ДАЛЬШЕ?)

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

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

Было еще несколько предложений "поднять data science" в каких-то непонятных проектах, где я так и не понял, что от меня хотят.

И, как последний штрих, собеседование на Аналитика в Сбер. Скажу прямо, такого унижения я не испытывал со времен первого настоящего мат-меховского экзамена.

Гоняли по всем возможным темам и разделам, заставляли "зашэрить" экран и писать код в блокноте, засыпали перекрестными вопросами. Больше половины вопросов жестко слил. Думал - без шансов. Но нет:

- "Все, конечно, на базовом уровне...", - и дали тестовое задание на следующий этап.

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

Подробнее..

Перевод Как Apache Spark 3.0 увеличивает производительность ваших SQL рабочих нагрузок

01.06.2021 12:20:22 | Автор: admin

Практически в каждом секторе, работающем со сложными данными, Spark "де-факто" быстро стал средой распределенных вычислений для команд на всех этапах жизненного цикла данных и аналитики. Одна из наиболее ожидаемых функций Spark 3.0 - это новая платформа Adaptive Query Execution (AQE), устраняющая проблемы, которые возникают при многих рабочих нагрузках Spark SQL. Они были задокументированы в начале 2018 года командой специалистов Intel и Baidu. Для более глубокого изучения фреймворка можно пройти наш обновленный курс по тюнингу производительности Apache Spark (Apache Spark Performance Tuning).

Наш опыт работы с Workload XM, безусловно, подтверждает реальность и серьезность этих проблем.

AQE был впервые представлен в Spark 2.4, но в Spark 3.0 и 3.1 он стал намного более развитым. Для начала, давайте посмотрим, какие проблемы решает AQE.

Недостаток первоначальной архитектуры Catalyst

На диаграмме ниже представлен вид распределенной обработки, которая происходит, когда вы выполняете простой group-by-count запрос с использованием DataFrames.

Spark определяет подходящее количество партиций для первого этапа, но для второго этапа использует по умолчанию "магическое число" - 200.

И это плохо по трем причинам:

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

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

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

Что можно сделать? Вручную установить значение этого свойства перед выполнением запроса с помощью такого оператора:

spark.conf.set(spark.sql.shuffle.partitions,2)

Но это также создает некоторые проблемы:

  • Задавать данный параметр перед каждым запросом утомительно.

  • Эти значения станут устаревшими по мере эволюции ваших данных.

  • Этот параметр будет применяться ко всем шаффлингах в вашем запросе.

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

Принцип работы Adaptive Query Execution

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

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

Поэтому AQE можно определить как слой поверх Spark Catalyst, который будет изменять план Spark "на лету".

Есть недостатки? Некоторые есть, но они второстепенные:

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

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

Адаптивное количество перемешиваемых партиций

Эта функция AQE доступна, начиная с версии Spark 2.4.

Чтобы включить ее, вам нужно установить для spark.sql.adaptive.enabled значение true, значение по умолчанию - false. Когда AQE включено, количество партиций в случайном порядке регулируется автоматически и больше не равно 200 по умолчанию или заданному вручную значению.

Вот как выглядит выполнение первого запроса TPC-DS до и после включения AQE:

Динамическая конвертация Sort Merge Joins в Broadcast Joins

AQE преобразует соединения sort-merge в broadcast хэш-соединения, если статистика времени выполнения любой из сторон соединения меньше порога broadcast хэш-соединения.

Вот как выглядят последние этапы выполнения второго запроса TPC-DS до и после включения AQE:

Динамическое объединение shuffle партиций

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

Когда оба:

spark.sql.adaptive.enabled и

spark.sql.adaptive.coalescePartitions.enabled

установлены на true, Spark объединит смежные перемешанные разделы в соответствии с целевым размером, указанным в spark.sql.adaptive.advisoryPartitionSizeInBytes. Это делается, чтобы избежать слишком большого количества мелких задач.

Динамическая оптимизация обьединений с перекосом

Skew (перекос) - это камень преткновения распределенной обработки. Это может задержать обработку буквально на несколько часов:

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

Оптимизация skew join, таким образом, разобъет раздел A0 на подразделы, используя значение, указанное park.sql.adaptive.advisoryPartitionSizeInBytes, и присоединит каждый из них к соответствующему разделу B0 таблицы B.

Следовательно, вам необходимо предоставить AQE свое определение перекоса.

Это включает в себя два параметра:

1. spark.sql.adaptive.skewJoin.skewedPartitionFactor является относительным: партиция считается с пересом, если ее размер больше, чем этот коэффициент, умноженный на средний размер партиции, а также, если он больше, чем

2. spark.sql.adaptive.skewedPartitionThresholdInBytes, который является абсолютным: это порог, ниже которого перекос будет игнорироваться.

Динамическое сокращение разделов

Идея динамического сокращения разделов (dynamic partition pruning, DPP) - один из наиболее эффективных методов оптимизации: считываются только те данные, которые вам нужны. Если в вашем запросе есть DPP, то AQE не запускается. DPP было перенесено в Spark 2.4 для CDP.

Эта оптимизация реализована как на логическом, так и на физическом уровне.

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

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

DPP в действительности может работать с другими типами обьединений (например, SortMergeJoin), если вы отключите spark.sql.optimizer.dynamicPartitionPruning.reuseBroadcastOnly.

В этом случае Spark оценит, действительно ли фильтр DPP улучшает производительность запроса.

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

Не все запросы получают такой впечатляющий прирост производительности, но 72 из 99 запросов TPC-DS положительно влияют на DPP.

Заключение

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

Анализ статического набора данных был пересмотрен из-за потоковой передачи: команда Spark сначала создала довольно неуклюжий дизайн на основе RDD, прежде чем придумать лучшее решение с использованием DataFrames.

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

Благодаря фреймворку AQE, DPP, усиленной поддержке графических процессоров и Kubernetes перспективы увеличения производительности теперь весьма многообещающие, поэтому мы и наблюдаем повсеместный переход на Spark 3.1

Подробнее..

Что нам стоит загрузить JSON в Data Platform

16.06.2021 16:13:28 | Автор: admin

Всем привет!

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

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

Схема из прошлой нашей статьи.Схема из прошлой нашей статьи.

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

Общая схема доставки данных из источников в ODS-слой Greenplum посредством разработанного нами frameworkа приведена ниже:

Общая схема доставки данных в ODS-слой Greenplum Общая схема доставки данных в ODS-слой Greenplum
  1. Данные из систем-источников пишутся в Kafka в AVRO-формате, обрабатываются в режиме реального времени Apache NiFi, который сохраняет их в формате parquet на S3.

  2. Затем эти файлы с сырыми данными с помощью Sparkа обрабатываются в два этапа:

    1. Compaction на данном этапе выполняется объединение для снижения количества выходных файлов с целью оптимизации записи и последующего чтения (то есть несколько более мелких файлов объединяются в несколько файлов побольше), а также производится дедубликация данных: простой distinct() и затем coalesce(). Результат сохраняется на S3. Эти файлы используются затем для parsing'а , а также являются своеобразным архивом сырых необработанных данных в формате как есть;

    2. Parsing на этой фазе производится разбор входных данных и сохранение их в плоские структуры согласно маппингу, описанному в метаданных. В общем случае из одного входного файла можно получить на выходе несколько плоских структур, которые в виде сжатых (как правило gzip) CSV-файлов сохраняются на S3.

  3. Заключительный этап загрузка данных CSV-файлов в ODS-слой хранилища: создается временная external table над данными в S3 через PXF S3 connector, после чего данные уже простым pgsql переливаются в таблицы ODS-слоя Greenplum

  4. Все это оркестрируется с помощью Airflow.

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

  • создать в ODS-слое Хранилища таблицы-приемники данных;

  • в репозитории метаданных в Git согласно принятым стандартам прописать в виде отдельных YAML-файлов:

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

    • маппинг данных объектов источника на таблицы слоя ODS (при этом поддерживаются как плоские, так и вложенные структуры и массивы, а также есть возможность данные из одного объекта раскладывать в ODS-слое по нескольким таблицам). То есть описать, как необходимо сложную вложенную структуру разложить в плоские таблицы;

До недавнего времени такой подход удовлетворял текущие наши потребности, но количество и разнообразие источников данных растет. У нас стали появляться источники, которые не являются реляционными базами данных, а генерируют данные в виде потока JSON-объектов. Кроме того на горизонте уже маячила интеграция источника, который под собой имел MongoDB и поэтому будет использовать MongoDB Kafka source connector для записи данных в Kafka. Поэтому остро встала необходимость доработки нашего frameworkа для поддержки такого сценария. Хотелось, чтобы данные источника сразу попадали на S3 в формате JSON - то есть в формате "как есть", без лишнего шага конвертации в parquet посредством Apache NiFi.

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

df = spark.read.format(in_format) \               .options(**in_options) \               .load(path) \               .distinct()    new_df = df.coalesce(div)new_df.write.mode("overwrite") \             .format(out_format) \            .options(**out_options) \            .save(path)

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

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

рассматривать файлы с JSON-объектами как DataFrame с одной колонкой, содержащей весь JSON-объект.

Попробуем сделать это. Допустим, мы имеем следующий файл данных:

file1:

{productId: 1, productName: ProductName 1, tags: [tag 1, tag 2], dimensions: {length: 10, width: 12, height: 12.5}}{productId: 2, price: 10.01, tags: [tag 1, tag 2], dimensions: {length: 10, width: 12, height: 12.5}}

Обратите внимание на формат этого файла. Это файл с JSON-объектами, где 1 строка = 1 объект. Оставаясь, по сути, JSON-ом, он при этом не пройдет синтаксическую JSON-валидацию. Именно в таком виде мы сохраняем JSON-данные на S3 (есть специальная "галочка в процессоре Apache NiFi).

Прочитаем файл предлагаемым способом:

# Читаем данныеdf = spark.read \          .format("csv") \          .option("sep", "\a") \          .load("file1.json")# Схема получившегося DataFramedf.printSchema()root |-- _c0: string (nullable = true)# Сами данныеdf.show()+--------------------+|                 _c0|+--------------------+|{"productId": 1, ...||{"productId": 2, ...|+--------------------+

То есть мы тут читаем JSON как обычный CSV, указывая разделитель, который никогда заведомо не встретится в наших данных. Например, Bell character. В итоге мы получим DataFrame из одного поля, к которому можно будет также применить dicstinct() и затем coalesce(), то есть менять существующий код не потребуется. Нам остается только определить опции в зависимости от формата:

# Для parquetin_format = "parquet"in_options = {}# Для JSONin_format = "csv"in_options = {"sep": "\a"}

Ну и при сохранении этого же DataFrame обратно на S3 в зависимости от формата данных опять применяем разные опции:

df.write.mode("overwrite") \           .format(out_format) \.options(**out_options) \  .save(path)  # для JSON     out_format = "text" out_options = {"compression": "gzip"}  # для parquet   out_format = input_format out_options = {"compression": "snappy"}

Следующей точкой доработки был шаг Parsing. В принципе, ничего сложного, если бы задача при этом упиралась в одну маленькую деталь: JSON -файл, в отличии от parquet, не содержит в себе схему данных. Для разовой загрузки это не является проблемой, так как при чтении JSON-файла Spark умеет сам определять схему, и даже в случае, если файл содержит несколько JSON-объектов с немного отличающимся набором полей, корректно выполнит mergeSchema. Но для регулярного процесса мы не могли уповать на это. Банально может случиться так, что во всех записях какого-то файла с данными может не оказаться некоего поля field_1, так как, например, в источнике оно заполняется не во всех случаях. Тогда в получившемся Spark DataFrame вообще не окажется этого поля, и наш Parsing, построенный на метаданных, просто-напросто упадет с ошибкой из-за того, что не найдет прописанное в маппинге поле.

Проиллюстрирую. Допустим,у нас есть два файла из одного источника со следующим наполнением:

file1 (тот же что и в примере выше):

{productId: 1, productName: ProductName 1, tags: [tag 1, tag 2], dimensions: {length: 10, width: 12, height: 12.5}}{productId: 2, price: 10.01, tags: [tag 1, tag 2], dimensions: {length: 10, width: 12, height: 12.5}}

file2:

{productId: 3, productName: ProductName 3, dimensions: {length: 10, width: 12, height: 12.5, package: [10, 20.5, 30]}}

Теперь прочитаем Sparkом их и посмотрим данные и схемы получившихся DataFrame:

df = spark.read \          .format("json") \          .option("multiline", "false") \          .load(path)df.printSchema()df.show()

Первый файл (схема и данные):

root |-- dimensions: struct (nullable = true) |    |-- height: double (nullable = true) |    |-- length: long (nullable = true) |    |-- width: long (nullable = true) |-- price: double (nullable = true) |-- productId: long (nullable = true) |-- productName: string (nullable = true) |-- tags: array (nullable = true) |    |-- element: string (containsNull = true)+--------------+-----+---------+-------------+--------------+|    dimensions|price|productId|  productName|          tags|+--------------+-----+---------+-------------+--------------+|[12.5, 10, 12]| null|        1|ProductName 1|[tag 1, tag 2]||[12.5, 10, 12]|10.01|        2|         null|[tag 1, tag 2]|+--------------+-----+---------+-------------+--------------+

Второй файл (схема и данные):

root |-- dimensions: struct (nullable = true) |    |-- height: double (nullable = true) |    |-- length: long (nullable = true) |    |-- package: array (nullable = true) |    |    |-- element: double (containsNull = true) |    |-- width: long (nullable = true) |-- productId: long (nullable = true) |-- productName: string (nullable = true)+--------------------+---------+-------------+|          dimensions|productId|  productName|+--------------------+---------+-------------+|[12.5, 10, [10.0,...|        3|ProductName 3|+--------------------+---------+-------------+

Как видно, Spark корректно выстроил схему отдельно для каждого файла. Если в какой-либо записи не было обнаружено поля, имеющегося в другой, то в DataFrame мы видим корректное проставление null (поля price и productName для первого файла).

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

root |-- price: double (nullable = true) |-- productId: long (nullable = true) |-- productName: string (nullable = true)

а во входных данных у нас присутствуют только файлы а-ля file2, где поля price нет ни у одной записи, то Spark упадет с ошибкой, так как не найдет поля price для формирования выходного DataFrame. С parquet-файлами такой проблемы как правило не возникает, так как сам parquet-файл генерируется из AVRO, который уже содержит полную схему данных и, соответственно, эта полная схема есть и в parquet-файле.

Еще надо отметить, что, естественно, мы хотели по максимум переиспользовать уже существующий и зарекомендовавший себя код нашего frameworkа, а не городить какую-то полностью отдельную ветку для обработки JSONов то есть все изменения хотелось сделать на этапе чтения JSON-файлов с S3.

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

df = spark.read \          .format("json") \          .option("multiline","false") \          .schema(df_schema) \          .load(path)

Первая мысль была использовать для хранения схемы имеющийся сервис метаданных - то есть описать схему в YAML-формате и сохранить в имеющемся репозитории. Но с учетом того, что все данные источников у нас проходят через Kafka, решили, что логично для хранения схем использовать имеющийся Kafka Schema Registry, а схему хранить в стандартном для JSON формате (другой формат, кстати говоря, Kafka Schema Registry не позволил бы хранить).

В общем, вырисовывалась следующая реализация:

  • Читаем из Kafka Schema Registry схему

  • Импортируем ее в pyspark.sql.types.StructType что-то типа такого:

# 1. получаем через Kafka Schema Registry REST API схему данных # 2. записываем ее в переменную schema и далее:df_schema = StructType.fromJson(schema)
  • Ну и с помощью полученной схемы читаем JSON-файлы

Звучит хорошо, если бы Давайте посмотрим на формат JSON-схемы, понятной Sparkу. Пусть имеем простой JSON из file2 выше. Посмотреть его схему в формате JSON можно, выполнив:

df.schema.json()  
Получившаяся схема
{    "fields":    [        {            "metadata": {},            "name": "dimensions",            "nullable": true,            "type":            {                "fields":                [                    {"metadata":{},"name":"height","nullable":true,"type":"double"},                    {"metadata":{},"name":"length","nullable":true,"type":"long"},                    {"metadata":{},"name":"width","nullable":true,"type":"long"}                ],                "type": "struct"            }        },        {            "metadata": {},            "name": "price",            "nullable": true,            "type": "double"        },        {            "metadata": {},            "name": "productId",            "nullable": true,            "type": "long"        },        {            "metadata": {},            "name": "productName",            "nullable": true,            "type": "string"        },        {            "metadata": {},            "name": "tags",            "nullable": true,            "type":            {                "containsNull": true,                "elementType": "string",                "type": "array"            }        }    ],    "type": "struct"}

Как видно, это совсем не стандартный формат JSON-схемы.

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

как сохранить схему уже прочитанного DataFrame в JSON, затем использовать повторно

либо на репозиторий https://github.com/zalando-incubator/spark-json-schema, который нам бы подошел, если мы использовали Scala, а не pySpark

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

К счастью, у нас уже был один источник, генерирующий данные в формате JSON. Как временное решение схема его интеграции в DataPlatform была незамысловата: NiFi читал данные из Kafka, преобразовывал их в parquet, использую прибитую гвоздями в NiFi схему в формате AVRO-schema, и складывал на S3. Схема данных была действительно непростой и с кучей вложенных структур и нескольких десятков полей - неплохой тест-кейс в общем-то:

Посмотреть длинную портянку, если кому интересно :)
root |-- taskId: string (nullable = true) |-- extOrderId: string (nullable = true) |-- taskStatus: string (nullable = true) |-- taskControlStatus: string (nullable = true) |-- documentVersion: long (nullable = true) |-- buId: long (nullable = true) |-- storeId: long (nullable = true) |-- priority: string (nullable = true) |-- created: struct (nullable = true) |    |-- createdBy: string (nullable = true) |    |-- created: string (nullable = true) |-- lastUpdateInformation: struct (nullable = true) |    |-- updatedBy: string (nullable = true) |    |-- updated: string (nullable = true) |-- customerId: string (nullable = true) |-- employeeId: string (nullable = true) |-- pointOfGiveAway: struct (nullable = true) |    |-- selected: string (nullable = true) |    |-- available: array (nullable = true) |    |    |-- element: string (containsNull = true) |-- dateOfGiveAway: string (nullable = true) |-- dateOfGiveAwayEnd: string (nullable = true) |-- pickingDeadline: string (nullable = true) |-- storageLocation: string (nullable = true) |-- currentStorageLocations: array (nullable = true) |    |-- element: string (containsNull = true) |-- customerType: string (nullable = true) |-- comment: string (nullable = true) |-- totalAmount: double (nullable = true) |-- currency: string (nullable = true) |-- stockDecrease: boolean (nullable = true) |-- offline: boolean (nullable = true) |-- trackId: string (nullable = true) |-- transportationType: string (nullable = true) |-- stockRebook: boolean (nullable = true) |-- notificationStatus: string (nullable = true) |-- lines: array (nullable = true) |    |-- element: struct (containsNull = true) |    |    |-- lineId: string (nullable = true) |    |    |-- extOrderLineId: string (nullable = true) |    |    |-- productId: string (nullable = true) |    |    |-- lineStatus: string (nullable = true) |    |    |-- lineControlStatus: string (nullable = true) |    |    |-- orderedQuantity: double (nullable = true) |    |    |-- confirmedQuantity: double (nullable = true) |    |    |-- assignedQuantity: double (nullable = true) |    |    |-- pickedQuantity: double (nullable = true) |    |    |-- controlledQuantity: double (nullable = true) |    |    |-- allowedForGiveAwayQuantity: double (nullable = true) |    |    |-- givenAwayQuantity: double (nullable = true) |    |    |-- returnedQuantity: double (nullable = true) |    |    |-- sellingScheme: string (nullable = true) |    |    |-- stockSource: string (nullable = true) |    |    |-- productPrice: double (nullable = true) |    |    |-- lineAmount: double (nullable = true) |    |    |-- currency: string (nullable = true) |    |    |-- markingFlag: string (nullable = true) |    |    |-- operations: array (nullable = true) |    |    |    |-- element: struct (containsNull = true) |    |    |    |    |-- operationId: string (nullable = true) |    |    |    |    |-- type: string (nullable = true) |    |    |    |    |-- reason: string (nullable = true) |    |    |    |    |-- quantity: double (nullable = true) |    |    |    |    |-- dmCodes: array (nullable = true) |    |    |    |    |    |-- element: string (containsNull = true) |    |    |    |    |-- timeStamp: string (nullable = true) |    |    |    |    |-- updatedBy: string (nullable = true) |    |    |-- source: array (nullable = true) |    |    |    |-- element: struct (containsNull = true) |    |    |    |    |-- type: string (nullable = true) |    |    |    |    |-- items: array (nullable = true) |    |    |    |    |    |-- element: struct (containsNull = true) |    |    |    |    |    |    |-- assignedQuantity: double (nullable = true) |-- linkedObjects: array (nullable = true) |    |-- element: struct (containsNull = true) |    |    |-- objectType: string (nullable = true) |    |    |-- objectId: string (nullable = true) |    |    |-- objectStatus: string (nullable = true) |    |    |-- objectLines: array (nullable = true) |    |    |    |-- element: struct (containsNull = true) |    |    |    |    |-- objectLineId: string (nullable = true) |    |    |    |    |-- taskLineId: string (nullable = true)

Естественно, я не захотел перебивать руками захардкоженную схему, а воспользовался одним из многочисленных онлайн-конвертеров, позволяющих из Avro-схемы сделать JSON-схему. И тут меня ждал неприятный сюрприз: все перепробованные мною конвертеры на выходе использовали гораздо больше синтаксических конструкций, чем понимала первая версия конвертера. Дополнительно пришло осознание, что также как и я, наши пользователи (а для нас пользователями в данном контексте являются владельцы источников данных) с большой вероятностью могут использовать подобные конвертеры для того, чтобы получить JSON-схему, которую надо зарегистрировать в Kafka Schema Registry, из того, что у них есть.

В результате наш SparkJsonSchemaConverter был доработан появилась поддержка более сложных конструкций, таких как definitions, refs (только внутренние) и oneOf. Сам же парсер был оформлен уже в отдельный класс, который сразу собирал на основании JSON-схемы объект pyspark.sql.types.StructType

У нас почти сразу же родилась мысль, что хорошо бы было поделиться им с сообществом, так как мы в Леруа Мерлен сами активно используем продукты Open Source, созданные и поддерживаемые энтузиастами со всего мира, и хотим не просто их использовать, но и контрибьютить обратно, участвуя в разработке Open Source продуктов и поддерживая сообщество. В настоящий момент мы решаем внутренние орг.вопросы по схеме выкладывания данного конвертера в Open Source и, уверен, что в ближайшее время поделимся с сообществом этой наработкой!

В итоге благодаря написанному SparkJsonSchemaConverterу доработка шага Parsing свелась только к небольшому тюнингу чтения данных с S3: в зависимости от формата входных данных источника (получаем из сервиса метаданных) читаем файлы с S3 немного по-разному:

# Для JSONdf = spark.read.format(in_format)\            .option("multiline", "false")\            .schema(json_schema) \            .load(path)# Для parquet:df = spark.read.format(in_format)\            .load(path)

А дальше отрабатывает уже существующий код, раскрывающий все вложенные структуры согласно маппингу и сохраняющий данные DataFrameа в несколько плоских CSV-файлов.

В итоге мы смогли при относительном минимуме внесенных изменений в код текущего frameworkа добавить в него поддержку интеграции в нашу Data Platform JSON-источников данных. И результат нашей работы уже заметен:

  • Всего через месяц после внедрения доработки у нас на ПРОДе проинтегрировано 4 новых JSON-источника!

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

Подробнее..

Clarion. Процесс миграции Clarion приложения на Microsoft SQL 2019

30.05.2021 18:13:46 | Автор: admin

Продолжаю повествовать о жизни с Clarion. В этом посте я опишу свой путь решения одной из частых задач, стоящих перед Clarion разработчиками, это миграция Clarion программы на СУБД Miscrosoft SQL.

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

Проблематика

Главная на мой взгляд проблема и сложность это работа программы только с нативной СУБД Clarion, доступ к данным при таком подходе очень неудачный, требуется большой объем кода для написания даже простейших задач, которые решаются отправкой на сервер простейшего Update или Insert в Clarion это десятки строчек кода по открытию файла, получению доступа к инфе и его последующего закрытия. Ниже пример:

       Access:Agent.Open !Открываем файл       Access:Agent.UseFile!Открываем файл       clear(AGN:Record)!Делаем очистку записи на всякий случай       AGN:ID_AGENT = some_id !Присваиваем ключу значение       set(AGN:BY_ID,AGN:BY_ID)!Устанавливаем "каретку" на первое значение ключа       next(agent)!Встаем на первую запись удовлетворяющую ключу       IF errorcode() or AGN:ID_AGENT <> some_id!Проверяем не вышла ли каретка за область ключа            RETVAL = 'Контрагент не найден'!Выкидываем ошибку          ELSE            RETVAL = AGN:N_AGENT!Возвращаем имя агента       .       Access:Agent.Close  !Закрываем файл

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

select agent.name where id = some_id

Задача

Первые задачи передо мной встали очень грустной перспективой, одна из них звучала как "написать модуль выгрузки данные в 1С Бухгалтерию", я сразу представил алгоритм длиной в пару тысяч строк, который можно заменить на один запрос SQL, и вопрос перехода на SQL уже был решен. Речь конечно не о полном переходе, а только о частичной замене части таблиц на версию SQL и поэтапной миграции системы в SQL.

Характеристики системы

Обе системы примерно схожие по объему кодовой базы и по количеству пользователей

Общее количество пользователей: около 80

Общее количество таблиц: около 250

Сфера деятельности: Торговля + Сфера обслуживания (Салоны красоты)

Подразделения:

3 Салона красоты

5 Подразделений торговых предприятий - мелкооптовая торговля

Используемые инструменты

  • Самодельная программа миграции

  • DCT2SQL

  • Cldump

  • BULK insert

  • UltimateSQL & Ultimate Debug

Самодельная программа миграции

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

DCT2SQL

Данный компонент позволяет генерировать скрипты для экспорта структуры БД из Dictionary в SQL, поддерживает экспорт всех возможных типов данных, также экспортирует индексы и foreign keys. Очень удобно работает, все импортируется в пару кликов. Данные скрипты я храню в таблице миграций.

Можно скачать на Github - https://github.com/RobertArtigas/DCT2SQL

Также есть обучающие ролики на youtube по работе и правильной выгрузке. Вообще там очень много инфы в этих роликах по миграции на SQL.

https://www.youtube.com/watch?v=MjMgQYMc_xY

https://www.youtube.com/watch?v=bAolfvrz2oE&t=7067s

CLDUMP

Данная программа конвертирует данные из *.dat файла в csv таблицы готовые для загрузки через скрипт BULK. Достоинство этой программы - скорость. Она может сконвертировать таблицу накладных за 10 лет за 15-20 секунд. Главная проблема данной утилиты в том, что она доступна только в репозиториях Linux, в частности debian. Пришлось на основе этой команды создать микро-сервис, который на входе принимает post запрос, а на выходе выдает ссылку для скачивания данного файла в виде csv таблицы.

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

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

apt-get install cldump

BULK insert

За доли секунд втягивает таблицу из csv в SQL. В данном случает, т.к. связки данных уже настроены на существующие ID, но при этом надо чтобы работал автоинкремент, поэтому его временно надо отключить, также потратил довольно много времени чтобы подобрать подходящие разделители:

BULK INSERT dbo.%table_name%FROM table_name.csv WITH ( FORMAT = 'CSV', FIELDQUOTE = '', FIRSTROW = 1, FIELDTERMINATOR = '0x3b', ROWTERMINATOR = '0x0a', CODEPAGE='65001',TABLOCK, KeepIdentity)

UltimateSQL & Ultimate Debug

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

SQL_Result = sql.query('select id, path_to_result from dbo.export_tasks as et where (status_complete = 0 or status_complete = 2) and export_table_id = '& exp:id,qexport_tasks)

Выполнять запросы без возвращаемых значений:

sql.Query('Update export_tasks set status_complete = 2 where id = ' & qexport_tasks.id)

Есть отличное описание как использовать на youtube:

https://www.youtube.com/watch?v=RVit-5RPncs&t=2259s

Также при установке внутри шаблонов есть "пасхалка" от автора, как решить квест описывается по ссылке:

https://clarionhub.com/t/need-some-help-with-ultimatesql-error-when-trying-to-include-it-in-my-project/4182

Подробнее..
Категории: Data engineering , Субд , Mssql , Database , Clarion 11

Категории

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

  • Имя: Макс
    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