Когда бизнес приуныл
Случается, что мои знакомые и друзья внезапно возбуждаются на тему ИИ и начинают тревожно звонить с вопросами: ну что там с ИИ? Уже случилась революция? Пора всех увольнять и срочно заменять чат-ботами?
Уволить конечно можно, особенно бездельников и когда на заводах/пароходах работать некому, но касаемо реальных бизнес-кейсов с ИИ все не то чтобы прям заладилось. Бизнес конечно по-прежнему возбуждается и визионирует на конференциях, но реальные проекты пока драйвово буксуют, а ванильный AI-вайб начинает попахивать болотной тиной.
Надо с этим что-то делать и срочно насыпать каких-нибудь корповых бизнес-кейсов и потом к этим кейсам прикрутить какую-нибудь новую ИИ-штуку чтобы вернуть радугу приунывшим единорогам.
Итак,
Кейс 1 Представим себе что есть некое важное учреждение, которое разбирает в день по тысяче обращений граждан. На первоначальном сортировочном этапе было бы здорово раскидать эти обращения по профильным отделам и позже автоматически подкладывать шаблоны ответов под каждый тип вопросов. Другими словами, имеем неструктурированный текст и задачу классификации. Не то чтобы это задача не решалась без ИИ на основе концепции “мешка со словами” (bag-of-words), но такой подход требовал дата сантиста, а самый известный дата сантист среди главного начальника почему то стоит много денег и делает мало хороших моделей. Вот бы штатный аналитик с помощью R или Python пулял компактный скриптик и все разлеталось бы по классам без особых усилий.
Кейс 2 Допустим, есть профессиональная компания, которая проводить опросы по заказу государства или даже нескольких государств и не просто государств, а прям правильных таких с передовыми ценностями. Часть вопросов в опросном листе подразумевают ответ в свободной форме, который нужно нормализовать, отклассифицировать, оценить настрой (sentiment). В общем сейчас это делается руками специальных подрядчиков, а мог бы делать ИИ желательно без серверов с GPU и команды девопсов.
Кейс 3 В данном случае скорее фантазия чем реальный опыт, но почему-бы не пофантазировать. Есть замороченный на персонале гендир. Вот прям абсолютное воплощения типажа ИНТЕГРАТОРА по Адизису, который бьется за корповую культуру и ценит команду превыше всего. Есть желание у такого директора оценивать общий уровень напряженности внутри компании и путь успеха он решил проложить через анализ сообщений в корповом мессенджере. Действительно, почему бы не получать ежедневный отчет о том в каком настроении компания и на каком уровне стресс. Естественно, читать сообщения никто не будет из моральных соображений, это и не нужно, но вот сделать какой-то скоринг напряженности или тот же (sentiment), потом агрегировать до уровня отдела и инструмент готов. Естественно, не хотелось бы городить дата-пайплайн, который бы пулял API-запросы в GPT. Во-первых это заморочено, а во-вторых не понятно как этим GPT денюжку плотить и какой черный ход использовать, потому как нормально платить напрямую банковским переводом не получится.
Кейс 4 Кейс про финансовые рынки. Все мы знаем, что глава ЦБ комментирует каждое действие и бездействие с ключевой ставкой. При этом заявление зачастую важнее самого решения поскольку таким вербальным образом ЦБ проецирует свои намерения на финансовую и банковскую системы. Другими словами, заявление ЦБ – это тщательно подготовленный эссенция денежно-кредитной политики, адресованная профильной аудитории. Так вот эта аудитория иногда делает анализ чтобы как то систематизировать свое восприятие выступлений ЦБ: считает количество слов “инфляция”, “жесткая политика” и прочее. Во общем, аналитикам хотелось бы как-то квантифицировать такие заявление и возможно далее использовать для прогнозов, но желательно без всех этих сложных Torch и Tensorflow.
Новый инструмент анализа
Признаюсь честно, эта заметка лежала у меня в черновиках 1.5 года и раньше она подразумевала более интенсивное ковыряние в архитектурах моделей с поиском приемлемого варианта и далее допиливание публичной версии заменой выходных слоев и дообучении на собственных примерах. Тогда меня остановило ощущение некого недостатка легкости и простоты в полученном техническом решении. В итоге заметка была законсервирована до появления ощутимого триггера.
Вот что действительно произошло в мире ИИ буквально за последний год-два – это радикальное снижение порога входа туда. Про чат-ботов уже все знают: оно и стихи пишет и задачки решает и крестиком вышивать научит, но для анализа данных пока ничего особо не предлагали, а пацаны ждали и дождались: наконец на улице дата аналитиков тоже праздник. Идея этого праздника очень простая - давайте возьмем денормализованную табличку из базы данных (так же известный как датафрейм) и пройдемся запросом(prompt) по колоночке чтобы на выходе получить другую колоночку как ответ (response). В общем идея простая и теперь она реализована в пакете mall
, который доступен на R и Python. Касаемо Python пакет разработан как расширение к Polars, если кто не знает, то это что-то вроде Pandas, но написанная на Rust и руками из верного места.
Установка mall
и понимание что к чему где-то заняло минут 20. Для описанных выше аналитических упражнений с данными потребуется готовая к работе сетка, она же – модель. На выбор хорошего варианта ушло несколько больше времени так как требовалось: поискать какие в принципе есть варианты, скачать исходники на ноутбук, потом потестировать немного. Локальная работа модели подразумевает некое ограничение на ресурсы посему поиск производился среди облегченных моделей на базе llama. Перебрав несколько вариантов, включая, базовую llama 3.2
, тиньковский тбанский t-lite
я остановился на bambucha/saiga-llama3
обученная Ильей Гусевым поверх llama3 от компании, которую нельзя называть в РФ без сноски на то, что она экстремистская. Спасибо им за их работу.
Тренируемся на кошках
В качестве разминки был задан простой вопрос кандидатам: “Если к двум кошкам добавить четыре и потом разделить на двух кошатниц, то сколько кошек будет у каждой?”
И получаем ответ от нейронки bambucha/saiga-llama3
:
Давайте посчитаем:
2 кошки + 4 кошки = 6 кошек в общей сложности. Их нужно разделить между двумя кошатницами. Чтобы это сделать, можно поделить количество кошек на 2: 6 кошек ÷ 2 кошатницы = 3 кошки каждой кошатнице. Таким образом, у каждого кошатника будет по 3 кошки.
Тбанский owl/t-lite
ответ:
Для начала давайте выясним, сколько всего кошачьих пар получится из добавления четырех котов. Если есть две первоначальные кошки, то при добавлении еще двух кошек и двух котов общее количество кошек (и котов) становится:
2 кошки + 4 кота = 6 питомцев.
Теперь нужно разделить эту сумму на два — по числу кошатниц. В каждой группе будет столько же животных, сколько их всего в партиях, поделенное на количество кошатниц:
Итак, у каждой из двух кошатниц будет по три питомца: две кошки и один кот.
Ответ GPT-4o
Если к двум кошкам добавить ещё четыре, то получится всего шесть кошек. Если этих шесть кошек разделить между двумя кошатницами поровну, то у каждой из них будет:
Ответ: по три кошки у каждой. 😊
В целом терпимо для трех вариантов, хотя конечно в случае GPT чувствуется уровень: ответ краткий, точный и с учетом иронии вопроса. Впрочем, меня больше интересовала практика определение тональности для текста, в котором явно говорилось о повышении и понижении ключевой ставки. Именно тут t-lite
поплыл, да и не только там: классификация, извлечение сущностей, кастомные запросы – все вот это работало так себе на текстах ЦБ. Как говориться, есть потенциал для улучшений, пожелаем коллегам не унывать и не останавливаться на достигнутом уровне.
Замену выходных слоев модели и дообучение на своих примерах сейчас не рассматриваем. У нас тут бизнеса нажимают за time-to-market, всякие IRR проектов считают, а еще про WACC вспомнили в контексте ключевой ставки 21%. Естественно, деньги стоят денег и сейчас они стоят очень очень дорого. Ну раз мы тут определили некий финансовый контекст, то предлагаю и боевой кейс взять из этой области. Пожалуй, рассмотрим публичные выступления ЦБ на тему ключевой ставки и пройдемся ка промтом по этим выступлениям.
Денежно-кредитный кейс
Три часа ИИ неустанно трудился, делая анализ в то время как я размышлял над сортами чая, которые хотел бы попробовать, неспешно почитывая новости и клацая по клавишам текст данной заметки. Ладно, не буду приукрашать картинку и честно скажу, что пару часов потратил на всякие регулярки и трансформации, необходимые для вычистки текста и получения ровненького, причесанного датасета. Результат этой работы мне настолько понравился, что я решил его выложить на Kaggle чтобы любой желающий мог поупражняться с этими данными.
Исходный код всех манипуляций с данными спрятан в самом конце заметки. После обработки пропусков, самое время визуализировать полученные оценки тональности вместе с ключевой ставкой:
Очевидно, что понимание тональности исходит из контекста предложения или абзаца. Другими словами слово “хорошо” - это еще не означает позитив. Действительно, реальные фразы из выступлений не так однозначны:
… Негативные последствия этого пути хорошо знакомы…
… Поэтому отток капитала — это не обязательно плохо, а приток капитала — не всегда хорошо для экономики…
… Это хорошо заметно по динамике инфляционных ожиданий…
В общем важен контекст, а для контекста необходимо некое логически-завершенное содержание, которое представляет собой абзац текста. Именно поэтому было решено остановится на таком уровне гранулярности и также исключить из анализа слишком короткие фрагменты. После получения оценок тональности абзаца было бы хорошо присвоить значимость каждому абзацу. В данном случае было принято решение сделать оценку текста заявления через взвешенную на количество слов тональность. Другими словами, если много негативного текста, то негатива больше, а если абзацы с позитивом маленькие, то и позитива в итоге мало.
Любопытно, что в начале 2010-х ЦБ смело и емко коммуницировал с аудиторией и это в дополнение к фирменным сигналам брошью. Чего только стоит брошь с мокрой тучкой, которая красуется на фото-тизере к этой заметке? Всем финансовым аналитикам сразу понятно, что выступление будет содержать негатив, потому как брошь с тучкой, а не с солнышком. К сожалению, глава ЦБ в последнее время не практикует такие невербальные формы коммуникации, а жаль.
Также можно заметить любопытный период резкого скачка ставки до 20% в начале 2022 года и скупой риторикой вокруг этого события. Действительно, всем и так было все понятно, что ничего никому не понятно.
Наконец, недавний период как-будто бы не очень многословен, но весьма богат на негативный окрас в выступлениях ЦБ. Хорошо бы посмотреть на картинку в виде пропорций по тональности и также запилить какую-нибудь простую модельку прогноза ставки для следующего заседания:
Спецификация модели
Формула модели:
,
-
- ключевая ставка будущего периода
-
- ключевая ставка текущего периода
-
- оценка негативной тональности в выступлении
-
- взаимодействие(произведение) оценок негативной и нейтральной тональностей в выступлении
-
- коэффициенты модели
-
- ошибка модели
Модель без свободного члена и следовательно предполагается близость значений ставки будущего периода к ставке текущего то есть k1k1 ожидается близким к единице, что соответствует практике прогнозирования аналитиков по денежным рынкам.
R |
R.adjusted |
---|---|
0.9958 |
0.9956 |
Estimate |
Std. Error |
t value |
Pr(>|t|) |
|
---|---|---|---|---|
log(key_rate) |
0.946980 |
0.022865 |
41.415675 |
0.000000 |
negative |
0.000265 |
0.000077 |
3.442827 |
0.001046 |
negative:neutral |
0.000000 |
0.000000 |
-2.194274 |
0.032034 |
Тональность вносит ограниченный вклад в оценку будущего значения ключевой ставки, но коэффициенты модели все формально значимы, хотя конечно остатки выглядят грустно даже после логорифмирования, но для быстрой модели “на коленках” и так сойдет. В любом случае, датасет доступен публично и кто-то может попробовать накинуть что-нибудь более строгое или продвинутое.
Касаемо недавних событий, а именно решение о сохранении ставки на уровне 21% нужно сказать, что модель не ожидала такого решения, а была нацелена на увеличение ставки до 23%. Однако модель была не одинока в своем заблуждении: банки, финансовые аналитики, почти все были уверены в повышении ставки, а ее не произошло несмотря на жесткую риторику последних выступлений. В отношении будущего февральского заседания модель также настроена пессимистично, указывая на возможное повышение ставки до 22%. Впрочем, если почитать внимательно текст, то ЦБ намерен принимать решения исходя из актуальных на тот момент данных, тщательно прокошмарив сейчас аудиторию в профилактических целях.
Выводы
В общем ИИ – это стильно, модно, молодежно. Для бизнеса больше новых кейсов роста и сокращения издержек, для аналитиков – новый инструмент, который позволяет делать новые виды анализа через обогащение данных результатами работы ИИ. Для все остальных позитив в появлении новых видов анализа и, в частности, прогноза по ключевой ставке на основе тональности заявлений главы ЦБ. Кстати, ставку могут поднять до 22%+ на следующем заседании или оставить на уровне 21%, а вообще прогнозы – дело неблагодарное и поэтому данный текст не является инвестиционной рекомендацией.
# Необходимые библиотеки
library(ggplot2) # пакет для визуализации
library(collapse) # пакет для манипуляций с таблицами (новый)
library(data.table) # пакет для манипуляций с таблицами (любимый)
library(ollamar) # пакет для запуска LLM локально на ноутбуке
library(mall) # пакет для колоночных операций с использованием LLM
# Тестирование моделей
test_connection() # проверка работы локального сервера Ollama
ollamar::pull("bambucha/saiga-llama3:latest") # загрузка готовой модели с ресурса https://ollama.com/
# Проверка на базовом вопросе
resp1 <- generate("llama3.2", "tell me a 5-word story")
ask <- "Если к двум кошкам добавить четыре и потом разделить на двух кошатниц,
то сколько кошек будет у каждой?"
resp2 <- generate("bambucha/saiga-llama3:latest", ask)
resp3 <- generate("owl/t-lite", ask)
# Тест ответов
resp_process(resp1, "text") |> cat()
resp_process(resp2, "text") |> cat()
resp_process(resp3, "text") |> cat()
# Установка модели и параметров по-умолчанию для дальнейшей работы
llm_use("ollama", "bambucha/saiga-llama3:latest", seed = 100, temperature = 0)
# Загрузка подготовленных данных с Kaggle
kaggle_link <- "https://www.kaggle.com/api/v1/datasets/download/dmitrykibalnikov/russian-central-bank-official-statements"
cbr_articles <- fread(kaggle_link)
# Определение тональности: может занять много-много времени (3 часа и более)
system.time({
res <- llm_sentiment(cbr_articlesб section)
})
# Подготовка данных
# 1. Стандартная функция ggplot2::geom_area() не умеет строить ступеньки
# 2. Стандартная функция ggplot2::geom_rect() не умеет строить position="stack"
# Данные готовятся таким образом чтобы вручную сделать аналог position="stack" для geom_rect()
# Поэтому код местами может выглядеть неуклюже
res1 <- replace_na(res, cols = ".sentiment", value = "neutral") |> # починка потерянной тональности
collap(words~id +.sentiment+key_rate+date, FUN = fsum) |> # агрегация данных на уровень заявления
join(CJ(id=funique(res$id), .sentiment=funique(res$.sentiment)), # исключение пропущенных категорий тональности
on = c("id", ".sentiment"), how = "right", verbose=F) |>
tfm(date = na_locf(date), key_rate = na_locf(key_rate)) |> # починка даты и ключевой ставки для потерянной тональности
replace_na(cols = "words", value = 0) |> # починка потерянного количества слов
roworder(date) |> # сортировка по дате
_[,date_end:= flag(date, -1, fill = Sys.Date()+60), by = .sentiment] |> # построение оконцовки интервала дат
fgroup_by(date) |>
fmutate(words_end=fcumsum(words)) |> # создание "стека" для визуализации
fmutate(words_start=words_end-words) |> # создание "стека" для визуализации
fungroup()
# Визуализация для анализа на основе geom_rect()
ggplot(res1) +
geom_rect(aes(xmin=date, xmax = date_end, ymin=words_start, ymax=words_end, fill = .sentiment), alpha = .8) +
geom_step(aes(date, key_rate*1e2, col = "ставка")) +
scale_fill_manual(values = c(negative="firebrick", positive="seagreen", neutral = "gray20")) +
scale_x_date(date_breaks = "6 month", date_labels = "%m-%y") +
scale_color_manual(values = c(ставка="white")) +
scale_y_continuous(sec.axis = sec_axis(~./1e2, breaks=1:24, name = "Ставка",
labels = scales::label_percent(accuracy = .01, scale =1))) +
theme(legend.position = "bottom", axis.text.x = element_text(angle=45)) +
labs(title = "Сравнение изменения ключевой ставки и тональности выступления главы ЦБ",
y = "Тональность (количество слов)", x = "Дата", fill="Тональность", col = "Ключеваяnставка")
# Подготовка данных для моделирования
mdl_dt <- res1[, .(key_rate, .sentiment, words, date)] |>
pivot(ids = c("key_rate", "date"), how = "w", names=".sentiment", values = "words") |>
slt(-date) |>
tfm(key_rate_fw = flag(key_rate, -1))
# Оценка модели
mdl <- mdl_dt|>
na_omit() |>
lm(data = _, log(key_rate_fw)~log(key_rate) +negative+negative:neutral+0)
# Характеристики модели
summary(mdl)
plot(mdl)
# Итоговая визуализация
fgroup_by(res1, id) |>
fmutate(words_sm = fsum(words)) |>
fmutate(words_end = words_end/words_sm, words_start=words_start/words_sm) |>
fungroup() |>
ggplot() +
geom_rect(aes(xmin=date, xmax = date_end, ymin=words_start, ymax=words_end, fill = .sentiment), alpha = .6) +
geom_step(aes(date, key_rate/21, col="факт"), direction = "vh") +
geom_step(data=data.table(date=flag(funique(res1$date),-1, fill = Sys.Date()+60), key_rate = predict.lm(mdl, mdl_dt) |> exp()),
aes(date, key_rate/21, col="модель"), direction = "vh") +
scale_fill_manual(values = c(negative="firebrick", positive="seagreen", neutral = "gray20")) +
scale_y_continuous(sec.axis = sec_axis(~.*21, breaks=1:24, name = "Ставка",
labels = scales::label_percent(accuracy = .01, scale =1)),
labels=scales::label_percent(accuracy = .01), n.breaks = 9) +
scale_color_manual(values = c("факт"="white", "модель"="tomato")) +
scale_x_date(date_breaks = "6 month", date_labels = "%m-%y") +
labs(title = "Оценка изменения ключевой ставки от тональности выступления главы ЦБ",
y = "Пропорция тональности", x = "Дата", fill="Тональность", col = "Ключеваяnставка") +
theme(axis.text.x = element_text(angle=45), legend.position = "bottom")
Оригинал статьи
Автор: welcome2hype