ML - примеры

This commit is contained in:
Mikhalkovich Stanislav 2026-03-25 14:07:27 +03:00
parent c89bba640f
commit a8d8ce82bf
18 changed files with 1421 additions and 123 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 = "3783"; public const string Revision = "3784";
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 @@
%MINOR%=11
%REVISION%=3783
%COREVERSION%=1 %COREVERSION%=1
%REVISION%=3784
%MINOR%=11
%MAJOR%=3 %MAJOR%=3

View file

@ -1,91 +0,0 @@
// Пример влияния регуляризации (RidgeRegression) на линейную модель
// в задаче с большим числом признаков и относительно малым числом объектов.
//
// Датасет:
// • 20 признаков, из которых информативны только 3, остальные — шум
// • добавлен существенный гауссов шум (noise = 2.0)
// • зависимость остаётся линейной, но сигнал частично скрыт шумом
//
// Такая постановка приводит к переобучению:
// модель может подстраиваться под шум и случайные колебания признаков.
//
// Наблюдения:
//
// • LinearRegression:
// – достигает меньшей ошибки на обучающей выборке (лучше подгонка)
// – но заметно хуже работает на тесте (переобучение)
//
// • RidgeRegression:
// – даёт большую ошибку на обучении (штраф за коэффициенты)
// – но существенно лучше обобщает и снижает ошибку на тесте
//
// Интерпретация:
//
// • обычная линейная регрессия переобучается на шуме и лишних признаках
// • Ridge вводит L2-регуляризацию и "сжимает" коэффициенты модели
// • регуляризация подавляет влияние неинформативных признаков
// • в результате модель становится более устойчивой к новым данным
//
// Этот пример демонстрирует:
// • разницу между train и test ошибками как признак переобучения
// • влияние лишних признаков на качество модели
// • практическую пользу регуляризации в задачах с шумом и мультиколлинеарностью
uses MLABC;
function Normal(rnd: System.Random): real;
begin
var u1 := rnd.NextDouble;
var u2 := rnd.NextDouble;
if u1 < 1e-12 then
u1 := 1e-12;
Result := Sqrt(-2 * Ln(u1)) * Cos(2 * Pi * u2);
end;
begin
var (X, y) := Datasets.MakeRegression(
n := 100,
nFeatures := 20,
nInformative := 3,
noise := 2.0,
coefScale := 1.0,
bias := 0.0,
nonlinearStrength := 0.0,
shuffle := True,
seed := 42
);
// --- добавляем корреляцию
var rnd := new System.Random(1);
for var i := 0 to X.RowCount - 1 do
begin
X[i, 1] := X[i, 0] + 0.01 * Normal(rnd);
X[i, 2] := X[i, 0] - 0.01 * Normal(rnd);
end;
var (XTrain, XTest, yTrain, yTest) := Validation.TrainTestSplit(
X, y,
testRatio := 0.3,
seed := 42
);
var lr := new LinearRegression;
lr.Fit(XTrain, yTrain);
var ridge := new RidgeRegression(50.0);
ridge.Fit(XTrain, yTrain);
var yTrainLR := lr.Predict(XTrain);
var yTestLR := lr.Predict(XTest);
var yTrainRidge := ridge.Predict(XTrain);
var yTestRidge := ridge.Predict(XTest);
Println('--- Train ---');
Println($'Linear MSE: {Metrics.MSE(yTrain, yTrainLR),0:F4}');
Println($'Ridge MSE: {Metrics.MSE(yTrain, yTrainRidge),0:F4}');
Println;
Println('--- Test ---');
Println($'Linear MSE: {Metrics.MSE(yTest, yTestLR),0:F4}');
Println($'Ridge MSE: {Metrics.MSE(yTest, yTestRidge),0:F4}');
end.

View file

@ -1,28 +1,19 @@
// Сравнение KMeans и DBSCAN на данных с шумом и разной плотностью кластеров. // Сравнение KMeans и DBSCAN на данных с шумом и разной плотностью.
// //
// Датасет содержит: // Оба алгоритма находят одинаковое число кластеров (k = 5),
// • несколько кластеров // поэтому сравнение по ARI является корректным.
// • различную плотность
// • дополнительные шумовые точки
// //
// KMeans: // KMeans предполагает компактные и одинаковые по форме кластеры,
// • предполагает компактные и примерно одинаковые по размеру кластеры // а также чувствителен к шуму, что снижает качество разбиения.
// • плохо работает при наличии шума и различной плотности
// • стремится "размазать" шум по кластерам
// //
// DBSCAN: // DBSCAN выделяет кластеры как области высокой плотности
// • выделяет кластеры как области высокой плотности // и автоматически игнорирует шумовые точки (label = -1),
// • естественно отделяет шум (метка -1) // благодаря чему лучше восстанавливает структуру данных.
// • лучше справляется с неоднородной структурой данных
// //
// По метрике ARI DBSCAN показывает лучший результат, // В результате DBSCAN показывает более высокий ARI,
// так как точнее восстанавливает истинную структуру кластеров. // так как точнее отражает реальную геометрию кластеров.
//
// Этот пример демонстрирует различие двух подходов:
// • центроидный (KMeans)
// • плотностный (DBSCAN)
// KMeans ARI: 0.9073 // KMeans ARI: 0.8973
// DBSCAN ARI: 0.9496 // DBSCAN ARI: 0.9496
uses MLABC, PlotML; uses MLABC, PlotML;
@ -43,13 +34,15 @@ begin
); );
// --- модели // --- модели
var kmeans := new KMeans(3); var kmeans := new KMeans(5);
kmeans.Fit(X); kmeans.Fit(X);
var yKM := kmeans.PredictLabels(X); var yKM := kmeans.PredictLabels(X);
kmeans.ClustersCount.Println;
var db := new DBSCAN(0.7, 5); var db := new DBSCAN(0.7, 5);
db.Fit(X); db.Fit(X);
var yDB := db.PredictLabels(X); var yDB := db.PredictLabels(X);
db.ClustersCount.Println;
// --- метрики // --- метрики
var ariKM := Metrics.AdjustedRandIndex(yTrue, yKM); var ariKM := Metrics.AdjustedRandIndex(yTrue, yKM);

View file

@ -0,0 +1,119 @@
// Подбор параметра eps для DBSCAN методом перебора с оценкой качества кластеризации.
//
// Для каждого eps вычисляются: число кластеров, доля крупнейшего кластера,
// доля шума и silhouette, после чего считается итоговый score.
//
// Score балансирует качество разделения (silhouette) и структуру кластеров,
// штрафуя решения с одним большим кластером или слишком большим шумом.
//
// Это позволяет избежать вырожденных случаев и выбрать eps,
// дающий наиболее устойчивое и интерпретируемое разбиение.
//
// Важно: перед применением DBSCAN данные масштабируются,
// так как значение eps зависит от масштаба признаков.
uses MLABC;
begin
// --- данные (сложный случай: разная плотность + шум)
var (X, yTrue) := Datasets.MakeBlobs(
n := 400,
centers := 3,
nFeatures := 2,
clusterStd := 0.9,
clusterStdVar := 1.0,
centerBox := 6.0,
classBalance := 1.0,
noisePoints := 60,
shuffle := True,
seed := 1
);
// --- подготовка данных
var scaler := new StandardScaler;
scaler.Fit(X);
X := scaler.Transform(X);
// --- сетка eps
var epsList := Arr(0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0);
var bestEps := -1.0;
var bestScore := -1e9;
Println('eps k max% noise% silhouette score');
Println('----------------------------------------------------------------------');
foreach var eps in epsList do
begin
var model := new DBSCAN(eps, 5);
model.Fit(X);
var labels := model.PredictLabels(X);
// --- статистика кластеров
var counts := new Dictionary<integer, integer>();
foreach var l in labels do
counts[l] := counts.Get(l, 0) + 1;
var n := X.RowCount;
var k := 0;
var maxCluster := 0;
var noise := counts.Get(-1, 0);
var sizes := new List<integer>;
foreach var kv in counts do
if kv.Key <> -1 then
begin
k += 1;
sizes.Add(kv.Value);
if kv.Value > maxCluster then
maxCluster := kv.Value;
end;
sizes.Sort;
sizes.Reverse;
var secondCluster :=
if sizes.Count >= 2 then sizes[1] else 0;
var maxFrac := maxCluster / n;
var noiseFrac := noise / n;
var secondFrac := secondCluster / n;
// --- silhouette
var sil := -1.0;
if k >= 2 then
sil := Metrics.SilhouetteScore(X, labels);
// --- нормализация silhouette (упрощённо)
var silNorm := (sil + 1) / 2; // [-1,1] → [0,1]
// --- штрафы (ключевая часть!)
var penalty :=
0.4 * maxFrac // доминирующий кластер
+ 0.3 * noiseFrac // шум
+ 0.3 * (1 - secondFrac);// слишком маленький второй кластер
// --- штраф за плохое k
if k < 2 then
penalty += 0.5;
// --- итоговый score
var score := silNorm - penalty;
if score > bestScore then
begin
bestScore := score;
bestEps := eps;
end;
Println($'{eps,4:F1} {k,4} {maxFrac,7:F2} {noiseFrac,8:F2} {sil,12:F4} {score,10:F4}');
end;
Println;
Println($'Выбран eps: {bestEps,0:F2}');
Println;
end.

