LastMaxBy LastMinBy исправления

This commit is contained in:
Mikhalkovich Stanislav 2026-04-22 14:08:27 +03:00
parent dd7a305533
commit 2294ce5844
14 changed files with 593 additions and 448 deletions

View file

@ -15,7 +15,7 @@ internal static class RevisionClass
public const string Major = "3"; public const string Major = "3";
public const string Minor = "11"; public const string Minor = "11";
public const string Build = "1"; public const string Build = "1";
public const string Revision = "3808"; public const string Revision = "3810";
public const string MainVersion = Major + "." + Minor; public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision; public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%COREVERSION%=1 %COREVERSION%=1
%REVISION%=3808 %REVISION%=3810
%MINOR%=11 %MINOR%=11
%MAJOR%=3 %MAJOR%=3

View file

@ -1 +1 @@
3.11.1.3808 3.11.1.3810

View file

@ -1 +1 @@
!define VERSION '3.11.1.3808' !define VERSION '3.11.1.3810'

View file

@ -25,6 +25,8 @@ const
'Столбец "{0}" должен быть категориальным для EncodeLabels!!Column "{0}" must be categorical for EncodeLabels'; 'Столбец "{0}" должен быть категориальным для EncodeLabels!!Column "{0}" must be categorical for EncodeLabels';
ER_ENCODELABELS_UNSUPPORTED_TYPE = ER_ENCODELABELS_UNSUPPORTED_TYPE =
'Неподдерживаемый тип столбца "{0}" для EncodeLabels!!Unsupported column type "{0}" for EncodeLabels'; 'Неподдерживаемый тип столбца "{0}" для EncodeLabels!!Unsupported column type "{0}" for EncodeLabels';
ER_TARGET_HAS_NA =
'Целевой столбец "{0}" содержит NA!!Target column "{0}" contains NA values';
function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod; function ToMatrix(Self: DataFrame; colNames: array of string): Matrix; extensionmethod;
begin begin
@ -89,6 +91,11 @@ begin
if not Self.IsCategorical(target) then if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target); ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
if Self.GetColumnType(target) = ColumnType.ctStr then if Self.GetColumnType(target) = ColumnType.ctStr then
begin begin
@ -130,15 +137,18 @@ begin
if not Self.IsCategorical(target) then if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target); ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
case Self.GetColumnType(target) of case Self.GetColumnType(target) of
ColumnType.ctStr: ColumnType.ctStr:
begin begin
var labels := Self.GetStrColumn(target).ToArray; var labels := Self.GetStrColumn(target).ToArray;
Result := EncodeLabels(labels, classes); Result := EncodeLabels(labels, classes);
end; end;
ColumnType.ctInt: ColumnType.ctInt:
begin begin
var labels := Self.GetIntColumn(target).ToArray; var labels := Self.GetIntColumn(target).ToArray;
@ -149,7 +159,6 @@ begin
// если API требует string classes: // если API требует string classes:
classes := intClasses.Select(x -> x.ToString).ToArray; classes := intClasses.Select(x -> x.ToString).ToArray;
end; end;
else else
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target); ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
end; end;
@ -176,15 +185,18 @@ begin
if not Self.IsCategorical(target) then if not Self.IsCategorical(target) then
ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target); ArgumentError(ER_ENCODELABELS_NOT_CATEGORICAL, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
case Self.GetColumnType(target) of case Self.GetColumnType(target) of
ColumnType.ctStr: ColumnType.ctStr:
begin begin
var data := Self.GetStrColumn(target).ToArray; var data := Self.GetStrColumn(target).ToArray;
Result := TransformLabels(data, classes); Result := TransformLabels(data, classes);
end; end;
ColumnType.ctInt: ColumnType.ctInt:
begin begin
var data := Self.GetIntColumn(target).ToArray; var data := Self.GetIntColumn(target).ToArray;
@ -195,7 +207,6 @@ begin
Result := TransformLabels(strData, classes); Result := TransformLabels(strData, classes);
end; end;
else else
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target); ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
end; end;
@ -224,6 +235,11 @@ begin
if Self.GetColumnType(target) <> ColumnType.ctInt then if Self.GetColumnType(target) <> ColumnType.ctInt then
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target); ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
var data := Self.GetIntColumn(target).ToArray; var data := Self.GetIntColumn(target).ToArray;
@ -253,6 +269,11 @@ begin
if Self.GetColumnType(target) <> ColumnType.ctInt then if Self.GetColumnType(target) <> ColumnType.ctInt then
ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target); ArgumentError(ER_ENCODELABELS_UNSUPPORTED_TYPE, target);
var col := Self[target];
for var i := 0 to Self.RowCount - 1 do
if not col.IsValid[i] then
ArgumentError(ER_TARGET_HAS_NA, target);
var labels := Self.GetIntColumn(target).ToArray; var labels := Self.GetIntColumn(target).ToArray;
Result := EncodeLabelsInt(labels, classes); Result := EncodeLabelsInt(labels, classes);

View file

