PascalABC.NET официальный канал
1.91K subscribers
599 photos
1 video
9 files
404 links
Официальный канал языка и системы программирования PascalABC.NET
Download Telegram
Машинное обучение в PascalABC.NET: логистическая регрессия

Продолжаем серию практических постов по ML-библиотеке в PascalABC.NET.
В этом посте — первый полный пример: обучение и применение модели.

Разберём пример на классическом датасете Iris 🌸

uses MLABC;

begin
var ds := Datasets.Iris;
var df := ds.Data;

var X := df.ToMatrix(ds.Features);
var y := df.EncodeLabels(ds.Target);

var (Xtrain, Xtest, ytrain, ytest) :=
Validation.TrainTestSplit(X, y, 0.2, 1);

var model := new LogisticRegression;

model.Fit(Xtrain, ytrain);

var pred := model.Predict(Xtest);

Println('Accuracy:', Metrics.Accuracy(ytest, pred):0:3);
end.


🔍 Что здесь происходит

1. Загрузка данных
▪️ Datasets.Iris — встроенный датасет
▪️ df — табличное представление (DataFrame)

2. Подготовка признаков
▪️ ToMatrix → преобразование в Matrix (числовое ядро)
▪️ EncodeLabels → кодирование классов в Vector<int>

3. Разбиение
▪️ TrainTestSplit → контроль воспроизводимости через seed

4. Модель
▪️ LogisticRegression
▫️ мультиклассовая (softmax)
▫️ работает через Matrix/Vector
▫️ типобезопасная реализация

5. Обучение и предсказание
▪️ Fit → обучение
▪️ Predict → классы

6. Метрика
▪️ Metrics.Accuracy → базовая проверка качества

💡 Важно
Это не “обёртка над Python”, а нативная ML-библиотека на PascalABC.NET:

▪️ все шаги явно прописаны в коде (нет скрытых преобразований)
▪️ данные и модели имеют строгие типы (Matrix, Vector<int>)
▪️ результат полностью воспроизводим (за счёт фиксированного seed)
🔥8🥰21
Гистограмма распределения цен

uses MLABC, PlotML;

begin
var ds := Datasets.MoscowHousing;
var df := ds.Data;

var price := df.ToVector(ds.Target);

Plot.Hist(price, bins := 40);
Plot.Title := 'Распределение цен на квартиры в Москве';
Plot.XLabel('Цена (руб)');
Plot.YLabel('Количество');
end.


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

В данном случае:

• по оси X — цена квартиры
• по оси Y — количество квартир с такой ценой

Каждый столбец (bin) — это интервал цен.

По гистограмме можно:

• понять, в каком диапазоне находится большинство значений
• увидеть редкие значения (очень дешёвые или очень дорогие квартиры)
• оценить форму распределения (например, симметричное или с «длинным хвостом» вправо)

Это базовый инструмент анализа данных перед построением моделей.

#MLABC
👍61
🌳 Рекурсия на примере идеально сбалансированного дерева

type
Node<T> = auto class
public
Value: T;
Left, Right: Node<T>;
end;

function RandomTree(n: integer): Node<integer>;
begin
if n = 0 then
Result := nil
else
Result := new Node<integer>(
Random(100),
RandomTree((n - 1) div 2),
RandomTree(n - 1 - (n - 1) div 2)
);
end;

procedure Infix<T>(r: Node<T>; act: Action<T>);
begin
if r = nil then exit;
Infix(r.Left, act);
act(r.Value);
Infix(r.Right, act);
end;

begin
var root := RandomTree(10);
Infix(root, x -> Print(x));
end.


💡 Что здесь происходит

1. Идеально сбалансированное дерево
Функция RandomTree(n) строит дерево из n узлов так, чтобы:

* левое поддерево получает (n - 1) div 2 узлов
* правое — остальные

Разница размеров поддеревьев не больше 1 ⇒ дерево максимально «ровное» по высоте.

2. Рекурсия

* База: n = 0 → nil
* Шаг: создаём узел и рекурсивно строим левое и правое поддеревья

То же самое в обходе:

* если узел пустой — выходим
* иначе обходим левое поддерево → текущий узел → правое

3. Инфиксный обход (LNR)
Порядок:
👉 Left → Node → Right

Это означает:

* сначала все элементы слева
* затем текущий
* затем справа

#pascalabc #рекурсия #деревья
👍102
🔍 Поиск выхода из лабиринта (DFS + backtracking)

Классическая задача: есть лабиринт → нужно найти путь от старта к выходу.

Решение — через DFS с откатом (backtracking):

* идём в глубину
* помечаем посещённые вершины
* если зашли в тупик — откатываемся назад

function FindPath(graph: List<List<integer>>; visited: array of boolean;
path: List<integer>; v, finish: integer): boolean;
begin
path.Add(v);
visited[v] := True;

