A Wolfram Language & Mathematica 14.2-es verziójának megjelenése: A nagy adatok találkozása a számítással és a mesterséges intelligenciával
A kiadások dobpergése tovább folytatódik…
Alig hat hónappal ezelőtt (pontosabban 176 nappal ezelőtt) adtuk ki a 14.1-es verziót. Ma örömmel jelentem be, hogy megjelenik a 14.2-es verzió, amely a legfrissebb fejlesztéseinket hozza el az R&D részlegünktől.
Izgalmas időszak ez a technológiánk számára – egyrészt azért, mert egyre több mindent tudunk megvalósítani, másrészt pedig azért, mert technológiánkat világszerte egyre szélesebb körben használják. Különösen figyelemre méltó jelenség, hogy a Wolfram Language-et nemcsak emberek, hanem egyre inkább mesterséges intelligenciák is használják. Nagyon jó látni, hogy a következetes nyelvtervezésbe, megvalósításba és dokumentációba fektetett sokéves munkánk most megtérül: a Wolfram Language páratlanul értékes eszközzé válik az MI-k számára, kiegészítve azok saját beépített képességeit.
De van egy másik oldala is a mesterséges intelligenciának. Az elmúlt hónapban megjelent Wolfram Notebook Assistant segítségével MI-technológiát (és még sok mást) használunk arra, hogy beszélgetésalapú felületet biztosítsunk a Wolfram Language-hez. Ahogy a Wolfram Notebook Assistant megjelenésekor elmondtam, ez egy rendkívül hasznos eszköz mind a szakértők, mind a kezdők számára. Végső soron azonban úgy gondolom, hogy a legfontosabb hatása az lesz, hogy felgyorsítja az átmenetet bármely „X” területről a „számítási X” irányába – lehetővé téve, hogy kihasználjuk a Wolfram Language köré épített teljes technológiai rendszerünket.
Szóval, mi az újdonság a 14.2-es verzióban? A motorháztető alatt számos változtatás történt annak érdekében, hogy a Wolfram Notebook Assistant hatékonyabb és letisztultabb legyen. Ugyanakkor sok jól látható bővítés és fejlesztés is történt a Wolfram Language felhasználói felületén. Összesen 80 teljesen új függvény került bevezetésre – és 177 meglévő függvény esetében történt jelentős frissítés.
Vannak hosszú távú K+F-projektek folytatásai is, például a videókezelés további funkciói, valamint a szimbolikus tömbökkel kapcsolatos új lehetőségek. Emellett teljesen új beépített funkcióterületek is megjelentek, például a játékelmélet. A 14.2-es verzió legnagyobb újdonsága azonban a táblázatos adatok, különösen a nagyméretű táblázatos adatok kezelésére szolgáló új fejlesztés. Ez egy teljesen új alrendszert jelent a Wolfram Language-ben, amely az egész rendszeren belül jelentős hatással bír. Már jó néhány éve dolgozunk rajta, és izgatottan jelentjük be, hogy a 14.2-es verzióban most először elérhetővé válik.
Ha már az új funkciók fejlesztéséről beszélünk: több mint hét évvel ezelőtt mi vezettük be az open software design (nyílt szoftvertervezés) koncepcióját, és elkezdtük élőben közvetíteni a szoftvertervezési megbeszéléseinket. Például a 14.1-es verzió megjelenése óta 43 szoftvertervezési livestreamet tartottunk, összesen 46 órányi időtartamban (emellett én magam 73 órányi más jellegű élő közvetítést is végeztem ez idő alatt). A 14.2-es verzióban most megjelent funkciók közül többnek a fejlesztése évekkel ezelőtt kezdődött. De mivel már régóta livestreamelünk, szinte minden, ami most bekerült a 14.2-be, valamikor élőben, nyilvánosan lett megtervezve egy ilyen adás során.
A szoftvertervezés kemény munka (ahogy ez jól látszik is, ha valaki nézi az adásokat). De mindig izgalmas látni, ahogyan ezek a törekvések végül kézzelfogható eredményekké válnak abban a rendszerben, amelyet hosszú évek óta folyamatosan építünk. És így ma valódi öröm számomra, hogy kiadhatjuk a 14.2-es verziót, és mindenki használatba veheti azokat a dolgokat, amelyeken olyan keményen dolgoztunk.
Notebook Assistant Chat bármely notebookon belül
A múlt hónapban megjelent a Wolfram Notebook Assistant, amelynek célja, hogy „a szavakat számítássá alakítsa” – és ezzel segítse a szakértőket és a kezdőket egyaránt abban, hogy szélesebb körben és mélyebben használják ki a Wolfram Language technológiáját. A 14.1-es verzióban a Notebook Assistant főként egy külön „oldalsó chat” ablakon keresztül volt elérhető. A 14.2-es verzióban azonban a „chat cellák” már minden jegyzetfüzet alapértelmezett részévé váltak, és elérhetők bárki számára, aki rendelkezik Notebook Assistant előfizetéssel.
Csak írj egy ‘ karaktert egy cella elejére, és az automatikusan chat cellává alakul:
Most pedig már elkezdhetsz beszélgetni a Notebook Assistanttal:

Az oldalsó chaten keresztül egy „külön csatornán” kommunikálhatsz a Notebook Assistanttal – ez például nem kerül mentésre a jegyzetfüzettel együtt. A chat cellák esetében viszont a beszélgetés szerves részévé válik a jegyzetfüzetnek.
A Chat Notebooks funkciót valójában már 2023 közepén bevezettük – néhány hónappal a ChatGPT megjelenése után. A Chat Notebooks meghatározta a felhasználói felületet, de akkoriban a chat cellák tartalma kizárólag külső LLM-ekből származott. A 14.2-es verzióban a chat cellák már nem korlátozódnak külön Chat Notebookokra, hanem bármilyen jegyzetfüzetben elérhetők.
Alapértelmezetten a teljes Notebook Assistant technológiai háttérre támaszkodnak, amely jóval többet nyújt, mint egy egyszerű nyers LLM. Ráadásul, ha rendelkezel Notebook Assistant + LLM Kit előfizetéssel, akkor a chat cellákat zökkenőmentesen használhatod, külső LLM szolgáltatónál való külön regisztráció nélkül.
A 14.2-es verzióban a chat cellák funkciói teljes mértékben öröklik a Chat Notebooks képességeit. Például ha egy új cellába ~ karaktert írsz, azzal chat szünetet hozol létre, amely lehetővé teszi egy „új beszélgetés” indítását. Amikor egy chat cellát használsz, az képes mindent látni a jegyzetfüzetedben a legutóbbi chat szünetig bezárólag. (Mellesleg, ha az oldalsó chat felületen keresztül használod a Notebook Assistantot, akkor az látja azt is, milyen kijelölést tettél a „fókuszban lévő” jegyzetfüzetedben.)
Alapértelmezés szerint a chat cellák a Notebook Assistanttal „beszélgetnek”. De ha szeretnéd, beállíthatod úgy is, hogy külső LLM-ekkel kommunikáljanak, ahogyan azt az eredeti Chat Notebookban is lehetett – és ehhez egy kényelmes menü áll rendelkezésre. Természetesen, ha külső LLM-et használsz, akkor nem férsz hozzá a Notebook Assistantban található fejlett technológiákhoz. Ha nem kifejezetten LLM-kutatással foglalkozol, akkor a legtöbb esetben sokkal hasznosabb és értékesebb, ha a chat cellákat az alapértelmezett beállítással használod – azaz a Notebook Assistanttal való kommunikációra.
Hozd el hozzánk gigabájtjait! Bemutatkozik a Tabular
Listák, asszociációk, adathalmazok – ezek mind rendkívül rugalmas módjai a strukturált adatok reprezentálásának a Wolfram Language nyelvben. De most, a 14.2-es verzióban, megjelent egy új lehetőség is: a Tabular.
A Tabular egy rendkívül letisztult és hatékony megközelítést kínál a soros-oszlopos elrendezésű adattáblák kezelésére. És amikor azt mondjuk, hogy „hatékony”, akkor valóban azt értjük alatta, hogy ez az új rendszer rutinszerűen képes gigabájtnyi vagy még több adat kezelésére – akár a memóriában, akár azon kívül (out of core).
Nézzünk egy példát! Kezdjük azzal, hogy importálunk valamilyen táblázatos adatot:

Ez az adatállomány New York City fáiról szól – 683 788 fa, mindegyikhez 45 tulajdonság tartozik (amelyek néha hiányosak). A Tabular számos új megközelítést vezet be, ezek egyike, hogy a táblázat oszlopait kvázi változóként kezeli.
Az alábbi példában ezt arra használjuk, hogy hisztogramot készítsünk a tree_dbh (fatörzs-átmérő) oszlop értékeiből ebben a Tabular objektumban:

A Tabular felfogható úgy, mint egy asszociációkból álló lista optimalizált változata, ahol minden sor egy olyan asszociáció, amelynek kulcsai az oszlopnevek.
Az olyan függvények, mint például a Select, egyszerűen működnek a Tabular objektumokon is:

A Length megadja a sorok számát:

A CountsBy a Tabular objektumot asszociációk listájaként kezeli: minden sorból kinyeri a “spc_latin” (azaz „latin fajnév”) kulcshoz tartozó értéket, majd összeszámolja, hogy az adott érték hányszor fordul elő.
(A “spc_latin” itt rövidítés a #"spc_latin"& kifejezésre.)

Az oszlopnevek lekérdezéséhez használhatjuk az új ColumnKeys függvényt:

Ha a Tabular objektumot asszociációk listájaként tekintjük, akkor részleteket is kinyerhetünk belőle – először a sorokat, majd az oszlopokat adva meg szűrési feltételként:

Most, hogy rendelkezünk a Tabular típussal, számos új műveletet vezettünk be. Egy példa erre az AggregrateRows, amely egy adott Tabularból hoz létre új Tabulart úgy, hogy csoportosítja a sorokat – jelen esetben azokat, amelyeknél a “spc_latin” értéke megegyezik –, majd egy függvényt alkalmaz ezekre a sorokra. Ebben a példában a “tree_dbh” (fatörzs-átmérő) átlagértékét számítja ki:

Egy olyan művelet, mint a ReverseSortBy, szintén „egyszerűen működik” ezen a táblán: itt például a “meandbh” értéke szerint fordított sorrendbe rendezi a sorokat.

Itt egy kis szeletet veszünk a Tabular adatainkból, és abból készítünk egy hagyományos mátrixot:

És most meg tudjuk jeleníteni az eredményt, azaz kirajzolhatjuk New York városban a Virginia-fenyők helyzetét:

Mikor érdemes inkább Tabular-t használni például egy Dataset helyett? A Tabular kifejezetten olyan adatokhoz van kialakítva, amelyek sorokba és oszlopokba rendezettek, és számos erőteljes műveletet támogat, amelyek értelmesek ebben a „téglalap alakú” formátumban lévő adatok esetén.
Ezzel szemben a Dataset általánosabb: tetszőleges hierarchikus adatdimenziókat képes kezelni, ezért általában nem támogatja teljes körűen a Tabular-ra jellemző „téglalap alakú” adatkezelési műveleteket.
Ráadásul mivel a Tabular a „téglalap alakú” adatokra specializálódott, sokkal hatékonyabb is lehet, és mi magunk is a legújabb, típus-specifikus módszereket használjuk vele nagy mennyiségű adat kezelésére.
Ha használod a TabularStructure függvényt, akkor betekintést nyerhetsz abba, hogy a Tabular miért olyan hatékony. Minden egyes oszlopot egy konkrét adattípusként kezel (és igen, ezek a típusok összhangban vannak a Wolfram Language fordítójában használt típusokkal).
Emellett a hiányzó adatok kezelése is optimalizált: a rendszer letisztult módon kezeli ezeket, és számos új függvény áll rendelkezésre, amelyeket kifejezetten a hiányzó adatok kezelésére vezettek be.

Amit eddig láttunk, az a Tabular használata memóriában tárolt („in-core”) adatokkal. Azonban a Tabular ugyanilyen átlátható módon használható „memórián kívüli” („out-of-core”) adatokkal is – például relációs adatbázisban tárolt adatokkal.
Íme egy példa arra, hogyan néz ez ki a gyakorlatban:

Ez egy olyan Tabular, amely egy relációs adatbázisban lévő táblára mutat. Alapértelmezés szerint nem jeleníti meg kifejezetten az adatokat, sőt – az adatokat nem is tölti be a memóriába (mivel azok hatalmas méretűek lehetnek, vagy éppen gyorsan változnak).
Ennek ellenére ugyanúgy meghatározhatsz rajta műveleteket, mint bármely más Tabular esetén. Például így tudhatod meg, milyen oszlopok találhatók benne:

És itt egy művelet kerül megadásra, amelynek eredménye egy szimbolikus, memórián kívüli (out-of-core) Tabular objektum lesz:

Ezt az objektumot „feloldhatod”, és a ToMemory függvény segítségével egy explicit, memóriában lévő Tabular-rá alakíthatod:

Adatkezelés táblázatos formában
Tegyük fel, hogy van egy Tabular objektumod – például ez, amely a pingvineken alapuló adatokat tartalmazza:

Számos olyan művelet létezik, amellyel strukturált módon manipulálhatod a Tabular adatait, és ezek eredményeként egy újabb Tabular-t kapsz vissza. Például egyszerűen lekérheted az utolsó 2 sort a Tabularból:

Vagy akár kiválaszthatsz 3 véletlenszerű sort is a Tabularból:

Más műveletek a Tabular tényleges tartalmától függenek. Mivel minden sort asszociációként kezelhetsz, olyan függvényeket is könnyedén létrehozhatsz, amelyek oszlopnevekre hivatkozva működnek az adatokkal:

Fontos megjegyezni, hogy az oszlopok elemeire mindig hivatkozhatunk a #[name] szintaxissal. Ha a name alfanumerikus karakterekből álló szöveg, akkor használhatjuk a rövidített formát, azaz #name-et is. Más, speciálisabb karaktereket tartalmazó neveknél pedig használhatjuk a #”name” formátumot. Egyes függvényeknél pedig egyszerűen csak a “name” megadásával is utalhatunk a #[“name”] függvényre.
Megjegyzésként: az oszlopelemekre mindig hivatkozhatunk a #[name] szintaxissal. Ha a name alfanumerikus karakterekből áll, akkor használhatjuk a rövidített #name formát is. Más, speciálisabb karaktereket tartalmazó neveknél pedig a #”name” formátumot alkalmazzuk. Néhány függvény pedig lehetővé teszi, hogy egyszerűen csak a “name” szöveget adjuk meg, ami a #[“name”] függvényt jelenti.

Eddig főként a sorok rendezéséről vagy kiválasztásáról beszéltünk a Tabular esetén. De mi a helyzet az oszlopokkal? Íme, hogyan készíthetünk egy új Tabulart, amely csak két oszlopot tartalmaz az eredeti Tabularból:

Mi van akkor, ha nemcsak a meglévő oszlopokat szeretnénk, hanem új oszlopokat is, amelyek a meglévő oszlopokból számított értékeket tartalmaznak? A ConstructColumns segítségével megadhatjuk az új oszlopok neveit és azokat a függvényeket, amelyekkel ezek értékeit kiszámítjuk:

(Megjegyzésként: a Function szó kiírásával elkerülhetjük a zárójelek használatát, ahogy a “species”(StringTake[#species,1]&) példában is látható.)
A ConstructColumns segítségével egy meglévő Tabularból építhetünk újat, míg a TransformColumns lehetővé teszi, hogy egy meglévő Tabular oszlopait módosítsuk — például itt az adott oszlopban a fajneveket az első betűjükre cseréljük:

A TransformColumns nemcsak lehetővé teszi, hogy meglévő oszlopokat módosíts, hanem új oszlopokat is hozzáadhatsz, hasonlóan a ConstructColumns-hoz, ahol megadod az oszlopok tartalmát. De hol helyezi el a TransformColumns az új oszlopokat? Alapértelmezés szerint a meglévő oszlopok végére kerülnek. Ha viszont megadsz egy konkrét meglévő oszlopot, akkor az lesz az elhelyezési pont, ahová az új oszlopokat beilleszti. (Egyébként a “name” -> Nothing pedig egy oszlop eltávolítására szolgál.)

Minden eddig bemutatott művelet soronként külön-külön működik a Tabular-ban. De mi van akkor, ha egy egész oszlopot szeretnénk „beolvasni” egyszerre, hogy aztán használhassuk a számításainkban — például kiszámítani egy oszlop átlagát, majd azt levonni az egyes értékekből?
A ColumnwiseValue pontosan ezt teszi lehetővé: a megadott oszlop(ok)ból egy értéklistát ad át a függvénynek (ebben az esetben a Mean-nek), amely aztán az egész oszlopra alkalmazható.

A ColumnwiseValue lehetővé teszi, hogy egy egész oszlopra alkalmazz egy függvényt, és így egy skalár értéket számolj ki. Van még egy hasonló művelet, a ColumnwiseThread, amely egy értéklistát állít elő, amelyet aztán „bevarr” az adott oszlopba.
Itt például egy olyan oszlopot hozunk létre, amely egy felhalmozott értékekből álló listából származik:

Egyébként, ahogy később még részletesebben is tárgyaljuk, ha külsőleg létrehoztál egy megfelelő hosszúságú értéklistát, amelyet oszlopként szeretnél használni, ezt közvetlenül megteheted az InsertColumns függvénnyel.
Van egy másik, nagyon hasznos fogalom is a táblázatos adatok kezelésében, mégpedig a csoportosítás (grouping). A pingvin adatainkban például minden egyes pingvinhez tartozik egy-egy sor, fajonként. De mi van akkor, ha inkább szeretnénk az adott faj összes pingvinjét összevonni, például kiszámolva az átlagos testtömegüket?
Erre a AggregateRows szolgál. Az AggregateRows hasonlóan működik, mint a ConstructColumns, vagyis megadod az oszlopokat és azok tartalmát, de új, „aggregált” sorokat hoz létre:

Mi ez az első oszlop itt? Az ebben az oszlopban lévő bejegyzések szürke háttérszíne azt jelzi, hogy ez egy úgynevezett „kulcsoszlop” — olyan oszlop, amelynek értékei (esetleg más kulcsoszlopokkal együtt) felhasználhatók a sorok egyedi azonosítására. Később meg fogjuk nézni, hogyan használható a RowKey arra, hogy egy sort pontosan egy kulcsoszlop értékével hivatkozzunk:

Folytassuk az aggregálással! Tegyük fel, hogy nemcsak a fajok szerint szeretnénk csoportosítani, hanem sziget szerint is. Így tehetjük meg ezt az AggregateRows segítségével:

Úgy is mondhatjuk, hogy itt egy olyan táblát látunk, amelynek sorait értékpárok (ebben az esetben „species” és „island”) határozzák meg. Ugyanakkor gyakran kényelmesebb úgy átrendezni az adatokat, hogy ezek az értékek egymásnak megfelelően sorokként és oszlopokként jelenjenek meg. Erre szolgál a PivotTable függvény:

Figyeld meg a – jeleket, amelyek hiányzó értékeket jelölnek; úgy tűnik, hogy például nincsenek Gentoo pingvinek a Dream szigeten, és hasonló esetek.
A PivotTable alapértelmezés szerint pontosan ugyanazokat az adatokat adja vissza, mint az AggregateRows, csak átrendezett formában. Egy további hasznos funkciója a PivotTable-nek az IncludeGroupAggregates opció, amely az All bejegyzéseket is belefoglalja, vagyis az egyes csoportok szerinti összesítő értékeket is megjeleníti:

Ha egyszerre több függvényt is számítasz ki, az AggregateRows az eredményeket külön oszlopokban adja vissza:

A PivotTable szintén kezel több függvényt egyszerre — ilyenkor úgynevezett „kiterjesztett kulcsokat” használva hoz létre oszlopokat:

És most már használhatod a RowKey-t és az ExtendedKey-t, hogy hivatkozz az így létrejött Tabular elemeire:

Adatok bevitele a táblázatba
Láttuk, milyen dolgokat lehet csinálni, ha az adatok Tabular formátumban vannak. De hogyan kerülnek az adatok egy Tabular objektumba? Többféleképpen is lehetséges. Az egyik mód, hogy átalakítjuk őket meglévő struktúrákból, például listákból vagy asszociációkból. A másik lehetőség, hogy fájlból importáljuk az adatokat, például CSV vagy XLSX fájlból (vagy nagyobb adatmennyiség esetén Parquet fájlból) – illetve külső adattárolóból, mint például S3 vagy Dropbox. A harmadik lehetőség egy adatbázishoz való csatlakozás. Emellett a Wolfram Knowledgebase-ből vagy a Wolfram Data Repository-ból is közvetlenül szerezhetünk be adatokat Tabular formátumban.
Így alakíthatsz át egy listák listáját Tabular formátumba:

És így alakíthatod vissza:

Ez ritka tömbökkel (sparse array) is működik – itt például azonnal létrehozunk egy egymillió soros Tabular objektumot:

amelynek tárolása 80 MB-ot igényel:

Íme, mi történik, ha egy asszociációkból álló listát alakítunk át Tabular formátumba:

Ugyanezt a Tabular objektumot megkaphatod úgy is, hogy külön adod meg az adatokat és az oszlopneveket:

Egyébként egy Tabular objektumot átalakíthatsz Dataset-té is:

És ebben az egyszerű esetben vissza is alakíthatod Tabular formátumba:

Általánosságban rengeteg lehetőség van arra, hogyan alakítsunk át listákat, dataseteket stb. Tabular objektumokká, és a ToTabular úgy van kialakítva, hogy ezeket testre tudd szabni. Például a ToTabular segítségével létrehozhatsz egy Tabulart oszlopokból sorok helyett:

Mi a helyzet a külső adatokkal? A 14.2-es verzióban az Import már támogatja a “Tabular” elemet a táblázatos adatformátumok esetén. Tehát például egy CSV fájl esetén így használhatod:
Az Import az adatokat azonnal Tabular formátumban is be tudja olvasni:

Ez a megoldás nagyon hatékonyan működik még hatalmas, millió bejegyzést tartalmazó CSV fájlok esetén is. Automatikusan felismeri az oszlopneveket és a fejlécet. Hasonlóan jól működik strukturáltabb fájlokkal is, például táblázatokkal vagy statisztikai adatformátumokkal, és támogatja a modern oszlopos tárolási formátumokat, mint a Parquet, ORC és Arrow.
Az Import átláthatóan kezeli a hagyományos fájlokat és URL-eket (illetve URI-kat), és szükség esetén kér hitelesítést. A 14.2-es verzióban bevezettük a DataConnectionObject új fogalmát, amely egy távoli adat szimbolikus reprezentációja, lényegében összefoglalva az adat elérésének részleteit. Például itt van egy DataConnectionObject egy S3 tárolóhoz, amelynek tartalmát azonnal be tudjuk importálni:

(A 14.2-es verzióban támogatjuk az Amazon S3-at, az Azure Blob Storage-t, a Dropboxot, az IPFS-t, és még sok más csatlakozót tervezünk hozzáadni. Emellett tervezzük adatraktárakhoz, API-khoz és egyéb forrásokhoz való kapcsolódás támogatását is.)
De mi a helyzet azokkal az adatokkal, amelyek túl nagyok vagy túl gyorsan változnak ahhoz, hogy érdemes legyen explicit módon importálni őket? Egy fontos jellemzője a Tabular-nak (amint fentebb említettük), hogy átlátható módon képes kezelni külső adatokat, például relációs adatbázisokban tárolt információkat.
Íme egy hivatkozás egy nagy külső adatbázisra:
Ez egy olyan Tabular-t határoz meg, amely egy külső adatbázisban található táblára mutat:
Lekérdezhetjük a Tabular méreteit — és láthatjuk, hogy 158 millió sorból áll:
Az általunk vizsgált tábla az OpenStreetMap összes vonalorientált adata. Íme az első 3 sor és 10 oszlop:
Mostantól a Tabular műveletek valójában az külső adatbázisban hajtódnak végre. Itt például olyan sorokat kérünk le, amelyeknek a “name” mezője tartalmazza a “Wolfram” szót:
A tényleges számítás csak akkor történik meg, amikor a ToMemory-t használjuk, és ebben az esetben (mivel sok adat van az adatbázisban) egy kis időbe telik. De hamarosan megkapjuk az eredményt, Tabular formátumban:
És megtudjuk, hogy az adatbázisban 58 olyan elem van, amelynek neve tartalmazza a “Wolfram” szót:
Egy másik adatforrás a Tabular számára a beépített Wolfram Knowledgebase. A 14.2-es verzióban az EntityValue támogatja, hogy az eredményt közvetlenül Tabular formátumban adja vissza:

A Wolfram Knowledgebase rengeteg jó példát kínál adatként a Tabular számára. Ugyanez igaz a Wolfram Data Repository-ra is, ahol általában egyszerűen csak alkalmazhatod a Tabular-t, hogy az adatok Tabular formátumban érhetők el:

Tabuláris tisztítási adatok
Sok szempontból a digitális adat a data science egyik nagy kihívása. Igen, az adatok digitális formában vannak, de nem tiszták, nem mindig számíthatók közvetlenül. A Wolfram Language régóta egyedülállóan hatékony eszköz az adatok rugalmas tisztítására, és például segít átlépni azokat a tíz szintet, amelyeket évekkel ezelőtt a “data computable” (adat számíthatóvá tétele) folyamatában definiáltam.
De most, a 14.2-es verzióban, a Tabular segítségével egy teljesen új, letisztult eszköztár áll rendelkezésünkre az adattisztításhoz. Kezdjük azzal, hogy importálunk néhány „vadonból származó” adatot (és egyébként ez a példa sokkal tisztább, mint sok más):

(Egyébként, ha tényleg nagyon furcsa adatokat tartalmazna a fájl, érdemes lehet a MissingValuePattern opciót használni, amellyel megadhatunk egy mintát, ami azonnal helyettesíti ezeket az „őrült” értékeket a Missing[…] értékkel.)
Rendben, kezdjük azzal, hogy áttekintjük, milyen adatokat hozott be a fájl, a TabularStructure segítségével:

Láthatjuk, hogy az Import sikeresen felismerte az adatok alapvető típusát a legtöbb oszlopban — bár például nem tudja eldönteni, hogy a számok egyszerű számok-e vagy egységgel rendelkező mennyiségek, stb. Emellett azt is jelzi, hogy néhány oszlopban bizonyos számú bejegyzés hiányzó érték.
Az adattisztítás első lépéseként szabaduljunk meg az látszólag irreleváns "id" oszloptól:

Ezután láthatjuk, hogy az első oszlop elemei karakterláncként (stringként) vannak azonosítva — pedig valójában dátumok, amelyeket a második oszlopban lévő időértékekkel kellene kombinálni. Ezt megtehetjük a TransformColumns segítségével, az „extra oszlopot” pedig eltávolíthatjuk úgy, hogy Nothing-nel helyettesítjük:

Ha megnézzük a különböző numerikus oszlopokat, láthatjuk, hogy ezek valójában mennyiségek, amelyekhez mértékegységek is tartoznának. De először, a könnyebb kezelhetőség érdekében, nevezzük át az utolsó két oszlopot:

Most alakítsuk át a numerikus oszlopokat olyan oszlopokká, amelyek mértékegységgel rendelkező mennyiségeket tartalmaznak, és ha már itt tartunk, a °C-t konvertáljuk °F-ra is:

Íme, hogyan ábrázolhatjuk most a hőmérsékletet az idő függvényében:

Elég sok ingadozás látható az ábrán. Ha megnézzük az adatokat, láthatjuk, hogy a hőmérséklet-értékek **több különböző meteorológiai állomásról** származnak. Az alábbi lekérdezés egy **egyetlen állomás** adatait választja ki:

Mi az oka a görbe megszakadásának? Ha egyszerűen oda görgetünk a Tabular megfelelő részére, láthatjuk, hogy az oka hiányzó adat:

Mit tehetünk ez ellen? Nos, van egy nagyon hasznos függvény, a TransformMissing, amely számos lehetőséget kínál a hiányzó adatok kezelésére. Itt például azt kérjük tőle, hogy interpolálással töltse ki a hiányzó hőmérsékletértékeket:

És most már nincsenek megszakítások — viszont kissé rejtélyesen az egész grafikon tovább nyúlik, mint korábban:

Ennek az az oka, hogy az interpolálás olyan esetekben is megtörtént, amikor valójában semmit sem mértek. Ezeket a sorokat eltávolíthatjuk a Discard függvénnyel:

És most már nem lesz ott az a „túlnyúlás” a végén:

Néha kifejezetten hiányzó adatokkal találkozunk; máskor (talán alattomosabban) az adatok egyszerűen hibásak. Nézzük meg az adataink nyomásértékeinek hisztogramját:

Hoppá! Mik ezek a kis értékek? Feltehetőleg hibásak (talán gépelési hibák). Az ilyen „anomáliákat” eltávolíthatjuk a TransformAnomalies segítségével. Itt megmondjuk neki, hogy teljesen törölje azokat a sorokat, ahol a nyomás értéke anomáliának számít:

A TransformAnomalies segítségével azt is megtehetjük, hogy megpróbálja “kijavítani” az adatokat. Itt például minden anomáliás nyomásértéket a tabularban az előző nyomásértékre cserélünk:

A TransformAnomalies-nek azt is megmondhatod, hogy „jelölje meg” az anomáliás értékeket, és tegye őket hiányzóvá (missing). De ha már vannak hiányzó értékeink, mi történik, ha ezekkel számításokat próbálunk végezni? Erre való a MissingFallback.
Ez alapvetően egy nagyon egyszerű függvény, amely mindig visszaadja az első nem hiányzó (non-missing) argumentumát:

Bár egyszerű, nagyon fontos szerepe van abban, hogy könnyen kezelhessük a hiányzó értékeket. Például ez kiszámít egy „northspeed” értéket, és ha a számításhoz szükséges adat hiányzik, akkor automatikusan 0-ra esik vissza:

A táblázat szerkezete
Azt mondtuk, hogy egy Tabular olyan, mint egy asszociációk listája. És valóban, ha rá alkalmazod a Normal függvényt, pontosan ezt kapod meg:

A Tabular azonban belsőleg sokkal kompaktabb és hatékonyabb formában van tárolva. Érdemes erről tudni valamennyit, hogy úgy tudj műveleteket végezni Tabular objektumokon, hogy közben nem kell „szétszedned” őket listákra vagy asszociációkra. Íme a mi alap mintánk, egy egyszerű Tabular:

Mi történik, ha kiveszünk egy sort? Egy TabularRow objektumot kapunk vissza:

Ha alkalmazzuk rá a Normal függvényt, akkor egy asszociációt kapunk:

Íme, mi történik, ha oszlopot választunk ki helyette:

Most a Normal egy listát ad vissza:

Létrehozhatunk egy TabularColumn-t egy listából:

Most a InsertColumns segítségével beilleszthetünk egy ilyen szimbolikus oszlopot egy meglévő Tabular-ba (az, hogy itt a “b” szerepel, azt jelzi, hogy az új oszlopot a „b” oszlop után illessze be):

De mi is valójában egy Tabular belülről? Nézzük meg a példánkat:

A TabularStructure összefoglalót ad a belső szerkezetről:

Az első dolog, ami feltűnik, hogy minden oszlopok alapján van megadva, ami tükrözi, hogy a Tabular alapvetően oszloporientált szerkezet. Ami pedig hatékonyá teszi a Tabular-t, az az, hogy egy oszlopon belül minden érték egységes típust képvisel. Emellett például mennyiségek és dátumok esetén az adatokat úgy tároljuk, hogy az oszlopban belül valójában csak egy számokból álló lista van, és egyetlen példányban tároljuk a „metaadatokat”, hogy hogyan kell ezeket az értékeket értelmezni.
És igen, ez nagy hatással van a hatékonyságra. Íme például a mérete bájtokban a fent említett New York-i fák Tabular objektumunknak:

Ha viszont Normal segítségével asszociációk listájává alakítjuk, az eredmény körülbelül 14-szer nagyobb lesz:

Rendben, de mik is azok a „oszloptípusok” a TabularStructure-ben? A ColumnTypes megadja ezek listáját:

Ezek alacsony szintű típusok, amelyeket a Wolfram Language fordítója is használ. Az, hogy ismerjük ezeket a típusokat, azonnal megmondja, milyen műveleteket végezhetünk el egy adott oszlopon. Ez hasznos mind az alacsony szintű feldolgozásban, mind pedig például abban, hogy tudjuk, milyen típusú vizualizációk lehetségesek.
Amikor az Import CSV fájlból olvas be adatokat, megpróbálja kitalálni, hogy milyen típusú az egyes oszlopok. De néha (ahogy fent említettük) szeretnéd egy oszlop típusát „átalakítani” egy másik típusra, megadva a „cél típust” a Wolfram Language típusleírásával. Például az alábbi kód az „b” oszlopot 32-bites valós számként, a „c” oszlopot pedig méter egységű mennyiségként alakítja át:

Amikor egy Tabular megjelenik egy jegyzetfüzetben, az oszlopfejlécek jelzik az adott oszlop adatainak típusát. Például az első oszlopban egy kis idézőjel mutatja, hogy karakterláncokat tartalmaz. A számok és dátumok egyszerűen megjelenítik az értéküket, a mennyiségek pedig a hozzájuk tartozó mértékegységeket jelzik. Az általános szimbolikus kifejezéseket (például a „f” oszlopot) szögletes zárójelek
jelzik. (Ha az egérrel rámutatsz egy oszlop fejlécére, további részleteket kapsz a típusokról.)
A következő téma a hiányzó adatok kezelése. A Tabular mindig egységes típusként kezeli az oszlopokat, ugyanakkor nyilvántartja, hogy hol vannak hiányzó értékek. Ha kiválasztasz egy oszlopot, ott szimbolikus Missing értékeket láthatsz a hiányzó adatok helyén.

De ha közvetlenül az adott Tabular oszloppal dolgozol, akkor úgy kezeli a hiányzó adatokat, mintha egyszerűen csak hiányoznának — vagyis a hiányzó értékek nem befolyásolják a számításokat, és nem okoznak hibát:

Egyébként, ha „vadonból” hozol be adatokat, az Import megpróbálja automatikusan felismerni az egyes oszlopok megfelelő típusát. Kezeli a gyakori anomáliákat is, például a NaN vagy null értékeket egy számokat tartalmazó oszlopban. Ha viszont más furcsa értékek vannak — például a notfound egy számokkal teli oszlop közepén —, akkor megadhatod az Import számára a MissingValuePattern opcióban, hogy ezeket az értékeket kezelje sima hiányzó adatként.
Van még néhány finomság, amit érdemes megemlíteni a Tabular objektumok szerkezetével kapcsolatban. Az egyik ilyen az úgynevezett kiterjesztett kulcsok (extended keys) fogalma. Tegyük fel, hogy a következő Tabular-unk van:

Ezt „átforgathatjuk oszlopokká” (pivotálhatjuk), úgy, hogy az x és y értékek oszlopfejlécekké válnak, de mindez az általános „value” oszlopfejléc „alatt” helyezkedik el:

De milyen a szerkezete ennek a Tabular-nak? A ColumnKeys segítségével megtudhatjuk:

Most már használhatod ezeket a kiterjesztett kulcsokat indexként a Tabular elemeinek eléréséhez:

Ebben a konkrét esetben, mivel a „al-kulcsok” x és y egyediek, elegendő csak ezeket használni az elemek eléréséhez, anélkül, hogy az extended key másik részét is meg kellene adnunk:

Az utolsó finomság (legalábbis egyelőre) ehhez kapcsolódik, és a kulcsoszlopokra vonatkozik. Normál esetben egy Tabular objektumban a sorokat a pozíciójuk alapján adjuk meg. De ha egy adott oszlop értékei egyediek, akkor azokat is használhatjuk a sor azonosítására. Nézzük ezt a példát:

A „fruit” oszlop különleges, mert minden bejegyzés csak egyszer fordul elő benne — ezért létrehozhatunk egy olyan Tabular-t, amely ezt az oszlopot használja kulcsoszlopként (key column):

Észreveheted, hogy a sorok számozása eltűnt, és a kulcsoszlopot szürke háttérrel jelölik. Ebben a Tabular-ban például a RowKey segítségével hivatkozhatsz egy adott sorra:

Ugyanígy használhatsz asszociációt is az oszlopnévvel:

Mi van akkor, ha egyetlen oszlop értékei nem elegendőek egy sor egyedi azonosításához, de több oszlop együtt már igen? (Például egy valós példában az egyik oszlopban a keresztnevek, a másikban a vezetéknevek, a harmadikban a születési dátumok szerepelnek.) Ebben az esetben kijelölheted az összes ilyen oszlopot kulcsoszlopnak (key columns):

Miután ezt beállítottad, a sorokra úgy hivatkozhatsz, hogy megadod az összes kulcsoszlop értékét egyszerre:

Mindenhol táblázatos
A Tabular egy fontos új módja a strukturált adatok ábrázolásának a Wolfram Language-ben. Már önmagában is erőteljes eszköz, de még hatékonyabbá válik azáltal, hogy szorosan integrálódik a Wolfram Language egyéb képességeivel. Számos függvény azonnal működik vele, és a 14.2-es verzióban több százat tovább fejlesztettek, hogy kihasználják a Tabular különleges funkcióit.
Leggyakrabban az a cél, hogy közvetlenül tudjunk műveleteket végezni a Tabular oszlopain. Például adott ez a Tabular:

Azonnal készíthetünk egy vizualizációt a Tabular két oszlopa alapján:

Ha az egyik oszlop kategóriás adatokat tartalmaz, akkor a rendszer felismeri ezt, és ennek megfelelően ábrázolja a grafikont:

Egy másik terület, ahol a Tabular azonnal használható, a gépi tanulás. Például ezzel létrehozhatsz egy osztályozó függvényt, amely megpróbálja meghatározni egy pingvin faját a hozzá tartozó adatok alapján:

Most ezt az osztályozó függvényt használhatjuk arra, hogy egy pingvin más adatai alapján megjósoljuk a faját:

Az egész Tabular-ból készíthetünk egy jellemzőtér-plotot is, amelyen a pontokat a fajok szerint címkézzük meg:

Vagy „megtanulhatjuk a lehetséges pingvinek eloszlását”:

És véletlenszerűen generálhatunk 3 „kitalált pingvint” ebből az eloszlásból:

Algebra szimbolikus tömbökkel
A 14.1-es verzió egyik nagy újítása volt a szimbolikus tömbök bevezetése — vagyis az a képesség, hogy vektor, mátrix és tömb változókat tartalmazó kifejezéseket hozzunk létre, és ezekről deriváltakat számoljunk. A 14.2-es verzióban ezt a szimbolikus tömbökkel való számolás koncepcióját tovább fejlesztjük: most először automatizáljuk azt a korábban kézzel végzett folyamatot, amely a szimbolikus tömbökkel való algebrai műveletek elvégzését és az ilyen kifejezések egyszerűsítését jelenti.
Kezdjük az ArrayExpand függvénnyel. A régóta ismert Expand függvény csak a szokásos skaláris szorzatok kifejtésével foglalkozik, így ebben az esetben nem csinál semmit:

De a 14.2-es verzióban már ott van az ArrayExpand, amely elvégzi ezt a kifejtést:

Az ArrayExpand kezeli a sok olyan szorzás általánosítását is, amelyek nem kommutatívak:

Ilyen példában valójában nem kell tudnunk semmit az a és b változókról. De előfordul, hogy a kifejtést nem tudjuk elvégezni anélkül, hogy például ismernénk a méreteiket. Egy módja annak, hogy megadjuk ezeket a méreteket, hogy feltételként adjuk meg az ArrayExpand-ben:

Alternatív megoldásként használhatunk egy kifejezetten szimbolikus tömb változót:

A 14.2-es verzióban az ArrayExpand általánosított szorzatok kifejtése mellett már a szimbolikus tömb kifejezések általános egyszerűsítését is támogatja:

Az ArraySimplify függvény kifejezetten a szimbolikus tömbök egyszerűsítésére szolgál, miközben az egyéb kifejezésrészeket változatlanul hagyja. A 14.2-es verzió sokféle tömb egyszerűsítést támogat:


Ezeket az egyszerűsítéseket elvégezhetnénk anélkül, hogy tudnánk bármit a és b méreteiről. De néha nem tudunk továbbmenni anélkül, hogy ne ismernénk a dimenziókat. Például, ha nem ismerjük a méreteket, akkor ezt kapjuk:

De ha megadjuk a dimenziókat, akkor kifejezetten egyszerűsíthetjük az eredményt egy n×n-es egységmátrixra:

Az ArraySimplify figyelembe tudja venni a tömbök szimmetriáját is. Például, állítsunk be egy szimbolikus szimmetrikus mátrixot:

És most az ArraySimplify azonnal ki tudja értékelni ezt az egyszerűsítést:

A szimbolikus formában történő algebrai műveletek teljes tömbökre nagyon erőteljes képességet jelentenek. Ugyanakkor néha fontos, hogy az egyes tömbelemekre is külön-külön ránézzünk. A 14.2-es verzióban ezért bevezettük a ComponentExpand függvényt, amely lehetővé teszi, hogy a tömbök komponenseit szimbolikus formában kifejtsd.
Például ez a függvény egy 2 komponensű vektort vesz, és kifejezi explicit listaként, két szimbolikus komponenssel:

Alulról nézve ezek a komponensek az Indexed segítségével vannak ábrázolva:

Íme egy 3×3-as mátrix determinánsa, szimbolikus komponensek felhasználásával kifejtve:

És itt egy mátrix hatványozás példája:

Adott két 3D vektor, és
, például kiszámíthatjuk a vektoriális (kereszt) szorzatukat:

Majd ezt követően elvégezhetjük a skaláris szorzatot (dot product) egy inverz mátrixszal:

Nyelvi felhangolás
Mint a Wolfram Language mindennapi felhasználója, nagy örömmel tapasztalom, milyen gördülékenyen tudom a számítási ötleteket kóddá alakítani. De minél könnyebbé tettük ezt a folyamatot, annál inkább látjuk, hol lehet még finomítani a nyelvet. A 14.2-es verzióban — mint minden korábbiban is — számos ilyen „nyelvi finomhangolás” került be.
Egy egyszerű példa — amelynek hasznossága különösen a Tabular-ral válik nyilvánvalóvá — a Discard függvény. Úgy is gondolhatunk rá, mint a Select párjára: ez az elemeket eldobja egy általad megadott feltétel szerint:

És a Discard bevezetése mellett a Select is tovább lett fejlesztve. Alapértelmezésben a Select egyszerűen visszaadja a kiválasztott elemek listáját. De a 14.2-es verzióban most már másfajta eredményeket is kérhetsz. Itt például az elemek „indexét” (vagyis pozícióját) kérjük le, amelyeket a NumberQ kiválaszt:

Valami, ami nagyon hasznos lehet nagy mennyiségű adat kezelésénél, az a Select (és Discard) által visszaadott bitvektor adatstruktúra — ez egy bitmaszkot ad, amely megmutatja, hogy mely elemek lettek kiválasztva, és melyek nem:

Egyébként így kérhetsz többféle eredményt is a Select és a Discard függvényektől:

A Tabular kapcsán már említettük a MissingFallback függvényt. Egy másik új, a kód robusztusabbá tételéhez és hibakezeléshez kapcsolódó funkció a Failsafe.
Tegyük fel, hogy van egy listád, amely néhány „sikertelen” (Failure) elemet tartalmaz. Ha erre a listára alkalmazol egy függvényt, például f-et, akkor az megpróbál végrehajtódni a hibás elemekre is — pont úgy, mint a többi elemre:

De nagyon valószínű, hogy az f függvényt nem úgy állítottuk be, hogy tudjon kezelni ilyen Failure típusú bemeneteket. És itt jön képbe a Failsafe.
Ugyanis a Failsafe[f][x] azt csinálja, hogy ha x nem hiba (Failure), akkor egyszerűen f[x]-et ad vissza; ha viszont x hiba, akkor magát a hibát adja vissza változatlanul. Így most már bátran alkalmazhatjuk f-et a listánkra, anélkül hogy attól kellene tartanunk, hogy a hibás elemek problémát okoznak:

Ha már a trükkös hibakezelési esetekről beszélünk: a 14.2-es verzió egy másik új függvénye a HoldCompleteForm.
A HoldForm lehetővé teszi, hogy egy kifejezést úgy jelenítsünk meg, hogy annak szokásos kiértékelése ne történjen meg. Azonban — a Hold-hoz hasonlóan — bizonyos átalakításokat továbbra is engedélyez. A HoldCompleteForm ezzel szemben — ahogy a HoldComplete is — minden ilyen átalakítást megakadályoz. Tehát míg a HoldForm például itt egy kicsit „összezavarodik”, amikor a sorozat „feloldódik”…

A HoldCompleteForm viszont teljesen megtartja és pontosan úgy jeleníti meg a kifejezést, ahogy az van — még a sorozatot sem fejti ki:

Egy másik finomhangolás a 14.2-es verzióban a Counts függvényt érinti. Gyakran előfordul, hogy szeretném megszámolni egy lista elemeit úgy, hogy akkor is 0-t kapjak, ha egy adott elem hiányzik. Alapértelmezésben a Counts csak a meglévő elemeket számolja meg:

De a 14.2-es verzióban hozzáadtunk egy második argumentumot a Counts függvényhez, amellyel megadhatod az összes olyan elemet, amelyet szeretnél megszámolni — akkor is, ha ezek esetleg nem szerepelnek a listában:

A 14.2-es verzió nyelvi finomhangolásainak utolsó példájaként megemlítem az AssociationComap-ot. A 14.0-ban vezettük be a Comap-ot, amely a Map „ko-” megfelelője (ahogy a „co-functor” kifejezésben is szerepel):

A 14.2-es verzióban bemutatjuk az AssociationComap-ot — az AssociationMap „ko-” változatát:

Gondolj rá úgy, mint egy szép módra, hogy címkézett táblázatokat hozz létre dolgokból, például így:

Színeink élénkítése; Felpörgés 2025-re
2014-ben — a 10.0-s verzióhoz — nagy átalakítást végeztünk az alapértelmezett színeken minden grafikai és vizualizációs funkciónkban, hogy egy jól működő megoldást alkossunk. (És ahogy nemrég észrevettük, meglepő módon az azt követő években sok más grafikai és vizualizációs könyvtár mintha lemásolta volna, amit mi csináltunk!) Most, egy évtizeddel később, megváltoztak a vizuális elvárások és a megjelenítési technológiák, úgyhogy eljött az ideje, hogy felfrissítsük a színeinket a 2025-ös évre.
Így nézett ki egy tipikus grafikon a 10.0 és 14.1-es verziók között:

És íme ugyanaz a grafikon a 14.2-es verzióban:

A tervezés szerint még mindig teljesen felismerhető, de egy kis plusz lendületet kapott.
Több görbével több szín is jár. Íme a régi verzió:

És itt a friss, új verzió:

A hisztogramok is élénkebbek lettek. Íme a régi verzió:

És itt a új változat:

Íme az összehasonlítás a régi („2014”) és az új („2025”) színek között:

Finom, de mégis számottevő különbség. Be kell vallanom, hogy az elmúlt években egyre gyakrabban éreztem, hogy szinte minden egyes Wolfram Language-ben készült ábrámon színeket kellett igazítanom. De örömmel mondhatom, hogy az új színekkel ez a késztetés megszűnt — most már gond nélkül használhatom újra az alapértelmezett színeinket!
LLM racionalizálás és streamelés
A Wolfram Language-ben először 2023 közepén vezettük be a programozott hozzáférést a nagy nyelvi modellekhez (LLM-ekhez), olyan függvényekkel, mint az LLMFunction és az LLMSynthesize. Akkor ezek a funkciók külső LLM szolgáltatásokhoz igényeltek hozzáférést. Azonban a múlt hónapban megjelent LLM Kit (a Wolfram Notebook Assistant mellett) révén ezek a funkciók zökkenőmentesen elérhetővé váltak mindenkinek, aki rendelkezik Notebook Assistant + LLM Kit előfizetéssel. Ha megvan az előfizetésed, a 14.2-es verzióban bárhol és bármikor használhatod a programozott LLM-funkciókat további beállítás nélkül.
Emellett két új függvényt is kaptunk: LLMSynthesizeSubmit és ChatSubmit. Ezek főleg arra szolgálnak, hogy inkrementális (folyamatos, részleges) eredményeket kapj vissza az LLM-ektől (ami mostanában fontos, mert az LLM-ek elég lassúak lehetnek). Hasonlóan a CloudSubmit vagy URLSubmit aszinkron függvényekhez, az LLMSynthesizeSubmit és a ChatSubmit is aszinkron módon működnek: elindítasz velük egy folyamatot, amely egy megadott esemény bekövetkeztekor meghív egy kezelő függvényt.
Mindkét függvény számos eseményt támogat. Például az egyik ilyen az “ContentChunkReceived”, ami akkor aktiválódik, amikor az LLM-től megérkezik egy tartalmi részlet (chunk).
Íme, hogyan használhatod ezt:

Az LLMSynthesizeSubmit egy TaskObject-et ad vissza, és elindítja a szöveg szintetizálását a megadott prompt alapján. Minden alkalommal, amikor az LLM kap egy szövegrészt (chunk), meghívja az általad megadott kezelőfüggvényt. Néhány pillanat múlva az LLM befejezi a szintetizálást, és ha lekéred a c értékét, meg fogod látni az általa előállított szövegrészeket egyenként:

Próbáljuk meg újra, de most dinamikus megjelenítést hozunk létre egy s nevű sztringhez, majd az LLMSynthesizeSubmit-tel folyamatosan gyűjtjük és frissítjük ebbe a sztringbe a generált szöveget:
A ChatSubmit a ChatEvaluate aszinkron megfelelője — és arra használható, hogy teljes csevegési élményt hozz létre, ahol a tartalom folyamatosan, valós időben áramlik be a jegyzetfüzetedbe, amint az LLM (vagy az LLM által meghívott eszközök) elkezdik generálni azt.
A párhuzamos számítások egyszerűsítése: Indítsa el az összes gépet!
Közel 20 éve kínálunk egyszerűen használható párhuzamos számítási lehetőséget a Wolfram Language-ben, olyan függvényeken keresztül, mint a ParallelMap, ParallelTable és Parallelize. A párhuzamos számítás történhet egyetlen gép több magján, vagy egy hálózaton elérhető több gépen. (Például az én jelenlegi beállításomban 7 gépem van, összesen 204 maggal.)
Az elmúlt években — részben válaszul arra, hogy az egyes gépeken is egyre több processzormag érhető el — fokozatosan egyszerűsítettük a párhuzamos számítási környezet elindításának módját. A 14.2-es verzióban pedig eljutottunk oda, hogy magát a párhuzamos környezet előkészítését is párhuzamosítottuk. Ez azt jelenti például, hogy az én 7 gépemen minden párhuzamos kernel egyszerre indul el — így az egész folyamat már csak néhány másodpercet vesz igénybe, szemben a korábbi percekkel.

A 14.2-es verzió egy másik újdonsága a párhuzamos számítás terén az a képesség, hogy a ParallelTable automatikusan párhuzamosítson több változó mentén is. A ParallelTable eddig is többféle algoritmust használt arra, hogy optimalizálja, hogyan ossza szét a számításokat a különböző kernelek között. Most ezt a képességet kibővítettük úgy, hogy többváltozós ciklusokkal is hatékonyan tudjon dolgozni.

Aki rendszeresen végez nagyszabású számításokat a Wolfram Language-ben, az tudja, milyen hatalmas előnyt jelentenek a beépített párhuzamos számítási lehetőségek — számomra is nehéz lenne túlhangsúlyozni ezek fontosságát. Általában először Map, Table stb. segítségével dolgozom ki a számítási logikát, majd amikor eljön az idő a teljes méretű futtatásra, egyszerűen lecserélem ezeket ParallelMap, ParallelTable stb. verziókra. És egészen elképesztő, milyen sokat számít egy akár 200-szoros gyorsulás (feltéve, hogy a számítás nem igényel túl sok kommunikációt a kernelek között).
(Egyébként, ha már szóba került a kommunikációs többlet: a 14.2-es verzióban két új függvény segít csökkenteni ezt — ezek a ParallelSelect és a ParallelCases. Ezek lehetővé teszik, hogy listákból párhuzamosan válasszunk ki elemeket vagy keressünk illeszkedő eseteket, úgy, hogy csak az eredményeket kell visszaküldeni a mester kernelnek. Ez a funkcionalitás korábban is elérhető volt például így: Parallelize[… Select[…] …], de most külön, célzott függvényekként is elérhető, egyszerűbb és hatékonyabb formában.)
Kövesd ezt ____! Követés videóban
Tegyük fel, hogy van egy videónk — például emberekről, akik egy vasútállomáson sétálnak. Már egy ideje elérhető a lehetőség a Wolfram Language-ben, hogy egy ilyen videóból kiválasszunk egyetlen képkockát, és azonosítsuk rajta az embereket. De a 14.2-es verzióban most valami újdonság érkezett: lehetőség nyílik a mozgó objektumok követésére a videó több képkockáján keresztül.
Kezdjük is el egy videóval:

Kivehetnénk egyetlen képkockát, és azon meghatározhatnánk a kép határoló dobozait (bounding boxokat). De a 14.2-es verziótól kezdve egyszerűen alkalmazhatjuk az ImageBoundingBoxes függvényt az egész videóra egyszerre.

Ezután a határoló dobozok adatait felhasználva kiemelhetjük az embereket a videóban — méghozzá a 14.2-ben bevezetett új HighlightVideo függvénnyel:

Ez azonban csak azt jelzi külön-külön, hogy hol vannak az emberek az egyes képkockákon; nem köti össze őket a képkockák között. A 14.2-es verzióban viszont bevezettük a VideoObjectTracking függvényt, amely képes követni az objektumokat a videó képkockái között.

Most, ha a HighlightVideo függvényt használjuk, a különböző objektumok eltérő színekkel lesznek megjelölve a videóban.

Ez kiválasztja az összes egyedi objektumot, amelyet a videó során azonosítottak, és megszámolja őket.

„Hol van a kutya?” – kérdezhetnéd. Nos, biztosan nincs ott sokáig:

És ha megkeressük az első képkockát, ahol elvileg meg kellett volna jelennie, úgy tűnik, hogy a jobb alsó sarokban lévő – feltehetően ember – tévesen kutyaként lett azonosítva.

És igen, ez az, amit a rendszer kutyának hitt.

Játékelmélet
„És mi a helyzet a játékelmélettel?” – kérdezték sokan már régóta. Nos, igen, a Wolfram Language-ben eddig is sok játékelméleti elemzést végeztek, és számos csomag készült különféle területeire. De a 14.2-es verzióban végre bevezetjük a beépített rendszerfüggvényeket játékelméleti számításokhoz (mind mátrix-, mind faalapú játékokhoz).
Így adhatunk meg egy (nullösszegű) 2-játékos mátrixjátékot:

Ez határozza meg a kifizetéseket, amikor mindkét játékos meghatározott lépést tesz. Ezt az adatokat egy Dataset formájában is megjeleníthetjük:

Egy másik lehetőség a játék „leplotolása” a MatrixGamePlot:

Rendben, de hogyan tudjuk „megoldani” ezt a játékot? Másképp fogalmazva: milyen lépést tegyen mindkét játékos, és milyen valószínűséggel, hogy hosszú távon a lehető legnagyobb átlagos nyereséget érjék el? (A feltételezés az, hogy minden egyes körben a játékosok egyidejűleg és egymástól függetlenül választanak lépést.)
Egy olyan „megoldást”, amely minden játékos számára maximalizálja a várható kifizetést, Nash-egyensúlynak nevezzük. (Egy történelmi érdekesség: John Nash régóta használója volt a Mathematica-nak, illetve a mai Wolfram Language-nek – bár évekkel azután, hogy kidolgozta a Nash-egyensúly fogalmát.)
Nos, a 14.2-es verzióban a FindMatrixGameStrategies függvény kiszámítja a mátrixjátékok optimális stratégiáit (más néven Nash-egyensúlyait):

Ez az eredmény azt jelenti, hogy ebben a játékban az 1-es játékosnak az 1-es lépést kell választania bizonyos valószínűséggel, a 2-est pedig más valószínűséggel, míg a 2-es játékosnak szintén megadott valószínűségekkel kell választania a saját lépései közül. De mennyi a várható kifizetésük? Ezt a MatrixGamePayoff függvénnyel számíthatjuk ki:

Elég nehézzé válhat követni a különböző eseteket egy játék során, ezért a MatrixGame lehetővé teszi, hogy tetszőleges címkéket adjunk meg a játékosoknak és a lépéseknek:

Ezeket a címkéket aztán a vizualizációkban használja fel a rendszer:

Amit most bemutattunk, valójában egy klasszikus példa – az úgynevezett „fogolydilemma”. A Wolfram Language-ben most már elérhető a GameTheoryData, amely körülbelül 50 standard játék gyűjteménye. Íme egy példa, amely négy játékost tartalmaz:

Ennek a játéknak a megoldása kevésbé egyszerű, de íme az eredmény — összesen 27 különböző megoldással:

Igen, a vizualizációk továbbra is működnek, még akkor is, ha több játékos van (itt például az 5 játékosos esetet mutatjuk, a 50. játék megoldását jelölve):

Érdemes megemlíteni, hogy az ilyen típusú játékok megoldásához a legújabb polinomiális egyenletmegoldó képességeinket használjuk — így nemcsak az összes lehetséges Nash-egyensúlyt találjuk meg rendszeresen (nem csak egyetlen fixpontot), hanem pontos, egzakt eredményeket is kapunk:

A mátrixjátékok mellett, amelyekben a játékosok egyszerre, egyszer választanak lépést, támogatjuk a fa-játékokat is, ahol a játékosok felváltva lépnek, és így egy lehetséges kimenetelekből álló fa jön létre, amely minden játékos számára meghatározott kifizetésekkel végződik. Íme egy példa egy nagyon egyszerű fa-játékra:

Legalább egy megoldást megkaphatunk ehhez a játékhoz — amelyet egy beágyazott struktúra ír le, ami megadja az optimális valószínűségeket minden játékos minden lépéséhez az egyes körökben:

A fa-játékok esetében a dolgok jóval összetettebbé válhatnak. Íme egy példa, ahol előfordul, hogy a játékosok néha nem tudják, mely ágakat választották (amit az egymással szaggatott vonalakkal összekötött állapotok jeleznek):

A 14.2-es verzióban elérhető funkciók szinte teljes körű lefedettséget nyújtanak egy tipikus bevezető játékelmélet kurzus alapfogalmaiból. Ugyanakkor, a Wolfram Language jellegzetességeként, mindez számítható és bővíthető – így valósághűbb játékokat is tanulmányozhatsz, és gyorsan végezhetsz sok példát az intuíció fejlesztéséhez.
Eddig főként a „klasszikus játékelméletre” koncentráltunk, különösen arra a tulajdonságra, hogy minden akciócsomópont eltérő akciósorozat eredménye. Azonban olyan játékok, mint a tic-tac-toe (amelyet nemrég multiway gráfokkal tanulmányoztam) egyszerűsíthetők az ekvivalens akciócsomópontok egyesítésével. Több akciósorozat is vezethet ugyanahhoz a tic-tac-toe játékhoz, ahogy ez gyakori az ismételt játékok esetén is. Ezek a gráfszerkezetek nem illeszthetők a 14.2-es verzióban bevezetett klasszikus játékelméleti fához – bár (ahogy az én tapasztalataim is mutatják) különösen jól elemezhetők a Wolfram Language segítségével.
A Syzygies számítása és a csillagászat egyéb fejlesztései
Az csillagászatban sokféle „véletlen egybeesés” fordul elő — olyan helyzetek, amikor dolgok egy különleges módon egyvonalba kerülnek. Az égi fedések (fogyatkozások) egy ilyen példa. De még számos más is létezik. A 14.2-es verzióban most már elérhető egy általános funkció, a FindAstroEvent, amely ezeknek az „egybeeséseknek”, technikailag szinódusoknak (angolul: syzygies, ejtsd: szizídzísz) és más „különleges konfigurációknak” a megtalálására szolgál.
Egy egyszerű példa az őszi napéjegyenlőség szeptemberben:

Nagyjából ez az az időpont, amikor a nappal és az éjszaka hossza megközelítőleg egyenlő. Pontosabban akkor történik, amikor a Nap az égbolton az ekliptika (vagyis a Föld Nap körüli pályasíkja) és a mennyei egyenlítő (a Föld egyenlítőjének vetülete) metszéspontjai közül az egyiknél tartózkodik — ahogy itt is látható (az ekliptika a sárga vonal, a mennyei egyenlítő pedig a kék):

Egy másik példaként nézzük meg, mikor lesz a következő évszázadban Jupiter és Szaturnusz legközelebb egymáshoz az égbolton:

Elég közel kerülnek egymáshoz ahhoz, hogy egyszerre láthatóak legyenek a holdjaik is:

Hihetetlen számú csillagászati konfigurációnak adtak történelmileg különleges neveket. Vannak napéjegyenlőségek, napfordulók, egyenlő fényű napok (equilux), delelések, együttállások, szembenállások, negyedállások, valamint periapszisok és apoapszisok (például perigeum, perihelion, periareion, perijove, perikrone, periuranion, periposeideum stb.). A 14.2-es verzióban mindezeket támogatjuk.
Például ez megadja a következő alkalmat, amikor Triton lesz a legközelebb a Neptunuszhoz:

Egy híres példa Merkúr perihelionjára (a Naphoz legközelebbi pontja). Számoljuk ki Merkúr pozícióját (a Napból nézve) az első néhány évtized perihelionjain a tizenkilencedik században:

Látható, hogy van egy rendszeres „előrehaladás” (némi ingadozással egyetemben):

Most tehát mennyiségileg kiszámoljuk ezt az előrehaladást. Először megtaláljuk az első perihelionok időpontjait 1800-ban és 1900-ban:

Most kiszámoljuk a Merkúr helyzetének szögeltérését ezekben az időpontokban:

Ezután osszuk el ezt az értéket az időeltolódással.

és átváltjuk a mértékegységeket:

Híres tény, hogy ebből az előrehaladásból 43 ívmásodperc századonként a gravitációs vonzás inverz négyzetes törvényétől való eltérés eredménye, amit az általános relativitáselmélet vezetett be — és természetesen ezt a csillagászati számítási rendszerünk is figyelembe veszi. (A maradék előrehaladást pedig a Vénusz, a Jupiter, a Föld és más bolygók hagyományos gravitációs hatásai okozzák.)
PDE-k most mágneses rendszerekhez is
Több mint másfél évtizeddel ezelőtt elköteleztük magunkat amellett, hogy a Wolfram Language egy teljes értékű PDE-modellezési környezetté váljon. Természetesen segítségünkre volt, hogy támaszkodhattunk a Wolfram Language összes többi képességére is — és amit létrehoztunk, az a rendszer többi részével való szinergiájának köszönhetően felbecsülhetetlenül értékesebb lett. Az évek során nagy erőfeszítéssel fokozatosan felépítettük a szimbolikus PDE-modellezési lehetőségeket az összes szokásos területen. És mostanra azt gondolom, joggal mondhatjuk, hogy ipari léptékben is képesek vagyunk kezelni a való életben felmerülő PDE-modellezések jelentős részét.
De mindig vannak újabb esetek, amelyekhez új beépített képességeket lehet fejleszteni, és a 14.2-es verzióban új modellezési alapépítőelemeket adtunk hozzá a statikus és kvázistatikus mágneses terekhez. Így például most már modellezhetünk egy homokóra alakú mágnest. Ez meghatározza a peremfeltételeket, majd megoldja a mágneses skalárpotenciál egyenleteit:

Ezután ezt az eredményt felhasználhatjuk, és például azonnal megjeleníthetjük az általa meghatározott mágneses erővonalakat:

A 14.2-es verzió emellett olyan alapelemekkel is bővült, amelyek lehetővé teszik a lassan változó elektromos áramok és az általuk létrehozott mágneses terek kezelését. Mindez azonnal integrálódik a többi modellezési területünkkel, mint például a hőátadás, a folyadékdinamika, az akusztika stb.
A parciális differenciálegyenletek (PDE) modellezéséről és annak alkalmazásairól sok mindent lehet mondani, és a 14.2-es verzióban több mint 200 oldalnyi új, tankönyv jellegű dokumentációt adtunk hozzá a PDE modellezés témájában – köztük néhány kutatási szintű példával is.
Új funkciók a grafikában, a geometriában és a grafikonokban
A grafika mindig is erőssége volt a Wolfram Language-nek, és az elmúlt évtized során nagyon fejlett számítógépes geometriai képességeket is kiépítettünk. A 14.2-es verzió további „habot tesz a tortára”, különösen a grafika és a geometria, valamint a geometria és a rendszer más részei közötti kapcsolatokban.
Például a 14.2-es verzióban már geometriai képességekkel is rendelkeznek olyan elemek, amelyek korábban csak grafikai primitívek voltak. Ez itt például egy geometriai tartomány, amely egy Bézier-görbe kitöltésével jön létre:

És most már elvégezhetjük rajta az összes szokásos számítógépes geometriai műveletet is:

Ilyesmi most már szintén működik:

A 14.2-es verzió másik újdonsága a MoleculeMesh, amely lehetővé teszi, hogy a molekuláris struktúrákból számítható geometriát építsünk fel. Íme egy molekula grafikus megjelenítése:

És itt van most a molekulának megfelelő geometriai háló:

Ezután számítógépes geometriai műveleteket végezhetünk ezen a hálón:


A 14.2-es verzióban egy új grafikonrajzolási módszer is megjelent, amely képes kihasználni a szimmetriákat. Ha egy szimmetrikus rácsból készítesz rétegzett gráfot, az nem jelenik meg azonnal szimmetrikusan:

De az új, “SymmetricLayeredEmbedding” gráfelrendezési módszerrel már meg fog jelenni szimmetrikusan:

Felhasználói felület hangolása
Egy nagyszerű felhasználói felület elkészítése mindig folyamatos csiszolás története, és a jegyzetfüzet (notebook) felületén már majdnem négy évtizede dolgozunk ezen. A 14.2-es verzióban több figyelemre méltó finomítás is megjelent. Az egyik ezek közül az opcióértékek automatikus kiegészítése.
Már régóta mutatjuk az opciók kiegészítéseit, amelyeknek diszkrét, jól körülhatárolt, gyakori beállításai vannak (például All, Automatic stb.). A 14.2-es verzióban „sablon kiegészítéseket” adunk hozzá, amelyek megmutatják a beállítások szerkezetét, és lehetővé teszik, hogy a tabulátorral végiglépkedve töltsd ki az egyes értékeket. Az évek során az egyik olyan hely, ahová szinte mindig visszatérek a dokumentációban, a FrameLabel beállításai voltak. Mostantól az automatikus kiegészítés rögtön megmutatja nekem ezeknek a beállításoknak a szerkezetét is:
Az automatikus kiegészítésben továbbá hozzáadtuk a kontextusnevek, kontextusaliasok és kontextusokat tartalmazó szimbólumok kiegészítésének képességét. Minden esetben az automatikus kiegészítés „rugalmas” (fuzzy), vagyis nemcsak a név elején lévő karakterekre reagál, hanem a név bármely pontján megjelenő karakterekre is — így elég beírni a szimbólum nevének néhány karakterét, és a releváns kontextusok megjelennek a kiegészítések között.
Egy másik apró kényelmi funkció a 14.2-es verzióban, hogy most már képes vagy képeket egyik jegyzetfüzetből a másikba húzni, vagy akár bármely más alkalmazásba, amely elfogadja a húzott képeket. Korábban már lehetett képeket más alkalmazásokból húzni a jegyzetfüzetekbe, de mostantól ez fordítva is működik.
Valami, ami jelenleg még csak macOS-re jellemző, az az ikon-előnézet (Icon Preview) és a Quick Look továbbfejlesztett támogatása. Így ha egy mappában jegyzetfüzetek vannak, és az Ikon nézetet választod, akkor az egyes jegyzetfüzetek tartalmának kis képi reprezentációját látod ikonként:
A 14.2-es verzió alatt a háttérben néhány olyan infrastruktúrális fejlesztés is zajlott, amelyek lehetővé teszik jelentős új funkciók megjelenését a következő verziókban. Ezek közül néhány a sötét mód általános támogatásával kapcsolatos. (Igen, eleinte azt gondolhatnánk, hogy a sötét mód valahogy triviális, de ha elkezdjük végiggondolni az összes színes grafikát és felületelemet, akkor egyértelművé válik, hogy nem az. Bár például jelentős erőfeszítés után nemrég megjelent a sötét mód a Wolfram|Alpha számára.)
Így például a 14.2-es verzióban találkozhatsz az új LightDarkSwitched szimbólummal, amely része annak a mechanizmusnak, amely automatikusan vált a világos és sötét módhoz illeszkedő stílusok között. És igen, van egy LightDark stílusopció is, amely a jegyzetfüzetekhez váltja a módot — amely legalábbis kísérleti jelleggel támogatott.
A világos/sötét módhoz kapcsolódik még a témaszínek fogalma is: ezek olyan színek, amelyeket szimbolikusan definiálnak, és egyszerre lehet váltani őket. És igen, van egy kísérleti ThemeColor szimbólum is ezekhez kapcsolódóan. Azonban a teljes mechanizmus bevezetése csak a következő verzióban várható.
A Going Native kezdetei GPU-kon
A Wolfram Language számos fontos funkciója automatikusan használja a GPU-kat, amikor elérhetők. Már 15 évvel ezelőtt bevezettünk alapszintű primitíveket a GPU-programozáshoz. A 14.2-es verzióban azonban megkezdjük a GPU-képességek könnyebb elérhetővé tételét, hogy optimalizáljuk a Wolfram Language általános használatát. Az új kulcsfontosságú elem a GPUArray, amely egy olyan adattömböt jelöl, amely – ha lehetséges – úgy tárolódik, hogy közvetlenül és azonnal hozzáférhető legyen a GPU számára. (Bizonyos rendszereken külön „GPU memóriában” tárolódik; másokon, például modern Mac gépeken, megosztott memóriában, úgy, hogy a GPU közvetlenül elérhesse.)
A 14.2-es verzióban támogatunk egy kezdeti műveletkészletet, amelyeket közvetlenül lehet végrehajtani GPU tömbökön. A támogatott műveletek némileg eltérnek az egyes GPU típusok között. Idővel várhatóan számos további GPU-könyvtárat fogunk használni vagy létrehozni, amelyek kiterjesztik a GPU tömbökön végezhető műveletek körét.
Íme egy véletlenszerű, tízmillió elemű vektor, amely GPU tömbként van tárolva:

Az a Mac, amelyen most írok, olyan GPU-val rendelkezik, amely támogatja a szükséges műveleteket ahhoz, hogy ezt teljes egészében a GPU-n végezze el, és visszaadjon egy GPUArray típusú eredményt:

Íme az időmérés:

És íme a megfelelő hagyományos (CPU) eredmény:

Ebben az esetben a GPUArray eredmény körülbelül kétszer gyorsabb. Az, hogy pontosan mekkora gyorsulást érsz el, attól függ, milyen műveleteket végzel, és milyen hardvert használsz. Eddig a legnagyobb gyorsulást körülbelül tízszeresnek láttam. Ahogy egyre több GPU-könyvtárat építünk, ezt várhatóan növekedni fog — különösen akkor, ha a számítások nagy része közvetlenül a GPU-n belül zajlik, és nem igényel túl sok memória-hozzáférést.
Egyébként, ha a kódodban GPUArray-t használsz, az általában nem fogja befolyásolni az eredményeket — mert a műveletek alapértelmezetten a CPU-n futnak, ha nem támogatottak a GPU-n. (Általában a GPUArray gyorsabbá teszi a dolgokat, de ha túl sok a „GPU miss”, vagyis ha gyakran nem található támogatás, akkor a folyamatos adatmozgatási kísérletek akár lassíthatják is a folyamatot.) Érdemes azonban tudni, hogy a GPU-s számítások még mindig nem szabványosítottak vagy egységesek. Néha csak vektorokra van támogatás, máskor mátrixokra is — és különböző adattípusok, különböző numerikus pontosságok lehetnek támogatottak különböző esetekben.
És Még Több…
A fentiek mellett a 14.2-es verzióban még számos más „apró” új funkció is megtalálható. Bár ezek az eddig tárgyalt nagyobb újdonságokhoz képest „kicsinek” tűnhetnek, ha épp arra a funkcionalitásra van szükséged, akkor óriási segítséget jelenthetnek.
Például itt van a MidDate — amely dátumok középpontját számítja ki:

És szinte minden, ami dátumokkal kapcsolatos, a MidDate is tele van finomságokkal. Itt például az év kétharmadánál lévő 2. hét középpontját számolja ki:

Matematikában a DSolve és a SurfaceIntegrate függvények immár képesek szimbolikus tömbváltozókkal is dolgozni:

A SumConvergence most már lehetővé teszi az összegzés tartományának megadását, és olyan feltételek megadását is, amelyek ehhez a tartományhoz kötöttek:

Egy apró kényelmi funkció, amit igen, én kértem, hogy a DigitCount most már lehetővé teszi megadni, hogy összesen hány számjegyűnek tekintsd a számodat, így helyesen számolja a vezető nullákat is:

Ha már a kényelmi funkcióknál tartunk, olyan függvényeknél, mint a MaximalBy és a TakeLargest, hozzáadtunk egy új argumentumot, amely megadja, hogyan rendezzük az elemeket a „legnagyobb” meghatározásához. Íme az alapértelmezett numerikus sorrend:

És íme, mi történik, ha ehelyett a „szimbolikus sorrendet” használjuk:

Mindig rengeteg apróságot kell csiszolni. Például a 14.2-es verzióban frissült a MoonPhase és a hozzá kapcsolódó függvények, új lekérdezési lehetőségekkel és új számítási módszerekkel:


Egy másik területen, a jelentős új import/export formátumok mellett (különösen a Tabular támogatására) frissült a "Markdown" import is, amely most szöveges (plaintext) eredményt ad, valamint a "PDF" import, amely vegyes listát ad szövegből és képekből.
És még rengeteg más újdonság is van, amiket megtalálsz a „14.2-es verzió új és továbbfejlesztett funkcióinak összefoglalójában”. Egyébként érdemes megemlíteni, hogy ha egy adott függvény dokumentációs oldalát nézed, mindig megtudhatod, mi az új ebben a verzióban, ha rákattintasz a „show changes” (változások megjelenítése) gombra: