Doomsday clock parsing and plotting

Introduction

The Doomsday Clock is a symbolic timepiece maintained by the Bulletin of the Atomic Scientists (BAS) since 1947. It represents how close humanity is perceived to be to global catastrophe, primarily nuclear war but also including climate change and biological threats. The clock’s hands are set annually to reflect the current state of global security; midnight signifies theoretical doomsday.

In this notebook we consider two tasks:

  • Parsing of Doomsday Clock reading statements
  • Evolution of Doomsday Clock times
    • We extract relevant Doomsday Clock timeline data from the corresponding Wikipedia page.
      • (Instead of using a page from BAS.)
    • We show how timeline data from that Wikipedia page can be processed with “standard” Wolfram Language (WL) functions and with LLMs.
    • The result plot shows the evolution of the minutes to midnight.
      • The plot could show trends, highlighting significant global events that influenced the clock setting.
      • Hence, we put in informative callouts and tooltips.

The data extraction and visualization in the notebook serve educational purposes or provide insights into historical trends of global threats as perceived by experts. We try to make the ingestion and processing code universal and robust, suitable for multiple evaluations now or in the (near) future.

Remark: Keep in mind that the Doomsday Clock is a metaphor and its settings are not just data points but reflections of complex global dynamics (by certain experts and a board of sponsors.)

Remark: Currently (2024-12-30) Doomsday Clock is set at 90 seconds before midnight.

Data ingestion

Here we ingest the Doomsday Clock timeline page and show corresponding statistics:

 url = "https://thebulletin.org/doomsday-clock/timeline/"; txtEN = Import[url, "Plaintext"]; TextStats[txtEN] (*<|"Characters" -> 77662, "Words" -> 11731, "Lines" -> 1119|>*) 