View file

@ -3,11 +3,11 @@
begin begin
var ds := Datasets.Iris; var ds := Datasets.Iris;
var df := ds.Data; var df := ds.Data;
var X := df.ToMatrix(ds.Features); var X := df.ToMatrix(ds.Features);
var labels := df.EncodeLabels(ds.Target); var labels := df.EncodeLabels(ds.Target);
Plot.PairPlot(X, labels, ds.Features); Plot.PairPlot(X, labels, ds.Features);
Plot.Title('Iris: пары признаков'); Plot.Title := 'Iris: пары признаков';
end. end.

View file

@ -15,6 +15,7 @@ begin
target, target,
features, features,
new OneHotEncoder('renovation'), new OneHotEncoder('renovation'),
new StandardScaler,
new LinearRegression new LinearRegression
); );

View file

@ -9,7 +9,7 @@ begin
seed := 1 seed := 1
); );
Plot.Title('MakeMoons синтетический датасет'); Plot.Title :='MakeMoons синтетический датасет';
Plot.Points(X.Col(0), X.Col(1), LabelsToInts(y), size := 4); Plot.Points(X.Col(0), X.Col(1), LabelsToInts(y), size := 4);
end. end.

View file

@ -0,0 +1,20 @@
uses MLABC;
begin
var X: Matrix :=
[[1.0,1.0],[1.2,1.3],[1.5,1.2],[1.8,1.6],[2.0,1.9],[2.2,2.1],
[2.4,2.3],[2.6,2.5],[2.8,2.7],[3.0,3.0],[3.2,3.1],[3.4,3.3]];
var y: Vector :=
[0.0,0.0,0.0,0.0,0.0,0.0, 1.0,1.0,1.0,1.0,1.0,1.0];
var (Xtrain, Xtest, ytrain, ytest) :=
Validation.TrainTestSplit(X, y, 0.3, seed := 4);
var model := new LogisticRegression;
model.Fit(Xtrain, ytrain);
var pred := model.Predict(Xtest);
Println('Accuracy:', Metrics.Accuracy(ytest, pred):0:3);
end.

View file

@ -0,0 +1,21 @@
uses MLABC;
begin
var df := CsvLoader.Load('towns_russia.csv', inferCategorical := true);
df := df.Select(['city','population','lat','lon','region_name','federal_district']);
df.Schema.Println;
// Заполняем пропуски в числовых столбцах средним значением
var imputer := new Imputer('population', 'lat', 'lon');
df := imputer.FitTransform(df);
// Кодируем категориальные признаки
var encoder := new LabelEncoder('region_name');
df := encoder.FitTransform(df);
var encoder2 := new LabelEncoder('federal_district');
df := encoder2.FitTransform(df);
df.PrintPreview(6);
end.

View file

@ -0,0 +1,38 @@
uses MLABC;
begin
var df := CsvLoader.Load('towns_russia.csv',
inferCategorical := true);
df := df.Select([
'population',
'lat',
'lon',
'region_name',
'federal_district'
]);
// Препроцессинг
var imputer := new Imputer('population', 'lat', 'lon');
df := imputer.FitTransform(df);
var le1 := new LabelEncoder('region_name');
df := le1.FitTransform(df);
var le2 := new LabelEncoder('federal_district');
df := le2.FitTransform(df);
// Явно задаём признаки
var features := ['lat', 'lon', 'region_name', 'federal_district'];
var X := df.ToMatrix(features);
var y := df.ToVector('population');
// Модель
var model := new LinearRegression;
model.Fit(X, y);
var preds := model.Predict(X);
Println('RMSE:', Metrics.RMSE(y, preds):0:3);
end.

View file

@ -0,0 +1,31 @@
uses MLABC;
begin
var df := CsvLoader.Load('towns_russia.csv', inferCategorical := true);
df := df.Select(['population', 'lat', 'lon', 'region_name', 'federal_district']);
var imputer := new Imputer('population', 'lat', 'lon');
df := imputer.FitTransform(df);
var le1 := new LabelEncoder('region_name');
df := le1.FitTransform(df);
var le2 := new LabelEncoder('federal_district');
df := le2.FitTransform(df);
var features := ['lat', 'lon', 'region_name', 'federal_district'];
var X := df.ToMatrix(features);
var y := df.ToVector('population');
var scaler := new StandardScaler;
scaler.Fit(X);
var Xscaled := scaler.Transform(X);
var model := new LinearRegression;
model.Fit(Xscaled, y);
var preds := model.Predict(Xscaled);
Println('RMSE:', Metrics.RMSE(y, preds):0:3);
end.