@ -49,24 +49,14 @@ type
procedure RebuildSchema; procedure RebuildSchema;
// Join методы
//procedure AppendJoinedRow(leftCur, rightCur: DataFrameCursor; leftKeyIdx, rightKeyIdx: array of integer);
//procedure AppendLeftOnlyRow(leftCur: DataFrameCursor; leftKeyIdx, rightKeyIdx: array of integer);
//procedure AppendRightOnlyRow(rightCur: DataFrameCursor; leftKeyIdx, rightKeyIdx: array of integer; leftColumnCount: integer);
// Single key методы // Single key методы
{function DataFrame.JoinInnerSingleKey(other: DataFrame; leftKey, rightKey: integer;
resultSchema: DataFrameSchema): DataFrame;}
function JoinInnerSingleKey(other: DataFrame; key: string): DataFrame; function JoinInnerSingleKey(other: DataFrame; key: string): DataFrame;
function JoinInnerSingleKeyInt(other: DataFrame; leftKey, rightKey: integer): DataFrame; function JoinInnerSingleKeyInt(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function JoinInnerSingleKeyFloat(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function JoinInnerSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame; function JoinInnerSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function JoinInnerSingleKeyBool(other: DataFrame; leftKey, rightKey: integer): DataFrame; function JoinInnerSingleKeyBool(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function LeftJoinSingleKey(other: DataFrame; key: string): DataFrame; function LeftJoinSingleKey(other: DataFrame; key: string): DataFrame;
function LeftJoinSingleKeyInt(other: DataFrame; leftKey, rightKey: integer): DataFrame; function LeftJoinSingleKeyInt(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function LeftJoinSingleKeyFloat(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function LeftJoinSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame; function LeftJoinSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame;
function LeftJoinSingleKeyBool(other: DataFrame; leftKey, rightKey: integer): DataFrame; function LeftJoinSingleKeyBool(other: DataFrame; leftKey, rightKey: integer): DataFrame;
@ -96,7 +86,6 @@ type
function CreateEmptyBySchema(schema: DataFrameSchema): DataFrame; function CreateEmptyBySchema(schema: DataFrameSchema): DataFrame;
function GetColumnIndex(name: string): integer;
function GetColumn(name: string): Column; function GetColumn(name: string): Column;
function CloneWithCopiedColumns: DataFrame; function CloneWithCopiedColumns: DataFrame;
@ -713,43 +702,6 @@ begin
res.AddIntColumn(name, data, valid); res.AddIntColumn(name, data, valid);
end; end;
procedure BuildMergedFloatKeyColumnFromFullJoin(
res: DataFrame;
name: string;
leftCol: FloatColumn;
rightCol: FloatColumn;
leftIdx, rightIdx: array of integer
);
begin
var n := leftIdx.Length;
var data := new real[n];
var valid := new boolean[n];
for var i := 0 to n - 1 do
begin
var li := leftIdx[i];
var ri := rightIdx[i];
if li >= 0 then
begin
data[i] := leftCol.Data[li];
valid[i] := leftCol.IsValid[li];
end
else if ri >= 0 then
begin
data[i] := rightCol.Data[ri];
valid[i] := rightCol.IsValid[ri];
end
else
begin
data[i] := 0.0;
valid[i] := False;
end;
end;
res.AddFloatColumn(name, data, valid);
end;
procedure BuildMergedStrKeyColumnFromFullJoin( procedure BuildMergedStrKeyColumnFromFullJoin(
res: DataFrame; res: DataFrame;
name: string; name: string;
@ -1032,23 +984,21 @@ function DataFrame.BuildJoinKey(cur: DataFrameCursor; layout: JoinKeyLayout; var
begin begin
hasNA := false; hasNA := false;
var ic := 0; var fc := 0; var sc := 0; var bc := 0; var ic := 0; var sc := 0; var bc := 0;
// считаем размеры // считаем размеры
for var i := 0 to layout.ColTypes.Length - 1 do for var i := 0 to layout.ColTypes.Length - 1 do
case layout.ColTypes[i] of case layout.ColTypes[i] of
ctInt: inc(ic); ctInt: inc(ic);
ctFloat: inc(fc);
ctStr: inc(sc); ctStr: inc(sc);
ctBool: inc(bc); ctBool: inc(bc);
end; end;
Result.Ints := new integer[ic]; Result.Ints := new integer[ic];
Result.Floats := new real[fc];
Result.Strs := new string[sc]; Result.Strs := new string[sc];
Result.Bools := new boolean[bc]; Result.Bools := new boolean[bc];
ic := 0; fc := 0; sc := 0; bc := 0; ic := 0; sc := 0; bc := 0;
for var i := 0 to layout.ColIndices.Length - 1 do for var i := 0 to layout.ColIndices.Length - 1 do
begin begin
@ -1066,10 +1016,7 @@ begin
inc(ic); inc(ic);
end; end;
ctFloat: ctFloat:
begin Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
Result.Floats[fc] := cur.Float(col);
inc(fc);
end;
ctStr: ctStr:
begin begin
Result.Strs[sc] := cur.Str(col); Result.Strs[sc] := cur.Str(col);
@ -1080,6 +1027,8 @@ begin
Result.Bools[bc] := cur.Bool(col); Result.Bools[bc] := cur.Bool(col);
inc(bc); inc(bc);
end; end;
else
Error(ER_UNSUPPORTED_COLUMN_TYPE, layout.ColTypes[i]);
end; end;
end; end;
end; end;
@ -1104,23 +1053,24 @@ end;
function DataFrame.LeftJoinSingleKey(other: DataFrame; key: string): DataFrame; function DataFrame.LeftJoinSingleKey(other: DataFrame; key: string): DataFrame;
begin begin
// 1. индексы ключей — через Schema
var leftKey := fSchema.IndexOf(key); var leftKey := fSchema.IndexOf(key);
var rightKey := other.fSchema.IndexOf(key); var rightKey := other.fSchema.IndexOf(key);
// 2. проверка типов ключей — через Schema
var lt := fSchema.ColumnTypeAt(leftKey); var lt := fSchema.ColumnTypeAt(leftKey);
var rt := other.fSchema.ColumnTypeAt(rightKey); var rt := other.fSchema.ColumnTypeAt(rightKey);
if lt <> rt then if lt <> rt then
Error(ER_JOIN_KEY_TYPE_MISMATCH); Error(ER_JOIN_KEY_TYPE_MISMATCH);
// 3. типоспецифичный алгоритм (КАК РАНЬШЕ) if lt = ctFloat then
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
case lt of case lt of
ctInt: Result := LeftJoinSingleKeyInt(other, leftKey, rightKey); ctInt: Result := LeftJoinSingleKeyInt(other, leftKey, rightKey);
ctFloat: Result := LeftJoinSingleKeyFloat(other, leftKey, rightKey);
ctStr: Result := LeftJoinSingleKeyStr(other, leftKey, rightKey); ctStr: Result := LeftJoinSingleKeyStr(other, leftKey, rightKey);
ctBool: Result := LeftJoinSingleKeyBool(other, leftKey, rightKey); ctBool: Result := LeftJoinSingleKeyBool(other, leftKey, rightKey);
else
Error(ER_UNSUPPORTED_COLUMN_TYPE, lt);
end; end;
end; end;
@ -1177,62 +1127,6 @@ begin
Result := BuildLeftJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey); Result := BuildLeftJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);
end; end;
function DataFrame.LeftJoinSingleKeyFloat(other: DataFrame; leftKey, rightKey: integer): DataFrame;
begin
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
Result := nil;
{var index := new Dictionary<real, List<integer>>;
var rcur := other.GetCursor;
while rcur.MoveNext do
if rcur.IsValid(rightKey) then
begin
var k := rcur.Float(rightKey);
var lst: List<integer>;
if not index.TryGetValue(k, lst) then
begin
lst := new List<integer>;
index[k] := lst;
end;
lst.Add(rcur.Position);
end;
var leftIdx := new List<integer>;
var rightIdx := new List<integer>;
var lcur := GetCursor;
while lcur.MoveNext do
begin
var lpos := lcur.Position;
if not lcur.IsValid(leftKey) then
begin
leftIdx.Add(lpos);
rightIdx.Add(-1);
continue;
end;
var k := lcur.Float(leftKey);
if index.ContainsKey(k) then
foreach var r in index[k] do
begin
leftIdx.Add(lpos);
rightIdx.Add(r);
end
else
begin
leftIdx.Add(lpos);
rightIdx.Add(-1);
end;
end;
Result := BuildLeftJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);}
end;
function DataFrame.LeftJoinSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame; function DataFrame.LeftJoinSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame;
begin begin
var index := new Dictionary<string, List<integer>>; var index := new Dictionary<string, List<integer>>;
@ -1540,14 +1434,7 @@ begin
); );
ctFloat: ctFloat:
BuildMergedFloatKeyColumnFromFullJoin( Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
res,
name,
FloatColumn(col),
FloatColumn(other.columns[ri]),
leftArr,
rightArr
);
ctStr: ctStr:
BuildMergedStrKeyColumnFromFullJoin( BuildMergedStrKeyColumnFromFullJoin(
@ -1685,15 +1572,7 @@ begin
IntColumn(other.columns[ri]), IntColumn(other.columns[ri]),
leftArr, rightArr leftArr, rightArr
); );
ctFloat:
BuildMergedFloatKeyColumnFromFullJoin(
res, name,
FloatColumn(col),
FloatColumn(other.columns[ri]),
leftArr, rightArr
);
ctStr: ctStr:
BuildMergedStrKeyColumnFromFullJoin( BuildMergedStrKeyColumnFromFullJoin(
res, name, res, name,
@ -1740,11 +1619,15 @@ begin
if lt <> rt then if lt <> rt then
Error(ER_JOIN_KEY_TYPE_MISMATCH); Error(ER_JOIN_KEY_TYPE_MISMATCH);
if lt = ctFloat then
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
case lt of case lt of
ctInt: Result := JoinInnerSingleKeyInt(other, leftKey, rightKey); ctInt: Result := JoinInnerSingleKeyInt(other, leftKey, rightKey);
ctFloat: Result := JoinInnerSingleKeyFloat(other, leftKey, rightKey);
ctStr: Result := JoinInnerSingleKeyStr(other, leftKey, rightKey); ctStr: Result := JoinInnerSingleKeyStr(other, leftKey, rightKey);
ctBool: Result := JoinInnerSingleKeyBool(other, leftKey, rightKey); ctBool: Result := JoinInnerSingleKeyBool(other, leftKey, rightKey);
else
Error(ER_UNSUPPORTED_COLUMN_TYPE, lt);
end; end;
end; end;
@ -1823,49 +1706,6 @@ begin
Result := BuildJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey); Result := BuildJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);
end; end;
function DataFrame.JoinInnerSingleKeyFloat(other: DataFrame; leftKey, rightKey: integer): DataFrame;
begin
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
Result := nil;
{var index := new Dictionary<real, List<integer>>;
var rcur := other.GetCursor;
while rcur.MoveNext do
if rcur.IsValid(rightKey) then
begin
var k := rcur.Float(rightKey);
var lst: List<integer>;
if not index.TryGetValue(k, lst) then
begin
lst := new List<integer>;
index[k] := lst;
end;
lst.Add(rcur.Position);
end;
var leftIdx := new List<integer>;
var rightIdx := new List<integer>;
var lcur := GetCursor;
while lcur.MoveNext do
if lcur.IsValid(leftKey) then
begin
var k := lcur.Float(leftKey);
var rows: List<integer>;
if index.TryGetValue(k, rows) then
foreach var r in rows do
begin
leftIdx.Add(lcur.Position);
rightIdx.Add(r);
end;
end;
Result := BuildJoinResult(Self, other, leftIdx, rightIdx, leftKey, rightKey);}
end;
function DataFrame.JoinInnerSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame; function DataFrame.JoinInnerSingleKeyStr(other: DataFrame; leftKey, rightKey: integer): DataFrame;
begin begin
var index := new Dictionary<string, List<integer>>; var index := new Dictionary<string, List<integer>>;
@ -2028,9 +1868,17 @@ end;
function DataFrame.BuildJoinKeyLayout(keyIndices: array of integer): JoinKeyLayout; function DataFrame.BuildJoinKeyLayout(keyIndices: array of integer): JoinKeyLayout;
begin begin
Result.ColIndices := keyIndices; Result.ColIndices := keyIndices;
Result.ColTypes := ArrGen(keyIndices.Length, Result.ColTypes := new ColumnType[keyIndices.Length];
i -> columns[keyIndices[i]].Info.ColType
); for var i := 0 to keyIndices.Length - 1 do
begin
var t := columns[keyIndices[i]].Info.ColType;
if t = ctFloat then
Error(ER_JOIN_FLOAT_KEY_NOT_SUPPORTED);
Result.ColTypes[i] := t;
end;
end; end;
function DataFrame.Join(other: DataFrame; keys: array of string; kind: JoinKind): DataFrame; function DataFrame.Join(other: DataFrame; keys: array of string; kind: JoinKind): DataFrame;
@ -2131,10 +1979,7 @@ function DataFrame.ColumnCount: integer := columns.Count;
function DataFrame.ColumnIndex(name: string): integer; function DataFrame.ColumnIndex(name: string): integer;
begin begin
for var i := 0 to columns.Count - 1 do Result := fSchema.IndexOf(name);
if columns[i].Info.Name = name then
exit(i);
Error(ER_COLUMN_NOT_FOUND, name);
end; end;
function DataFrame.HasColumn(name: string): boolean; function DataFrame.HasColumn(name: string): boolean;
@ -3121,12 +2966,7 @@ begin
rightIdx[i] := right.fSchema.IndexOf(rightKeys[i]); rightIdx[i] := right.fSchema.IndexOf(rightKeys[i]);
end; end;
Result := BuildJoinSchema( Result := BuildJoinSchema(right, leftIdx, rightIdx, 'right_');
right,
leftIdx,
rightIdx,
'right_'
);
end; end;
function DataFrame.CreateEmptyBySchema(schema: DataFrameSchema): DataFrame; function DataFrame.CreateEmptyBySchema(schema: DataFrameSchema): DataFrame;
@ -3150,14 +2990,9 @@ begin
Result := new DataFrame(cols, schema); Result := new DataFrame(cols, schema);
end; end;
function DataFrame.GetColumnIndex(name: string): integer;
begin
Result := fschema.IndexOf(name); // если у вас есть такой метод
end;
function DataFrame.GetColumn(name: string): Column; function DataFrame.GetColumn(name: string): Column;
begin begin
var idx := GetColumnIndex(name); var idx := ColumnIndex(name);
Result := columns[idx]; Result := columns[idx];
end; end;
@ -3212,20 +3047,8 @@ begin
while cur.MoveNext do while cur.MoveNext do
begin begin
try data[i] := f(cur);
data[i] := f(cur);
except
on e: Exception do
begin
data[i] := 0;
valid[i] := False;
i += 1;
continue;
end;
end;
valid[i] := True; valid[i] := True;
i += 1; i += 1;
end; end;
@ -3251,20 +3074,8 @@ begin
while cur.MoveNext do while cur.MoveNext do
begin begin
try data[i] := f(cur);
data[i] := f(cur);
except
on e: Exception do
begin
data[i] := 0.0;
valid[i] := False;
i += 1;
continue;
end;
end;
valid[i] := True; valid[i] := True;
i += 1; i += 1;
end; end;
@ -3290,20 +3101,8 @@ begin
while cur.MoveNext do while cur.MoveNext do
begin begin
try data[i] := f(cur);
data[i] := f(cur);
except
on e: Exception do
begin
data[i] := nil;
valid[i] := False;
i += 1;
continue;
end;
end;
valid[i] := True; valid[i] := True;
i += 1; i += 1;
end; end;
@ -3329,20 +3128,8 @@ begin
while cur.MoveNext do while cur.MoveNext do
begin begin
try data[i] := f(cur);
data[i] := f(cur);
except
on e: Exception do
begin
data[i] := False;
valid[i] := False;
i += 1;
continue;
end;
end;
valid[i] := True; valid[i] := True;
i += 1; i += 1;
end; end;
@ -3403,20 +3190,8 @@ begin
end; end;
// --- вычисление // --- вычисление
try data[row] := f(cur);
data[row] := f(cur);
except
on e: Exception do
begin
data[row] := 0.0;
valid[row] := False;
row += 1;
continue;
end;
end;
valid[row] := True; valid[row] := True;
row += 1; row += 1;
end; end;
@ -3455,18 +3230,22 @@ begin
var cur := GetCursor; var cur := GetCursor;
var row := 0; var row := 0;
while cur.MoveNext do while cur.MoveNext do
begin begin
try // NA из исходных данных
data[row] := f(cur); if not cur.IsValid(colIndex) then
valid[row] := True; begin
except data[row] := 0;
on e: Exception do valid[row] := false;
begin row += 1;
data[row] := 0; continue;
valid[row] := False;
end;
end; end;
// строгий расчёт
data[row] := f(cur);
valid[row] := True;
row += 1; row += 1;
end; end;
@ -4117,6 +3896,8 @@ end;
procedure DataFrame.AssertSchemaConsistent; procedure DataFrame.AssertSchemaConsistent;
begin begin
{$IFNDEF Test} {$IFNDEF Test}
if columns.Count = 0 then exit;
// --- 1. одинаковая RowCount у всех столбцов --- // --- 1. одинаковая RowCount у всех столбцов ---
var rc := columns[0].RowCount; var rc := columns[0].RowCount;
@ -4136,7 +3917,7 @@ begin
if not fSchema.HasColumn(name) then if not fSchema.HasColumn(name) then
Error(ER_SCHEMA_COLUMN_MISSING, name); Error(ER_SCHEMA_COLUMN_MISSING, name);
var idx := GetColumnIndex(name); var idx := ColumnIndex(name);
if idx <> i then if idx <> i then
Error(ER_SCHEMA_COLUMN_INDEX_INCONSISTENT, name, idx, i); Error(ER_SCHEMA_COLUMN_INDEX_INCONSISTENT, name, idx, i);
end; end;

View file

@ -98,6 +98,7 @@ type
Column = abstract class Column = abstract class
Info: ColumnInfo; Info: ColumnInfo;
public public
IsValid: array of boolean; // Флаги валидности (может быть nil)
/// Пытается извлечь i-тое данное из столбца как числовое если это возможно /// Пытается извлечь i-тое данное из столбца как числовое если это возможно
function TryGetNumericValue(i: integer; var value: real): boolean; virtual; abstract; function TryGetNumericValue(i: integer; var value: real): boolean; virtual; abstract;
/// Возвращает количество строк в столбце /// Возвращает количество строк в столбце
@ -108,7 +109,6 @@ type
IntColumn = class(Column) IntColumn = class(Column)
// Data и IsValid считаются immutable после создания // Data и IsValid считаются immutable после создания
Data: array of integer; // Данные столбца Data: array of integer; // Данные столбца
IsValid: array of boolean; // Флаги валидности (может быть nil)
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -121,7 +121,6 @@ type
/// Столбец вещественных чисел /// Столбец вещественных чисел
FloatColumn = class(Column) FloatColumn = class(Column)
Data: array of real; // Данные столбца Data: array of real; // Данные столбца
IsValid: array of boolean; // Флаги валидности
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -135,7 +134,6 @@ type
/// Столбец строк /// Столбец строк
StrColumn = class(Column) StrColumn = class(Column)
Data: array of string; // Данные столбца Data: array of string; // Данные столбца
IsValid: array of boolean; // Флаги валидности
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -153,7 +151,6 @@ type
/// Столбец булевых значений /// Столбец булевых значений
BoolColumn = class(Column) BoolColumn = class(Column)
Data: array of boolean; // Данные столбца Data: array of boolean; // Данные столбца
IsValid: array of boolean; // Флаги валидности
public public
constructor Create; begin end; constructor Create; begin end;
constructor Create(name: string); constructor Create(name: string);
@ -177,9 +174,9 @@ type
ColTypes: array of ColumnType; ColTypes: array of ColumnType;
end; end;
// Нет Floats - по ним нельзя Join!!!
JoinKey = record JoinKey = record
Ints: array of integer; Ints: array of integer;
Floats: array of real;
Strs: array of string; Strs: array of string;
Bools: array of boolean; Bools: array of boolean;
function Equals(oth: object): boolean; override; function Equals(oth: object): boolean; override;
@ -731,16 +728,12 @@ begin
var other := JoinKey(oth); var other := JoinKey(oth);
if Ints.Length <> other.Ints.Length then exit(false); if Ints.Length <> other.Ints.Length then exit(false);
if Floats.Length <> other.Floats.Length then exit(false);
if Strs.Length <> other.Strs.Length then exit(false); if Strs.Length <> other.Strs.Length then exit(false);
if Bools.Length <> other.Bools.Length then exit(false); if Bools.Length <> other.Bools.Length then exit(false);
for var i := 0 to Ints.Length - 1 do for var i := 0 to Ints.Length - 1 do
if Ints[i] <> other.Ints[i] then exit(false); if Ints[i] <> other.Ints[i] then exit(false);
for var i := 0 to Floats.Length - 1 do
if Floats[i] <> other.Floats[i] then exit(false);
for var i := 0 to Strs.Length - 1 do for var i := 0 to Strs.Length - 1 do
if Strs[i] <> other.Strs[i] then exit(false); if Strs[i] <> other.Strs[i] then exit(false);
@ -757,9 +750,6 @@ begin
foreach var v in Ints do foreach var v in Ints do
h := h * 31 + v.GetHashCode; h := h * 31 + v.GetHashCode;
foreach var v in Floats do
h := h * 31 + v.GetHashCode;
foreach var v in Strs do foreach var v in Strs do
h := h * 31 + (if v = nil then 0 else v.GetHashCode); h := h * 31 + (if v = nil then 0 else v.GetHashCode);

View file

@ -1,4 +1,8 @@
// ============================================================= /// Основной модуль библиотеки машинного обучения.
/// Объединяет модели, метрики, валидацию и вспомогательные компоненты.
unit MLABC;
// =============================================================
// СТАТИСТИЧЕСКАЯ ПОЛИТИКА БИБЛИОТЕКИ ML PascalABC.NET // СТАТИСТИЧЕСКАЯ ПОЛИТИКА БИБЛИОТЕКИ ML PascalABC.NET
// //
// В библиотеке используются следующие соглашения: // В библиотеке используются следующие соглашения:
@ -17,9 +21,20 @@
// • алгоритмы ML — дисперсия генеральной совокупности // • алгоритмы ML — дисперсия генеральной совокупности
// ============================================================= // =============================================================
/// Основной модуль библиотеки машинного обучения. // =============================================================
/// Объединяет модели, метрики, валидацию и вспомогательные компоненты. // PIPELINES
unit MLABC; //
// DataFrame-based:
// DataPipeline
// UDataPipeline
//
// Matrix/Vector-based:
// MatrixPipeline
// UMatrixPipeline
//
// Оба варианта являются равноправными и используются
// в зависимости от представления данных.
// =============================================================
interface interface
@ -74,7 +89,7 @@ type
NormType = MLModelsABC.NormType; NormType = MLModelsABC.NormType;
Activations = MLModelsABC.Activations; Activations = MLModelsABC.Activations;
Pipeline = MLModelsABC.Pipeline; Pipeline = MLModelsABC.MatrixPipeline;
LinearRegression = MLModelsABC.LinearRegression; LinearRegression = MLModelsABC.LinearRegression;
LogisticRegression = MLModelsABC.LogisticRegression; LogisticRegression = MLModelsABC.LogisticRegression;
@ -113,7 +128,7 @@ type
IModel = MLCoreABC.IModel; IModel = MLCoreABC.IModel;
ISupervisedModel = MLCoreABC.ISupervisedModel; ISupervisedModel = MLCoreABC.ISupervisedModel;
IUnsupervisedModel = MLCoreABC.IUnsupervisedModel; IUnsupervisedModel = MLCoreABC.IUnsupervisedModel;
UPipeline = MLModelsABC.UPipeline; UPipeline = MLModelsABC.UMatrixPipeline;
UDataPipeline = MLPipelineABC.UDataPipeline; UDataPipeline = MLPipelineABC.UDataPipeline;
TaskKind = MLPipelineABC.TaskKind; TaskKind = MLPipelineABC.TaskKind;

View file

@ -355,7 +355,9 @@ type
fCheckConvergence: boolean; fCheckConvergence: boolean;
fMinImprovement: real; fMinImprovement: real;
fClassLabels: array of string; // В каждой модели классификации fClassLabels: array of string;
fUseFastExp: boolean;
function GetWeights: Matrix; function GetWeights: Matrix;
function GetIntercept: Vector; function GetIntercept: Vector;
@ -369,7 +371,8 @@ type
epochs: integer := 1000; epochs: integer := 1000;
tol: real := 1e-6; tol: real := 1e-6;
checkConvergence: boolean := true; checkConvergence: boolean := true;
minImprovement: real := 1e-8 minImprovement: real := 1e-8;
useFastExp: boolean := True
); );
/// Обучает модель логистической регрессии. /// Обучает модель логистической регрессии.
@ -380,7 +383,7 @@ type
/// Примечание: /// Примечание:
/// • обученное состояние модели НЕ копируется методом Clone /// • обученное состояние модели НЕ копируется методом Clone
function Fit(X: Matrix; y: Vector): ISupervisedModel; function Fit(X: Matrix; y: Vector): ISupervisedModel;
/// Возвращает матрицу вероятностей (m x k). /// Возвращает матрицу вероятностей (m x k).
function PredictProba(X: Matrix): Matrix; function PredictProba(X: Matrix): Matrix;
@ -1571,9 +1574,6 @@ type
fTouched: array of integer; fTouched: array of integer;
fEpoch: integer; fEpoch: integer;
fSampleIdx: array of integer;
fSampleSize: integer;
public public
/// Создаёт классификатор kNN. /// Создаёт классификатор kNN.
/// k — число ближайших соседей (k > 0). /// k — число ближайших соседей (k > 0).
@ -1589,14 +1589,22 @@ type
/// • обученное состояние модели (обучающая выборка) НЕ копируется методом Clone /// • обученное состояние модели (обучающая выборка) НЕ копируется методом Clone
function Fit(X: Matrix; y: Vector): ISupervisedModel; override; function Fit(X: Matrix; y: Vector): ISupervisedModel; override;
/// Выполняет предсказание меток классов для объектов X. /// Выполняет предсказание меток классов для объектов X.
/// Возвращает вектор предсказанных меток /// Возвращает вектор предсказанных меток.
/// Не является потокобезопасным: не вызывать одновременно из нескольких потоков
/// для одного экземпляра модели.
function Predict(X: Matrix): Vector; override; function Predict(X: Matrix): Vector; override;
/// Выполняет предсказание меток классов для объектов X.
/// Возвращает массив индексов классов.
/// Не является потокобезопасным: не вызывать одновременно из нескольких потоков
/// для одного экземпляра модели.
function PredictLabels(X: Matrix): array of integer; function PredictLabels(X: Matrix): array of integer;
/// Возвращает матрицу вероятностей размера (nSamples × nClasses). /// Возвращает матрицу вероятностей размера (nSamples × nClasses).
/// Столбцы соответствуют классам в порядке, возвращаемом GetClasses() /// Столбцы соответствуют классам в порядке, возвращаемом GetClasses().
/// Не является потокобезопасным: не вызывать одновременно из нескольких потоков
/// для одного экземпляра модели.
function PredictProba(X: Matrix): Matrix; function PredictProba(X: Matrix): Matrix;
/// Возвращает массив меток классов в порядке столбцов PredictProba /// Возвращает массив меток классов в порядке столбцов PredictProba
@ -1839,7 +1847,7 @@ type
{$endregion Models} {$endregion Models}
{$region Pipeline} {$region MatrixPipeline}
/// Последовательный конвейер машинного обучения (supervised). /// Последовательный конвейер машинного обучения (supervised).
/// Гарантирует строгий порядок выполнения шагов: /// Гарантирует строгий порядок выполнения шагов:
/// [преобразователи] → [модель]. /// [преобразователи] → [модель].
@ -1854,7 +1862,10 @@ type
/// ///
/// Обеспечивает единый интерфейс Fit(X, y) / Predict(X) /// Обеспечивает единый интерфейс Fit(X, y) / Predict(X)
/// и воспроизводимость полного процесса обучения /// и воспроизводимость полного процесса обучения
Pipeline = class(ISupervisedModel) ///
/// MatrixPipeline используется, когда данные уже представлены
/// в виде Matrix X и Vector y
MatrixPipeline = class(ISupervisedModel)
private private
fTransformers: List<ITransformer>; fTransformers: List<ITransformer>;
fModel: ISupervisedModel; fModel: ISupervisedModel;
@ -1870,7 +1881,7 @@ type
constructor Create; constructor Create;
{ {
// Pipeline.Build используется, когда данные уже представлены // MatrixPipeline.Build используется, когда данные уже представлены
// в виде числовой матрицы признаков X и вектора целевой переменной y. // в виде числовой матрицы признаков X и вектора целевой переменной y.
// В этом случае DataFrame и препроцессоры уровня таблицы не требуются. // В этом случае DataFrame и препроцессоры уровня таблицы не требуются.
// //
@ -1881,7 +1892,7 @@ type
// • подбор гиперпараметров // • подбор гиперпараметров
// • тестирование моделей // • тестирование моделей
// //
// Pipeline объединяет несколько матричных преобразований (ITransformer) // MatrixPipeline объединяет несколько матричных преобразований (ITransformer)
// и модель (IModel) в единый объект, который можно обучать и использовать // и модель (IModel) в единый объект, который можно обучать и использовать
// для предсказаний. // для предсказаний.
// //
@ -1892,13 +1903,13 @@ type
// перед преобразованием данных в Matrix/Vector. // перед преобразованием данных в Matrix/Vector.
var pipe1 := var pipe1 :=
Pipeline.Build( MatrixPipeline.Build(
new StandardScaler, new StandardScaler,
new LogisticRegression new LogisticRegression
); );
var pipe2 := var pipe2 :=
Pipeline.Build( MatrixPipeline.Build(
new StandardScaler, new StandardScaler,
new PCATransformer(2), new PCATransformer(2),
new LogisticRegression new LogisticRegression
@ -1931,13 +1942,13 @@ type
/// сначала преобразователи, затем модель. /// сначала преобразователи, затем модель.
/// Последний шаг обязан быть моделью (IModel). /// Последний шаг обязан быть моделью (IModel).
/// Возвращает сконструированный конвейер. /// Возвращает сконструированный конвейер.
static function Build(params steps: array of IPipelineStep): Pipeline; static function Build(params steps: array of IPipelineStep): MatrixPipeline;
/// Устанавливает или заменяет модель. /// Устанавливает или заменяет модель.
function SetModel(m: ISupervisedModel): Pipeline; function SetModel(m: ISupervisedModel): MatrixPipeline;
/// Добавляет преобразование в конец пайплайна /// Добавляет преобразование в конец пайплайна
function Add(t: ITransformer): Pipeline; function Add(t: ITransformer): MatrixPipeline;
/// Обучает пайплайн на данных. /// Обучает пайплайн на данных.
/// X — матрица m × n (m объектов, n признаков). /// X — матрица m × n (m объектов, n признаков).
@ -1962,6 +1973,7 @@ type
/// Показывает, был ли пайплайн обучен (вызван метод Fit). /// Показывает, был ли пайплайн обучен (вызван метод Fit).
property IsFitted: boolean read fFitted; property IsFitted: boolean read fFitted;
/// Возвращает строковое представление пайплайна.
function ToString: string; override; function ToString: string; override;
/// Создаёт копию пайплайна с теми же шагами и моделью. /// Создаёт копию пайплайна с теми же шагами и моделью.
@ -1979,19 +1991,48 @@ type
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
end; end;
UPipeline = class(IUnsupervisedModel) /// Последовательный конвейер машинного обучения (unsupervised).
/// Гарантирует строгий порядок выполнения шагов:
/// [преобразователи].
///
/// Поддерживает:
/// • преобразователи без учёта целевой переменной (unsupervised).
///
/// Все преобразователи применяются последовательно к признакам X.
///
/// Обеспечивает единый интерфейс Fit(X) / Transform(X)
/// и воспроизводимость полного процесса преобразования данных.
///
/// UMatrixPipeline используется, когда данные уже представлены
/// в виде Matrix X и отсутствует целевая переменная
UMatrixPipeline = class(IUnsupervisedModel)
private private
fTransformers: List<ITransformer>; fTransformers: List<ITransformer>;
fModel: IModel; fModel: IModel;
fFitted: boolean; fFitted: boolean;
public public
/// Создаёт конвейер машинного обучения для заданной модели:
/// • model — модель, которая будет применена
/// после последовательного применения всех преобразователей.
/// Модель должна реализовывать интерфейс IModel (без учёта целевой переменной)
constructor Create(model: IModel); constructor Create(model: IModel);
/// Создаёт пустой пайплайн (конвейер машинного обучения).
/// Модель должна быть установлена через SetModel
constructor Create; constructor Create;
static function Build(params steps: array of IPipelineStep): UPipeline; /// Строит конвейер машинного обучения из последовательности шагов.
/// Шаги указываются в порядке выполнения:
/// сначала преобразователи, затем модель.
/// Последний шаг обязан быть моделью (IModel).
/// Возвращает сконструированный конвейер.
static function Build(params steps: array of IPipelineStep): UMatrixPipeline;
function SetModel(m: IModel): UPipeline; /// Устанавливает или заменяет модель.
function Add(t: ITransformer): UPipeline; function SetModel(m: IModel): UMatrixPipeline;
/// Добавляет преобразование в конец пайплайна
function Add(t: ITransformer): UMatrixPipeline;
/// Обучает пайплайн на данных. /// Обучает пайплайн на данных.
/// X — матрица m × n (m объектов, n признаков). /// X — матрица m × n (m объектов, n признаков).
@ -2001,11 +2042,30 @@ type
/// Примечание: /// Примечание:
/// • обученное состояние пайплайна НЕ копируется методом Clone /// • обученное состояние пайплайна НЕ копируется методом Clone
function Fit(X: Matrix): IUnsupervisedModel; function Fit(X: Matrix): IUnsupervisedModel;
/// Применяет последовательность преобразований к данным.
/// X — матрица m × n (m объектов, n признаков).
/// Последовательно применяет все трансформеры к данным,
/// после чего возвращает преобразованную матрицу.
///
/// Примечание:
/// • обученное состояние пайплайна НЕ копируется методом Clone
function Transform(X: Matrix): Matrix; function Transform(X: Matrix): Matrix;
/// Применяет пайплайн и возвращает результат модели.
/// X — матрица m × n (m объектов, n признаков).
/// После применения всех преобразований вызывает модель,
/// реализующую интерфейс IModel.
///
/// Примечание:
/// • семантика результата зависит от конкретной модели
/// (например, кластерные метки, оценки и т.д.)
function Predict(X: Matrix): Vector; function Predict(X: Matrix): Vector;
/// Показывает, был ли пайплайн обучен (вызван метод Fit у шагов пайплайна).
property IsFitted: boolean read fFitted; property IsFitted: boolean read fFitted;
/// Возвращает строковое представление пайплайна.
function ToString: string; override; function ToString: string; override;
/// Создаёт копию пайплайна с теми же шагами и моделью. /// Создаёт копию пайплайна с теми же шагами и моделью.
@ -2016,13 +2076,13 @@ type
/// • Все шаги и модель клонируются через их Clone (config-only) /// • Все шаги и модель клонируются через их Clone (config-only)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate и Pipeline /// • использование в CrossValidate и MatrixPipeline
/// • обеспечивает независимое переобучение пайплайна /// • обеспечивает независимое переобучение пайплайна
function Clone: IModel; function Clone: IModel;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
end; end;
{$endregion Pipeline} {$endregion MatrixPipeline}
{$region Transformers} {$region Transformers}
@ -2078,7 +2138,7 @@ type
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) /// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline /// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров /// • гарантирует независимое переобучение трансформеров
/// ///
/// Примечание: /// Примечание:
@ -2136,7 +2196,7 @@ type
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) /// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline /// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров /// • гарантирует независимое переобучение трансформеров
/// ///
/// Примечание: /// Примечание:
@ -2190,7 +2250,7 @@ type
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) /// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline /// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров /// • гарантирует независимое переобучение трансформеров
/// ///
/// Примечание: /// Примечание:
@ -2243,7 +2303,7 @@ type
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) /// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline /// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров /// • гарантирует независимое переобучение трансформеров
/// ///
/// Примечание: /// Примечание:
@ -2340,7 +2400,7 @@ type
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) /// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline /// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров /// • гарантирует независимое переобучение трансформеров
/// ///
/// Примечание: /// Примечание:
@ -2398,7 +2458,7 @@ type
/// • Возвращаемый объект эквивалентен новому экземпляру после Create(...) /// • Возвращаемый объект эквивалентен новому экземпляру после Create(...)
/// ///
/// Назначение: /// Назначение:
/// • использование в CrossValidate, GridSearch и Pipeline /// • использование в CrossValidate, GridSearch и MatrixPipeline
/// • гарантирует независимое переобучение трансформеров /// • гарантирует независимое переобучение трансформеров
/// ///
/// Примечание: /// Примечание:
@ -3100,7 +3160,7 @@ end;
//----------------------------- //-----------------------------
constructor LogisticRegression.Create(lambda: real; learningRate: real; epochs: integer; constructor LogisticRegression.Create(lambda: real; learningRate: real; epochs: integer;
tol: real; checkConvergence: boolean; minImprovement: real); tol: real; checkConvergence: boolean; minImprovement: real; useFastExp: boolean);
begin begin
fLambda := lambda; fLambda := lambda;
fLearningRate := learningRate; fLearningRate := learningRate;
@ -3109,8 +3169,19 @@ begin
fTol := tol; fTol := tol;
fCheckConvergence := checkConvergence; fCheckConvergence := checkConvergence;
fMinImprovement := minImprovement; fMinImprovement := minImprovement;
fUseFastExp := useFastExp
end; end;
function FastExp(x: real): real;
begin
if x < -5 then exit(0.0);
if x > 5 then x := 5;
Result := 1.0 + x + 0.5*x*x + (1.0/6.0)*x*x*x;
end;
type RealArr = array of real;
function LogisticRegression.Fit(X: Matrix; y: Vector): ISupervisedModel; function LogisticRegression.Fit(X: Matrix; y: Vector): ISupervisedModel;
begin begin
if X = nil then if X = nil then
@ -3179,6 +3250,228 @@ begin
fIndexToClass[i] := unique[i]; fIndexToClass[i] := unique[i];
end; end;
// --- init
fW := new Matrix(p, fClassCount);
var scale := 0.01;
for var j := 0 to p - 1 do
for var k := 0 to fClassCount - 1 do
fW.Data[j,k] := (Random - 0.5) * 2 * scale;
fIntercept := new Vector(fClassCount);
var prevLoss := real.PositiveInfinity;
var xRows := X.Data.Rows;
var gradW := new RealArr[fClassCount];
for var k := 0 to fClassCount - 1 do
gradW[k] := new real[p];
var gradB := new real[fClassCount];
var zi := new real[fClassCount];
for var epoch := 1 to fEpochs do
begin
var WCols := fW.Data.Cols;
//var WRows := fW.Data.Rows;
var loss := 0.0;
// --- zero gradients
&Array.Clear(gradB, 0, fClassCount);
for var k := 0 to fClassCount - 1 do
&Array.Clear(gradW[k], 0, p);
// --- one pass: logits -> softmax -> loss -> gradient
for var i := 0 to m - 1 do
begin
var xi := XRows[i];
var yi := yEncoded[i];
// --- logits
for var k := 0 to fClassCount - 1 do
begin
var wk := WCols[k];
var s := fIntercept.Data[k];
for var j := 0 to p - 1 do
s += xi[j] * wk[j];
zi[k] := s;
end;
// --- stable softmax
var maxVal := zi[0];
for var k := 1 to fClassCount - 1 do
if zi[k] > maxVal then
maxVal := zi[k];
var sumExp := 0.0;
for var k := 0 to fClassCount - 1 do
begin
var v: real;
if fUseFastExp then
v := FastExp(zi[k] - maxVal)
else
v := Exp(zi[k] - maxVal);
zi[k] := v;
sumExp += v;
end;
if sumExp <= 0 then
begin
var uniformProb := 1.0 / fClassCount;
for var k := 0 to fClassCount - 1 do
zi[k] := uniformProb;
end
else
begin
var invSum := 1.0 / sumExp;
for var k := 0 to fClassCount - 1 do
zi[k] *= invSum;
end;
// --- loss
var prob := zi[yi];
if prob < 1e-300 then
prob := 1e-300;
loss -= Ln(prob);
// --- gradient
for var k := 0 to fClassCount - 1 do
begin
var diff := zi[k] - Ord(k = yi);
gradB[k] += diff;
var gwk := gradW[k];
for var j := 0 to p - 1 do
gwk[j] += xi[j] * diff;
end;
end;
loss /= m;
// --- L2 penalty
if fLambda <> 0 then
begin
var l2 := 0.0;
for var j := 0 to p - 1 do
for var k := 0 to fClassCount - 1 do
l2 += fW.Data[j,k] * fW.Data[j,k];
loss += 0.5 * fLambda * l2;
end;
// --- divergence check
if double.IsNaN(loss) or double.IsInfinity(loss) then
ArgumentError(ER_LOGISTIC_INVALID_LOSS);
// --- convergence check
if fCheckConvergence then
begin
if Abs(prevLoss - loss) < Max(fMinImprovement, fTol * Max(1.0, Abs(prevLoss))) then
break;
prevLoss := loss;
end;
// --- update
var invM := 1.0 / m;
for var k := 0 to fClassCount - 1 do
begin
gradB[k] *= invM;
fIntercept.Data[k] -= fLearningRate * gradB[k];
for var j := 0 to p - 1 do
begin
var g := gradW[k][j] * invM;
if fLambda <> 0 then
g += fLambda * fW.Data[j,k];
fW.Data[j,k] -= fLearningRate * g;
end;
end;
end;
fFitted := true;
Result := Self;
end;
{function LogisticRegression.FitOld(X: Matrix; y: Vector): ISupervisedModel;
begin
if X = nil then
ArgumentNullError(ER_X_NULL);
if y = nil then
ArgumentNullError(ER_Y_NULL);
if MLConfig.ValidateFiniteInputs then
begin
CheckXForFit(X);
CheckYForFit(y);
end;
if X.RowCount = 0 then
ArgumentError(ER_EMPTY_DATASET);
if X.RowCount <> y.Length then
DimensionError(ER_DIM_MISMATCH, X.RowCount, y.Length);
if fLearningRate <= 0 then
ArgumentOutOfRangeError(ER_LEARNING_RATE_INVALID, fLearningRate);
if fEpochs <= 0 then
ArgumentOutOfRangeError(ER_EPOCHS_INVALID, fEpochs);
if fLambda < 0 then
ArgumentOutOfRangeError(ER_LAMBDA_NEGATIVE, fLambda);
if fCheckConvergence and (fTol <= 0) then
ArgumentOutOfRangeError(ER_TOL_INVALID, fTol);
var m := X.RowCount;
var p := X.ColCount;
// --- convert to integer labels
var yInt := new integer[m];
for var i := 0 to m - 1 do
begin
var r := y[i];
var ir := Round(r);
if Abs(r - ir) > 1e-12 then
ArgumentError(ER_LABELS_NOT_INTEGER);
yInt[i] := ir;
end;
// --- encode (порядок первого появления)
var unique: array of integer;
var yEncoded := EncodeLabelsInt(yInt, unique);
fClassCount := unique.Length;
if fClassCount < 2 then
ArgumentError(ER_LOGISTIC_NEED_AT_LEAST_TWO_CLASSES);
// --- build mappings
fClassToIndex := new Dictionary<integer, integer>;
SetLength(fIndexToClass, fClassCount);
for var i := 0 to fClassCount - 1 do
begin
fClassToIndex[unique[i]] := i;
fIndexToClass[i] := unique[i];
end;
// --- init // --- init
fW := new Matrix(p, fClassCount); fW := new Matrix(p, fClassCount);
fIntercept := new Vector(fClassCount); fIntercept := new Vector(fClassCount);
@ -3301,7 +3594,7 @@ begin
fFitted := true; fFitted := true;
Result := Self; Result := Self;
end; end;}
function LogisticRegression.PredictProba(X: Matrix): Matrix; function LogisticRegression.PredictProba(X: Matrix): Matrix;
begin begin
@ -3394,7 +3687,8 @@ begin
fEpochs, fEpochs,
fTol, fTol,
fCheckConvergence, fCheckConvergence,
fMinImprovement fMinImprovement,
fUseFastExp
); );
end; end;
@ -4050,6 +4344,7 @@ begin
fMinSamplesSplit, fMinSamplesSplit,
fMinSamplesLeaf, fMinSamplesLeaf,
fCriterion, fCriterion,
fClassCount,
fMaxFeatures, fMaxFeatures,
fRandomSeed fRandomSeed
); );
@ -7256,15 +7551,6 @@ begin
// --- copy train data // --- copy train data
fXTrain := X.Clone; fXTrain := X.Clone;
fSampleSize := Min(3000, fXTrain.RowCount); // попробуй 2000–4000
SetLength(fSampleIdx, fSampleSize);
var rnd := new System.Random(42);
for var i := 0 to fSampleSize - 1 do
fSampleIdx[i] := rnd.Next(fXTrain.RowCount);
// ========================================================= // =========================================================
// ЕДИНЫЙ ENCODING // ЕДИНЫЙ ENCODING
// ========================================================= // =========================================================
@ -8269,17 +8555,17 @@ end;
function DBSCAN.ClustersCount: integer := fClusterCount; function DBSCAN.ClustersCount: integer := fClusterCount;
//----------------------------- //-----------------------------
// Pipeline // MatrixPipeline
//----------------------------- //-----------------------------
constructor Pipeline.Create; constructor MatrixPipeline.Create;
begin begin
fTransformers := new List<ITransformer>; fTransformers := new List<ITransformer>;
fModel := nil; fModel := nil;
fFitted := false; fFitted := false;
end; end;
constructor Pipeline.Create(model: ISupervisedModel); constructor MatrixPipeline.Create(model: ISupervisedModel);
begin begin
Create; Create;
if model = nil then if model = nil then
@ -8287,7 +8573,7 @@ begin
fModel := model; fModel := model;
end; end;
class function Pipeline.Build(params steps: array of IPipelineStep): Pipeline; class function MatrixPipeline.Build(params steps: array of IPipelineStep): MatrixPipeline;
begin begin
if (steps = nil) or (Length(steps) = 0) then if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS); ArgumentError(ER_PIPELINE_NO_STEPS);
@ -8301,7 +8587,7 @@ begin
if not (last is ISupervisedModel) then if not (last is ISupervisedModel) then
ArgumentError(ER_PIPELINE_LAST_NOT_SUPERVISED_MODEL); ArgumentError(ER_PIPELINE_LAST_NOT_SUPERVISED_MODEL);
var pipe := new Pipeline(last as ISupervisedModel); var pipe := new MatrixPipeline(last as ISupervisedModel);
// все шаги кроме последнего — трансформеры // все шаги кроме последнего — трансформеры
for var i := 0 to High(steps) - 1 do for var i := 0 to High(steps) - 1 do
@ -8320,7 +8606,7 @@ begin
Result := pipe; Result := pipe;
end; end;
function Pipeline.Add(t: ITransformer): Pipeline; function MatrixPipeline.Add(t: ITransformer): MatrixPipeline;
begin begin
if t = nil then if t = nil then
ArgumentError(ER_TRANSFORMER_NULL); ArgumentError(ER_TRANSFORMER_NULL);
@ -8329,7 +8615,7 @@ begin
Result := Self; Result := Self;
end; end;
function Pipeline.SetModel(m: ISupervisedModel): Pipeline; function MatrixPipeline.SetModel(m: ISupervisedModel): MatrixPipeline;
begin begin
if m = nil then if m = nil then
ArgumentError(ER_MODEL_NULL); ArgumentError(ER_MODEL_NULL);
@ -8338,7 +8624,7 @@ begin
Result := Self; Result := Self;
end; end;
function Pipeline.Fit(X: Matrix; y: Vector): ISupervisedModel; function MatrixPipeline.Fit(X: Matrix; y: Vector): ISupervisedModel;
begin begin
if fModel = nil then if fModel = nil then
ArgumentError(ER_MODEL_NULL); ArgumentError(ER_MODEL_NULL);
@ -8386,7 +8672,7 @@ begin
Result := Self; Result := Self;
end; end;
function Pipeline.Transform(X: Matrix): Matrix; function MatrixPipeline.Transform(X: Matrix): Matrix;
begin begin
if not fFitted then if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED); NotFittedError(ER_FIT_NOT_CALLED);
@ -8410,7 +8696,7 @@ begin
Result := Xt; Result := Xt;
end; end;
function Pipeline.Predict(X: Matrix): Vector; function MatrixPipeline.Predict(X: Matrix): Vector;
begin begin
if not fFitted then if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED); NotFittedError(ER_FIT_NOT_CALLED);
@ -8422,7 +8708,7 @@ begin
Result := fModel.Predict(Xt); Result := fModel.Predict(Xt);
end; end;
function Pipeline.PredictProba(X: Matrix): Matrix; function MatrixPipeline.PredictProba(X: Matrix): Matrix;
begin begin
if not fFitted then if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED); NotFittedError(ER_FIT_NOT_CALLED);
@ -8439,9 +8725,9 @@ begin
.PredictProba(Xt); .PredictProba(Xt);
end; end;
function Pipeline.ToString: string; function MatrixPipeline.ToString: string;
begin begin
var sb := 'Pipeline (' + var sb := 'MatrixPipeline (' +
(if fFitted then 'trained' else 'not trained') + '):' + NewLine; (if fFitted then 'trained' else 'not trained') + '):' + NewLine;
var idx := 1; var idx := 1;
@ -8458,12 +8744,12 @@ begin
Result := sb; Result := sb;
end; end;
function Pipeline.Clone: IModel; function MatrixPipeline.Clone: IModel;
begin begin
if fModel = nil then if fModel = nil then
ArgumentError(ER_MODEL_NULL); ArgumentError(ER_MODEL_NULL);
var p := new Pipeline; var p := new MatrixPipeline;
foreach var t in fTransformers do foreach var t in fTransformers do
p.Add(t.Clone); p.Add(t.Clone);
@ -8479,16 +8765,16 @@ begin
end; end;
//----------------------------- //-----------------------------
// UPipeline // UMatrixPipeline
//----------------------------- //-----------------------------
constructor UPipeline.Create; constructor UMatrixPipeline.Create;
begin begin
fTransformers := new List<ITransformer>; fTransformers := new List<ITransformer>;
fModel := nil; fModel := nil;
fFitted := false; fFitted := false;
end; end;
constructor UPipeline.Create(model: IModel); constructor UMatrixPipeline.Create(model: IModel);
begin begin
Create; Create;
if model = nil then if model = nil then
@ -8496,7 +8782,7 @@ begin
fModel := model; fModel := model;
end; end;
class function UPipeline.Build(params steps: array of IPipelineStep): UPipeline; class function UMatrixPipeline.Build(params steps: array of IPipelineStep): UMatrixPipeline;
begin begin
if (steps = nil) or (Length(steps) = 0) then if (steps = nil) or (Length(steps) = 0) then
ArgumentError(ER_PIPELINE_NO_STEPS); ArgumentError(ER_PIPELINE_NO_STEPS);
@ -8509,7 +8795,7 @@ begin
if not (last is IModel) then if not (last is IModel) then
ArgumentError(ER_PIPELINE_LAST_NOT_MODEL); ArgumentError(ER_PIPELINE_LAST_NOT_MODEL);
var pipe := new UPipeline(last as IModel); var pipe := new UMatrixPipeline(last as IModel);
for var i := 0 to High(steps) - 1 do for var i := 0 to High(steps) - 1 do
begin begin
@ -8527,7 +8813,7 @@ begin
Result := pipe; Result := pipe;
end; end;
function UPipeline.Add(t: ITransformer): UPipeline; function UMatrixPipeline.Add(t: ITransformer): UMatrixPipeline;
begin begin
if t = nil then if t = nil then
ArgumentError(ER_TRANSFORMER_NULL); ArgumentError(ER_TRANSFORMER_NULL);
@ -8536,7 +8822,7 @@ begin
Result := Self; Result := Self;
end; end;
function UPipeline.SetModel(m: IModel): UPipeline; function UMatrixPipeline.SetModel(m: IModel): UMatrixPipeline;
begin begin
if m = nil then if m = nil then
ArgumentError(ER_MODEL_NULL); ArgumentError(ER_MODEL_NULL);
@ -8545,7 +8831,7 @@ begin
Result := Self; Result := Self;
end; end;
function UPipeline.Fit(X: Matrix): IUnsupervisedModel; function UMatrixPipeline.Fit(X: Matrix): IUnsupervisedModel;
begin begin
if fModel = nil then if fModel = nil then
ArgumentError(ER_MODEL_NULL); ArgumentError(ER_MODEL_NULL);
@ -8585,7 +8871,7 @@ begin
Result := Self; Result := Self;
end; end;
function UPipeline.Transform(X: Matrix): Matrix; function UMatrixPipeline.Transform(X: Matrix): Matrix;
begin begin
if not fFitted then if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED); NotFittedError(ER_FIT_NOT_CALLED);
@ -8609,7 +8895,7 @@ begin
Result := Xt; Result := Xt;
end; end;
function UPipeline.Predict(X: Matrix): Vector; function UMatrixPipeline.Predict(X: Matrix): Vector;
begin begin
if not fFitted then if not fFitted then
NotFittedError(ER_FIT_NOT_CALLED); NotFittedError(ER_FIT_NOT_CALLED);
@ -8625,9 +8911,9 @@ begin
Result := (fModel as IPredictiveModel).Predict(Xt); Result := (fModel as IPredictiveModel).Predict(Xt);
end; end;
function UPipeline.ToString: string; function UMatrixPipeline.ToString: string;
begin begin
var sb := 'UPipeline (' + var sb := 'UMatrixPipeline (' +
(if fFitted then 'trained' else 'not trained') + '):' + NewLine; (if fFitted then 'trained' else 'not trained') + '):' + NewLine;
var idx := 1; var idx := 1;
@ -8644,12 +8930,12 @@ begin
Result := sb; Result := sb;
end; end;
function UPipeline.Clone: IModel; function UMatrixPipeline.Clone: IModel;
begin begin
if fModel = nil then if fModel = nil then
ArgumentError(ER_MODEL_NULL); ArgumentError(ER_MODEL_NULL);
var p := new UPipeline; var p := new UMatrixPipeline;
foreach var t in fTransformers do foreach var t in fTransformers do
p.Add(t.Clone); p.Add(t.Clone);

