pascalabcnet/bin/Lib/MLUtilsABC.pas

312 lines
12 KiB
ObjectPascal
Raw Normal View History

/// Вспомогательные функции для ML.
///
/// Содержит утилиты, используемые в нескольких модулях,
/// не привязанные к DataFrame или конкретным моделям.
unit MLUtilsABC;
interface
uses LinearAlgebraML;
/// Определяет фактический seed для генератора случайных чисел.
///
/// Если seed >= 0:
/// • используется заданное пользователем значение
/// • userProvided := true
///
/// Если seed < 0:
/// • генерируется псевдослучайный seed на основе системного времени
/// • userProvided := false
///
/// Возвращает:
/// • неотрицательный seed, используемый для инициализации RNG
///
/// Назначение:
/// • обеспечить единое поведение случайности во всех моделях
/// • сохранить воспроизводимость при явном задании seed
function ResolveRandomSeed(seed: integer; var userProvided: boolean): integer;
/// Преобразует вектор меток классов в массив целых чисел.
/// Используется при визуализации и других задачах,
/// где метки должны быть представлены как 0,1,2,...
/// Значения округляются функцией Round, чтобы устранить
/// возможные небольшие численные ошибки
function LabelsToInts(y: Vector): array of integer;
/// Преобразует массив целых меток в Vector.
function IntsToLabels(a: array of integer): Vector;
/// Кодирует строковые метки классов в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления меток.
/// Используется при обучении моделей и визуализации.
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabels(labels: array of string): array of integer;
/// Кодирует строковые метки классов в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления меток.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Используется при обучении моделей и визуализации
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabels(labels: array of string; var classes: array of string): array of integer;
/// Кодирует целые метки классов в целочисленные индексы.
/// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления меток.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Используется при обучении моделей и визуализации
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabelsInt(labels: array of integer; var classes: array of integer): array of integer;
/// Преобразует строковые метки классов в целочисленные индексы
/// с использованием заранее заданного массива classes (mapping).
/// classes должен быть получен из EncodeLabels.
/// Если встречается неизвестная метка — выбрасывается исключение.
/// Используется для применения кодирования к тестовым данным (Transform).
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function TransformLabels(labels: array of string; classes: array of string): array of integer;
2026-04-19 21:07:00 +03:00
/// Преобразует целочисленные метки классов в индексы (0,1,2,...)
/// с использованием заранее заданного массива classes (mapping: индекс → значение).
/// classes должен быть получен из EncodeLabelsInt.
/// Если встречается неизвестное значение — выбрасывается исключение.
/// Используется для применения кодирования к тестовым данным (Transform).
/// Предполагается, что входные данные уже очищены от пропущенных значений.
2026-04-19 21:07:00 +03:00
function TransformLabelsInt(labels: array of integer; classes: array of integer): array of integer;
/// Преобразует целочисленные индексы классов обратно в строковые метки.
/// Массив classes задаёт соответствие: classes[i] — имя класса с индексом i.
/// Используется для получения текстовых предсказаний моделей.
/// Предполагается, что входные данные уже корректны.
function DecodeLabels(y: array of integer; classes: array of string): array of string;
/// Возвращает список уникальных меток классов.
/// Порядок соответствует первому появлению значений во входном массиве.
/// Используется для определения множества классов в задаче классификации.
function UniqueLabels(labels: array of string): array of string;
2026-04-19 21:07:00 +03:00
function CloneOrNil(v: Vector): Vector;
procedure CheckSameLength(a, b: Vector);
implementation
uses MLExceptions;
const
ER_LABELS_NULL =
2026-04-27 09:34:44 +03:00
'{0} не может быть nil!!{0} cannot be nil';
ER_LABELS_ARRAY_NULL =
'labels не может быть nil!!labels cannot be nil';
2026-04-27 09:34:44 +03:00
ER_LABELS_NOT_INTEGER =
'Метки классов должны быть целыми числами!!Class labels must be integers';
ER_UNKNOWN_CLASS_IN_TRANSFORM =
'Неизвестное значение класса "{0}" при преобразовании меток!!Unknown class value "{0}" in TransformLabels';
ER_LABEL_INDEX_OUT_OF_RANGE =
'Индекс метки {0} вне диапазона [0, {1})!!Label index {0} is out of range [0, {1})';
function ResolveRandomSeed(seed: integer; var userProvided: boolean): integer;
begin
if seed < 0 then
begin
userProvided := false;
Result := System.Environment.TickCount and integer.MaxValue;
end
else
begin
userProvided := true;
Result := seed;
end;
end;
function LabelsToInts(y: Vector): array of integer;
begin
if y = nil then
ArgumentNullError(ER_LABELS_NULL, 'y');
Result := new integer[y.Length];
for var i := 0 to y.Length - 1 do
2026-04-27 09:34:44 +03:00
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
Result[i] := ir;
end;
end;
function IntsToLabels(a: array of integer): Vector;
begin
if a = nil then
ArgumentNullError(ER_LABELS_ARRAY_NULL, 'a');
Result := new Vector(a);
end;
function EncodeLabels(labels: array of string; var classes: array of string): array of integer;
begin
if labels = nil then
ArgumentNullError(ER_ARG_NULL, 'labels');
var classList := new List<string>;
var map := new Dictionary<string, integer>;
// собираем классы в порядке первого появления
for var i := 0 to labels.Length - 1 do
begin
var lbl := labels[i];
2026-04-27 09:34:44 +03:00
if lbl not in map then
begin
map[lbl] := classList.Count;
classList.Add(lbl);
end;
end;
// кодируем
var res := new integer[labels.Length];
for var i := 0 to labels.Length - 1 do
res[i] := map[labels[i]];
classes := classList.ToArray;
Result := res;
end;
function TransformLabels(labels: array of string; classes: array of string): array of integer;
begin
if labels = nil then
ArgumentNullError(ER_ARG_NULL, 'labels');
var map := new Dictionary<string, integer>;
for var i := 0 to classes.Length - 1 do
map[classes[i]] := i;
var res := new integer[labels.Length];
for var i := 0 to labels.Length - 1 do
begin
var lbl := labels[i];
2026-04-27 09:34:44 +03:00
if lbl not in map then
Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, lbl);
res[i] := map[lbl];
end;
Result := res;
end;
2026-04-19 21:07:00 +03:00
function TransformLabelsInt(labels: array of integer; classes: array of integer): array of integer;
begin
if labels = nil then
ArgumentNullError(ER_ARG_NULL, 'labels');
if classes = nil then
ArgumentNullError(ER_ARG_NULL, 'classes');
// mapping: значение → индекс
var map := new Dictionary<integer, integer>;
for var i := 0 to classes.Length - 1 do
map[classes[i]] := i;
var res := new integer[labels.Length];
for var i := 0 to labels.Length - 1 do
begin
var v := labels[i];
2026-04-27 09:34:44 +03:00
if v not in map then
2026-04-19 21:07:00 +03:00
Error(ER_UNKNOWN_CLASS_IN_TRANSFORM, v);
res[i] := map[v];
end;
Result := res;
end;
function EncodeLabels(labels: array of string): array of integer;
begin
var classes: array of string;
Result := EncodeLabels(labels, classes);
end;
function EncodeLabelsInt(labels: array of integer; var classes: array of integer): array of integer;
begin
if labels = nil then
ArgumentNullError(ER_ARG_NULL, 'labels');
var classList := new List<integer>;
var map := new Dictionary<integer, integer>;
// собираем уникальные значения в порядке первого появления
for var i := 0 to labels.Length - 1 do
begin
var lbl := labels[i];
2026-04-27 09:34:44 +03:00
if lbl not in map then
begin
map[lbl] := classList.Count;
classList.Add(lbl);
end;
end;
// кодируем
var res := new integer[labels.Length];
for var i := 0 to labels.Length - 1 do
res[i] := map[labels[i]];
classes := classList.ToArray;
Result := res;
end;
function DecodeLabels(y: array of integer; classes: array of string): array of string;
begin
var res := new string[y.Length];
for var i := 0 to y.Length - 1 do
begin
var idx := y[i];
if (idx < 0) or (idx >= classes.Length) then
Error(ER_LABEL_INDEX_OUT_OF_RANGE, idx, classes.Length);
res[i] := classes[idx];
end;
Result := res;
end;
function UniqueLabels(labels: array of string): array of string;
begin
var seen := new HashSet<string>;
var res := new List<string>;
for var i := 0 to labels.Length - 1 do
2026-04-27 09:34:44 +03:00
if labels[i] not in seen then
begin
seen.Add(labels[i]);
res.Add(labels[i]);
end;
Result := res.ToArray;
end;
2026-04-19 21:07:00 +03:00
function CloneOrNil(v: Vector): Vector;
begin
if v = nil then
exit(nil);
Result := v.Clone;
end;
procedure CheckSameLength(a, b: Vector);
begin
if a.Length <> b.Length then
DimensionError(ER_DIM_MISMATCH, a.Length, b.Length);
end;
2026-04-27 09:34:44 +03:00
end.