View file

@ -0,0 +1,29 @@
uses MLABC;
begin
var ds := Datasets.MoscowHousing;
var df := ds.Data;
var features := ['rooms', 'area', 'kitchen_area', 'floor', 'floors_total', 'metro_minutes', 'renovation'];
var target := 'price';
var (trainDf, testDf) := df.TrainTestSplit(0.2, 42);
var pipe :=
DataPipeline.Build( // сборка pipeline: target + features + шаги
target, // целевая переменная
features, // список признаков
new OneHotEncoder('renovation'), // DataFrame-уровень: кодирование категории
// Внутри - преобразование .ToMatrix .ToVector
new StandardScaler, // Matrix-уровень: масштабирование признаков
new LinearRegression // модель (всегда последний шаг)
);
pipe.Fit(trainDf); // обучение: последовательно encoding → scaling → model
var pred := pipe.Predict(testDf); // предсказание: те же преобразования
var y := testDf.ToVector(target);
Println('R²:', Metrics.R2(y, pred):0:3);
end.

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
3.11.1.3783 3.11.1.3784

View file

@ -1 +1 @@
!define VERSION '3.11.1.3783' !define VERSION '3.11.1.3784'

View file

@ -238,7 +238,9 @@ type
/// ///
/// Пример: /// Пример:
/// var ds := Datasets.Iris; /// var ds := Datasets.Iris;
/// var (X,y) := ds.ToXYInt; /// var df := ds.Data;
/// var X := df.ToMatrix(ds.Features);
/// var y := df.EncodeLabels(ds.Target);
static function Iris: Dataset; static function Iris: Dataset;
/// Датасет цен на квартиры (задача регрессии) /// Датасет цен на квартиры (задача регрессии)
@ -648,7 +650,7 @@ begin
begin begin
var row := idx[i]; var row := idx[i];
y[row] := -1; y[row] := rnd.Next(centers);
for var j := 0 to nFeatures - 1 do for var j := 0 to nFeatures - 1 do
X[row, j] := (rnd.NextDouble * 2 - 1) * centerBox; X[row, j] := (rnd.NextDouble * 2 - 1) * centerBox;

View file

@ -1276,6 +1276,9 @@ type
/// Создаёт глубокую копию модели. /// Создаёт глубокую копию модели.
function Clone: IModel; function Clone: IModel;
/// Количество кластеров
function ClustersCount: integer;
/// Количество кластеров. /// Количество кластеров.
property NClusters: integer read fNClusters; property NClusters: integer read fNClusters;
@ -1345,7 +1348,9 @@ type
function Clone: IModel; function Clone: IModel;
property Labels: array of integer read fLabels; property Labels: array of integer read fLabels;
property ClusterCount: integer read fClusterCount;
/// Количество кластеров, найденное моделью
function ClustersCount: integer;
function Name: string := Self.GetType.Name; function Name: string := Self.GetType.Name;
@ -1972,6 +1977,8 @@ const
'Model (type: {0}) is not an unsupervised model'; 'Model (type: {0}) is not an unsupervised model';
ER_DBSCAN_PREDICT_ONLY_TRAIN_DATA = ER_DBSCAN_PREDICT_ONLY_TRAIN_DATA =
'DBSCAN: Predict поддерживается только для обучающей выборки!!DBSCAN: Predict is only supported for training data'; 'DBSCAN: Predict поддерживается только для обучающей выборки!!DBSCAN: Predict is only supported for training data';
ER_MODEL_NOT_FITTED =
'Модель "{0}" не обучена. Сначала вызовите Fit()|Model "{0}" is not fitted. Call Fit() first';
{$endregion ErrConstants} {$endregion ErrConstants}
@ -7168,6 +7175,14 @@ begin
Result := km; Result := km;
end; end;
function KMeans.ClustersCount: integer;
begin
if not fFitted then
ArgumentError(ER_MODEL_NOT_FITTED, 'KMeans');
Result := fCenters.RowCount;
end;
//----------------------------- //-----------------------------
// DBSCAN // DBSCAN
//----------------------------- //-----------------------------
@ -7343,6 +7358,8 @@ begin
Result := m; Result := m;
end; end;
function DBSCAN.ClustersCount: integer := fClusterCount;
//----------------------------- //-----------------------------
// Pipeline // Pipeline
//----------------------------- //-----------------------------