if v = finish then
exit(True);

foreach var toV in graph[v] do
if not visited[toV] then
if FindPath(graph, visited, path, toV, finish) then
exit(True);

path.RemoveAt(path.Count - 1);
Result := False;
end;

begin
// 3x3 лабиринт (граф)
var graph := Lst(
Lst(1,3),
Lst(0,2,4),
Lst(1,5),
Lst(0,4,6),
Lst(1,3,5),
Lst(2,4,8),
Lst(3,7),
Lst(6,8),
Lst(5,7)
);

var n := graph.Count;

var visited := n *[False];
var path := new List<integer>;

if FindPath(graph, visited, path, 0, 8) then
Println(path.JoinToString(' -> '))
else
Println('No path');
end.


💡 Что важно:

* path.RemoveAt(...) — ключевой шаг отката
* Lst(...) удобно задаёт граф декларативно

📌 Такой код — отличный мост:
школьные задачи → графы → алгоритмы поиска → основы AI/ML
👍10
Scan в PascalABC.NET — строим путь по шагам

Одна из самых недооценённых функций в работе с последовательностями — это Scan.

Она позволяет не просто «посчитать итог», а увидеть, как меняется состояние на каждом шаге.

📌 Пример — строим путь по последовательности шагов:

begin
var steps := Seq((1,0),(0,1),(1,0),(0,-1),(0,-1),(-1,0),(0,1),(0,1));

steps.Scan((p, step) -> (p[0] + step[0], p[1] + step[1])).Print
end.


📊 Что происходит:

(1,0) → старт
затем добавляем каждый следующий шаг
получаем всю траекторию движения

👉 Результат — последовательность координат:

(1,0) (1,1) (2,1) (2,0) (2,-1) (1,-1) (1,0) (1,1)
🔥5
Новый метод последовательностей DistinctAdjacent

В стандартной библиотеке последовательностей появился новый метод: DistinctAdjacent.

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

Пример с числами

begin
Seq(1,1,2,2,2,3,1,1).DistinctAdjacent.Println
end.


Результат:

1 2 3 1


Пример со строкой

begin
var s := 'aaabbbcca'.ToCharArray;
s.DistinctAdjacent.Println
end.


Результат:

a b c a


Пример с логами

begin
var logs := Seq('INFO','INFO','WARN','WARN','ERROR','INFO');
logs.DistinctAdjacent.Println
end.


Результат:

INFO WARN ERROR INFO


Когда это полезно

* очистка логов от повторяющихся подряд сообщений
* сжатие последовательностей (run-length preprocessing)
* обработка сигналов и данных с “залипанием” значений

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

#новое
👍7
Не надо слов, не надо паники

(это мой последний день на Титанике 🚢)

В новой библиотеке ML для PascalABC.NET появился встроенный датасет TitanicRu — классическая задача бинарной классификации: предсказать, выжил пассажир или нет.

Причём поля датасета уже русифицированы:

Возраст
Пол
Класс
ЦенаБилета
ПортПосадки
Выжил

и т.д.

Ниже — полный пример ML pipeline:

● загрузка датасета,
● обработка пропусков,
● кодирование категориальных признаков,
● разделение train/test,
● нормализация,
● обучение моделей,
● сравнение качества.

uses MLABC;

begin
var ds := Datasets.TitanicRu;
var df := ds.Data.Drop(['Id', 'Имя']);

// Заполняем пропуски
var ageImputer := new Imputer(['Возраст']);
df := ageImputer.FitTransform(df);

var portImputer := new Imputer('Саутгемптон', ['ПортПосадки']);
df := portImputer.FitTransform(df);

// Кодируем категориальные признаки числами.
var sexEncoder := new LabelEncoder('Пол');
df := sexEncoder.FitTransform(df);

var portEncoder := new LabelEncoder('ПортПосадки');
df := portEncoder.FitTransform(df);

var features := ['Класс', 'Пол', 'Возраст', 'БратьяИСупруги',
'РодителиИДети', 'ЦенаБилета', 'ПортПосадки'];

var X := df.ToMatrix(features);
var y := df.GetIntColumn('Выжил');

var (Xtrain, Xtest, ytrain, ytest) :=
Validation.TrainTestSplit(X, y, testRatio := 0.2, seed := 42);

var scaler := new StandardScaler;
scaler.Fit(Xtrain);

var XtrainScaled := scaler.Transform(Xtrain);
var XtestScaled := scaler.Transform(Xtest);

var lr := new LogisticRegression(
learningRate := 0.01,
epochs := 2000
);

lr.Fit(XtrainScaled, ytrain);
var predLR := lr.Predict(XtestScaled);