View file

@ -298,7 +298,8 @@ const
'Model is not a classifier'; 'Model is not a classifier';
ER_MODEL_CLONE_TYPE = ER_MODEL_CLONE_TYPE =
'Clone модели вернул неподдерживаемый тип!!Model Clone returned unsupported type'; 'Clone модели вернул неподдерживаемый тип!!Model Clone returned unsupported type';
ER_INVALID_MODEL_TYPE =
'Clone модели вернул неподдерживаемый тип (ожидается {0})!!Model Clone returned unsupported type (expected {0})';
//----------------------------- //-----------------------------
// DataPipeline // DataPipeline
//----------------------------- //-----------------------------
@ -1143,7 +1144,14 @@ begin
// --- модель // --- модель
if fModel <> nil then if fModel <> nil then
p.fModel := fModel.Clone as IUnsupervisedModel; begin
var m := fModel.Clone;
if not (m is IUnsupervisedModel) then
Error(ER_INVALID_MODEL_TYPE, 'IUnsupervisedModel');
p.fModel := m as IUnsupervisedModel;
end;
// --- состояние НЕ копируем // --- состояние НЕ копируем
p.fFinalFeatures := nil; p.fFinalFeatures := nil;

View file

@ -40,6 +40,7 @@ function IntsToLabels(a: array of integer): Vector;
/// Каждому уникальному значению присваивается номер 0,1,2,... /// Каждому уникальному значению присваивается номер 0,1,2,...
/// Порядок кодирования соответствует порядку первого появления меток. /// Порядок кодирования соответствует порядку первого появления меток.
/// Используется при обучении моделей и визуализации. /// Используется при обучении моделей и визуализации.
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabels(labels: array of string): array of integer; function EncodeLabels(labels: array of string): array of integer;
/// Кодирует строковые метки классов в целочисленные индексы. /// Кодирует строковые метки классов в целочисленные индексы.
@ -47,6 +48,7 @@ function EncodeLabels(labels: array of string): array of integer;
/// Порядок кодирования соответствует порядку первого появления меток. /// Порядок кодирования соответствует порядку первого появления меток.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования. /// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Используется при обучении моделей и визуализации /// Используется при обучении моделей и визуализации
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabels(labels: array of string; var classes: array of string): array of integer; function EncodeLabels(labels: array of string; var classes: array of string): array of integer;
/// Кодирует целые метки классов в целочисленные индексы. /// Кодирует целые метки классов в целочисленные индексы.
@ -54,6 +56,7 @@ function EncodeLabels(labels: array of string; var classes: array of string): ar
/// Порядок кодирования соответствует порядку первого появления меток. /// Порядок кодирования соответствует порядку первого появления меток.
/// В параметр classes возвращается массив уникальных значений в порядке кодирования. /// В параметр classes возвращается массив уникальных значений в порядке кодирования.
/// Используется при обучении моделей и визуализации /// Используется при обучении моделей и визуализации
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function EncodeLabelsInt(labels: array of integer; var classes: array of integer): array of integer; function EncodeLabelsInt(labels: array of integer; var classes: array of integer): array of integer;
/// Преобразует строковые метки классов в целочисленные индексы /// Преобразует строковые метки классов в целочисленные индексы
@ -61,6 +64,7 @@ function EncodeLabelsInt(labels: array of integer; var classes: array of integer
/// classes должен быть получен из EncodeLabels. /// classes должен быть получен из EncodeLabels.
/// Если встречается неизвестная метка — выбрасывается исключение. /// Если встречается неизвестная метка — выбрасывается исключение.
/// Используется для применения кодирования к тестовым данным (Transform). /// Используется для применения кодирования к тестовым данным (Transform).
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function TransformLabels(labels: array of string; classes: array of string): array of integer; function TransformLabels(labels: array of string; classes: array of string): array of integer;
/// Преобразует целочисленные метки классов в индексы (0,1,2,...) /// Преобразует целочисленные метки классов в индексы (0,1,2,...)
@ -68,11 +72,13 @@ function TransformLabels(labels: array of string; classes: array of string): arr
/// classes должен быть получен из EncodeLabelsInt. /// classes должен быть получен из EncodeLabelsInt.
/// Если встречается неизвестное значение — выбрасывается исключение. /// Если встречается неизвестное значение — выбрасывается исключение.
/// Используется для применения кодирования к тестовым данным (Transform). /// Используется для применения кодирования к тестовым данным (Transform).
/// Предполагается, что входные данные уже очищены от пропущенных значений.
function TransformLabelsInt(labels: array of integer; classes: array of integer): array of integer; function TransformLabelsInt(labels: array of integer; classes: array of integer): array of integer;
/// Преобразует целочисленные индексы классов обратно в строковые метки. /// Преобразует целочисленные индексы классов обратно в строковые метки.
/// Массив classes задаёт соответствие: classes[i] — имя класса с индексом i. /// Массив classes задаёт соответствие: classes[i] — имя класса с индексом i.
/// Используется для получения текстовых предсказаний моделей. /// Используется для получения текстовых предсказаний моделей.
/// Предполагается, что входные данные уже корректны.
function DecodeLabels(y: array of integer; classes: array of string): array of string; function DecodeLabels(y: array of integer; classes: array of string): array of string;
/// Возвращает список уникальных меток классов. /// Возвращает список уникальных меток классов.
@ -266,7 +272,17 @@ end;
function UniqueLabels(labels: array of string): array of string; function UniqueLabels(labels: array of string): array of string;
begin begin
Result := labels.Distinct.ToArray; var seen := new HashSet<string>;
var res := new List<string>;
for var i := 0 to labels.Length - 1 do
if not seen.Contains(labels[i]) then
begin
seen.Add(labels[i]);
res.Add(labels[i]);
end;
Result := res.ToArray;
end; end;
function CloneOrNil(v: Vector): Vector; function CloneOrNil(v: Vector): Vector;

View file

@ -227,6 +227,9 @@ type
/// Ожидается, что yTrue и yPred содержат закодированные метки классов /// Ожидается, что yTrue и yPred содержат закодированные метки классов
/// (целые значения 0..K-1, допускается хранение в Vector типа real). /// (целые значения 0..K-1, допускается хранение в Vector типа real).
/// Для строковых меток необходимо предварительно применить EncodeLabels /// Для строковых меток необходимо предварительно применить EncodeLabels
///
/// Метки классов упорядочиваются по возрастанию для обеспечения
/// стабильного и предсказуемого представления результатов
ConfusionMatrix = class ConfusionMatrix = class
private private
fMatrix: array[,] of integer; fMatrix: array[,] of integer;

View file

@ -11641,103 +11641,128 @@ end;
// Partition, ZipTuple, UnZipTuple, Interleave, Numerate, Tabulate, Pairwise, Batch // Partition, ZipTuple, UnZipTuple, Interleave, Numerate, Tabulate, Pairwise, Batch
// Дополнения 2024: Zip - синоним ZipTuple // Дополнения 2024: Zip - синоним ZipTuple
/// Возвращает первый элемент последовательности с минимальным значением ключа
/// Возвращает первый элемент последовательности с минимальным значением ключа /// Возвращает первый элемент последовательности с минимальным значением ключа
function MinBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod; function MinBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod;
begin begin
var enumerator := Self.GetEnumerator();
if not enumerator.MoveNext() then
raise new System.ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
var minElement := enumerator.Current;
var minKey := keySelector(minElement);
var comp := Comparer&<TKey>.Default; var comp := Comparer&<TKey>.Default;
while enumerator.MoveNext() do var minElement: T;
begin var minKey: TKey;
var currentElement := enumerator.Current; var hasValue := False;
var currentKey := keySelector(currentElement);
if comp.Compare(currentKey,minKey) < 0 then
begin
minKey := currentKey;
minElement := currentElement;
end;
end;
foreach var current in Self do
if not hasValue then
begin
minElement := current;
minKey := keySelector(current);
hasValue := True;
end
else
begin
var currentKey := keySelector(current);
if comp.Compare(currentKey, minKey) < 0 then // только строго меньше
begin
minKey := currentKey;
minElement := current;
end;
end;
if not hasValue then
raise new ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
Result := minElement; Result := minElement;
end; end;
/// Возвращает первый элемент последовательности с максимальным значением ключа /// Возвращает первый элемент последовательности с максимальным значением ключа
function MaxBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod; function MaxBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod;
begin begin
var enumerator := Self.GetEnumerator();
if not enumerator.MoveNext() then
raise new System.ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
var maxElement := enumerator.Current;
var maxKey := keySelector(maxElement);
var comp := Comparer&<TKey>.Default; var comp := Comparer&<TKey>.Default;
while enumerator.MoveNext() do var maxElement: T;
begin var maxKey: TKey;
var currentElement := enumerator.Current; var hasValue := False;
var currentKey := keySelector(currentElement);
if comp.Compare(currentKey,maxKey) > 0 then
begin
maxKey := currentKey;
maxElement := currentElement;
end;
end;
foreach var current in Self do
if not hasValue then
begin
maxElement := current;
maxKey := keySelector(current);
hasValue := True;
end
else
begin
var currentKey := keySelector(current);
if comp.Compare(currentKey, maxKey) > 0 then
begin
maxKey := currentKey;
maxElement := current;
end;
end;
if not hasValue then
raise new ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
Result := maxElement; Result := maxElement;
end; end;
/// Возвращает последний элемент последовательности с минимальным значением ключа /// Возвращает последний элемент последовательности с минимальным значением ключа
function LastMinBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod; function LastMinBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod;
begin begin
var enumerator := Self.GetEnumerator();
if not enumerator.MoveNext() then
raise new System.ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
var minElement := enumerator.Current;
var minKey := keySelector(minElement);
var comp := Comparer&<TKey>.Default; var comp := Comparer&<TKey>.Default;
while enumerator.MoveNext() do var minElement: T;
begin var minKey: TKey;
var currentElement := enumerator.Current; var hasValue := False;
var currentKey := keySelector(currentElement);
if comp.Compare(currentKey,minKey) <= 0 then
begin
minKey := currentKey;
minElement := currentElement;
end;
end;
foreach var current in Self do
if not hasValue then
begin
minElement := current;
minKey := keySelector(current);
hasValue := True;
end
else
begin
var currentKey := keySelector(current);
if comp.Compare(currentKey, minKey) <= 0 then // <= для последнего минимума
begin
minKey := currentKey;
minElement := current;
end;
end;
if not hasValue then
raise new ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
Result := minElement; Result := minElement;
end; end;
/// Возвращает последний элемент последовательности с максимальным значением ключа /// Возвращает последний элемент последовательности с максимальным значением ключа
function LastMaxBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod; function LastMaxBy<T, TKey>(Self: sequence of T; keySelector: T -> TKey): T; extensionmethod;
begin begin
var enumerator := Self.GetEnumerator();
if not enumerator.MoveNext() then
raise new System.ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
var maxElement := enumerator.Current;
var maxKey := keySelector(maxElement);
var comp := Comparer&<TKey>.Default; var comp := Comparer&<TKey>.Default;
while enumerator.MoveNext() do var maxElement: T;
begin var maxKey: TKey;
var currentElement := enumerator.Current; var hasValue := False;
var currentKey := keySelector(currentElement);
if comp.Compare(currentKey,maxKey) > 0 then
begin
maxKey := currentKey;
maxElement := currentElement;
end;
end;
foreach var current in Self do
if not hasValue then
begin
maxElement := current;
maxKey := keySelector(current);
hasValue := True;
end
else
begin
var currentKey := keySelector(current);
if comp.Compare(currentKey, maxKey) >= 0 then
begin
maxKey := currentKey;
maxElement := current;
end;
end;
if not hasValue then
raise new ArgumentException(GetTranslation(SEQUENCE_CANNOT_BE_EMPTY));
Result := maxElement; Result := maxElement;
end; end;

View file

@ -323,11 +323,11 @@ begin
var cnt := pair.Value.Count; var cnt := pair.Value.Count;
// Класс может иметь меньше объектов, чем число фолдов. // Класс может иметь меньше объектов, чем число фолдов.
// В этом случае он не будет представлен во всех фолдах. // В библиотеке принята строгая политика: такие случаи считаются ошибкой,
// Это допустимо, но может привести к ошибкам при обучении модели. // так как не гарантируется присутствие класса во всех train-fold.
// Поэтому выполняется fail-fast проверка (см. ниже).
//if cnt < k then if cnt < k then
// ArgumentError(ER_STRATIFIED_CLASS_TOO_SMALL, cls, cnt, k); ArgumentError(ER_STRATIFIED_CLASS_TOO_SMALL, cls, cnt, k);
end; end;
// --- 2. Контейнеры фолдов // --- 2. Контейнеры фолдов