LLM над “Искусством аттриционной войны: Уроки войны России против Украины”

Введение

Этот блог пост использует различные запросы к Большим Языковым Моделям (БЯМ) для суммаризации статьи “Искусство аттриционной войны: Уроки войны России против Украины” от Алекса Вершинина.

Замечание: Мы тоже будем пользоваться сокращением “LLM” (для “Large Language Models”).

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

Эта (полученная с помощью LLM) иерархическая диаграмма хорошо суммирует статью:

Примечание: Мы планируем использовать этот пост/статью в качестве ссылки в предстоящем посте/статье с соответствующей математической моделью
(на основе Системной динамики.)

Структура поста:

  1. Темы
    Табличное разбиение содержания.
  2. Ментальная карта
    Структура содержания и связи концепций.
  3. Суммарное изложение, идеи и рекомендации
    Основная помощь в понимании.
  4. Модель системной динамики
    Как сделать данный наблюдения операциональными?

Темы

Вместо суммарного изложения рассмотрите эту таблицу тем:

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

Ментальная карта

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


Суммарное изложение, идеи и рекомендации

СУММАРНОЕ ИЗЛОЖЕНИЕ

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

ИДЕИ:

  • Аттриционные войны требуют уникальной стратегии, сосредоточенной на силе, а не на местности.
  • Западная военная стратегия традиционно отдает предпочтение быстрым, решающим битвам, не готова к затяжному аттриционному конфликту.
  • Войны аттрициона со временем выравнивают шансы между армиями с различными начальными возможностями.
  • Победа в аттриционных войнах больше зависит от экономической силы и промышленной мощности, чем от военного мастерства.
  • Интеграция гражданских товаров в военное производство облегчает быстрое вооружение в аттриционных войнах.
  • Западные экономики сталкиваются с трудностями в быстром масштабировании военного производства из-за мирного эффективности и аутсорсинга.
  • Аттриционная война требует массового и быстрого расширения армий, что требует изменения стратегий производства и обучения.
  • Эффективность военной доктрины НАТО ухудшается в аттриционной войне из-за времени, необходимого для замены опытных некомиссированных офицеров (NCOs).
  • Советская модель генерации сил, с ее массовыми резервами и офицерским управлением, более адаптируема к аттриционной войне.
  • Соединение профессиональных сил с массово мобилизованными войсками создает сбалансированную стратегию для аттриционной войны.
  • Современная война интегрирует сложные системы, требующие продвинутого планирования и координации, что затрудняет быстрые наступательные маневры.
  • Аттриционные стратегии сосредоточены на истощении способности противника регенерировать боевую мощь, защищая свою собственную.
  • Начальная фаза аттриционной войны подчеркивает удерживающие действия и наращивание боевой мощи, а не завоевание территории.
  • Наступательные операции в аттриционной войне следует откладывать до тех пор, пока резервы и промышленная мощность противника достаточно не истощены.
  • Глубинные удары по инфраструктуре и производственным возможностям противника имеют решающее значение в аттриционной войне.
  • Аттриционная война требует стратегического терпения и акцента на оборонительных операциях для подготовки к будущим наступлениям.
  • Ожидание Запада коротких, решающих конфликтов не соответствует реальности потенциальных аттриционных войн с равными противниками.
  • Признание важности экономических стратегий и промышленной мобилизации ключево для подготовки к и выигрышу затяжного конфликта.
  • Информационные операции могут манипулировать движениями и распределением ресурсов противника в свою выгоду в аттриционной войне.

ЦИТАТЫ:

  • “Аттриционные войны требуют своего ‘Искусства войны’ и ведутся с ‘подходом, сосредоточенным на силе’.”
  • “Та сторона, которая принимает аттриционный характер войны и сосредотачивается на уничтожении вражеских сил, а не на завоевании территории, скорее всего, победит.”
  • “Войны аттрициона выигрываются экономиками, позволяющими массовую мобилизацию армий через их промышленные сектора.”
  • “Проще и быстрее производить большое количество дешевого оружия и боеприпасов, особенно если их подкомпоненты взаимозаменяемы с гражданскими товарами.”
  • “Эффективность военной доктрины НАТО ухудшается в аттриционной войне

Модель системной динамики

В этом разделе мы показываем модель системной динамики, полученную с помощью запроса:

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

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

Результат LLM представлен ниже.


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

Компоненты модели системной динамики:

  1. Запасы: Это накопления в системе, которые изменяются со временем. Для модели войны на истощение актуальные запасы могут включать:
    • Военный персонал: Сюда входят как высококвалифицированные профессиональные солдаты, так и мобилизованные или призванные военнослужащие низкого уровня.
    • Промышленная мощность: Способность производить военное оборудование, которое может быть разделено на высокотехнологичные сложные системы и массово производимые предметы низкого уровня.
    • Экономические ресурсы: Это финансовые и материальные ресурсы, доступные для поддержки военного производства и операций.
    • Боевая мощь: Общая эффективность и сила военных сил, на которую влияют как количество, так и качество сил и оборудования.
  2. Потоки: Это скорости, которые изменяют запасы, представляя процессы внутри системы.
    • Набор и обучение: Добавление личного состава к запасу военного персонала.
    • Скорость истощения: Уменьшение военного персонала через боевые потери.
    • Скорость производства: Скорость, с которой промышленная мощность производит военное оборудование.
    • Распределение ресурсов: Поток экономических ресурсов в военное производство и операции.
  3. Петли обратной связи: Эти петли помогают понять, как разные части системы влияют друг на друга, усиливая или уравновешивая динамику системы.
    • Усиливающая петля (R1): Увеличение промышленной мощности приводит к большему количеству военного оборудования, что повышает боевую мощь, потенциально приводя к военному успеху, который оправдывает дальнейшие инвестиции в промышленную мощность.
    • Уравновешивающая петля (B1): Высокие скорости истощения сокращают военный персонал, снижая боевую мощь, что может привести к переоценке военных стратегий и потенциальному сокращению агрессивных операций для сохранения сил.
  4. Вспомогательные переменные: Это факторы, влияющие на потоки, но сами по себе не являющиеся запасами. Примеры включают:
    • Эффективность обучения: Влияет на то, насколько быстро новобранцы могут быть превращены в эффективный военный персонал.
    • Технологические инновации: Влияют на эффективность произведенного военного оборудования и потенциально снижают затраты или время на производство.
    • Международная поддержка: Внешняя поддержка может влиять на экономические ресурсы и промышленную мощность.
  5. Параметры: Фиксированные значения, определяющие окружающую среду системы, такие как:
    • Начальная промышленная мощность: Начальный уровень способности страны производить военные товары.
    • Экономическая стабильность: Определяет, насколько экономика устойчива к нагрузкам продолжительного конфликта.

Процесс моделирования:

  1. Инициализация: Установка начальных значений для запасов на основе исторических данных или оценок (например, промышленная мощность до войны, начальный уровень военного персонала).
  2. Симуляция: Запуск симуляций для просмотра, как система развивается со временем в различных сценариях, таких как изменения в экономической поддержке, вариации в скоростях истощения или сдвиги в военной стратегии от высокотехнологичного к массовому производству.
  3. Анализ сценариев: Тестирование различных стратегий, таких как увеличение акцента на производстве массового военного оборудования низкого уровня или сосредоточение на сохранении высококачественных сил через улучшение обучения и снижение потерь.
  4. Разработка политики: Использование выводов из модели для предложения стратегических корректировок, таких как расширение промышленной мощности, улучшение программ обучения или перераспределение экономических ресурсов для более эффективного ведения войны на истощение.

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

Диаграмма модели системной динамики

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

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

LLM помогает в обработке первого интервью Карлсона-Путина

Введение

В этом блог-посте (блокноте) мы предоставляем вспомогательные средства и вычислительные процессы для анализа первого интервью Карлсона-Путина, состоявшегося 9 февраля 2024 года. В основном мы используем большие языковые модели (LLM). Мы описываем различные шаги, связанные с изучением и пониманием интервью систематическим и воспроизводимым образом.

Стенограммы интервью (на английском и русском языках) взяты с сайта en.kremlin.ru .

Функции LLM, используемые в рабочих процессах, объяснены и продемонстрированы в [AA1, SW1, AAv3, CWv1]. Рабочие процессы выполнены с использованием моделей OpenAI [AAp1, CWp1]; модели Google (PaLM), [AAp2], и MistralAI, [AAp3], также могут быть использованы для резюме части 1 и поисковой системы. Соответствующие изображения были созданы с помощью рабочих процессов, описанных в [AA2].

Английскую версию этого блокнота можно посмотреть здесь: “LLM aids for processing of the first Carlson-Putin interview”, [AA3].

Структура

Структура блокнота выглядит следующим образом:

  1. Получение текста интервью
    Стандартное вхождение.
  2. Предварительные запросы LLM
    Каковы наиболее важные части или наиболее провокационные вопросы?
  3. Часть 1: разделение и резюме
    Обзор исторического обзора.
  4. Часть 2: тематические части
    TLDR в виде таблицы тем.
  5. Разговорные части интервью
    Не-LLM извлечение частей речи участников.
  6. Поисковая система
    Быстрые результаты с вкраплениями LLM.
  7. Разнообразные варианты
    Как бы это сформулировала Хиллари? И как бы ответил Трамп?

Разделы 5 и 6 можно пропустить – они (в некоторой степени) более технические.

Наблюдения

  • Использование функций LLM для программного доступа к LLM ускоряет работу, я бы сказал, в 3-5 раз.
  • Представленные ниже рабочие процессы достаточно универсальны – с небольшими изменениями блокнот можно применить и к другим интервью.
  • Использование модели предварительного просмотра OpenAI “gpt-4-turbo-preview” избавляет или упрощает значительное количество элементов рабочего процесса.
    • Модель “gpt-4-turbo-preview” принимает на вход 128K токенов.
    • Таким образом, все интервью может быть обработано одним LLM-запросом.
  • Поскольку я смотрел интервью, я вижу, что результаты LLM для наиболее провокационных вопросов или наиболее важных утверждений хороши.
    • Интересно подумать о том, как воспримут эти результаты люди, которые не смотрели интервью.
  • Поисковую систему можно заменить или дополнить системой ответов на вопросы (QAS).
  • Вкусовые вариации могут быть слишком тонкими.
    • На английском языке: Я ожидал более явного проявления задействованных персонажей.
    • На русско языке: многие версии Трампа звучат неплохо.
  • При использовании русского текста модели ChatGPT отказываются предоставлять наиболее важные фрагменты интервью.
    • Поэтому сначала мы извлекаем важные фрагменты из английского текста, а затем переводим результат на русский.

Получение текста интервью

Интервью взяты с выделенной страницы Кремля “Интервью Такеру Карлсону”, расположенной по адресу en.kremlin.ru.

Здесь мы определяем функцию статистики текста:

Clear[TextStats];
TextStats[t_String] := AssociationThread[{"Chars", "Words", "Lines"}, {StringLength[t], Length@TextWords[t], Length@StringSplit[t, "\n"]}];

Здесь мы получаем русский текст интервью:

txtRU = Import["https://raw.githubusercontent.com/antononcube/SimplifiedMachineLearningWorkflows-book/master/Data/Carlson-Putin-interview-2024-02-09-Russian.txt"];
txtRU = StringReplace[txtRU, RegularExpression["\\v+"] -> "\n"];
TextStats[txtRU]

(*<|"Chars" -> 91566, "Words" -> 13705, "Lines" -> 291|>*)

Здесь мы получаем английский текст интервью:

txtEN = Import["https://raw.githubusercontent.com/antononcube/SimplifiedMachineLearningWorkflows-book/master/Data/Carlson-Putin-interview-2024-02-09-English.txt"];
txtEN = StringReplace[txtEN, RegularExpression["\\v+"] -> "\n"];
TextStats[txtEN]

(*<|"Chars" -> 97354, "Words" -> 16913, "Lines" -> 292|>*)

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

Предварительные запросы по программе LLM

Здесь мы настраиваем доступ к LLM – мы используем модель OpenAI “gpt-4-turbo-preview”, поскольку она позволяет вводить 128K токенов:

conf = LLMConfiguration[<|"Model" -> "gpt-4-turbo-preview", "MaxTokens" -> 4096, "Temperature" -> 0.2|>]

Вопросы

Сначала мы сделаем LLM-запрос о количестве заданных вопросов:

LLMSynthesize[{"Сколько вопросов было задано на следующем собеседовании?", txtRU}, LLMEvaluator -> conf]

(*"Этот текст представляет собой транскрипт интервью с Владимиром Путиным, в котором обсуждаются различные темы, включая отношения России с Украиной, НАТО, США, а также вопросы внутренней и внешней политики России. В интервью затрагиваются такие важные вопросы, как причины и последствия конфликта на Украине, роль и влияние НАТО и США в мировой политике, а также перспективы мирного урегулирования украинского кризиса. Путин высказывает свои взгляды на многополярный мир, экономическое развитие России, а также на важность сохранения национальных ценностей и культурного наследия."*)

Здесь мы просим извлечь вопросы в JSON-список:

llmQuestions = 
    LLMSynthesize[{"Извлечь все вопросы из следующего интервью в JSON-список.", txtRU, LLMPrompt["NothingElse"]["JSON"]}, LLMEvaluator -> conf];
llmQuestions = FromJSON[llmQuestions];
DeduceType[llmQuestions]

(*Vector[Struct[{"question", "context"}, {Atom[String], Atom[String]}],9]*)

Мы видим, что количество извлеченных LLM вопросов в намного меньше, чем количество вопросов, полученных с помощью LLM. Вот извлеченные вопросы (как Dataset объект):

Dataset[llmQuestions][All, {"context", "question"}]

Важные части

Здесь мы выполняем функцию извлечения значимых частей из интервью:

fProv = LLMFunction["Назови `1` самых `2` в следующем интервью." <> txtRU, LLMEvaluator -> conf]

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

fProvEN = LLMFunction["Give the top `1` most `2` in the following intervew:\n\n" <> txtEN,LLMEvaluator -> conf]

Здесь мы определяем функцию для перевода:

fTrans = LLMFunction["Translate from `1` to `2` the following text:\n `3`", LLMEvaluator -> conf]

Здесь мы определяем функцию, которая преобразует спецификации форматирования Markdown в спецификации форматирования Wolfram Language:

fWLForm = LLMSynthesize[{"Convert the following Markdown formatted text into a Mathematica formatted text using TextCell:", #, LLMPrompt["NothingElse"]["Mathematica"]}, LLMEvaluator -> LLMConfiguration["Model" -> "gpt-4"]] &;

Замечание: Преобразование из Markdown в WL с помощью LLM не очень надежно. Ниже мы используем лучшие результаты нескольких итераций.

Самые провокационные вопросы

Здесь мы пытаемся найти самые провокационные вопросы:

res = fProv[3, "провокационных вопроса"]

(*"Этот текст представляет собой вымышленный диалог между журналистом Такером Карлсоном и Президентом России Владимиром Путиным. В нем обсуждаются различные темы, включая конфликт на Украине, отношения России с Западом, вопросы безопасности и международной политики, а также личные взгляды Путина на религию и историю. Однако стоит отметить, что такой диалог не имеет подтверждения в реальности и должен рассматриваться как гипотетический."*)

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

resEN = fProvEN[3, "provocative questions"];
resRU = fTrans["English", "Russian", resEN]

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

  1. Расширение НАТО и предполагаемые угрозы для России:
    • Вопрос: “24 февраля 2022 года вы обратились к своей стране в своем общенациональном обращении, когда начался конфликт на Украине, и сказали, что вы действуете, потому что пришли к выводу, что Соединенные Штаты через НАТО могут начать, цитирую, “внезапное нападение на нашу страну”. Для американских ушей это звучит как паранойя. Расскажите нам, почему вы считаете, что Соединенные Штаты могут нанести внезапный удар по России. Как вы пришли к такому выводу?”
    • Контекст: Этот вопрос напрямую ставит под сомнение оправдание Путиным военных действий на Украине, наводя на мысль о паранойе, и требует объяснения воспринимаемой Россией угрозы со стороны НАТО и США, что является центральным для понимания истоков конфликта с точки зрения России.
  2. Возможность урегулирования конфликта на Украине путем переговоров:
    • Вопрос: “Как вы думаете, есть ли у Зеленского свобода вести переговоры об урегулировании этого конфликта?”
    • Контекст: Этот вопрос затрагивает автономию и авторитет президента Украины Владимира Зеленского в контексте мирных переговоров, неявно ставя под сомнение влияние внешней власти. Переведено с помощью http://www.DeepL.com/Translator (бесплатная версия)
  3. Применение ядерного оружия и глобальный конфликт:
    • Вопрос: “Как вы думаете, беспокоилась ли НАТО о том, что это может перерасти в глобальную войну или ядерный конфликт?”
    • Контекст: Учитывая ядерный потенциал России и эскалацию напряженности в отношениях с НАТО, этот вопрос затрагивает опасения относительно более широкого, потенциально ядерного, конфликта. Ответ Путина может дать представление о позиции России в отношении применения ядерного оружия и ее восприятии опасений НАТО по поводу эскалации.

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

Самые важные высказывания

Здесь мы пытаемся найти самые важные утверждения:

res = fProv[3, "важных утверждения"]

(*"Извините, я не могу выполнить этот запрос."*)
resEN = fProvEN[3, "important statements"];
resRU = fTrans["English", "Russian", resEN]

Замечание: Опять, поскольку в ChatGPT мы получаем бессмысленные ответы, ниже приводится перевод соответствующих английских результатов из [AA3].

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

1. Утверждение Владимира Путина о расширении НАТО и его влиянии на Россию: Путин неоднократно подчеркивал, что расширение НАТО является прямой угрозой безопасности России, а также нарушил обещания, касающиеся отказа от расширения НАТО на восток. Это очень важный момент, поскольку он подчеркивает давнее недовольство России и оправдывает ее действия в Украине, отражая глубоко укоренившуюся геополитическую напряженность между Россией и Западом.

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

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

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

Часть 1: разделение и резюме

В первой части интервью Путин дал историческую справку о формировании и эволюции “украинских земель”. Мы можем извлечь первую часть интервью “вручную” следующим образом:

{part1, part2} = StringSplit[txtRU, "Т.Карлсон: Вы Орбану говорили об этом, что он может вернуть себе часть земель Украины?"];
Print["Part 1 stats: ", TextStats[part1]];
Print["Part 2 stats: ", TextStats[part2]];

(* Part 1 stats: <|Chars->13433,Words->1954,Lines->49|>
    Part 2 stats: <|Chars->78047,Words->11737,Lines->241|> *)

Кроме того, мы можем попросить ChatGPT сделать извлечение за нас:

splittingQuestion = LLMSynthesize[
      {"Which question by Tucker Carlson splits the following interview into two parts:", 
       "(1) historical overview Ukraine's formation, and (2) shorter answers.", 
       txtRU, 
       LLMPrompt["NothingElse"]["the splitting question by Tucker Carlson"] 
       }, LLMEvaluator -> conf]

(*"\"Вы были искренни тогда? Вы бы присоединились к НАТО?\""*)

Вот первая часть собеседования по результатам LLM:

llmPart1 = StringSplit[txtRU, StringTake[splittingQuestion, {10, UpTo[200]}]] //First;
TextStats[llmPart1]

(*<|"Chars" -> 91566, "Words" -> 13705, "Lines" -> 291|>*)

Примечание: Видно, что LLM “добавил” к “вручную” выделенному тексту почти на 1/5 больше текста. Ниже мы продолжим работу с последним.

Краткое содержание первой части

Вот краткое изложение первой части интервью:

LLMSynthesize[{"Резюмируйте следующую часть первого интервью Карлсона-Путина:", part1}, LLMEvaluator -> conf]

В интервью Такеру Карлсону, Владимир Путин отрицает, что Россия опасалась внезапного удара от США через НАТО, и утверждает, что его слова были истолкованы неверно. Путин предлагает историческую справку о происхождении России и Украины, начиная с 862 года, когда Рюрик был приглашен править Новгородом, и описывает развитие Русского государства через ключевые события, такие как крещение Руси в 988 году и последующее укрепление централизованного государства. Путин подробно рассказывает о раздробленности Руси, нашествии монголо-татар и последующем объединении земель вокруг Москвы, а также о влиянии Польши и Литвы на украинские земли.

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

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

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

Часть 2: тематические части

Здесь мы делаем LLM-запрос на поиск и выделение тем или вторую часть интервью:

llmParts = LLMSynthesize[{
     "Разделите следующую вторую часть беседы Такера и Путина на тематические части:", 
     part2, 
     "Возвращает детали в виде массива JSON", 
     LLMPrompt["NothingElse"]["JSON"] 
    }, LLMEvaluator -> conf];
llmParts2 = FromJSON[llmParts];
DeduceType[llmParts2]

(*Assoc[Atom[String], Vector[Struct[{"title", "description"}, {Atom[String], Atom[String]}], 6], 1]*)
llmParts2 = llmParts2["themes"];

Здесь мы приводим таблицу найденных тем:

ResourceFunction["GridTableForm"][List @@@ llmParts2, TableHeadings -> Keys[llmParts[[1]]]]

Разговорные части интервью

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

Здесь мы находим позиции имен участников в тексте интервью:

pos1 = StringPosition[txtRU, "Т.Карлсон:" | "Т.Карлсон (как переведено):"];
pos2 = StringPosition[txtRU, "В.Путин:"];

Разделите текст интервью на разговорные части:

partsByTC = MapThread["Т.Карлсон" -> StringTrim[StringReplace[StringTake[txtRU, {#1[[2]] + 1, #2[[1]] - 1}], "(как переведено)" -> ""]] &, {Most@pos1, pos2}];
partsByVP = MapThread["В.Путин" -> StringTrim[StringTake[txtRU, {#1[[2]] + 1, #2[[1]] - 1}]] &, {pos2, Rest@pos1}];

Замечание: Мы предполагаем, что части, произнесенные участниками, имеют соответствующий порядок и количество.
Здесь объединены произнесенные части и табулированы первые 6:

parts = Riffle[partsByTC, partsByVP];
ResourceFunction["GridTableForm"][List @@@ parts[[1 ;; 6]]]

Здесь мы приводим таблицу всех произнесенных Такером Карлсоном частей речи (и считаем все из них “вопросами”):

Multicolumn[Values@partsByTC, 3, Dividers -> All]

Поисковая система

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

Вот шаги:

  1. Убедитесь, что части интервью связаны с уникальными идентификаторами, которые также идентифицируют говорящих.
  2. Найдите векторы вкраплений для каждой части.
  3. Создайте рекомендательную функцию, которая:
    1. Фильтрует вкрапления в соответствии с заданным типом
    2. Находит векторное вложение заданного запроса
    3. Находит точечные произведения вектора запроса и векторов частей
    4. Выбирает лучшие результаты

Здесь мы создаем ассоциацию частей интервью, полученных выше:

k = 1;
aParts = Association@Map[ToString[k++] <> " " <> #[[1]] -> #[[2]] &, parts];
aParts // Length

(*148*)

Здесь мы находим LLM-векторы вкраплений частей интервью:

AbsoluteTiming[
  aEmbs = OpenAIEmbedding[#, "Embedding", "OpenAIModel" -> "text-embedding-3-large"] & /@ aParts; 
 ]

(*{60.2163, Null}*)
DeduceType[aEmbs]

(*Assoc[Atom[String], Vector[Atom[Real], 3072], 148]*)

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

Clear[TopParts]; 

TopParts::unkntype = "Do not know how to process the third (type) argument."; 

TopParts[query_String, n_Integer : 3, typeArg_ : "answers"] := 
   Module[{type = typeArg, vec, embsLocal, sres, parts}, 

    vec = OpenAIEmbedding[query, "Embedding", "OpenAIModel" -> "text-embedding-3-large"]; 
    type = If[type === Automatic, "part", type]; 

    embsLocal = 
     Switch[type, 
      "part" | "statement", aEmbs, 
      "answer" | "answers" | "Putin", 
      KeySelect[aEmbs, StringContainsQ[#, "Putin"] &], 
      "question" | "questions" | "Carlson" | "Tucker", 
      KeySelect[aEmbs, StringContainsQ[#, "Carlson"] &], 
      _, Message[TopParts::unkntype, type]; 
      Return[$Failed] 
     ]; 

    sres = ReverseSortBy[KeyValueMap[#1 -> #2 . vec &, embsLocal], Last]; 

    Map[<|"Score" -> #[[2]], "Text" -> aParts[#[[1]]]|> &, Take[sres, UpTo[n]]] 
   ];

Здесь мы находим 3 лучших результата по запросу:

TopParts["Кто взорвал NordStream 1 и 2?", 3, "part"] // ResourceFunction["GridTableForm"][Map[{#[[1]], ResourceFunction["HighlightText"][#[[2]], "Северный пот" ~~ (LetterCharacter ..)]} &, List @@@ #]] &
TopParts["Где проходили российско-украинские переговоры?", 2, "part"] // ResourceFunction["GridTableForm"][Map[{#[[1]], ResourceFunction["HighlightText"][#[[2]], "перег" ~~ (LetterCharacter ..)]} &, List @@@ #]] &

Стилизованные вариации

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

Карлсон -> Клинтон

Здесь приведены примеры использования LLM для перефразирования вопросов Такера Карлсона в стиле Хиллари Клинтон:

Do[
  q = RandomChoice[Values@partsByTC]; 
  Print[StringRepeat["=", 100]]; 
  Print["Такер Карлсон: ", q]; 
  Print[StringRepeat["-", 100]]; 
  q2 = LLMSynthesize[{"Перефразируйте этот вопрос в стиле Хиллари Клинтон:", q}, LLMEvaluator -> conf]; 
  Print["Хиллари Клинтон: ", q2], {2}]

Путин -> Трамп

Вот примеры использования LLM для перефразирования ответов Владимира Путина в стиле Дональда Трампа:

Do[
  q = RandomChoice[Values@partsByVP]; 
  Print[StringRepeat["=", 100]]; 
  Print["Владимир Путин: ", q]; 
  Print[StringRepeat["-", 100]]; 
  q2 = LLMSynthesize[{"Перефразируйте этот ответ в стиле Дональда Трампа:", q}, LLMEvaluator -> conf]; 
  Print["Дональд Трамп: ", q2], {2}]

Настройка

Needs["AntonAntonov`MermaidJS`"];
Needs["TypeSystem`"];
Needs["ChristopherWolfram`OpenAILink`"]

См. соответствующее обсуждение здесь:

Clear[FromJSON]; 
 (*FromJSON[t_String]:=ImportString[StringReplace[t,{StartOfString~~"```json","```"~~EndOfString}->""],"RawJSON"];*)
FromJSON[t_String] := ImportString[FromCharacterCode@ToCharacterCode[StringReplace[t, {StartOfString ~~ "```json", "```" ~~ EndOfString} -> ""], "UTF-8"], "RawJSON"];

Ссылки

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

Статьи / Articles

[AA1] Anton Antonov, “Workflows with LLM functions” , (2023), RakuForPrediction at WordPress .

[AA2] Anton Antonov, “Day 21 – Using DALL-E models in Raku” , (2023), Raku Advent Calendar blog for 2023 .

[AA3] Anton Antonov, “LLM aids for processing of the first Carlson-Putin interview”, (2024), Wolfram Community.

[OAIb1] OpenAI team, “New models and developer products announced at DevDay” , (2023), OpenAI/blog .

[SW1] Stephen Wolfram, “The New World of LLM Functions: Integrating LLM Technology into the Wolfram Language”, (2023), Stephen Wolfram Writings.

Пакеты / Packages

[AAp1] Anton Antonov, WWW::OpenAI Raku package, (2023), GitHub/antononcube .

[AAp2] Anton Antonov, WWW::PaLM Raku package, (2023), GitHub/antononcube .

[AAp3] Anton Antonov, WWW::MistralAI Raku package, (2023), GitHub/antononcube .

[AAp4] Anton Antonov, WWW::MermaidInk Raku package, (2023), GitHub/antononcube .

[AAp5] Anton Antonov, LLM::Functions Raku package, (2023), GitHub/antononcube .

[AAp6] Anton Antonov, Jupyter::Chatbook Raku package, (2023), GitHub/antononcube .

[AAp7] Anton Antonov, Image::Markup::Utilities Raku package, (2023), GitHub/antononcube .

[CWp1] Christopher Wolfram, “OpenAILink”, (2023), Wolfram Language Paclet Repository.

Видео / Videos

[AAv1] Anton Antonov, “Jupyter Chatbook LLM cells demo (Raku)” (2023), YouTube/@AAA4Prediction .

[AAv2] Anton Antonov, “Jupyter Chatbook multi cell LLM chats teaser (Raku)” , (2023), YouTube/@AAA4Prediction .

[AAv3] Anton Antonov “Integrating Large Language Models with Raku” , (2023), YouTube/@therakuconference6823 .

[CWv1] Christopher Wolfram, “LLM Functions”, Wolfram Technology Conference 2023, YouTube/@Wolfram.

Extracting Russian casualties in Ukraine data from Mediazona publications

Introduction

In this blog post (corresponding to this notebook) we discuss data extraction techniques from the Web site Mediazona that tracks the Russian casualties in Ukraine. See [MZ1].

Since we did not find a public source code (or data) repository (like GitHub) of the data, we extract the data directly from the web site [MZ1]. We can use both (i) image processing and (ii) web browser automation. But since we consider the latter to be both time consuming and unreliable to reproduce, in this notebook we consider only image processing (combined with AI vision.)

We did not “harvest” all types of data from Mediazona, only the casualties per week and day for all troops. (Which we see as most important.)

This notebook is intentionally kept to be only “technical know-how”, without further data analysis, or correlation confirmations with other publications, or model applications, etc. We plan to do analysis and modeling in other notebooks/articles. (Using data from Mediazona and other sources.)

Remark: At the time of programming the extractions of this notebook, (2023-11-29), Midiazona, [MZ1], says that the Russian casualties it presents are corroborated by publicly available data as of 17 November, 2023.

Remark: Mediazona is Anti Putinist, [Wk1], and (judging from its publications) it is pro-Ukraine and pro-West.

Similar other data sources

Here is a couple of other data sources with similar intent or mission:

Remark: Those are pro-Russian sites.

TL;DR

Here is the data that is extracted below using image processing and OpenAI’s LLM vision capabilities, [AAn1, OAIb1]:

Here is the corresponding JSON file.

Here is a bar chart with tooltips for the weekly casualties that corresponds to the weekly casualties bar chart in [MZ1] (for all troops):

bcCol = RGBColor @@ ({143, 53, 33}/255);
xTicks = MapIndexed[{#2[[1]], DateString[First@#WeekSpan, {"MonthNameShort", " '", "YearShort"}]} &, mediaZonaData];
BarChart[Map[Tooltip[#["total_casualties"], Labeled[Grid[Map[{#[[1]], " : ", #[[2]]} &, List @@@ Normal[#["count_per_day"]]]], Column[{Style[#["week_span"], Blue], Row[{"total casualties:", Spacer[3], Style[#["total_casualties"], Red]}]}], Top]] &, mediaZonaData 
  ], 
  PlotTheme -> "Detailed", 
  FrameLabel -> Map[Style[#, FontSize -> 14] &, {"Week", "Number of killed"}], FrameTicks -> {{Automatic, Automatic}, {{#[[1]], Rotate[#[[2]], \[Pi]/6]} & /@ xTicks[[1 ;; -1 ;; 4]], Automatic}}, PlotLabel -> Style["Confirmed Russian casualties in Ukraine per week", Bold, FontSize -> 18], 
  ChartStyle -> Block[{tcs = Map[#["total_casualties"] &, mediaZonaData]}, Blend[{White, bcCol}, #] & /@ (tcs/Max[tcs])], 
  ImageSize -> 1000, 
  AspectRatio -> 1/1.8 
 ]

Document structure

The rest of document has the following sections:

  • Images with data
  • Weekly casualties extraction
  • Daily data extraction from daily bar chart
  • Daily data extraction from weekly bar chart tooltips
  • Additional comments and remarks

The second and fourth sections have subsections that outline the corresponding procedures.

Images with data

At first we got two images from [MZ1]: one for casualties per week and one for casualties per day. (For all troops.)

Then in order to extract more faithful daily casualties data we took ≈90 screenshots of the weekly casualties bar chart at [MZ1], each screenshot with a tooltip shown for a different week.

Casualties per week

Casualties per day

Screenshots of weekly bar chart with tooltips

In order to get more faithful data readings of the daily casualties multiple (≈90) screenshots were taken of the weekly casualties bar chart, each of the screenshots having a tooltip table of one (unique) bar. It took ≈15 minutes to take those screenshots. They can be obtained from this Google Drive link.

Here is how one of them looks like:

Number of days and number weeks

Here is the number of weeks we expect to see in the “Casualties per week” plot:

nWeeks = Round@DateDifference[DateObject[{2022, 02, 24}], DateObject[{2023, 11, 17}], "Week"]

(* 90 wk *)

Here is the number of days we expect to see in the “Casualties per day” plot:

nDays = Round@DateDifference[DateObject[{2022, 02, 24}], DateObject[{2023, 11, 03}]]

(*617 days*)

Weekly data extraction

Procedure

Here is the outline of the procedure:

  • Crop the image, so only the bar chart elements are on it
  • Binarize the image, and negated
    • So all visible bars are white on black background
  • Extracting morphological components
  • Find the bar sizes from the extracted components
  • Rescale to match real data
  • Check the absolute and relative errors between derived total number of casualties and the published one

Crop image

Here we take “the bars only” part of the image:

imgCasualtiesPerWeek2 = ImageTake[imgCasualtiesPerWeek, {120, -140}, {100, -60}]

Binarization and color negation

Binarize the cropped the image:

img = Binarize[imgCasualtiesPerWeek2, 0.85]

Here we binarize and color negate the image:

img2 = ColorNegate@Binarize[img]

Extracting morphological components

Here is the result of an application of morphological components finder:

MorphologicalComponents[img2] // Colorize

Find the bounding boxes of the morphological components:

aBoxes = SortBy[Association[ComponentMeasurements[img2, "BoundingBox"]], #[[1, 1]] &];
aBoxes = AssociationThread[Range@Length@aBoxes, Values@aBoxes];
aBoxes[[1 ;; 4]]

(*<|1 -> {{14., 6.}, {24., 473.}}, 2 -> {{25., 6.}, {35., 533.}}, 3 -> {{37., 6.}, {47., 402.}}, 4 -> {{48., 6.}, {58., 235.}}|>*)

Here we see are all component bounding boxes having the same minimum y-coordinate:

Tally@Values[aBoxes][[All, 1, 2]]

(*{{6., 66}, {7., 22}}*)

Find the heights of the rectangles and make a corresponding bar plot:

(*aHeights=Map[#\[LeftDoubleBracket]2,2\[RightDoubleBracket]-#\[LeftDoubleBracket]1,2\[RightDoubleBracket]&,aBoxes];*)
  aHeights = Map[#[[2, 2]] - Min[Values[aBoxes][[All, 1, 2]]] &, aBoxes]; 
   BarChart[aHeights, PlotTheme -> "Detailed", ImageSize -> 900]

Rescaling to match real data

The extracted data has to be rescaled to match the reported data. (We can see we have to “calibrate” the extracted data over a few points of the real data.)

Here we remake the plot above to include characteristic points we can use the calibration:

pos = Position[aHeights, Max[aHeights]][[1, 1, 1]];
pos2 = 23;
aHeights2 = aHeights;
Do[aHeights2[p] = Callout[aHeights2[[p]]], {p, {1, pos2, pos}}];
BarChart[aHeights2, GridLines -> {pos, None}, PlotTheme -> "Detailed",ImageSize -> 900]

Here are a few characteristic points of the real data

aRealHeights = <|1 -> 544, 7 -> 167, 23 -> 96, pos2 -> 414, pos -> 687|>

(*<|1 -> 544, 7 -> 167, 23 -> 414, 50 -> 687|>*)

Rescaling formula:

frm = Rescale[x, {aHeights[pos2], aHeights[pos]}, {aRealHeights[pos2], aRealHeights[pos]}]

(*369.219 + 0.539526 x*)
frm = Rescale[x, {0, aHeights[pos]}, {0, aRealHeights[pos]}]

(*0. + 1.16638 x*)

Rescaling function:

f = With[{fb = frm /. x -> Slot[1]}, fb &]

(*0. + 1.16638 #1 &*)

Apply the rescaling function:

aHeightsRescaled = Ceiling@*f /@ aHeights

(*<|1 -> 545, 2 -> 615, 3 -> 462, 4 -> 268, 5 -> 370, 6 -> 205, 7 -> 168, 8 -> 213, 9 -> 321, 10 -> 247, 11 -> 299, 12 -> 200, 13 -> 335, 14 -> 261, 15 -> 202, 16 -> 174, 17 -> 202, 18 -> 233, 19 -> 234, 20 -> 215, 21 -> 201, 22 -> 139, 23 -> 97, 24 -> 152, 25 -> 187, 26 -> 150, 27 -> 222, 28 -> 333, 29 -> 263, 30 -> 256, 31 -> 385, 32 -> 440, 33 -> 356, 34 -> 352, 35 -> 404, 36 -> 415, 37 -> 408, 38 -> 378, 39 -> 331, 40 -> 311, 41 -> 530, 42 -> 418, 43 -> 399, 44 -> 404, 45 -> 616, 46 -> 549, 47 -> 614, 48 -> 580, 49 -> 647, 50 -> 687, 51 -> 504, 52 -> 469, 53 -> 486, 54 -> 516, 55 -> 500, 56 -> 511, 57 -> 427, 58 -> 336, 59 -> 311, 60 -> 250, 61 -> 289, 62 -> 259, 63 -> 313, 64 -> 320, 65 -> 238, 66 -> 195, 67 -> 284, 68 -> 269, 69 -> 282, 70 -> 234, 71 -> 235, 72 -> 214, 73 -> 196, 74 -> 242, 75 -> 179, 76 -> 156, 77 -> 125, 78 -> 165, 79 -> 173, 80 -> 171, 81 -> 163, 82 -> 159, 83 -> 122, 84 -> 114, 85 -> 163, 86 -> 207, 87 -> 144, 88 -> 47|>*)

Here are some easy to check points (post-rescaling):

KeyTake[aHeightsRescaled, {1, 2, 7, Length[aHeightsRescaled]}]

(*<|1 -> 545, 2 -> 615, 7 -> 168, 88 -> 47|>*)

Verification check

Here is the image-extraction, estimated total:

imgTotal = aHeightsRescaled // Total

(*26961*)

The estimated total is close to the reported $26882$, with $79$absolute error and$\approx 3$‰ relative error:

reportTotal = 26882;
errAbs = N@Abs[reportTotal - imgTotal]
errRatio = N@Abs[reportTotal - imgTotal]/reportTotal

(*79.*)

(*0.00293877*)

Remark: The reported total number of casualties can be seen in the original weekly casualties screenshot above.

Daily data extraction from daily bar chart

Daily casualties extraction is not that easy with technique applied to the weekly casualties plot. One of the reasons is that the daily casualties plot is also a user input interface(on that web page).

Since we want to get daily data for calibration of (generalized) Lanchester law models we can simply extrapolate the weekly data with daily averages. We can also over-impose in some way the two images (or plots) in order to convince ourselves that we have a faithful enough interpolation.

lsDailyHeightsRescaled = Flatten@Map[Table[#, 7]/7 &, Values[aHeightsRescaled]];
BarChart[lsDailyHeightsRescaled, ImageSize -> 900, AspectRatio -> 1/8,PlotTheme -> "Web"]

Nevertheless, more faithful daily data can be obtained by image- and LLM processing the tooltips of the weekly casualties chart. (See the next section.)


Daily data extraction from weekly bar chart tooltips

Procedure

Here is the procedure outline:

  • Take multiple screenshots of the weekly casualties bar chart
    • A screenshot for each week with the corresponding tooltip shown
    • Make sure all screenshots have the same size (or nearly the same size)
      • E.g. take “window screenshots”
    • ≈90 screenshots can be taken within 15 minutes
  • Crop the screenshots appropriately
  • In order to get the tooltip tables only for each screenshot:
  • Verify good tooltips table image is obtained for each screenshot (week)
  • Do Optical Character Recognition (OCR) over the images
    • One option is to send them to an Artificial Intelligence (AI) vision service
    • Another option is to use WL’s TextRecognize
  • Parse or otherwise process the obtained OCR (or AI vision) results
  • Verify that each week is reflected in the data
    • It might happen that screenshots are not “a full set“
  • Make time series with the obtained data and compare or verify with published data and plots
    • Check are the casualties totals the same, do the plots look similar, etc.
  • Make an informative bar chart with tooltips
    • That resembles the one the screenshots were taken from
    • See the subsection “TL;DR” in the introduction

Remark: When using AI vision the prompt engineering might take a few iterations, but not that many.

Remark: The few experiments with the WL built-in text recognition produced worse results than using AI vision. Hence, they were not extended further.

Screenshots ingestion

Get screenshot file names

dirNameImport = FileNameJoin[{NotebookDirectory[], "Screenshots-Mediazona-weekly-casualties-histogram"}];
lsFileNames = FileNames["*.png", dirNameImport];
Length[lsFileNames]

(*94*)

Import images

AbsoluteTiming[
  lsImgs = Import /@ lsFileNames; 
 ]

(*{2.50844, Null}*)

Here is one of the imported images:

ImageResize[lsImgs[[14]], 900]

Definition

Here define a function that is used to batch transform the screenshots:

Clear[MakeEasyToRead];
Options[MakeEasyToRead] = {"BoundingBox" -> Automatic, "BinarizingLimits" -> Automatic};
MakeEasyToRead[img_?ImageQ, opts : OptionsPattern[]] := 
   Block[{boundingBox, mbLimits, img2, img3}, 
    
    boundingBox = OptionValue[MakeEasyToRead, "BoundingBox"]; 
    If[TrueQ[boundingBox === Automatic], boundingBox = {{380, -180}, {280, -280}}]; 
    
    mbLimits = OptionValue[MakeEasyToRead, "BinarizingLimits"]; 
    If[TrueQ[mbLimits === Automatic], mbLimits = {0.2, 0.75}]; 
    
    img2 = ImageTake[img, Sequence @@ boundingBox]; 
    img3 = MorphologicalBinarize[ColorNegate@img2, mbLimits]; 
    ImageCrop[ColorNegate[img3]] 
   ];

Remark: This function corresponds to the second and third step of the procedure outlined above.

Batch transform

AbsoluteTiming[
  lsImgTables = MakeEasyToRead[#, "BoundingBox" -> {{380, -100}, {280, -280}}, "BinarizingLimits" -> {0.4, 0.76}] & /@ lsImgs; 
 ]

(*{9.76089, Null}*)
MapIndexed[Labeled[#, #2[[1]], Top] &, lsImgTables]

Batch AI-vision application

Load the package “LLMVision.m”, [AAp1, AAn1]:

Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/Misc/LLMVision.m"]

Here we do batch AI vision application, [AAn1], using an appropriate prompt:

h = 11;
AbsoluteTiming[
  lsImgTableJSONs = 
    Table[(
      Echo[Style[{i, i + (h - 1)}, Purple, Bold], "Span:"]; 
      t = 
       LLMVisionSynthesize[{
         "Get the 1) week span, 2) total casualties 3) count per day from the image.\n", 
         "Give the result as a JSON record with keys 'week_span', 'total_casualties', and 'count_per_day'.\n", 
         "Here is example of the JSON record for each image:{\"week_span\": \"10 Mar 2022 - 16 Mar 2022\",\"total_casualties\": 462,\"count_per_day\": {\"10 Mar\": 50,\"11 Mar\": 64,\"12 Mar\": 98,\"13 Mar\": 65,\"14 Mar\": 76,\"15 Mar\": 57,\"16 Mar\": 52}}", 
         LLMPrompt["NothingElse"]["JSON"] 
        }, 
        Take[lsImgTables, {i, UpTo[i + (h - 1)]}], 
        "MaxTokens" -> 1200, "Temperature" -> 0.1]; 
      Echo[t, "OCR:"]; 
      t 
     ), 
     {i, 1, Length[lsImgs], h}]; 
 ]
(*{260.739, Null}*)

Process AI-vision results

Extract JSONs and import them as WL structures:

pres1 = Map[ImportString[StringReplace[#, {"```json" -> "", "```" -> ""}], "RawJSON"] &, lsImgTableJSONs];
pres1[[1 ;; 2]]

(*{{<|"week_span" -> "24 Feb 2022 - 2 Mar 2022", "total_casualties" -> 544, "count_per_day" -> <|"24 Feb" -> 109, "25 Feb" -> 93, "26 Feb" -> 89, "27 Feb" -> 98, "28 Feb" -> 69, "1 Mar" -> 39, "2 Mar" -> 47|>|>, <|"week_span" -> "3 Mar 2022 - 9 Mar 2022", "total_casualties" -> 614, "count_per_day" -> <|"3 Mar" -> 84, "4 Mar" -> 71, "5 Mar" -> 94, "6 Mar" -> 132, "7 Mar" -> 83, "8 Mar" -> 88, "9 Mar" -> 62|>|>, <|"week_span" -> "10 Mar 2022 - 16 Mar 2022", "total_casualties" -> 462, "count_per_day" -> <|"10 Mar" -> 50, "11 Mar" -> 64, "12 Mar" -> 98, "13 Mar" -> 65, "14 Mar" -> 76, "15 Mar" -> 57, "16 Mar" -> 52|>|>, <|"week_span" -> "17 Mar 2022 - 23 Mar 2022","total_casualties" -> 266, "count_per_day" -> <|"17 Mar" -> 28, "18 Mar" -> 44, "19 Mar" -> 33, "20 Mar" -> 36, "21 Mar" -> 51, "22 Mar" -> 28, "23 Mar" -> 46|>|>, <|"week_span" -> "24 Mar 2022 - 30 Mar 2022","total_casualties" -> 369, "count_per_day" -> <|"24 Mar" -> 61, "25 Mar" -> 70, "26 Mar" -> 49, "27 Mar" -> 30, "28 Mar" -> 46, "29 Mar" -> 57, "30 Mar" -> 56|>|>, <|"week_span" -> "31 Mar 2022 - 6 Apr 2022", "total_casualties" -> 204, "count_per_day" -> <|"31 Mar" -> 40, "1 Apr" -> 53, "2 Apr" -> 31, "3 Apr" -> 14, "4 Apr" -> 17, "5 Apr" -> 28, "6 Apr" -> 21|>|>, <|"week_span" -> "7 Apr 2022 - 13 Apr 2022", "total_casualties" -> 167, "count_per_day" -> <|"7 Apr" -> 12, "8 Apr" -> 12, "9 Apr" -> 25, "10 Apr" -> 25, "11 Apr" -> 21, "12 Apr" -> 24, "13 Apr" -> 48|>|>, <|"week_span" -> "14 Apr 2022 - 20 Apr 2022","total_casualties" -> 212, "count_per_day" -> <|"14 Apr" -> 35, "15 Apr" -> 26, "16 Apr" -> 28, "17 Apr" -> 21, "18 Apr" -> 37, "19 Apr" -> 36, "20 Apr" -> 29|>|>, <|"week_span" -> "21 Apr 2022 - 27 Apr 2022","total_casualties" -> 320, "count_per_day" -> <|"21 Apr" -> 55, "22 Apr" -> 67, "23 Apr" -> 41, "24 Apr" -> 30, "25 Apr" -> 57, "26 Apr" -> 27, "27 Apr" -> 43|>|>, <|"week_span" -> "28 Apr 2022 - 4 May 2022", "total_casualties" -> 245, "count_per_day" -> <|"28 Apr" -> 40, "29 Apr" -> 22, "30 Apr" -> 40, "1 May" -> 31, "2 May" -> 37, "3 May" -> 45, "4 May" -> 30|>|>, <|"week_span" -> "5 May 2022 - 11 May 2022", "total_casualties" -> 298, "count_per_day" -> <|"5 May" -> 42, "6 May" -> 62, "7 May" -> 41, "8 May" -> 47, "9 May" -> 30, "10 May" -> 37, "11 May" -> 39|>|>}, {<|"week_span" -> "12 May 2022 - 18 May 2022", "total_casualties" -> 199, "count_per_day" -> <|"12 May" -> 29, "13 May" -> 25, "14 May" -> 30, "15 May" -> 29, "16 May" -> 28, "17 May" -> 38, "18 May" -> 20|>|>, <|"week_span" -> "19 May 2022 - 25 May 2022","total_casualties" -> 334, "count_per_day" -> <|"19 May" -> 74, "20 May" -> 50, "21 May" -> 45, "22 May" -> 43, "23 May" -> 56, "24 May" -> 39, "25 May" -> 27|>|>, <|"week_span" -> "26 May 2022 - 1 Jun 2022", "total_casualties" -> 260, "count_per_day" -> <|"26 May" -> 45, "27 May" -> 37, "28 May" -> 41, "29 May" -> 44, "30 May" -> 26, "31 May" -> 26, "1 Jun" -> 41|>|>, <|"week_span" -> "2 Jun 2022 - 8 Jun 2022", "total_casualties" -> 201, "count_per_day" -> <|"2 Jun" -> 21, "3 Jun" -> 33, "4 Jun" -> 25, "5 Jun" -> 42, "6 Jun" -> 24, "7 Jun" -> 31, "8 Jun" -> 25|>|>, <|"week_span" -> "9 Jun 2022 - 15 Jun 2022", "total_casualties" -> 173, "count_per_day" -> <|"9 Jun" -> 35, "10 Jun" -> 22, "11 Jun" -> 24,"12 Jun" -> 24, "13 Jun" -> 21, "14 Jun" -> 34, "15 Jun" -> 13|>|>, <|"week_span" -> "16 Jun 2022 - 22 Jun 2022","total_casualties" -> 201, "count_per_day" -> <|"16 Jun" -> 23, "17 Jun" -> 37, "18 Jun" -> 14, "19 Jun" -> 26, "20 Jun" -> 27, "21 Jun" -> 40, "22 Jun" -> 34|>|>, <|"week_span" -> "30 Jun 2022 - 6 Jul 2022", "total_casualties" -> 233, "count_per_day" -> <|"30 Jun" -> 39, "1 Jul" -> 13, "2 Jul" -> 40, "3 Jul" -> 43, "4 Jul" -> 41, "5 Jul" -> 28, "6 Jul" -> 29|>|>, <|"week_span" -> "7 Jul 2022 - 13 Jul 2022", "total_casualties" -> 214, "count_per_day" -> <|"7 Jul" -> 39, "8 Jul" -> 48, "9 Jul" -> 47, "10 Jul" -> 18, "11 Jul" -> 14, "12 Jul" -> 17, "13 Jul" -> 31|>|>, <|"week_span" -> "14 Jul 2022 - 20 Jul 2022","total_casualties" -> 200, "count_per_day" -> <|"14 Jul" -> 17, "15 Jul" -> 13, "16 Jul" -> 29, "17 Jul" -> 28, "18 Jul" -> 24, "19 Jul" -> 46, "20 Jul" -> 43|>|>, <|"week_span" -> "21 Jul 2022 - 27 Jul 2022","total_casualties" -> 138, "count_per_day" -> <|"21 Jul" -> 21, "22 Jul" -> 44, "23 Jul" -> 22, "24 Jul" -> 11, "25 Jul" -> 20, "26 Jul" -> 3, "27 Jul" -> 17|>|>}}*)

Make a list of weekly records and make sure to have unique data records:

pres2 = Union[Flatten[pres1]];
Length[pres2]

(*89*)

To each record add a WL expression for the extracted week span and sort the records by week start date:

pres3 = Map[Prepend[#, "WeekSpan" -> Map[DateObject@*StringTrim, StringSplit[#["week_span"], "-"]]] &, pres2];
pres3 = SortBy[pres3, First@#WeekSpan &];

Here are the first two records:

pres3[[1 ;; 2]]

Verification (all weeks are present)

Summarize the starts of week:

ResourceFunction["RecordsSummary"][Map[First@#WeekSpan &, pres3]]

Make sure consistent weekly data is obtained:

Differences[Sort@Map[First@#WeekSpan &, pres3]] // Tally

Plots

Here is bar chart with tooltips based using the extracted data:

BarChart[Tooltip[#["total_casualties"], Labeled[Grid[Map[{#[[1]], " : ", #[[2]]} &, List @@@ Normal[#["count_per_day"]]]], Column[{Style[#["week_span"], Blue], Row[{"total casualties:", Spacer[3], Style[#["total_casualties"], Red]}]}], Top]] & /@ pres3, AxesLabel -> {"Week", "Number of\nkilled"}, ImageSize -> 700]

Remark: See the subsection “TL;DR” in the introduction for a better plot.

Here we make the corresponding daily casualties time series and plot it:

pres4 = Map[AssociationThread[DateRange @@ #WeekSpan, Values[#["count_per_day"]]] &, pres3];
pres5 = Join @@ pres4;
tsCasualties = TimeSeries[pres5];
DateListPlot[tsCasualties, PlotRange -> All, AspectRatio -> 1/6, FrameLabel -> {"Time", "Number of killed"}, ImageSize -> 1200]

Verification (with published results)

Here is the total number casualties based on the extracted data:

tooltipTotal = Total@tsCasualties

(*26879*)

It compares very well with the total number in the Mediazona’s plot — $3$ as an absolute error and$\approx 0.1$‰ relative error:

reportTotal = 26882;
errAbs = N@Abs[reportTotal - tooltipTotal]
errRatio = N@Abs[reportTotal - tooltipTotal]/reportTotal

(*3.*)

(*0.000111599*)

Additional comments and remarks

Good agreement between the two procedures

The two data extraction procedures agree very well over the extracted totals of casualties.

(Also good agreement with the “official” published total — approximately $3$‰ and $0.1$‰ respectively.)

LLMVision package

The function LLMVisionSynthesize used above is from the package “LLMVision.m”, [AAp1, AAn1]. One of the primary reasons to develop the package “LLMvision.m” was to use it in workflows like those above — extracting data from different sources in order to do war simulations.

Remark: In the section above LLMVisionSynthesize uses Base64 conversion of images. OpenAI’s Vision documentation advices to use URLs instead of Base64 images in long conversations.

Why apply image transformations when using AI vision?

One can ask:

Why do certain image transformations, or other image preprocessing, if we are using AI vision functionalities? 

Can’t we just apply the AI?!

There are multiple reasons for preprocessing the images that are on different conceptual and operational levels:

  • We want to be able to use the same workflow but with different OCR algorithms that are “smaller” and “less AI”
  • Images having only the information to be extracted produce more reliable results
    • This obvious when OCR functions are used (like TextRecognize)
    • Less prompt engineering would be needed with AI-vision (most likely)
  • It is much cheaper — both computationally and money-wise — to use some smaller images for processed conveniently

Remark: OpenAI’s vision documentation discusses the money costs, preferred image formats, and reliability — see this “Limitations” section.

JSON data

The extracted daily Mediazona data was exported to JSON with this command:

(*Export[FileNameJoin[{NotebookDirectory[],"mediaZonaData.json"}],Map[Normal,mediaZonaData]/.d_DateObject:>DateString[d,"ISODate"]]*)

References

Articles

[MZ1] Mediazona, Russian casualties in Ukraine, (2022-2023).

[OAIb1] OpenAI team, “New models and developer products announced at DevDay” , (2023), OpenAI/blog .

[Wk1] Wikipedia, “Mediazona”.

Functions

[WRIf1] Wolfram Research, Inc., MorphologicalBinarize, Wolfram Language function,(2010), (updated 2012).

[WRIf2] Wolfram Research, Inc, ImageCrop, Wolfram Language function,(2008), (updated 2021).

[WRIf3] Wolfram Research, Inc, TextRecognize, Wolfram Language function,(2010), (updated 2020).

Notebooks

[AAn1] Anton Antonov, “AI vision via Wolfram Language​​”, November 26, (2023), Wolfram Community, STAFF PICKS.

Packages, paclets

[AAp1] Anton Antonov, LLMVision.m, Mathematica package, (2023), GitHub/antononcube .

Generalized Lanchester combat models

Introduction

In this blog post (based on a WL notebook) we present generalized Lanchester combat models and use one of them for simulation and calibration examples with data of the Battle of Iwo Jima.

Remark: The content of this notebook is intentionally theoretically fundamental, but short and simple both mathematically and code-wise. Author’s intent is to develop similar computational workflows for (1) field battles, like the Normandy campaign during Word War II, 1944, or (2) urban warfare, like Second Battle of Fallujah, Iraq 2009, or Battle of Bakhmut, Ukraine 2023.

The main computational workflows are done with a System Dynamics (SD) software monad implemented in the paclet “MonadicSystemDynamics”, [AAp2]. That paclet is based on the monad implementation presented in “A monad for Epidemiologic Compartmental Modeling Workflows”, [AA1]. The functions provided by the paclet [AAp2] have the prefix “SDMon”, which stands for “System Dynamics Monad”.

Why use the Battle of Iwo Jima in examples?

The examples below use the Battle of Iwo Jima because that is convenient both data-wise and mathematics-wise. Here are our reasons:

  • The battle is important for the USA military, hence well documented and used in multiple contexts.
    • See, for example, mathematical articles like [JE1] and [RS1].
  • (Relatively) well curated data can be found. Like:
    • Sizes of the military forces
    • Battle duration
  • There is no need to:
    • Take care of negative stocks
    • Simulate “will to fight” — Japanese soldiers fought to the last one
      • Japanese Prisoners Of War (POWs) became POWs because they were found unconscious…

Here is the invasion map of Iwo Jima, prepared in February 1945, [DR1]:

Document structure

  • Generalized model and variants Main theory.
  • SDMon Model Programmer’s version.
  • Direct model simulation Using the Battle of Iwo Jima data and related pre-computed rates.
  • Calibration We can get the theoretically computed rates by using numerics!
  • Future plans Make models, not war.

Generalized models

This section presents a translation to English of introductory paragraphs of [NM1]. (The same general model and breakdown is presented in [AS1].)

In the most general form, the Lanchester models can be described by the by the equation:

where:

  • $a$ and $e$ define the rate of non-combat losses
  • $b$ and $f$ define the rate of losses due to exposure to area targets
  • $c$ and $g$ are losses due to forward enemy exposure
  • $d$ and $h$ are approaching or retreating reserves

To determine the casualties of wars, actual or potential, the following four models are of greatest importance.

1. Lanchester proper model (only the coefficients $b$ and $f$ are available)

In this case:

  • The number of casualties is proportional to the number of encounters between individuals.
  • The number of encounters between individuals of the opposing sides is proportional to the number of victims.
  • The number of victims is proportional to the number of encounters between individuals of the opposing parties.
    • Product of the number of parties: $x \times y$.
  • This interaction is most relevant when the two sides are located in a common territory:
    • Guerrilla warfare, repression, enmity between two ethnic groups, etc.

2. Osipov model (coefficients $a$ and $e$)

  • The number of victims is proportional to the number of the opposing side.
  • This could be a classic military engagement where the two sides are in contact only on the front lines.

3. Peterson model (coefficients $a$ and $e$)

  • The number of casualties is determined by the size of one’s side.
  • This could be a model of the Cold War, where the more of their submarines are on alert, the more of them die.

4. Brackney model (coefficients $a$ and $f$ or $b$ and $e$)

  • The casualties of one side is proportional to the number of encounters and the other to the number of its opponent.
  • The model was inspired of the Vietnam War and describes quite satisfactory.
    • I.e. a conflict in which one side is engaged in classical warfare and the other in guerrilla warfare.

Model “rigidness”

The simplest, with Osipov terms only, generalized Lanchester model is:

That model is a “rigid model” that admits an exact solution. (See Arnold’s book “«Rigid» and «soft» mathematical models”, [VA1].)

Here is the solution:

The evolution of the number of soldiers of armies $x$ and $y$ occurs along the hyperbola given by the equation $a x(t) ^2- b y(t)^2\text{==}\text{const}.$ The war evolves along that hyperbola, hence it depends on the starting point.

The corresponding manifold of hyperbolas is separated by the line $a x = b y$. If the starting point lies above this line, then the hyperbola finishes on the $y$-axis. This means that in the course of the war army $x$ decreases to zero and army $y$ wins.

Remark: Note that if the efficiency coefficients $a$ and $b$ are not constant then the equation $a x = b y$ determines a curve.

Here is an interactive interface that illustrates the properties of the simplest model:

Manipulate[
ContourPlot[fX[a, x] x - fY[b, y] y, {x, 0, 10}, {y, 0, 10},
PlotLegends -> Automatic,
Contours -> Evaluate[Join[{{0, {Red, Thick}}}, DeleteCases[Table[{i, Blue}, {i, -100, 100, 0.25}], {0 | 0., _}]]],
ContourShading -> False,
FrameLabel -> {"x", "y"}],
{{a, 0.1, "a, efficiency constant X:"}, 0, 1, 0.001, Appearance -> "Open"},
{{b, 0.2, "b, efficiency constant Y:"}, 0, 1, 0.001, Appearance -> "Open"},
{{fX, Sqrt[#1 Sqrt[#2]] &, "efficiency function X:"},
{(Sqrt[#1] &) -> "\!\(\*SqrtBox[\(a\)]\)",
(Sqrt[#1 Sqrt[#2]] &) -> "\!\(\*SqrtBox[\(a \*SqrtBox[\(x\)]\)]\)"},
ControlType -> SetterBar},
{{fY, Sqrt[#1] &, "efficiency function Y:"},
{(Sqrt[#1] &) -> "\!\(\*SqrtBox[\(b\)]\)",
(Sqrt[#1 Sqrt[#2]] &) -> "\!\(\*SqrtBox[\(b \*SqrtBox[\(y\)]\)]\)"},
ControlType -> SetterBar}]

SDMon model

In this section we define the general model in a simple programmatic form using the paclet “MonadicSystemDynamics”, [AAp2].

Remark: A better programmatic form would have equation elements that prevent (army) stocks to become negative.

Remark: Compared to the previous section, below we follow “wordier” but self-explanatory notation that helps model understanding, evaluation, and enhancements.

Here are the stocks:

aStocks = <| X[t] -> "Soldiers of army X", Y[t] -> "Soldiers of army Y" |>;

Here are the rates:

aRates = <|
    fireEfficiencyX -> "Efficiency of force X ", 
    fireEfficiencyY -> "Efficiency of force Y", 
    fireEfficiencyXonY -> "Efficiency of force X on Y", 
    fireEfficiencyYonX -> "Efficiency of force Y on X", 
    growthX -> "Growth rate of force X due to new recruits", 
    growthY -> "Growth rate of force Y due to new recruits", 
    diseaseX -> "Disease rate in force X", 
    diseaseY -> "Disease rate in force Y", 
    fireFriendlyX -> "Friendly fire rate in force X", 
    fireFriendlyY -> "Friendly fire rate in force Y" 
   |>;

Here are rules that assign concrete values to the rates:

aRateRules = <| fireEfficiencyX -> 0.01, fireEfficiencyY -> 0.015, fireEfficiencyXonY -> 0.05, fireEfficiencyYonX -> 0.03, growthX -> 0, growthY -> 0, diseaseX -> 0.01, diseaseY -> 0.01, fireFriendlyX -> 0.001, fireFriendlyY -> 0.001 |>;

Initial conditions (number of soldiers):

lsInitConds = {X[0] == 100000, Y[0] == 100000};

Lanchester (or Lotka-Volterra) interactions:

lotkaVolterraX = fireEfficiencyYonX*X[t]*Y[t]; lotkaVolterraY = fireEfficiencyXonY*X[t]*Y[t];

Osipov interactions:

osipovX = fireEfficiencyY*Y[t]; osipovY = fireEfficiencyX*X[t];

Equations:

lsEquations = { X'[t] == growthX - diseaseX*X[t] - fireFriendlyX*X[t] - lotkaVolterraX - osipovX, Y'[t] == growthY - diseaseY*Y[t] - fireFriendlyY*Y[t] - lotkaVolterraY - osipovY };

Make the model data structure:

model1 = <| "Rates" -> aRates, "Stocks" -> aStocks, "RateRules" -> aRateRules, "InitialConditions" -> lsInitConds, "Equations" -> lsEquations |>;

Display the model in tabular format:

ModelGridTableForm[model1]


Direct model simulation

Here we follow the equations setup and data in the article [JE1].

Remark: The data in [JE1] is later revisited and new computations were made with almost the same results. See [RS1].

Clear[growthIwoJimaX]; growthIwoJimaX[t_] := Piecewise[{{54000, 0 <= t < 1}, {6000, 2 <= t < 3}, {13000, 5 <= t < 6}}, 0]

Here replace the constant term of the first equation in the generalized model with the growth function growthIwoJimaX.

model1a = model1 /. growthX -> growthIwoJimaX[t];

Here create the monadic pipeline, put the fire efficiency rates found in [JE1], simulate the for battles number of days, and plot the result:

sdObj =
  SDMonUnit[]⟹
   SDMonSetSingleSiteModel[model1a]⟹
   SDMonAssignRateRules[<|
     growthY -> 0,
     diseaseX -> 0, diseaseY -> 0,
     fireEfficiencyYonX -> 0, fireEfficiencyXonY -> 0,
     fireEfficiencyX -> 0.0106, fireEfficiencyY -> 0.0544
     |>]⟹
   SDMonAssignInitialConditions[<|X[0] -> 0, Y[0] -> 21000|>]⟹
   SDMonEcho[Style["Model's tabular form:", Bold, Purple, FontSize -> 18, FontFamily -> "Times"]]⟹
   SDMonEchoModelGridTableForm⟹
   SDMonSimulate[35]⟹
   SDMonEcho[Style["Plot simulated stocks evolution:", Bold, Purple, FontSize -> 18, FontFamily -> "Times"]]⟹
   SDMonPlotSolutions[];

Calibration

In this section we show that using optimization methods — calibration — we can obtain the same efficiency rates as the ones theoretically computed in [JE1] using the same data.

Remark: This should bring some confidence in using SDMon; and since the calibration process is easy to specify, it should encourage SDMon’s use for other SD models.

aTargets2 = N@
    <|
     X -> Map[Interpolation[{{0, Total@Map[growthIwoJimaX, Range[0, 36]]}, {35, 51300}}, InterpolationOrder -> 1], Range[0, 35]], 
     Y -> Interpolation[{{0, 21000}, {35, 0}}, InterpolationOrder -> 1] /@ Range[0, 35] 
    |>;

ListLinePlot[aTargets2, PlotTheme -> "Detailed"]

Here is the first point of the stock $X$ (USA soldiers):

Total@Map[growthIwoJimaX, Range[0, 35]] (*73000*)

Here is a model setup pipeline:

sdObj2 = 
SDMonUnit[]⟹
SDMonSetSingleSiteModel[model1a]⟹
SDMonAssignRateRules[<|
growthY -> 0,
diseaseX -> 0, diseaseY -> 0,
fireEfficiencyYonX -> 0, fireEfficiencyXonY -> 0
|>]⟹
SDMonAssignInitialConditions[<|X[0] -> 0, Y[0] -> 21000|>]⟹
SDMonEchoModelGridTableForm;

Here is a calibration pipeline that specifies:

  1. Target time series of the stocks
  2. Parameters to calibrate
  3. Parameter ranges, within which to search the calibration values
  4. Distance function from the computed time series to the target time series
  5. Parameters for NMinimize
resCalibration = 
  sdObj2⟹
   SDMonCalibrate[
    "Target" -> KeyTake[aTargets2, {X, Y}], 
    "Parameters" -> <|fireEfficiencyX -> {0, 0.1}, fireEfficiencyY -> {0, 0.1}|>, 
    DistanceFunction -> EuclideanDistance, 
    Method -> {"NelderMead", "PostProcess" -> False}, 
    MaxIterations -> 1000]⟹
   SDMonTakeValue

(*{83614.5, {fireEfficiencyX -> 0.00990159, fireEfficiencyY -> 0.0399577}}*)

Here we plot the simulated army sizes using the calibrated values:

sdObj2⟹
SDMonSimulate[35]⟹
SDMonAssignRateRules[
Association@resCalibration[[2]]]⟹
SDMonEcho[
Style["Plot simulated stocks evolution with calibrated values:",
Bold, Purple, FontSize -> 18,
FontFamily -> "Times"]]⟹
SDMonPlotSolutions[];

Here we use a more “complicated” distance function that takes only the known points of the time series:

resCalibration2 = 
  sdObj2⟹
   SDMonCalibrate[
    "Target" -> KeyTake[aTargets2, {X, Y}], 
    "Parameters" -> <|fireEfficiencyX -> {0, 0.1}, fireEfficiencyY -> {0, 0.1}|>, 
    DistanceFunction -> (EuclideanDistance[#1[[{1, -1}]], #2[[{1, -1}]]] &), 
    Method -> {"NelderMead", "PostProcess" -> False}, MaxIterations -> 1000]⟹
   SDMonTakeValue

(*{73000., {fireEfficiencyX -> 0.0106351, fireEfficiencyY -> 0.0547895}}*)

Remark: Note that with that new calibration distance function we get almost the same results as the theoretically computed efficiency rates in [JE1]:

<|fireEfficiencyX -> 0.0106, fireEfficiencyY -> 0.0544|>

Remark: Proper calibration time series for the USA soldiers stock $X$ can be obtained from the web page “Iwo Jima, a look back”. (The corresponding data ingestion notebook is going to be published soon.)


Future plans

Here are a few directions to extend this work into:

  • Inclusion of different types of forces
  • Simulation of “will to fight”
    • Easy with NDSolve and, hence, with SDMon.
  • Inclusion of weapon and ammunition production stocks and related supply rates
    • For example, as in [AA2].
  • Modeling the war impact on countries’ economics and populations
  • Modeling the role of propaganda
  • Make interactive interfaces with knobs for the parameters
    • With selectors of scenarios based on known battles.

Setup

PacletInstall["AntonAntonov/EpidemiologicalModeling"]; PacletInstall["AntonAntonov/MonadicSystemDynamics"];
Needs["AntonAntonov`EpidemiologicalModeling`"] Needs["AntonAntonov`MonadicSystemDynamics`"]

References

Articles

[AA1] Anton Antonov, “A monad for Epidemiologic Compartmental Modeling Workflows”, (2021), MathematicaForPrediction at WordPress.

[AS1] Andrei Shatyrko, Bedrik Puzha, Veronika Novotná, “Comparative Analysis and New Field of Application Lanchester’s Combat Models”, (2018), Post-conference proceedings of selected papers extended version Conference MITAV-2018, Brno, Czech Republic, 2018. P.118-133. ISBN 978-80-7582-065-5.

[JE1] J.H. Engel, “A verification of Lanchester’s law”, (1953), Journal of the Operations Research Society of America, Vol. 2, No. 2. (May, 1954), pp. 163-171. (JSTOR link.)

[DR1] J. David Rogers, “Iwo Jima: The costliest battle in American history”, Missouri S&T. (jdavidrogers.net.)

[NM1] Nikolai V. Mityukov. Determining the casualties of wars through Lanchester models, (2009), Historical Psychology and Sociology of History.

[NM1ru] Н. В. МИТЮКОВ, ОПРЕДЕЛЕНИЕ ЖЕРТВ ВОЙН ЧЕРЕЗ ЛАНЧЕСТЕРСКИЕ МОДЕЛИИсторическая психология и социология истории 2/2009 122–140.

[RS1] Robert W. Samz, ⎡Some Comments on Engel’s “A Verification of Lanchester’s Law”⎦, (1972), Operations Research, Vol. 20, No. 1 (Jan. – Feb., 1972), pp. 49-52 (4 pages) (JSTOR link.)

[Wk1] Lanchester’s laws, Wikipedia.

[Wk2] Battle of Iwo Jima, Wikipedia.

Books

[VA1] Vladimir I. Arnold, Rigid and soft mathematical models, 2nd ed. (2008), Moscow Center of Continuous Mathematical Education. In Russian: Владимир И. Арнольд, “«Жесткие» и «мягкие» математические модели”, (2008), М.: МЦНМО, 2014, 32 с. ISBN 978-5-94057-427-9.

Packages, paclets

[AAp1] Anton Antonov, EpidemiologicalModeling, WL paclet, (2023), Wolfram Language Paclet Repository.

[AAp2] Anton Antonov, MonadicSystemDynamics, WL paclet, (2023), Wolfram Language Paclet Repository.

Repositories

[AAr1] Anton Antonov, System Modeling, (2020-2023), GitHub/antononcube.

AI vision via Wolfram Language

Introduction

In the fall of 2023 OpenAI introduced the image vision model “gpt-4-vision-preview”, [OAIb1].

The model “gpt-4-vision-preview” represents a significant enhancement to the GPT-4 model, providing developers and AI enthusiasts with a more versatile tool capable of interpreting and narrating images alongside text. This development opens up new possibilities for creative and practical applications of AI in various fields.

For example, consider the following Wolfram Language (WL), developer-centric applications:

  • Narration of UML diagrams
  • Code generation from narrated (and suitably tweaked) narrations of architecture diagrams and charts
  • Generating presentation content draft from slide images
  • Extracting information from technical plots
  • etc.

A more diverse set of the applications would be:

  • Dental X-ray images narration
  • Security or baby camera footage narration
    • How many people or cars are seen, etc.
  • Transportation trucks content descriptions
    • Wood logs, alligators, boxes, etc.
  • Web page visible elements descriptions
    • Top menu, biggest image seen, etc.
  • Creation of recommender systems for image collections
    • Based on both image features and image descriptions
  • etc.

As a first concrete example, consider the following image that fable-dramatizes the name “Wolfram” (https://i.imgur.com/UIIKK9w.jpg):

RemoveBackground@Import[URL["https://i.imgur.com/UIIKK9wl.jpg"]]
1xg1w9gct6yca

Here is its narration:

LLMVisionSynthesize["Describe very concisely the image", "https://i.imgur.com/UIIKK9w.jpg", "MaxTokens" -> 600]

You are looking at a stylized black and white illustration of a wolf and a ram running side by side among a forest setting, with a group of sheep in the background. The image has an oval shape.

Remark: In this notebook Mathematica and Wolfram Language (WL) are used as synonyms.

Remark: This notebook is the WL version of the notebook “AI vision via Raku”, [AA3].

Ways to use with WL

There are five ways to utilize image interpretation (or vision) services in WL:

  • Dedicated Web API functions, [MT1, CWp1]
  • LLM synthesizing, [AAp1, WRIp1]
  • LLM functions, [AAp1, WRIp1]
  • Dedicated notebook cell type, [AAp2, AAv1]
  • Any combinations of the above

In this document are demonstrated the second, third, and fifth. The first one is demonstrated in the Wolfram Community post “Direct API access to new features of GPT-4 (including vision, DALL-E, and TTS)” by Marco Thiel, [MT1]. The fourth one is still “under design and consideration.”

Remark: The model “gpt-4-vision-preview” is given as a “chat completion model” , therefore, in this document we consider it to be a Large Language Model (LLM).

Packages and paclets

Here we load WL package used below, [AAp1, AAp2, AAp3]:

Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/Misc/LLMVision.m"]

Remark: The package LLMVision is “temporary” – It should be made into a Wolfram repository paclet, or (much better) its functionalities should be included in the “LLMFunctions” framework, [WRIp1].

Images

Here are the links to all images used in this document:

tblImgs = {{Row[{"Wolf and ram running together in forest"}], Row[{"https://i.imgur.com/UIIKK9w.jpg", ""}]}, {Row[{"LLM", " ", "functionalities", " ", "mind-map", ""}], Row[{"https://i.imgur.com/kcUcWnql.jpg", ""}]}, {Row[{"Single", " ", "sightseer", ""}], Row[{"https://i.imgur.com/LEGfCeql.jpg", ""}]}, {Row[{"Three", " ", "hunters", ""}], Row[{"https://raw.githubusercontent.com/antononcube/Raku-WWW-OpenAI/main/resources/ThreeHunters.jpg", ""}]}, {Row[{"Cyber", " ", "Week", " ", "Spending", " ", "Set", " ", "to", " ", "Hit", " ", "New", " ", "Highs", " ", "in", " ", "2023", ""}], Row[{"https://cdn.statcdn.com/Infographic/images/normal/7045.jpeg", ""}]}};
tblImgs = Map[Append[#[[1 ;; 1]], Hyperlink[#[[-1, 1, 1]]]] &, tblImgs];
TableForm[tblImgs, TableHeadings -> {None, {"Name", "Link"}}] /. {ButtonBox[n_, BaseStyle -> "Hyperlink", ButtonData -> { URL[u_], None}] :> Hyperlink[n, URL[u]]}
Name Link
Wolf and ram running together in forest Link
LLM functionalities mind-map Link
Single sightseer Link
Three hunters Link
Cyber Week Spending Set to Hit New Highs in 2023 Link

Document structure

Here is the structure of the rest of the document:

  • LLM synthesizing
    … using multiple image specs of different kind.
  • LLM functions
    … workflows over technical plots.
  • Dedicated notebook cells
    … just excuses why they are not programmed yet.
  • Combinations (fairytale generation)
    … Multi-modal applications for replacing creative types.
  • Conclusions and leftover comments
    … frustrations untold.

LLM synthesizing

The simplest way to use the OpenAI’s vision service is through the function LLMVisionSynthesize of the package “LLMVision”, [AAp1]. (Already demoed in the introduction.)

If the function LLMVisionSynthesize is given a list of images, a textual result corresponding to those images is returned. The argument “images” is a list of image URLs, image file names, or image Base64 representations. (Any combination of those element types can be specified.)

Before demonstrating the vision functionality below we first obtain and show a couple of images.

Images

Here is a URL of an image: (https://i.imgur.com/LEGfCeql.jpg). Here is the image itself:

Import[URL["https://i.imgur.com/LEGfCeql.jpg"]]
1u02ytqvf7xi9

OpenAI’s vision endpoint accepts POST specs that have image URLs or images converted into Base64 strings. When we use the LLMVisionSynthesize function and provide a file name under the “images” argument, the Base64 conversion is automatically applied to that file. Here is an example of how we apply Base64 conversion to the image  from a given file path:

img1 = Import[$HomeDirectory <> "/Downloads/ThreeHunters.jpg"];
ColumnForm[{
   img1, 
   Spacer[10], 
   ExportString[img1, {"Base64", "JPEG"}] // Short}]
0wmip47gloav0

Image narration

Here is an image narration example with the two images above, again, one specified with a URL, the other with a file path:

LLMVisionSynthesize["Give concise descriptions of the images.", {"https://i.imgur.com/LEGfCeql.jpg", $HomeDirectory <> "/Downloads/ThreeHunters.jpg"}, "MaxTokens" -> 600]

1. The first image depicts a single raccoon perched on a tree branch, surrounded by a plethora of vibrant, colorful butterflies in various shades of blue, orange, and other colors, set against a lush, multicolored foliage background.

2. The second image shows three raccoons sitting together on a tree branch in a forest setting, with a warm, glowing light illuminating the scene from behind. The forest is teeming with butterflies, matching the one in the first image, creating a sense of continuity and shared environment between the two scenes.

Description of a mind-map

Here is an application that should be more appealing to WL-developers – getting a description of a technical diagram or flowchart. Well, in this case, it is a mind-map from [AA2]:

Import[URL["https://i.imgur.com/kcUcWnql.jpeg"]]
1ukmn97ui4o98

Here are get the vision model description of the mind-map above (and place the output in Markdown format):

mmDescr = LLMVisionSynthesize["How many branches this mind-map has? Describe each branch separately. Use relevant emoji prefixes.", "https://imgur.com/kcUcWnq.jpeg", "MaxTokens" -> 900]
This mind map has four primary branches, each diverging from a \
central node labeled "LLM functionalities." I will describe each one \
using relevant emoji prefixes:

1. 🖼️ **DALL-E** branch is in yellow and represents an access point to \
the DALL-E service, likely a reference to a Large Language Model \
(LLM) with image generation capabilities.

2. 🤖 **ChatGPT** branch in pink is associated with the ChatGPT \
service, suggesting it's a conversational LLM branch. There are two \
sub-branches:
   - **LLM prompts** indicates a focus on the prompts used to \
communicate with LLMs.
   - **Notebook-wide chats** suggests a feature or functionality for \
conducting chats across an entire notebook environment.

3. 💬 **LLM chat objects** branch in purple implies that there are \
objects specifically designed for chat interactions within LLM \
services.

4. ✍️ **LLM functions** branch in green seems to represent various \
functional aspects or capabilities of LLMs, with a sub-branch:
   - **Chatbooks** which may indicate a feature or tool related to \
managing or organizing chat conversations as books or records.

Converting descriptions to diagrams

Here from the obtained description we request a (new) Mermaid-JS diagram to be generated:

mmdChart = LLMSynthesize[{LLMPrompt["CodeWriter"], "Make the corresponding Mermaid-JS diagram code for the following description. Give the code only, without Markdown symbols.", mmDescr}]
graph TB
    center[LLM functionalities]
    center --> dalle[DALL-E]
    center --> chat[ChatGPT]
    center --> chatobj[LLM chat objects]
    center --> functions[LLM functions]
    chat --> prompts[LLM prompts]
    chat --> notebook[Notebook-wide chats]
    functions --> chatbooks[Chatbooks]

Here is a diagram made with the Mermaid-JS spec obtained above using the resource function of “MermaidInk”, [AAf1]:

ResourceFunction["MermaidInk"][mmdChart]
1qni2g4n8vywf

Below is given an instance of one of the better LLM results for making a Mermaid-JS diagram over the “vision-derived” mind-map description.

ResourceFunction["MermaidInk"]["
graph 
 TBA[LLM services access] --> B[DALL-E]
 A --> C[ChatGPT]
 A --> D[PaLM]
 A --> E[LLM chat objects]
 A --> F[Chatbooks]
 B -->|related to| G[DALL-E AI system]
 C -->|associated with| H[ChatGPT]
 D -->|related to| I[PaLM model]
 E -->|part of| J[chat-related objects/functionalities]
 F -->|implies| K[Feature or application related to chatbooks]
"]
0f0fuo9nexxl8

Code generation from image descriptions

Here is an example of code generation based on the “vision derived” mind-map description above:

LLMSynthesize[{LLMPrompt["CodeWriter"], "Generate the Mathematica code of a graph that corresponds to the description:\n", mmDescr}]
Graph[{"LLM services access" -> "DALL-E","LLM services access" -> "ChatGPT",
"LLM services access" -> "PaLM",
"LLM services access" -> "LLM functionalities",
"LLM services access" -> "Chatbooks","LLM services access" -> "Notebook-wide chats",
"LLM services access" -> "Direct access of LLM services","LLM functionalities" -> "LLM prompts",
"LLM functionalities" -> "LLM functions","LLM functionalities" -> "LLM chat objects"},
VertexLabels -> "Name"]
ToExpression[%]
0cmyq0lep1q7f

Analyzing graphical WL results

Consider another “serious” example – that of analyzing chess play positions. Here we get a chess position using the paclet “Chess”, [WRIp3]:

175o8ba3cxgoh
0scq7lbpp7xfs

Here we describe it with “AI vision”:

LLMVisionSynthesize["Describe the position.", Image[b2], "MaxTokens" -> 1000, "Temperature" -> 0.05]
This is a chess position from a game in progress. Here's the \
description of the position by algebraic notation for each piece:

White pieces:
- King (K) on c1
- Queen (Q) on e2
- Rooks (R) on h1 and a1
- Bishops (B) on e3 and f1
- Knights (N) on g4 and e2
- Pawns (P) on a2, b2, c4, d4, f2, g2, and h2

Black pieces:
- King (K) on e8
- Queen (Q) on e7
- Rooks (R) on h8 and a8
- Bishops (B) on f5 and g7
- Knights (N) on c6 and f6
- Pawns (P) on a7, b7, c7, d7, f7, g7, and h7

It's Black's turn to move. The position suggests an ongoing middle \
game with both sides having developed most of their pieces. White has \
castled queenside, while Black has not yet castled. The white knight \
on g4 is putting pressure on the black knight on f6 and the pawn on \
h7. The black bishop on f5 is active and could become a strong piece \
depending on the continuation of the game.

Remark: In our few experiments with these kind of image narrations, a fair amount of the individual pieces are described to be at wrong chessboard locations.

Remark: In order to make the AI vision more successful, we increased the size of the chessboard frame tick labels, and turned the “a÷h” ticks uppercase (into “A÷H” ticks.) It is interesting to compare the vision results over chess positions with and without that transformation.

LLM Functions

Let us show more programmatic utilization of the vision capabilities.

Here is the workflow we consider:

  1. Ingest an image file and encode it into a Base64 string
  2. Make an LLM configuration with that image string (and a suitable model)
  3. Synthesize a response to a basic request (like, image description)
    • Using LLMSynthesize
  4. Make an LLM function for asking different questions over image
    • Using LLMFunction
  5. Ask questions and verify results
    • ⚠️ Answers to “hard” numerical questions are often wrong.
    • It might be useful to get formatted outputs

Remark: The function LLMVisionSynthesize combines LLMSynthesize and step 2. The function LLMVisionFunction combines LLMFunction and step 2.

Image ingestion and encoding

Here we ingest an image and display it:

imgBarChart = Import[$HomeDirectory <> "/Downloads/Cyber-Week-Spending-Set-to-Hit-New-Highs-in-2023-small.jpeg"]
0iyello2xfyfo

Remark: The image was downloaded from the post “Cyber Week Spending Set to Hit New Highs in 2023” .

Configuration and synthesis

Here we synthesize a response of a image description request:

LLMVisionSynthesize["Describe the image.", imgBarChart, "MaxTokens" -> 600]
The image shows a bar chart infographic titled "Cyber Week Spending \
Set to Hit New Highs in 2023" with a subtitle "Estimated online \
spending on Thanksgiving weekend in the United States." There are \
bars for five years (2019, 2020, 2021, 2022, and 2023) across three \
significant shopping days: Thanksgiving Day, Black Friday, and Cyber \
Monday.

The bars represent the spending amounts, with different colors for \
each year. The spending for 2019 is shown in navy blue, 2020 in a \
lighter blue, 2021 in yellow, 2022 in darker yellow, and 2023 in dark \
yellow, with a pattern that clearly indicates the 2023 data is a \
forecast.

From the graph, one can observe an increasing trend in estimated \
online spending, with the forecast for 2023 being the highest across \
all three days. The graph also has an icon that represents online \
shopping, consisting of a computer monitor with a shopping tag.

At the bottom of the infographic, there is a note that says the \
data's source is Adobe Analytics. The image also contains the \
Statista logo, which indicates that this graphic might have been \
created or distributed by Statista, a company that specializes in \
market and consumer data. Additionally, there are Creative Commons \
(CC) icons, signifying the sharing and use permissions of the graphic.

It's important to note that without specific numbers, I cannot \
provide actual figures, but the visual trend is clear -- \
there is substantial year-over-year growth in online spending during \
these key shopping dates, culminating in a forecasted peak for 2023.

Repeated questioning

Here we define an LLM function that allows multiple question request invocations over the image:

fst = LLMVisionFunction["For the given image answer the question: ``. Be as concise as possible in your answers.", imgBarChart, "MaxTokens" -> 300]
0nmz56wwuboz3
fst["How many years are presented in that image?"]
"Five years are presented in the image."
fst["Which year has the highest value? What is that value?"]
"2023 has the highest value, which is approximately $11B on Cyber Monday."

Remark: Numerical value readings over technical plots or charts seem to be often wrong. Often enough, OpenAI’s vision model warns about this in the responses.

Formatted output

Here we make a function with a specially formatted output that can be more easily integrated in (larger) workflows:

fjs = LLMVisionFunction["How many `1` per `2`? " <> LLMPrompt["NothingElse"]["JSON"], imgBarChart, "MaxTokens" -> 300, "Temperature" -> 0.1]
032vcq74auyv9

Here we invoke that function (in order to get the money per year “seen” by OpenAI’s vision):

res = fjs["money", "shopping day"]
```json
{
  "Thanksgiving Day": {
    "2019": "$4B",
    "2020": "$5B",
    "2021": "$6B",
    "2022": "$7B",
    "2023": "$8B"
  },
  "Black Friday": {
    "2019": "$7B",
    "2020": "$9B",
    "2021": "$9B",
    "2022": "$10B",
    "2023": "$11B"
  },
  "Cyber Monday": {
    "2019": "$9B",
    "2020": "$11B",
    "2021": "$11B",
    "2022": "$12B",
    "2023": "$13B"
  }
}
```

Remark: The above result should be structured as shopping-day:year:value. But occasionally it might be structured as year::shopping-day::value. In the latter case just re-run LLM invocation.

Here we parse the obtained JSON into WL association structure:

aMoney = ImportString[StringReplace[res, {"```json" -> "", "```" -> ""}], "RawJSON"]
<|"Thanksgiving Day" -> <|"2019" -> "$4B", "2020" -> "$5B", 
   "2021" -> "$6B", "2022" -> "$7B", "2023" -> "$8B"|>, 
 "Black Friday" -> <|"2019" -> "$7B", "2020" -> "$9B", 
   "2021" -> "$9B", "2022" -> "$10B", "2023" -> "$11B"|>, 
 "Cyber Monday" -> <|"2019" -> "$9B", "2020" -> "$11B", 
   "2021" -> "$11B", "2022" -> "$12B", "2023" -> "$13B"|>|>

Remark: Currently LLMVisionFunction does not have an interpreter (or “form”) parameter as LLMFunction does. This can be seen as one of the reasons to include LLMVisionFunction in the “LLMFunctions” framework.

Here we convert the money strings into money quantities:

AbsoluteTiming[
  aMoney2 = Map[SemanticInterpretation, aMoney, {-1}] 
 ]
08ijuwuchj31q

Here is the corresponding bar chart and the original bar chart (for
comparison):

0rt43fezbbp4b
1lpfhko7c2g6e

Remark: The comparison shows “pretty good vision” by OpenAI! But, again, small (or maybe significant) discrepancies are observed.

Dedicated notebook cells

In the context of the “well-established” notebook solutions OpenAIMode, [AAp2], or Chatbook,
[WRIp2], we can contemplate extensions to integrate OpenAI’s vision service.

The main challenges here include determining how users will specify images in the notebook, such as through URLs, file names, or Base64 strings, each with unique considerations. Additionally, we have to explore how best to enable users to input prompts or requests for image processing by the AI/LLM service.

This integration, while valuable, it is not my immediate focus as there are programmatic ways to access OpenAI’s vision service already. (See the previous sections.)

Combinations (fairy tale generation)

Consider the following computational workflow for making fairy tales:

  1. Draw or LLM-generate a few images that characterize parts of a story.
  2. Narrate the images using the LLM “vision” functionality.
  3. Use an LLM to generate a story over the narrations.

Remark: Multi-modal LLM / AI systems already combine steps 2 and 3.

Remark: The workflow above (after it is programmed) can be executed multiple times until satisfactory results are obtained.

Here are image generations using DALL-E for four different requests with the same illustrator name in them:

storyImages = 
   Map[
    ImageSynthesize["Painting in the style of John Bauer of " <> #] &,
    {"a girl gets a basket with wine and food for her grandma.", 
     "a big bear meets a girl carrying a basket in the forest.", 
     "a girl that gives food from a basket to a big bear.", 
     "a big bear builds a new house for girl's grandma."} 
   ];
storyImages // Length

(*4*)

Here we display the images:

storyImages
13qqfe3pzqfn9

Here we get the image narrations (via the OpenAI’s “vision service”):

storyImagesDescriptions = LLMVisionSynthesize["Concisely describe the images.", storyImages, "MaxTokens" -> 600]
1. A painting of a woman in a traditional outfit reaching into a
    basket filled with vegetables and bread beside a bottle.
2. An illustration of a person in a cloak holding a bucket and
    standing next to a large bear in a forest.
3. An artwork depicting a person sitting naked by a birch tree,
    sharing a cake with a small bear.
4. A picture of a person in a folk costume sitting next to a bear
    with a ladder leaning against a house.

Here we extract the descriptions into a list:

descr = StringSplit[storyImagesDescriptions, "\n"];

Here we generate the story from the descriptions above (using OpenAI’s ChatGPT):

 LLMSynthesize[{"Write a story that fits the following four descriptions:", Sequence @@ descr}, LLMEvaluator -> LLMConfiguration["MaxTokens" -> 1200]]
In a small village nestled deep within a lush forest, lived a woman \
named Anya. She was gentle and kind-hearted, known for her artistic \
talent and love for nature. Anya had a keen eye for capturing the \
beauty of the world around her through her paintings. Each stroke of \
her brush seemed to hold a piece of her soul, and her art touched the \
hearts of all who laid their eyes upon it.

One sunny day, on the outskirts of the village, Anya set up her easel \
amidst a lively farmers' market. In front of her, she placed a large \
canvas, ready to bring her latest vision to life. With her palette \
filled with vibrant colors, she began painting a woman dressed in a \
traditional outfit, delicately reaching into a woven basket filled to \
the brim with fresh vegetables and warm bread. Beside the basket lay \
an empty bottle, hinting at a joyous feast anticipated for the day.

As Anya skillfully brought her painting to life, a cloak-wrapped \
figure caught her attention. Intrigued, she turned her easel slightly \
to capture the essence of this mysterious wanderer standing beside a \
mighty bear deep within the heart of the forest. In her illustration, \
she depicted the cloaked person, holding a bucket, their gaze met by \
the curious eyes of the regal woodland creature. The bond between \
them was palpable, a silent understanding as they stood together, \
guardians of the ancient woods.

Meanwhile, in a clearing not too far away, Anya discovered a scene \
that touched her deeply. She stumbled upon a person sitting naked \
beneath the shade of a majestic birch tree, a cake placed lovingly \
between them and a small bear. The artwork she created was a tender \
portrayal of the intimate connection shared by the two, a testament \
to the innate kindness that existed between species. Together, they \
enjoyed the sweet treat, their hearts entwined in empathy, neither \
fearing the vulnerability of their exposed selves.

Driven by her artistry, Anya's imagination continued to explore the \
fascinating relationship between humans and bears in her village. In \
her final artwork, she turned her focus to a person in a folk \
costume, sitting comfortably beside a towering bear. A ladder leaned \
against a charming wooden house in the background, illustrating the \
close bond shared between the village folks and their wild \
companions. Together, they stood tall, their spirits entwined in a \
balance of mutual respect and harmony.

As Anya showcased her artwork to the villagers, they were captivated \
by the depth of emotion expressed through her brushstrokes. Her \
paintings served as a reminder that love and understanding knew no \
boundaries, whether lived within the confines of villages or amidst \
the enchanting wilderness.

Anya became a celebrated artist, known far and wide for her ability \
to weave tales of compassion and unity through her exquisite \
paintings. Her work inspired generations to see the world through the \
lens of empathy, teaching them that even in unconventional \
connections between humans and animals, beauty could be found.

And so, her legacy lived on, her art continuing to touch the hearts \
of those who recognized the profound messages hidden within her \
strokes of color. For in every stroke, Anya immortalized the timeless \
bond between humanity and the natural world, forever reminding us of \
the kinship we share with the creatures that roam our earth.

Conclusions and leftover comments

  • The new OpenAI vision model, “gpt-4-vision-preview”, as all LLMs produces too much words, and it has to be reined in and restricted.
  • The functions LLMVisionSynthesize and LLMVisionFunction have to be part of the “LLMFunctions” framework.
    • For example, “LLMVision*” functions do not have an interpreter (or “form”) argument.
  • The package “LLMVision” is meant to be simple and direct, not covering all angles.
  • It would be nice a dedicated notebook cell interface and workflow(s) for interacting with “AI vision” services to be designed and implemented.
    • The main challenge is the input of images.
  • Generating code from hand-written diagrams might be really effective demo using WL.
  • It would be interesting to apply the “AI vision” functionalities over displays from, say, chess or play-cards paclets.

References

Articles

[AA1] Anton Antonov, “Workflows with LLM functions (in WL)”,​ August 4, (2023), Wolfram Community, STAFF PICKS.

[AA2] Anton Antonov, “Raku, Python, and Wolfram Language over LLM functionalities”, (2023), Wolfram Community.

[AA3] Anton Antonov, “AI vision via Raku”, (2023), Wolfram Community.

[MT1] Marco Thiel, “Direct API access to new features of GPT-4 (including vision, DALL-E, and TTS)​​”, November 8, (2023), Wolfram Community, STAFF PICKS.

[OAIb1] OpenAI team, “New models and developer products announced at DevDay” , (2023), OpenAI/blog .

Functions, packages, and paclets

[AAf1] Anton Antonov, MermaidInk, WL function, (2023), Wolfram Function Repository.

[AAp1] Anton Antonov, LLMVision.m, Mathematica package, (2023), GitHub/antononcube .

[AAp2] Anton Antonov, OpenAIMode, WL paclet, (2023), Wolfram Language Paclet Repository.

[AAp3] Anton Antonov, OpenAIRequest.m, Mathematica package, (2023), GitHub/antononcube .

[CWp1] Christopher Wolfram, OpenAILink, WL paclet, (2023), Wolfram Language Paclet Repository.

[WRIp1] Wolfram Research, Inc., LLMFunctions, WL paclet, (2023), Wolfram Language Paclet Repository.

[WRIp2] Wolfram Research, Inc., Chatbook, WL paclet, (2023), Wolfram Language Paclet Repository.

[WRIp3] Wolfram Research, Inc., Chess, WL paclet, (2023), Wolfram Language Paclet Repository.

Videos

[AAv1] Anton Antonov, “OpenAIMode demo (Mathematica)”, (2023), YouTube/@AAA4Prediction.

Nightcore “Conflict” video making

Introduction

This notebook shows how to make Nightcore modifications to the animation video, “Conflict” (1983), Soyuzmultfilm.

Remark: In Russian: “Конфликт”, (1983), Союзмультфилм .

Remark: We use “Conflict” since its licencing allows copies of it to be (publicly) played via YouTube.

Remark: The notebook follows closely a previous post of mine about making Nightcore version of the song “Schweine”.

The Nightcore transformation of the video was fairly straightforward with Mathematica / WL. The video transformation and combination are also fairly straightforward or easy.

Remark: Here is the final result uploaded to YouTube: https://youtu.be/C2TtkKfQa9I

Get movies

Here are links to that video:

I downloaded the videos from after searching yandex.ru (dzen.ru). Alternatively, one can find and download videos in Firefox or Google Chrome via relevant plugins. (Or use VLC; or utilize the paclet described in the post “Playing with YouTube from Mathematica”, [BMI1].)

At this point I have a small official video and larger one. This gives the opportunity to demonstrate transferring of the “Dolphin” signature from the “official” video to the larger one. (See the frame manipulation below.)

Here we import the downloaded video:

vdConflict0 = Import["~/Downloads/Conflict-Soviet-Animation.mp4"]

Make Nightcore audio

The process for making a song to be in the Nightcore style is described in Wikipedia, [Wk1]. Basically, we just make the tempo 20-30% faster and raise the pitch with, $\approx 5.5$ semitones.

Remark: An alternative of the process shown in this section is to use audio transformation programs like Audacity and AmadeusPro.

Here we get the audio from the video:

auConflict = Audio[vdConflict0]

Here we change the tempo to be 20% faster:

AbsoluteTiming[ auConflictFaster = AudioTimeStretch[auConflict, 1/1.2] ]

Here we raise the pitch with $5.5$ semitones:

AbsoluteTiming[ auConflictNightcore = AudioPitchShift[auConflictFaster, Quantity[5.5, "Semitones"]] ]

Direct video styling

If we only wanted to change how the video looks we can directly manipulate the video frames with VideoFrameMap, [WRI6] :

AbsoluteTiming[ k = 0; vdConflict4 = VideoFrameMap[ImageEffect[#, "Tritanopia"] &, vdConflict0]; ] (*{75.6817, Null}*)

vdConflict4

Remark: Since we want to make both the audio and video shorter we have to use video frames.

Make Nightcore video

Get the frames of the video:

AbsoluteTiming[ lsFrames = VideoExtractFrames[vdConflict0, All]; ] (*{2.65153, Null}*)

Show the number of frames:

Length[lsFrames] (*10730*)

Generate (audio-less) video from the list of frames that have the same length as the generated audio:

AbsoluteTiming[ vdConflictNew = VideoGenerator[lsFrames, Duration[auConflictNightcore]]; ] (*{56.1004, Null}*)

Combine the video and audio (into a new video):

AbsoluteTiming[ vdConflictNightcore = VideoCombine[{vdConflictNew, auConflictNightcore}]; ] (*{0.144603, Null}*)

Here is the result:

vdConflictNightcore

Remark: Here we do not export the video, since Mathematica saves it in a “standard” location of the host operating system.

Cutting-off and re-splicing the movie credits

In order to engage better people from the Millennials and Gen Z generational cohorts, I want to move the movie credits from the start of the movie to be at the end. We use the function VideoSplit, [WRI10], and VideoJoin, [WRI11].

Here we show frames that indicate where to split the obtained Nightcore movie:

VideoExtractFrames[vdConflictNightcore, {1, 30, 36, 37, 38}]

Here we split the video:

{v1, v2} = VideoSplit[vdConflictNightcore, 37];

In order to have a better visual flow we color-invert the frames of the credits part:

v1Negative = VideoFrameMap[ColorNegate, v1];

Here we splice the “main” part with the “negated” credits part:

vdConflictNightcore2 = VideoJoin[v2, v1Negative]

References

[BMA1] b3m2ma1, “Playing with YouTube from Mathematica”, (2018), Wolfram Community. (GitHub link.)

[Wk1] Wikipedia entry, “Nightcore”.

[WRI1] Wolfram Research (2010), TextRecognize, Wolfram Language function, https://reference.wolfram.com/language/ref/TextRecognize.html (updated 2020).

[WRI2] Wolfram Research (2016), Audio, Wolfram Language function, https://reference.wolfram.com/language/ref/Audio.html (updated 2020).

[WRI3] Wolfram Research (2016), AudioTimeStretch, Wolfram Language function, https://reference.wolfram.com/language/ref/AudioTimeStretch.html (updated 2020).

[WRI4] Wolfram Research (2016), AudioPitchShift, Wolfram Language function, https://reference.wolfram.com/language/ref/AudioPitchShift.html (updated 2020).

[WRI5] Wolfram Research (2020), VideoExtractFrames, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoExtractFrames.html.

[WRI6] Wolfram Research (2020), VideoFrameMap, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoFrameMap.html (updated 2021).

[WRI7] Wolfram Research (2008), ImageEffect, Wolfram Language function, https://reference.wolfram.com/language/ref/ImageEffect.html (updated 13).

[WRI8] Wolfram Research (2020), VideoGenerator, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoGenerator.html (updated 2021).

[WRI9] Wolfram Research (2020), VideoCombine, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoCombine.html.

[WRI10] Wolfram Research (2020), VideoSplit, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoSplit.html.

[WRI11] Wolfram Research (2020), VideoJoin, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoJoin.html.

Wolfram Language conference in St. Petersburg

Two weeks ago (June 1st and 2nd) I participated in the Wolfram Language conference in St. Petersburg, Russia.
Here are the corresponding announcements:

The conference was co-organized by Kirill Belov and Petr Genadievich Tenishev.

Link to the GitHub repository with my conference presentation materials.

Here is a mind-map of the potential presentations Kirill and I discussed:

System dynamics presentation

I presented “Динамические системы : Расширение и улучшение эпидемиологических моделей”
(in English: “Dynamics systems: extending and improving epidemiological models”.)

The main presentation slides had a dozen supplements:

Making two presentations

Interestingly, I first prepared a Latent Semantic Analysis (LSA) talk, but then found out that the organizers listed another talk I discussed with them, on extending dynamic systems models. (The latter one is the first we discussed, so, it was my “fault” that I wanted to talk about LSA.)

Here are the presentation notebooks for LSA in English and Russian .

Some afterthoughts

  • Тhe conference was very “strong”, my presentation was the “weakest.”
    • With “strong” I refer to the content and style of the presentations.
  • This was also the first scientific presentation I gave in Russian. I also got a participation diploma .

to demonstrate generation of epidemiological modeling code.

  • Preparing for the conference reminded me of some the COVID-19 modeling hackathons I participated in.
  • I prepared the initial models of artillery shells manufacturing, but much more work has to be done in order to have a meaningful article or presentation. (Hopefully, I am finishing that soon.)

References

Articles, posts, presentations

[AA1] Антон Антонов,
“Динамические системы : Расширение и улучшение эпидемиологических моделей” .

[AA2] Антон Антонов,
“COVID-19 modeling over Botswana” ,

[AA3] Anton Antonov,
“WirVsVirus 2020 hackathon participation” ,
(2020),
MathematicaForPrediction at WordPress .

Quantile regression 3D examples

Introduction

In this notebook we develop in detail Quantile Regression (QR) examples over 3D data via the functions of the paclet “AntonAntonov/QuantileRegression”, [AAp1].

The QR examples demonstrate using:

  1. QuantileRegression with “automatic”, NURBS functions (created via BSplineFunction.)
  1. QuantileRegressionFit with custom made basis functions

Remark: In order to get a better idea of the how the data looks like, use the 3D graphics interactivity feature rotation.

Remark: This notebook automatically installs the paclet “AntonAntonov/QuantileRegression”, [AAp1] — see the section “Preparation” below.

Remark: A version of this notebook is a technical note in the paclet “AntonAntonov/QuantileRegression” , [AAp1].

0jsvdcl9qkyre


First neat example

This section has a first neat example with data with triangular domain.

Generate noisy 3D data:


Block[{c = 2, h = 0.1}, 
  data = Flatten[Table[{x, y, Exp[-(x^2 + y^2)/4]*(1 + RandomReal[1.2])}, {x, -c, c, h}, {y, x, c, h}], 1]; 
 ]

Compute Quantile regression for probabilities 0.1 and 0.9 :


probs = {0.1, 0.9};
AbsoluteTiming[
  funcs = AssociationThread[probs -> QuantileRegression[data, 4, probs]]; 
 ]

(*{0.205853, Null}*)

Plot the data points and regression quantiles together:


reg = ConvexHullRegion[Most /@ data];
QRPlot3D[data, funcs, RegionFunction -> Function[{x, y}, RegionMember[reg, {x, y}]]]

0v91aa1kds5qd


Noisy arrow wave

Generate random, “noisy wave” data:


{b, c} = {-6, 6};
data = RandomReal[{b, c}, {1200, 2}];
data = Map[Append[#, Sqrt[#[[1]] - b] + Sin[#[[1]] + Abs[#[[2]]]] + RandomVariate[NormalDistribution[0, 0.3]]] &, data];
Dimensions[data]

(*{1200, 3}*)

3D list plot of the data:


ListPointPlot3D[data, PlotRange -> All, PlotLabel -> "Noisy wave data", BoxRatios -> {1, 1, 2/3}, ImageSize -> Medium]

02j0iws4gln7f

Data summary:


ResourceFunction["RecordsSummary"][data]

0a1smh8l7wt2l

Compute Quantile regression for probability 0.5 using a grid of 7*4 NURBS basis functions:


AbsoluteTiming[
  funcs = QuantileRegression[data, {7, 4}, 0.5]; 
 ]

(*{0.379605, Null}*)

Plot the obtained function and data:


QRPlot3D[data, <|0.5 -> First[funcs]|>]

1f8t1q0hl5h41

Quantile regression for probabilities 0.1 and 0.9:


probs = {0.1, 0.9};
AbsoluteTiming[
  funcs = AssociationThread[probs, QuantileRegression[data, {7, 4}, probs]]; 
 ]

(*{0.591367, Null}*)

3D list plot of the data:


QRPlot3D[data, funcs]

0cv8341ro0uh9

Count the number of points under each surface:


sepPoints = Map[Function[{f}, Length@Select[data, #[[-1]] < First[Apply[f, Most[#]]] &]], funcs]

(*<|0.1 -> 120, 0.9 -> 1087|>*)

Show the corresponding fractions (should correspond to the probabilities):


sepFractions = N[sepPoints/Length[data]]

(*<|0.1 -> 0.1, 0.9 -> 0.905833|>*)

Square sombrero

In this section we show the use of QuantileRegressionFit over a pre-generated basis functions.

Remark: The QR computational steps below correspond to the implementation of QuantileRegression .

Generate random, “square sombrero” data:


{b, c} = {-6, 6};
data = RandomReal[{b, c}, {1200, 2}];
data = Map[Append[#, Exp[-# . #/(c - b)/2] (Sin[Abs[#[[1]]] + Abs[#[[2]]]] + RandomVariate[NormalDistribution[0, 0.2]])] &, data];
Dimensions[data]

(*{1200, 3}*)

3D list plot of the data:


ListPointPlot3D[data, PlotRange -> All, PlotLabel -> "Square sombrero data", BoxRatios -> {1, 1, 1/1.5}, ImageSize -> Medium]

05prs9945g8ut

Data summary:


ResourceFunction["RecordsSummary"][data]

01uldvdrnv1ms

NURBS basis of 12*12 functions:


basis = NURBSBasis[data[[All, 1 ;; 2]], 12];
Length[basis]

(*144*)

Quantile regression for probability 0.9:


probs = {0.9};
AbsoluteTiming[
  funcs = AssociationThread[probs, QuantileRegressionFit[data, Through[Values[basis][x, y]], {x, y}, probs]]; 
 ]

1nfo6woxheuyi


(*{6.53867, Null}*)

Plot functions and data:


QRPlot3D[data, ToPureFunctions[funcs, {x, y}], ColorFunction -> (Lighter[Blue] &), PlotLegends -> SwatchLegend[{Lighter[Blue]}, Keys[funcs]], Exclusions -> None]

0r0hf9eso48k0


Pyramid basis

In this section we demonstrate the use of custom made basis functions.

Define a pyramid basis function:

07fp09l6v901b

Here is a plot of a basis function:


Plot3D[PyramidBasisFunc[{0, 0}, {x, y}], {x, -1.5, 1.5}, {y, -1.5, 1.5}, PlotRange -> All, ImageSize -> Medium]

0u466o4h1w930

Make a basis for the sombrero data:


basis = Flatten@Table[PyramidBasisFunc[{i, j}, {x, y}], {i, b, c}, {j, b, c}];
Length[basis]

(*169*)

Find regression quantiles for probability 0.2:


probs = {0.2};
AbsoluteTiming[
  funcs = 
    AssociationThread[
     probs, QuantileRegressionFit[data, basis, {x, y}, probs, Method -> {LinearProgramming, Method -> "InteriorPoint"}]]; 
 ]

(*{0.866452, Null}*)

Plot data and regression quantiles together:


QRPlot3D[data, ToPureFunctions[funcs], ColorFunction -> (Lighter[Blue] &), Mesh -> True, PlotLegends -> SwatchLegend[{Lighter[Blue]}, Keys[funcs]], Exclusions -> None]

0d53u1xiop4c9


Partial sombrero

In this section we demonstrate the built-in basis filtering in the function NURBSBasis . The basis filtering is used to alleviate (or diminish) fitting problems for data that partially occupies the functional basis domain.

Remark: For a more detailed discussion of how basis filtering is implemented and how it addresses “partial data occupation” problems see the section “Possible issues” of the function page for NURBSBasis .

Generate random, “partial sombrero” data:


{b, c} = {-6, 6};
data = RandomReal[{b, c}, {1600, 2}];
sombreroFunc = Exp[-# . #/(c - b)/4]*Sin[Sqrt[#[[1]]^2 + #[[2]]^2]] &;
data = Map[Append[#, sombreroFunc[##]*(1 + RandomVariate[NormalDistribution[0, 0.6]])] &, data];
Dimensions[data]

(*{1600, 3}*)

3D list plot of the data:


data2 = Select[data, #[[1]] <= 0.5*#[[2]] &];
ListPointPlot3D[data2, PlotLabel -> "Partial sombrero data", BoxRatios -> {1, 1, 2/3}, ImageSize -> Medium]

1tabu951donl8

Regression quantiles:


probs = {0.1, 0.9};
AbsoluteTiming[
  funcs = AssociationThread[probs, QuantileRegression[data2, 5, probs]]; 
 ]

(*{0.295488, Null}*)

Plot data and regression quantiles together:


QRPlot3D[data2, funcs]

15k2lwvoyo9jx

Create a region object for the convex hull of the data points:


reg = ConvexHullRegion[data2[[All, 1 ;; 2]]]

0e0do4t5knu03

The plot data and regression quantiles over the region above:


QRPlot3D[data2, funcs, RegionFunction -> Function[{x, y}, RegionMember[reg, {x, y}]]]

0eqieqqzjjm8y

Count the number of points under each surface:


sepPoints = Map[Function[{f}, Length[Select[data2, #[[-1]] < (f @@ Most[#])[[1]] &]]], funcs]

(*<|0.1 -> 91, 0.9 -> 731|>*)

Show the corresponding fractions (should correspond to the probabilities):


sepFractions = N[sepPoints/Length[data2]]

(*<|0.1 -> 0.112485, 0.9 -> 0.903585|>*)

Preparation

Install the paclet:


PacletInstall["AntonAntonov/QuantileRegression"]

Load the Quantile regression paclet:


Needs["AntonAntonov`QuantileRegression`"]

Set random seed:


SeedRandom[9903];

Define plot function:


QRPlot3D[data_List, funcs_Association, opts___] := 
   Show[
    ListPointPlot3D[data, FilterRules[{opts}, {{BoxRatios -> {1, 1, 1/1.5}}}], {BoxRatios -> {1, 1, 1/1.5}}, PlotStyle -> Red, PlotRange -> All], 
    Plot3D[Evaluate@Through[Values[funcs][x, y]], {x, Min[data[[All, 1]]], Max[data[[All, 1]]]}, {y, Min[data[[All, 2]]], Max[data[[All, 2]]]}, 
     opts, 
     PlotRange -> All, PlotStyle -> {Opacity[0.7]}, Mesh -> True, PlotTheme -> "LightMesh", PerformanceGoal -> "Quality", PlotLegends -> Keys[funcs]], ImageSize -> Medium];

Define pure functions converter:


ToPureFunctions[funcs_, vars_List : {x, y}] := 
   Map[With[{f = #}, f &] &, ReplaceAll[funcs, Thread[vars -> Map[Slot, Range[Length[vars]]]]]];

References

Articles

[Wk1] Wikipedia entry, Quantile regression.

[Wk2] Wikipedia entry, NURBS.

[AA1] Anton Antonov, “Quantile regression through linear programming”, (2013), MathematicaForPrediction at WordPress.

[AA2] Anton Antonov, “Quantile regression with B-splines”, (2014), MathematicaForPrediction at WordPress.

Books

Roger Koenker, (2005). *Quantile Regression* (Econometric Society Monographs). 2005. Cambridge: Cambridge University Press. doi:10.1017/CBO9780511754098

Functions, packages, paclets

[AAf1] Anton Antonov, QuantileRegression, (2019), Wolfram Function Repository.

[AAp1] Anton Antonov, QuantileRegression paclet, (2023), Wolfram Language Paclet Repository.

[AAp2] Anton Antonov, QuantileRegression Mathematica package, (2013-2023), MathematicaForPrediction at GitHub.

Videos

[AAv1] Anton Antonov, “Quantile Regression—Theory, Implementations, and Applications”, (2014), Wolfram channel at YouTube.

Nightcore restyling of Dolphin’s “Spring”

Introduction

This post shows how to make Nightcore modifications to a song video. We use Dolphin ‘s song “Spring” (“Весна”).

Remark: We use “Spring” since its licencing allows copies of it to be (publicly) played via YouTube .

Remark: The post follows closely a previous post of mine about making Nightcore version of the song “Schweine”.

The Nightcore transformation of the song was fairly straightforward with Mathematica / WL. The video transformation and combination are also fairly straightforward or easy.

Remark: Here is the final result uploaded to YouTube:

NightcoreSpring-YouTube

Get movies

Here is a link to the official video: https://www.youtube.com/watch?v=P47V4SASwGc .

I downloaded the videos from after searching yandex.ru (dzen.ru). Alternatively, one can find and download videos in Firefox or Google Chrome via relevant plugins. (Or use VLC ; or utilize the paclet described in the post “Playing with YouTube from Mathematica” , [BMI1].)

At this point I have a small official video and larger one. This gives the opportunity to demonstrate transferring of the “Dolphin” signature from the “official” video to the larger one. (See the frame manipulation below.)

Here we import the downloaded small “official” video:


vdSpring0 = Import["~/Downloads/Dolphin-Spring-videoclip.mp4"]

0up57w194t7mb

Here we import the larger downloaded video:


vdSpring = Import["~/Downloads/Дельфин \[LongDash] Весна.mp4"]

1bd1zbdxppned

Getting Dolphin’s “signature”

Here we is the first frame from the “official” video:


frameSign = VideoExtractFrames[vdSpring0, 0]

0u3peo5aimdel

Here we binarize the image:


frameSignBW = Binarize[frameSign]

14sznwdgmt6t7

Before adding that image to video frames it is resized, darkened, and “smoothed.” Something like this:


ImageEffect[#, "OilPainting"] &@Darker[ImageResize[frameSignBW, 2*ImageDimensions[frameSignBW]], 0.7]

05dqdfit561tp

Make Nightcore audio

The process for making a song Nightcore is described in Wikipedia, [Wk1]. Basically, we just make the tempo 20-30% faster and raise the pitch with, \approx 5.5 semitones.

Remark: An alternative of the process shown in this section is to use audio transformation programs like Audacity and AmadeusPro .

Here we get the audio from the video:


auSpring = Audio[vdSpring]

0vxw2kdih3hmg

Here we change the tempo to be 20% faster:


AbsoluteTiming[
  auSpringFaster = AudioTimeStretch[auSpring, 1/1.2] 
 ]

0cztw139kthxy

Here we raise the pitch with 5.5 semitones:


AbsoluteTiming[
  auSpringNightcore = AudioPitchShift[auSpringFaster, Quantity[5.5, "Semitones"]] 
 ]

0rs8kv89xo3y9

Get lyrics

Although, we have a video with English subtitles, it would be interesting to experiment with adding subtitles to the video or “discovering” the subtitles in the video frames.

Instead of just copy-&-pasting the text I took screenshot of lyrics here: https://lyrics-on.net/en/1096839-vesna-vesna-lyrics.html

0cg71l6powom8

Here the image above is split into two halves and they displayed in a grid:


imgLyrics1 = ImageTake[imgLyrics, All, {1, ImageDimensions[imgLyrics][[2]]/1.3}];
imgLyrics2 = ImageTake[imgLyrics, All, {ImageDimensions[imgLyrics][[2]]/1.3, -1}];
GraphicsGrid[{{imgLyrics1, imgLyrics2}}, Dividers -> All, ImageSize -> 700]

Here we recognize the lyrics within each half:


Grid[{{TextRecognize[imgLyrics1, Language -> "Russian"], TextRecognize[imgLyrics2, Language -> "English"]}}, Dividers -> All]

11pimedkmg0cz

Remark: Because we found a video with subtitles, we do not use further the extracted lyrics in this notebook.

Direct video styling

If we only wanted to change how the video looks we can directly manipulate the video frames with VideoFrameMap, [WRI6] :


AbsoluteTiming[
  k = 0; 
  vdSpring4 = VideoFrameMap[Switch[Mod[k++, 500] < 250, True, EdgeDetect[#], False, ImageEffect[#, "EdgeStylization"]] &, vdSpring]; 
 ]

(*{1221.98, Null}*)

vdSpring4

16vqoanffmhne

Remark: Since we want to make both the audio and video shorter we have to use video frames.

Make Nightcore video

Get the frames of the video:


AbsoluteTiming[
  lsFrames = VideoExtractFrames[vdSpring, All]; 
 ]

(*{8.04196, Null}*)

Show the number of frames:


Length[lsFrames]

(*7156*)

Change all the frames to have the “ColorBoosting” image effect:


AbsoluteTiming[
  lsFramesBoost = ParallelMap[ImageEffect[#, "ColorBoosting"] &, lsFrames]; 
 ]

(*{239.268, Null}*)

Here we resize the “signature” image, “smooth” it, and then add it to all of the “boosted” frames:


AbsoluteTiming[
  lsFramesBoostSigned = 
    Block[{frameSignBW = ImageEffect[#, "OilPainting"] &@Darker[ImageResize[frameSignBW, ImageDimensions[lsFramesBoost[[1]]]], 0.85]}, 
     Map[ImageAdd[#, frameSignBW] &, lsFramesBoost] 
    ]; 
 ]

(*{8.61014, Null}*)

Here is how the 10-th frame looks like:


lsFramesBoostSigned[[10]]

0r7uiekngv7f3

Generate (audio-less) video from the list of frames that have the same length as the generated audio:


AbsoluteTiming[
  vdSpringNew = VideoGenerator[lsFramesBoostSigned, Duration[auSpringNightcore]]; 
 ]

(*{95.209, Null}*)

Combine the video and audio (into a new video):


AbsoluteTiming[
  vdSpringNightcore = VideoCombine[{vdSpringNew, auSpringNightcore}]; 
 ]

(*{0.07271, Null}*)

vdSpringNightcore

0dvupxody23tu

Remark: Here we do not export the video, since Mathematica saves it in a “standard” location of the host operating system.

References

[BMA1] b3m2ma1, “Playing with YouTube from Mathematica” , (2018), Wolfram Community. ([GitHub link](https://b3m2a1.github.io/playing-with-youtube-from-mathematica.html).)

[DM1] Dolphin, https://dolphinmusic.ru .

[Wk1] Wikipedia entry, “Nightcore” .

[WRI1] Wolfram Research (2010), TextRecognize, Wolfram Language function, https://reference.wolfram.com/language/ref/TextRecognize.html (updated 2020).

[WRI2] Wolfram Research (2016), Audio, Wolfram Language function, https://reference.wolfram.com/language/ref/Audio.html (updated 2020).

[WRI3] Wolfram Research (2016), AudioTimeStretch, Wolfram Language function, https://reference.wolfram.com/language/ref/AudioTimeStretch.html (updated 2020).

[WRI4] Wolfram Research (2016), AudioPitchShift, Wolfram Language function, https://reference.wolfram.com/language/ref/AudioPitchShift.html (updated 2020).

[WRI5] Wolfram Research (2020), VideoExtractFrames, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoExtractFrames.html.

[WRI6] Wolfram Research (2020), VideoFrameMap, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoFrameMap.html (updated 2021).

[WRI7] Wolfram Research (2008), ImageEffect, Wolfram Language function, https://reference.wolfram.com/language/ref/ImageEffect.html (updated 13).

[WRI8] Wolfram Research (2020), VideoGenerator, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoGenerator.html (updated 2021).

[WRI9] Wolfram Research (2020), VideoCombine, Wolfram Language function, https://reference.wolfram.com/language/ref/VideoCombine.html.

OpenAIMode (notebook style)

This blog post has the “slides” of the presentation “OpenAIMode demo” in which is demonstrated creation and evaluation of OpenAI interaction cells in Mathematica notebooks. (Via a special notebook style.)


Setup

  • It is assumed that the paclet OpenAILink is installed
    • … and the required setup steps are completed.
  • Install the paclet OpenAIMode
PacletInstall["AntonAntonov/OpenAIMode"]
Needs["AntonAntonov`OpenAIMode`"]

Demo

Let us show how the notebook style works:

  • Needs
  • OpenAIMode
  • Text completion cell (shortcut: “Shift-|”)
  • Tweak invocation parameters with SetOptions
  • Image generation cell (shortcuts: “Tab”)

Screenshots


How does it work?

Consider the following flowchart:


Concluding remarks

Similar work

Future plans (…maybe)

More documentation

Should this notebook style functions be part of OpenAILink?

Based on feedback:

  • Better default options
  • Additional OpenAI cells