var tree := new DecisionTreeClassifier(
maxDepth := 5,
minSamplesLeaf := 3,
minSamplesSplit := 6
);

tree.Fit(Xtrain, ytrain);
var predTree := tree.Predict(Xtest);

var forest := new RandomForestClassifier(
nTrees := 100,
maxDepth := 6,
minSamplesLeaf := 3,
minSamplesSplit := 6
);

forest.Fit(Xtrain, ytrain);
var predForest := forest.Predict(Xtest);

Println('Сравнение моделей на TitanicRu');

Println($'LogisticRegression: Accuracy = {Metrics.Accuracy(ytest, predLR):F3}');
Println($'DecisionTreeClassifier: Accuracy = {Metrics.Accuracy(ytest, predTree):F3}');
Println($'RandomForestClassifier: Accuracy = {Metrics.Accuracy(ytest, predForest):F3}');
end.


Типичный результат:

LogisticRegression:    Accuracy = 0.787
DecisionTreeClassifier: Accuracy = 0.809
RandomForestClassifier: Accuracy = 0.831


Получается практически sklearn-style ML прямо в PascalABC.NET:

Imputer
LabelEncoder
StandardScaler
TrainTestSplit
LogisticRegression
DecisionTreeClassifier
RandomForestClassifier
Metrics.Accuracy

без внешних зависимостей и с полностью типизированным API.

#MLABC
👏53👍1
Табличная предобработка: Imputer и OrdinalEncoder

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

Пример: сначала заполним пропуск в числовом столбце population средним значением, а затем закодируем категориальный столбец region числами.
uses MLABC;

begin
var df := DataFrame.FromCsvText('''
city,population,region
Ростов-на-Дону,1142,Юг
Таганрог,NA,Юг
Воронеж,1058,Центр
Курск,452,Центр
Белгород,392,Центр
''');

Println('Исходные данные:');
df.Print;
Println;

// Заполняем пропуск в числовом столбце средним значением
var imp := new Imputer(['population']);
df := imp.FitTransform(df);

// Кодируем названия регионов числами 0, 1, 2, ...
var encoder := new OrdinalEncoder('region');
df := encoder.FitTransform(df);

Println('После Imputer и OrdinalEncoder:');
df.Print;
end.

Вывод:
Исходные данные:
city population region
Ростов-на-Дону 1142 Юг
Таганрог NA Юг
Воронеж 1058 Центр
Курск 452 Центр
Белгород 392 Центр

После Imputer и OrdinalEncoder:
city population region
Ростов-на-Дону 1142.00 0
Таганрог 761.00 0
Воронеж 1058.00 1
Курск 452.00 1
Белгород 392.00 1


Imputer
анализирует данные на этапе Fit и запоминает значение, которым нужно заменить пропуски. В данном примере для столбца population используется среднее значение.
OrdinalEncoder преобразует строковые категории в числовые коды: разные значения столбца region получают номера 0, 1, 2, ...

Такой подход реализует привычную для ML-библиотек схему Fit/Transform: сначала объект настраивается по данным, затем применяет преобразование к таблице.

#MLABC
👍62🤩1
Простой текстовый редактор в GraphWPF

uses GraphWPF;

var (x,y) := (0.0,0.0);

procedure KeyPress(c: char);
begin
if c = ' ' then
c := Chr($A0);
TextOut(x,y,c);
x += TextWidth(c);
end;

procedure KeyDown(k: Key);
begin
case k of
Key.Enter: begin
x := 0;
y += TextHeight('q');
end;
Key.Back: if x > 0 then
begin
x -= TextWidth('n');
FillRectangle(x,y,TextWidth('n'),TextHeight('n'));
end;
end;
end;

begin
Font.Size := 20;
Font.Name := 'Consolas';
OnKeyPress := KeyPress;
OnKeyDown := KeyDown;
end.


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

Можно набирать текст, стирать последний символ, используя BackSpace, и переходить на следующую строку
❤‍🔥6👍6
Олимпиада PascalABC.NET 2026

16 мая прошла олимпиада по программированию Мехмат - PascalABC.NET 2026

В ней приняло участие 49 школьников Ростова и области

Приводим задачу C1 и ее решение

begin
var words := ReadString.ToWords;
Print(words.GroupBy(w -> w.Order.JoinToString).Max(g -> g.Count));
end.
👍6
Конкатенация DataFrame-ов в MLABC

uses MLABC;

begin
var df1 := DataFrame.FromCsvText('''
id,name,age
1,Ann,10
2,Bob,12
''');

var df2 := DataFrame.FromCsvText('''
id,name,age
3,Cat,11
4,Dan,13
''');

var df := DataFrame.Concat(df1, df2);
df.Print;
end.