By observing the (plain) text of that page we see the Doomsday Clock time setting can be extracted from the sentence(s) that begin with the following phrase:

 startPhrase = "Bulletin of the Atomic Scientists"; sentence = Select[Map[StringTrim, StringSplit[txtEN, "\n"]], StringStartsQ[#, startPhrase] &] // First (*"Bulletin of the Atomic Scientists, with a clock reading 90 seconds to midnight"*) 

Grammar and parsers

Here is a grammar in Extended Backus-Naur Form (EBNF) for parsing Doomsday Clock statements:

 ebnf = " <TOP> = <clock-reading> ; <clock-reading> = <opening> , ( <minutes> | [ <minutes> , [ 'and' | ',' ] ] , <seconds> ) , 'to' , 'midnight' ; <opening> = [ { <any> } ] , 'clock' , [ 'is' ] , 'reading' ; <any> = '_String' ; <minutes> = <integer> <& ( 'minute' | 'minutes' ) <@ \"Minutes\"->#&; <seconds> = <integer> <& ( 'second' | 'seconds' ) <@ \"Seconds\"->#&; <integer> = '_?IntegerQ' ;"; 

Remark: The EBNF grammar above can be obtained with LLMs using a suitable prompt with example sentences. (We do not discuss that approach further in this notebook.)

Here the parsing functions are generated from the EBNF string above:

 ClearAll["p*"] res = GenerateParsersFromEBNF[ParseToEBNFTokens[ebnf]]; res // LeafCount (*375*) 

We must redefine the parser pANY (corresponding to the EBNF rule “”) in order to prevent pANY of gobbling the word “clock” and in that way making the parser pOPENING fail.

 pANY = ParsePredicate[StringQ[#] && # != "clock" &]; 

Here are random sentences generated with the grammar:

 SeedRandom[32]; GrammarRandomSentences[GrammarNormalize[ebnf], 6] // Sort // ColumnForm 
 54jfnd 9y2f clock is reading 46 second to midnight clock is reading 900 minutes to midnight clock is reading 955 second to midnight clock reading 224 minute to midnight clock reading 410 minute to midnight jdsf5at clock reading 488 seconds to midnight 

Verifications of the (sub-)parsers:

 pSECONDS[{"90", "seconds"}] (*{{{}, "Seconds" -> 90}}*) 
 pOPENING[ToTokens@"That doomsday clock is reading"] (*{{{}, {{"That", "doomsday"}, {"clock", {"is", "reading"}}}}}*) 

Here the “top” parser is applied:

 str = "the doomsday clock is reading 90 seconds to midnight"; pTOP[ToTokens@str] (*{{{}, {{{"the", "doomsday"}, {"clock", {"is", "reading"}}}, {{{}, "Seconds" -> 90}, {"to", "midnight"}}}}}*) 

Here the sentence extracted above is parsed and interpreted into an association with keys “Minutes” and “Seconds”:

 aDoomReading = Association@Cases[Flatten[pTOP[ToTokens@sentence]], _Rule] (*<|"Seconds" -> 90|>*) 

Plotting the clock

Using the interpretation derived above here we make a list suitable for ClockGauge:

 clockShow = DatePlus[{0, 0, 0, 12, 0, 0}, {-(Lookup[aDoomReading, "Minutes", 0]*60 + aDoomReading["Seconds"]), "Seconds"}] (*{-2, 11, 30, 11, 58, 30}*) 

In that list, plotting of a Doomsday Clock image (or gauge) is trivial.

 ClockGauge[clockShow, GaugeLabels -> Automatic] 

Let us define a function that makes the clock-gauge plot for a given association.

 Clear[DoomsdayClockGauge]; Options[DoomsdayClockGauge] = Options[ClockGauge]; DoomsdayClockGauge[m_Integer, s_Integer, opts : OptionsPattern[]] := DoomsdayClockGauge[<|"Minutes" -> m, "Seconds" -> s|>, opts]; DoomsdayClockGauge[a_Association, opts : OptionsPattern[]] := Block[{clockShow}, clockShow = DatePlus[{0, 0, 0, 12, 0, 0}, {-(Lookup[a, "Minutes", 0]*60 + Lookup[a, "Seconds", 0]), "Seconds"}]; ClockGauge[clockShow, opts, GaugeLabels -> Placed[Style["Doomsday\nclock", RGBColor[0.7529411764705882, 0.7529411764705882, 0.7529411764705882], FontFamily -> "Krungthep"], Bottom]] ]; 

Here are examples:

 Row[{ DoomsdayClockGauge[17, 0], DoomsdayClockGauge[1, 40, GaugeLabels -> Automatic, PlotTheme -> "Scientific"], DoomsdayClockGauge[aDoomReading, PlotTheme -> "Marketing"] }] 

More robust parsing

More robust parsing of Doomsday Clock statements can be obtained in these three ways:

  • “Fuzzy” match of words
    • For misspellings like “doomsdat” instead of “doomsday.”
  • Parsing of numeric word forms.
    • For statements, like, “two minutes and twenty five seconds.”
  • Delegating the parsing to LLMs when grammar parsing fails.

Fuzzy matching

The parser ParseFuzzySymbol can be used to handle misspellings (via EditDistance):

 pDD = ParseFuzzySymbol["doomsday", 2]; lsPhrases = {"doomsdat", "doomsday", "dumzday"}; ParsingTestTable[pDD, lsPhrases] 

In order to include the misspelling handling into the grammar we manually rewrite the grammar. (The grammar is small, so, it is not that hard to do.)

 pANY = ParsePredicate[StringQ[#] && EditDistance[#, "clock"] > 1 &]; pOPENING = ParseOption[ParseMany[pANY]]⊗ParseFuzzySymbol["clock", 1]⊗ParseOption[ParseSymbol["is"]]⊗ParseFuzzySymbol["reading", 2]; pMINUTES = "Minutes" -> # &⊙(pINTEGER ◁ ParseFuzzySymbol["minutes", 3]); pSECONDS = "Seconds" -> # &⊙(pINTEGER ◁ ParseFuzzySymbol["seconds", 3]); pCLOCKREADING = Cases[#, _Rule, Infinity] &⊙(pOPENING⊗(pMINUTES⊕ParseOption[pMINUTES⊗ParseOption[ParseSymbol["and"]⊕ParseSymbol["&"]⊕ParseSymbol[","]]]⊗pSECONDS)⊗ParseSymbol["to"]⊗ParseFuzzySymbol["midnight", 2]); 

Here is a verification table with correct- and incorrect spellings:

 lsPhrases = { "doomsday clock is reading 2 seconds to midnight", "dooms day cloc is readding 2 minute and 22 sekonds to mildnight"}; ParsingTestTable[pCLOCKREADING, lsPhrases, "Layout" -> "Vertical"] 

Parsing of numeric word forms

One way to make the parsing more robust is to implement the ability to parse integer names (or numeric word forms) not just integers.

Remark: For a fuller discussion — and code — of numeric word forms parsing see the tech note “Integer names parsing” of the paclet “FunctionalParsers”, [AAp1].

First, we make an association that connects integer names with corresponding integer values

 aWordedValues = Association[IntegerName[#, "Words"] -> # & /@ Range[0, 100]]; aWordedValues = KeyMap[StringRiffle[StringSplit[#, RegularExpression["\\W"]], " "] &, aWordedValues]; Length[aWordedValues] (*101*) 

Here is how the rules look like:

 aWordedValues[[1 ;; -1 ;; 20]] (*<|"zero" -> 0, "twenty" -> 20, "forty" -> 40, "sixty" -> 60, "eighty" -> 80, "one hundred" -> 100|>*) 

Here we program the integer names parser:

 pUpTo10 = ParseChoice @@ Map[ParseSymbol[IntegerName[#, {"English", "Words"}]] &, Range[0, 9]]; p10s = ParseChoice @@ Map[ParseSymbol[IntegerName[#, {"English", "Words"}]] &, Range[10, 100, 10]]; pWordedInteger = ParseApply[aWordedValues[StringRiffle[Flatten@{#}, " "]] &, p10s\[CircleTimes]pUpTo10\[CirclePlus]p10s\[CirclePlus]pUpTo10]; 

Here is a verification table of that parser:

 lsPhrases = {"three", "fifty seven", "thirti one"}; ParsingTestTable[pWordedInteger, lsPhrases] 

There are two parsing results for “fifty seven”, because pWordedInteger is defined with p10s⊗pUpTo10⊕p10s… . This can be remedied by using ParseJust or ParseShortest:

 lsPhrases = {"three", "fifty seven", "thirti one"}; ParsingTestTable[ParseJust@pWordedInteger, lsPhrases] 

Let us change pINTEGER to parse both integers and integer names:

 pINTEGER = (ToExpression\[CircleDot]ParsePredicate[StringMatchQ[#, NumberString] &])\[CirclePlus]pWordedInteger; lsPhrases = {"12", "3", "three", "forty five"}; ParsingTestTable[pINTEGER, lsPhrases] 

Let us try the new parser using integer names for the clock time:

 str = "the doomsday clock is reading two minutes and forty five seconds to midnight"; pTOP[ToTokens@str] (*{{{}, {"Minutes" -> 2, "Seconds" -> 45}}}*) 

Enhance with LLM parsing

There are multiple ways to employ LLMs for extracting “clock readings” from arbitrary statements for Doomsday Clock readings, readouts, and measures. Here we use LLM few-shot training:

 flop = LLMExampleFunction[{ "the doomsday clock is reading two minutes and forty five seconds to midnight" -> "{\"Minutes\":2, \"Seconds\": 45}", "the clock of the doomsday gives 92 seconds to midnight" -> "{\"Minutes\":0, \"Seconds\": 92}", "The bulletin atomic scienist maybe is set to a minute an 3 seconds." -> "{\"Minutes\":1, \"Seconds\": 3}" }, "JSON"] 

Here is an example invocation:

 flop["Maybe the doomsday watch is at 23:58:03"] (*{"Minutes" -> 1, "Seconds" -> 57}*) 

The following function combines the parsing with the grammar and the LLM example function — the latter is used for fallback parsing:

 Clear[GetClockReading]; GetClockReading[st_String] := Block[{op}, op = ParseJust[pTOP][ToTokens[st]]; Association@ If[Length[op] > 0 && op[[1, 1]] === {}, Cases[op, Rule], (*ELSE*) flop[st] ] ]; 

Robust parser demo

Here is the application of the combine function above over a certain “random” Doomsday Clock statement:

 s = "You know, sort of, that dooms-day watch is 1 and half minute be... before the big boom. (Of doom...)"; GetClockReading[s] (*<|"Minutes" -> 1, "Seconds" -> 30|>*) 

Remark: The same type of robust grammar-and-LLM combination is explained in more detail in the video “Robust LLM pipelines (Mathematica, Python, Raku)”, [AAv1]. (See, also, the corresponding notebook [AAn1].)

Timeline

In this section we extract Doomsday Clock timeline data and make a corresponding plot.

Parsing page data

Instead of using the official Doomsday clock timeline page we use Wikipedia:

 url = "https://en.wikipedia.org/wiki/Doomsday_Clock"; data = Import[url, "Data"]; 

Get timeline table:

 tbl = Cases[data, {"Timeline of the Doomsday Clock [ 13 ] ", x__} :> x, Infinity] // First; 

Show table’s columns:

 First[tbl] (*{"Year", "Minutes to midnight", "Time ( 24-h )", "Change (minutes)", "Reason", "Clock"}*) 

Make a dataset:

 dsTbl = Dataset[Rest[tbl]][All, AssociationThread[{"Year", "MinutesToMidnight", "Time", "Change", "Reason"}, #] &]; dsTbl = dsTbl[All, Append[#, "Date" -> DateObject[{#Year, 7, 1}]] &]; dsTbl[[1 ;; 4]] 

Here is an association used to retrieve the descriptions from the date objects:

 aDateToDescr = Normal@dsTbl[Association, #Date -> BreakStringIntoLines[#Reason] &]; 

Using LLM-extraction instead

Alternatively, we can extract the Doomsday Clock timeline using LLMs. Here we get the plaintext of the Wikipedia page and show statistics:

 txtWk = Import[url, "Plaintext"]; TextStats[txtWk] (*<|"Characters" -> 43623, "Words" -> 6431, "Lines" -> 315|>*) 

Here we get the Doomsday Clock timeline table from that page in JSON format using an LLM:

 res = LLMSynthesize[{ "Give the time table of the doomsday clock as a time series that is a JSON array.", "Each element of the array is a dictionary with keys 'Year', 'MinutesToMidnight', 'Time', 'Description'.", txtWk, LLMPrompt["NothingElse"]["JSON"] }, LLMEvaluator -> LLMConfiguration[<|"Provider" -> "OpenAI", "Model" -> "gpt-4o", "Temperature" -> 0.4, "MaxTokens" -> 5096|>] ] (*"```json[{\"Year\": 1947, \"MinutesToMidnight\": 7, \"Time\": \"23:53\", \"Description\": \"The initial setting of the Doomsday Clock.\"},{\"Year\": 1949, \"MinutesToMidnight\": 3, \"Time\": \"23:57\", \"Description\": \"The Soviet Union tests its first atomic bomb, officially starting the nuclear arms race.\"}, ... *) 

Post process the LLM result:

 res2 = ToString[res, CharacterEncoding -> "UTF-8"]; res3 = StringReplace[res2, {"```json", "```"} -> ""]; res4 = ImportString[res3, "JSON"]; res4[[1 ;; 3]] (*{{"Year" -> 1947, "MinutesToMidnight" -> 7, "Time" -> "23:53", "Description" -> "The initial setting of the Doomsday Clock."}, {"Year" -> 1949, "MinutesToMidnight" -> 3, "Time" -> "23:57", "Description" -> "The Soviet Union tests its first atomic bomb, officially starting the nuclear arms race."}, {"Year" -> 1953, "MinutesToMidnight" -> 2, "Time" -> "23:58", "Description" -> "The United States and the Soviet Union test thermonuclear devices, marking the closest approach to midnight until 2020."}}*) 

Make a dataset with the additional column “Date” (having date-objects):

 dsDoomsdayTimes = Dataset[Association /@ res4]; dsDoomsdayTimes = dsDoomsdayTimes[All, Append[#, "Date" -> DateObject[{#Year, 7, 1}]] &]; dsDoomsdayTimes[[1 ;; 4]] 

Here is an association that is used to retrieve the descriptions from the date objects:

 aDateToDescr2 = Normal@dsDoomsdayTimes[Association, #Date -> #Description &]; 

Remark: The LLM derived descriptions above are shorter than the descriptions in the column “Reason” of the dataset obtained parsing the page data. For the plot tooltips below we use the latter.

Timeline plot

In order to have informative Doomsday Clock evolution plot we obtain and partition dataset’s time series into step-function pairs:

 ts0 = Normal@dsDoomsdayTimes[All, {#Date, #MinutesToMidnight} &]; ts2 = Append[Flatten[MapThread[Thread[{#1, #2}] &, {Partition[ts0[[All, 1]], 2, 1], Most@ts0[[All, 2]]}], 1], ts0[[-1]]]; 

Here are corresponding rule wrappers indicating the year and the minutes before midnight:

 lbls = Map[Row[{#Year, Spacer[3], "\n", IntegerPart[#MinutesToMidnight], Spacer[2], "m", Spacer[2], Round[FractionalPart[#MinutesToMidnight]*60], Spacer[2], "s"}] &, Normal@dsDoomsdayTimes]; lbls = Map[If[#[[1, -3]] == 0, Row@Take[#[[1]], 6], #] &, lbls]; 

Here the points “known” by the original time series are given callouts:

 aRules = Association@MapThread[#1 -> Callout[Tooltip[#1, aDateToDescr[#1[[1]]]], #2] &, {ts0, lbls}]; ts3 = Lookup[aRules, Key[#], #] & /@ ts2; 

Finally, here is the plot:

 DateListPlot[ts3, PlotStyle -> Directive[{Thickness[0.007`], Orange}], Epilog -> {PointSize[0.01`], Black, Point[ts0]}, PlotLabel -> Row[(Style[#1, FontSize -> 16, FontColor -> Black, FontFamily -> "Verdana"] &) /@ {"Doomsday clock: minutes to midnight,", Spacer[3], StringRiffle[MinMax[Normal[dsDoomsdayTimes[All, "Year"]]], "-"]}], FrameLabel -> {"Year", "Minutes to midnight"}, Background -> GrayLevel[0.94`], Frame -> True, FrameTicks -> {{Automatic, (If[#1 == 0, {0, Style["00:00", Red]}, {#1, Row[{"23:", 60 - #1}]}] &) /@ Range[0, 17]}, {Automatic, Automatic}}, GridLines -> {None, All}, AspectRatio -> 1/3, ImageSize -> 1200 ] 

Remark: By hovering with the mouse over the black points the corresponding descriptions can be seen. We considered using clock-gauges as tooltips, but showing clock-settings reasons is more informative.

Remark: The plot was intentionally made to resemble the timeline plot in Doomsday Clock’s Wikipedia page.

Conclusion

As expected, parsing, plotting, or otherwise processing the Doomsday Clock settings and statements are excellent didactic subjects for textual analysis (or parsing) and temporal data visualization. The visualization could serve educational purposes or provide insights into historical trends of global threats as perceived by experts. (Remember, the clock’s settings are not just data points but reflections of complex global dynamics.)

One possible application of the code in this notebook is to make a “web service“ that gives clock images with Doomsday Clock readings. For example, click on this button:

Setup

 Needs["AntonAntonov`FunctionalParsers`"] 
 Clear[TextStats]; TextStats[s_String] := AssociationThread[{"Characters", "Words", "Lines"}, Through[{StringLength, Length@*TextWords, Length@StringSplit[#, "\n"] &}[s]]]; 
 BreakStringIntoLines[str_String, maxLength_Integer : 60] := Module[ {words, lines, currentLine}, words = StringSplit[StringReplace[str, RegularExpression["\\v+"] -> " "]]; lines = {}; currentLine = ""; Do[ If[StringLength[currentLine] + StringLength[word] + 1 <= maxLength, currentLine = StringJoin[currentLine, If[currentLine === "", "", " "], word], AppendTo[lines, currentLine]; currentLine = word; ], {word, words} ]; AppendTo[lines, currentLine]; StringJoin[Riffle[lines, "\n"]] ] 

References

Articles, notebooks

[AAn1] Anton Antonov, “Making robust LLM computational pipelines from software engineering perspective”, (2024), Wolfram Community.

Paclets

[AAp1] Anton Antonov, “FunctionalParsers”, (2023), Wolfram Language Paclet Repository.

Videos

[AAv1] Anton Antonov, “Robust LLM pipelines (Mathematica, Python, Raku)”, (2024), YouTube/@AAA4prediction.

Robust LLM pipelines

… or “Making Robust LLM Computational Pipelines from Software Engineering Perspective”

Abstract

Large Language Models (LLMs) are powerful tools with diverse capabilities, but from Software Engineering (SE) Point Of View (POV) they are unpredictable and slow. In this presentation we consider five ways to make more robust SE pipelines that include LLMs. We also consider a general methodological workflow for utilizing LLMs in “every day practice.”

Here are the five approaches we consider:

  1. DSL for configuration-execution-conversion
    • Infrastructural, language-design level solution
  2. Detailed, well crafted prompts
    • AKA “Prompt engineering”
  3. Few-shot training with examples
  4. Via a Question Answering System (QAS) and code templates
  5. Grammar-LLM chain of responsibility
  6. Testings with data types and shapes over multiple LLM results

Compared to constructing SE pipelines, Literate Programming (LP) offers a dual or alternative way to use LLMs. For that it needs support and facilitation of:

  • Convenient LLM interaction (or chatting)
  • Document execution (weaving and tangling)

The discussed LLM workflows methodology is supported in Python, Raku, Wolfram Language (WL). The support in R is done via Python (with “reticulate”, [TKp1].)

The presentation includes multiple examples and showcases.

Modeling of the LLM utilization process is hinted but not discussed.

Here is a mind-map of the presentation:

Here are the notebook used in the presentation:


General structure of LLM-based workflows

All systematic approaches of unfolding and refining workflows based on LLM functions, will include several decision points and iterations to ensure satisfactory results.

This flowchart outlines such a systematic approach:


References

Articles, blog posts

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

Notebooks

[AAn1] Anton Antonov, “Workflows with LLM functions (in Raku)”, (2023), Wolfram Community.

[AAn2] Anton Antonov, “Workflows with LLM functions (in Python)”, (2023), Wolfram Community.

[AAn3] Anton Antonov, “Workflows with LLM functions (in WL)”, (2023), Wolfram Community.

Packages

Raku

[AAp1] Anton Antonov, LLM::Functions Raku package, (2023-2024), GitHub/antononcube. (raku.land)

[AAp2] Anton Antonov, LLM::Prompts Raku package, (2023-2024), GitHub/antononcube. (raku.land)

[AAp3] Anton Antonov, Jupyter::Chatbook Raku package, (2023-2024), GitHub/antononcube. (raku.land)

Python

[AAp4] Anton Antonov, LLMFunctionObjects Python package, (2023-2024), PyPI.org/antononcube.

[AAp5] Anton Antonov, LLMPrompts Python package, (2023-2024), GitHub/antononcube.

[AAp6] Anton Antonov, JupyterChatbook Python package, (2023-2024), GitHub/antononcube.

[MWp1] Marc Wouts, jupytext Python package, (2021-2024), GitHub/mwouts.

R

[TKp1] Tomasz Kalinowski, Kevin Ushey, JJ Allaire, RStudio, Yuan Tang, reticulate R package, (2016-2024)

Videos

[AAv1] Anton Antonov, “Robust LLM pipelines (Mathematica, Python, Raku)”, (2024), YouTube/@AAA4Predictions.

[AAv2] Anton Antonov, “Integrating Large Language Models with Raku”, (2023), The Raku Conference 2023 at YouTube.

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 .