Результат
┌────┬──────┬─────┐
│ id │ name │ age │
├────┼──────┼─────┤
│ 1 │ Ann │ 10 │
│ 2 │ Bob │ 12 │
│ 3 │ Cat │ 11 │
│ 4 │ Dan │ 13 │
└────┴──────┴─────┘

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

Проверяются:
- количество столбцов;
- имена столбцов;
- порядок столбцов;
- типы данных.

Если схемы несовместимы — будет сгенерировано исключение. Это защищает от случайного объединения несовместимых таблиц и делает работу с данными более предсказуемой и безопасной.

Всё как в pandas только лучше :)
👍9👎1
7 июня на мехмате ЮФУ состоялось награждение победителей и призёров олимпиады PascalABC.NET 2026.

Поздравляем всех участников, победителей и призёров!

Для школьников 10–11 классов эта олимпиада имеет и практическое значение: победители и призёры, поступающие на мехмат ЮФУ, получат дополнительно 10 баллов к суммарному баллу ЕГЭ.

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

Желаем ребятам дальнейших успехов в программировании и ждём новых участников в следующем году!
👍7
В DataFrameABC появился тип DateTime

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

Теперь в DataFrameABC для таких данных есть отдельный тип столбца:
ColumnType.ctDateTime

Это значит, что дату и время можно хранить не как строку, а как полноценное значение DateTime.
uses MLABC;

begin
var text := '''
id,created_at,name
1,2024-01-15,Alice
2,2024-01-16 12:30:00,Bob
''';

var schema := new Dictionary<string, ColumnType>;
schema['created_at'] := ColumnType.ctDateTime;

var df1 := CsvLoader.LoadFromLines(
text.ToLines,
schema := schema
);

Println('Явная схема:');
df1.Print;
Println(df1.DateTime('created_at')[0].Year);

var df2 := CsvLoader.LoadFromLines(
text.ToLines,
inferTypes := True
);

Println;
Println('Автоопределение:');
df2.Print(dateTimeFormat := 'dd.MM.yy');
Println(df2.GetColumnType('created_at'));
end.

В первом случае тип столбца задаётся явно через схему:
schema['created_at'] := ColumnType.ctDateTime;

После этого к значениям можно обращаться как к DateTime:
df1.DateTime('created_at')[0].Year

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

CsvLoader сам распознаёт столбец created_at как дату и время.
При выводе можно задать формат отображения:
df2.Print(dateTimeFormat := 'dd.MM.yy');

Тип DateTime делает работу с временными данными в DataFrameABC более естественной: даты больше не нужно хранить как обычные строки, а значит, их можно корректно обрабатывать, форматировать и использовать дальше при подготовке данных для анализа и машинного обучения.
👍2
📌 ЕГЭ по информатике: задача №3 в новой версии PascalABC.NET

В преддверии ЕГЭ по информатике покажем, как можно решать задачу №3 из демо варианта с помощью новых возможностей PascalABC.NET.
Задача №3 обычно связана с поиском данных в таблицах: нужно объединить несколько листов, отфильтровать строки по условиям и посчитать итоговую величину.

В новой версии PascalABC.NET это можно сделать в стиле современных инструментов анализа данных — через DataFrame.
uses MLABC;

begin
var movement := DataFrame.FromODS('DEMO_3.ods', 'Движение_товаров');
var goods := DataFrame.FromODS('DEMO_3.ods', 'Товар');
var shops := DataFrame.FromODS('DEMO_3.ods', 'Магазин');

var d1 := new DateTime(2024, 10, 5);
var d2 := new DateTime(2024, 10, 14);

var df := movement
.Join(goods, 'Артикул')
.Join(shops, 'ID магазина');

var answer :=
df.Filter(r ->
(r['Наименование товара'] = 'Варенец термостатный') and
(r['Район'] = 'Нагорный') and
(r['Тип операции'] = 'Продажа') and
(r['Дата'] >= d1) and
(r['Дата'] <= d2)
)
.Sum(r ->
r['Количество упаковок, шт'] *
r['Цена за упаковку']
);

Println(answer);
end.

Что здесь происходит:

1️⃣ Загружаем три таблицы из ODS-файла: движение товаров, список товаров и список магазинов.

2️⃣ Объединяем таблицы:
movement
.Join(goods, 'Артикул')
.Join(shops, 'ID магазина')

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

3️⃣ Фильтруем только нужные строки:
товар — Варенец термостатный;
район — Нагорный;
операция — Продажа;
дата — с 5 по 14 октября 2024 года включительно.

4️⃣ Считаем итоговую сумму продаж:
Количество упаковок * Цена за упаковку

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

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

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

🚀 PascalABC.NET — современное программирование для реальных задач анализа данных.

Скачайте последнюю версию!
👍8🔥4❤‍🔥11🥰1👏1🙈1🫡1