数据科学与机器学习(第 20 部分):算法交易洞察,MQL5 中 LDA 与 PCA 之间的较量
—— 你拥有的越多,看到的就越少!
什么是线性判别分析(LDA)?
LDA 是一种监督泛化机器学习算法,旨在找到一个特征的线性组合,以最好地分离数据集中的类。
与主成分分析(PCA)一样,它是一种降维算法。这两种算法是降维的常见选择,在本文中,我们将对它们进行比较,并观察每种算法在什么情况下最有效。我们在本系列的前几篇文章中已经讨论过 PCA。让我们从观察 LDA 算法的全部内容开始,因为我们将主要讨论它,最后我们将在一个简单的数据集和策略测试器上比较它们的性能,确保你坚持到最后的数据科学内容。

目标/理论:
线性判别分析(LDA)的目标包括:
- 最大化类的可分离性:LDA 旨在找到特征的线性组合,以最大化数据中类之间的分离。通过将数据投影到这些判别维度上,LDA 有助于增加不同类之间的区分度,从而使分类更有效。
- 降低维数:LDA 通过将数据投影到较低维的子空间上来降低特征空间的维数。这种降维是在尽可能多地保留类判别信息的同时实现的。减少的特征空间可以导致更简单的模型、更快的计算和更好的泛化性能。
- 最小化类内可变性:LDA 旨在通过确保属于同一类的数据点在转换空间中紧密聚集在一起,来最小化类内分散或可变性。通过减少类内可变性,LDA 有助于增强类之间的可分离性,提高分类模型的鲁棒性。
- 最大化类间可变性:相反,LDA 试图通过最大化变换空间中类均值之间的距离来最大化类间散布或可变性。通过最大化类间可变性同时最小化类内可变性,LDA实现了更好的类间区分,从而获得了更准确的分类结果。
- 处理多类分类:LDA 可以处理有两个以上类的多类分类问题。通过同时考虑所有类之间的关系,LDA 找到了一个最佳分离所有类的公共子空间,从而在高维特征空间中实现了有效的分类边界。
假设:
线性判别分析做出了几个假设,看看成千上万的蛋白质数据集,它假设:
- 测量结果彼此独立
- 数据在特征内呈正态分布
- 数据集中的类具有相同的协方差矩阵
线性判别算法的步骤
01:计算类内散度矩阵 (SW)
计算每个类的散度矩阵。
matrix SW, SB; //within and between scatter matrices SW.Init(num_features, num_features); SB.Init(num_features, num_features); for (ulong i=0; i<num_classes; i++) { matrix class_samples = {}; for (ulong j=0, count=0; j<x.Rows(); j++) { if (y[j] == classes[i]) //Collect a matrix for samples belonging to a particular class { count++; class_samples.Resize(count, num_features); class_samples.Row(x.Row(j), count-1); } } matrix diff = Base::subtract(class_samples, class_means.Row(i)); //Each row subtracted to the mean if (diff.Rows()==0 && diff.Cols()==0) //if the subtracted matrix is zero stop the program for possible bugs or errors { DebugBreak(); return x_centered; } SW += diff.Transpose().MatMul(diff); //Find within scatter matrix vector mean_diff = class_means.Row(i) - x_centered.Mean(0); SB += class_samples.Rows() * mean_diff.Outer(mean_diff); //compute between scatter matrix }
将这些单独的散度矩阵相加以获得类内散度矩阵。
02:计算类间散度矩阵 (SB)
计算每个类的平均向量。
matrix SW, SB; //within and between scatter matrices SW.Init(num_features, num_features); SB.Init(num_features, num_features); for (ulong i=0; i<num_classes; i++) { matrix class_samples = {}; for (ulong j=0, count=0; j<x.Rows(); j++) { if (y[j] == classes[i]) //Collect a matrix for samples belonging to a particular class { count++; class_samples.Resize(count, num_features); class_samples.Row(x.Row(j), count-1); } } matrix diff = Base::subtract(class_samples, class_means.Row(i)); //Each row subtracted to the mean if (diff.Rows()==0 && diff.Cols()==0) //if the subtracted matrix is zero stop the program for possible bugs or errors { DebugBreak(); return x_centered; } SW += diff.Transpose().MatMul(diff); //Find within scatter matrix vector mean_diff = class_means.Row(i) - x_centered.Mean(0); SB += class_samples.Rows() * mean_diff.Outer(mean_diff); //compute between scatter matrix }
计算类间散度矩阵。
SB += class_samples.Rows() * mean_diff.Outer(mean_diff); //compute between scatter matrix 03:计算特征值和特征向量:
解决涉及 SW 和 SB 的广义特征值问题,获得特征值及其对应的特征向量。
matrix eigen_vectors; vector eigen_values; matrix SBSW = SW.Inv().MatMul(SB); SBSW += this.m_regparam * MatrixExtend::eye((uint)SBSW.Rows()); if (!SBSW.Eig(eigen_vectors, eigen_values)) { Print("%s Failed to calculate eigen values and vectors Err=%d",__FUNCTION__,GetLastError()); DebugBreak(); matrix empty = {}; return empty; }
选择判别特征
按降序对特征值进行排序。
vector args = MatrixExtend::ArgSort(eigen_values);
MatrixExtend::Reverse(args);
eigen_values = Base::Sort(eigen_values, args);
eigen_vectors = Base::Sort(eigen_vectors, args); 选择前 k 个特征向量以形成变换矩阵。
this.m_components = extract_components(eigen_values); 由于线性判别分析和主成分分析都具有类似的降维目的,我们可以使用类似的技术来提取方差和碎石图, 就像我们在 PCA 文章中使用的一样。
我们可以扩展 LDA 类,使其能够在默认情况下选择 NULL 数量的组件时为自己提取组件。
if (this.m_components == NULL) this.m_components = extract_components(eigen_values); else //plot the scree plot extract_components(eigen_values);
将数据投影到新的特征空间
将原始数据乘以所选特征向量,以获得新的特征空间。
this.projection_matrix = Base::Slice(eigen_vectors, this.m_components); return x_centered.MatMul(projection_matrix.Transpose());
所有这些代码都在函数 fit_transform 内部执行,该函数负责训练和准备线性判别分析算法。为了使我们的类能够处理新的/看不见的数据,我们需要添加进一步转换的函数。
matrix CLDA::transform(const matrix &x) { if (this.projection_matrix.Rows() == 0) { printf("%s fit_transform method must be called befor transform",__FUNCTION__); matrix empty = {}; return empty; } matrix x_centered = Base::subtract(x, this.mean); return x_centered.MatMul(this.projection_matrix.Transpose()); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ vector CLDA::transform(const vector &x) { matrix m = MatrixExtend::VectorToMatrix(x, this.num_features); if (m.Rows()==0) { vector empty={}; return empty; //return nothing since there is a failure in converting vector to matrix } m = transform(m); return MatrixExtend::MatrixToVector(m); }
LDA 类概述
我们的整体 LDA 类现在如下所示:
enum lda_criterion //selecting best components criteria selection { CRITERION_VARIANCE, CRITERION_KAISER, CRITERION_SCREE_PLOT }; class CLDA { CPlots plt; protected: uint m_components; lda_criterion m_criterion; matrix projection_matrix; ulong num_features; double m_regparam; vector mean; uint CLDA::extract_components(vector &eigen_values, double threshold=0.95); public: CLDA(uint k=NULL, lda_criterion CRITERION_=CRITERION_SCREE_PLOT, double reg_param =1e-6); ~CLDA(void); matrix fit_transform(const matrix &x, const vector &y); matrix transform(const matrix &x); vector transform(const vector &x); };
表示正则化参数的 reg_param 不太重要,因为它只帮助正则化 SW 和 SB 矩阵,使特征值和向量计算不易出错。
SW += this.m_regparam * MatrixExtend::eye((uint)num_features); SB += this.m_regparam * MatrixExtend::eye((uint)num_features);
对数据集使用线性判别分析
让我们将我们的 LDA 类应用到流行的 Iris 数据集并观察它的作用。
string headers; matrix data = MatrixExtend::ReadCsv("iris.csv",headers); //Read csv
请记住,这是一种监督机器学习技术,这意味着我们必须分别收集独立变量和目标变量,并将其提供给模型。
matrix x; vector y; MatrixExtend::XandYSplitMatrices(data, x, y);
#include <MALE5\Dimensionality Reduction\LDA.mqh> CLDA *lda; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- string headers; matrix data = MatrixExtend::ReadCsv("iris.csv",headers); //Read csv matrix x; vector y; MatrixExtend::XandYSplitMatrices(data, x, y); Print("Original X\n",x); lda = new CLDA(); matrix transformed_x = lda.fit_transform(x, y); Print("Transformed X\n",transformed_x); return(INIT_SUCCEEDED); }
输出
HH 0 10:18:21.210 LDA Test (EURUSD,H1) Original X IQ 0 10:18:21.210 LDA Test (EURUSD,H1) [[5.1,3.5,1.4,0.2] HF 0 10:18:21.210 LDA Test (EURUSD,H1) [4.9,3,1.4,0.2] ... ... ES 0 10:18:21.211 LDA Test (EURUSD,H1) [6.5,3,5.2,2] ML 0 10:18:21.211 LDA Test (EURUSD,H1) [6.2,3.4,5.4,2.3] EI 0 10:18:21.211 LDA Test (EURUSD,H1) [5.9,3,5.1,1.8]] IL 0 10:18:21.243 LDA Test (EURUSD,H1) DD 0 10:18:21.243 LDA Test (EURUSD,H1) Transformed X DM 0 10:18:21.243 LDA Test (EURUSD,H1) [[-1.058063221542643,2.676898315513957] JD 0 10:18:21.243 LDA Test (EURUSD,H1) [-1.060778666796316,2.532150351483708] DM 0 10:18:21.243 LDA Test (EURUSD,H1) [-0.9139922886488467,2.777963946569435] ... ... IK 0 10:18:21.244 LDA Test (EURUSD,H1) [1.527279343196588,-2.300606221030168] QN 0 10:18:21.244 LDA Test (EURUSD,H1) [0.9614855249192527,-1.439559895222919] EF 0 10:18:21.244 LDA Test (EURUSD,H1) [0.6420061576026481,-2.511057690832021…]
图表上还显示了美丽的碎石图:

从碎石图中我们可以看到最佳组件数量位于拐点处,即 2,这正是我们的类返回的组件数量,太棒了。现在,让我们可视化返回的组件,看看它们是否与众不同,因为我们都知道,降维的目的是得到解释原始数据中所有方差的最小数量的组件,只需将我们的数据简化即可。
我决定将 EA 中的组件保存到 csv 文件中,然后在此笔记本上使用 python 绘制它们 https://www.kaggle.com/code/omegajoctan/lda-vs-pca-components-iris-data
MatrixExtend::WriteCsv("iris-data lda-components.csv",transformed_x); 
组件看起来很清楚,表明实现成功。现在让我们看看 PCA 组件是什么样子的:

这两种方法都很好地分离了数据,我们不能通过查看图表来判断哪种方法表现更好,让我们在相同的参数下对相同的数据集使用相同的模型,并观察两种模型在训练和样本外测试中的准确性。
训练测试期间的 LDA 与 PCA
对分别从 LDA 和 PCA 算法获得的两个单独数据使用具有相同参数的决策树模型。
#include <MALE5\Dimensionality Reduction\LDA.mqh> #include <MALE5\Dimensionality Reduction\PCA.mqh> #include <MALE5\Decision Tree\tree.mqh> #include <MALE5\Metrics.mqh> CLDA *lda; CPCA *pca; CDecisionTreeClassifier *classifier_tree; input int random_state_ = 42; input double training_sample_size = 0.7; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- string headers; matrix data = MatrixExtend::ReadCsv("iris.csv",headers); //Read csv Print("<<<<<<<< LDA Applied >>>>>>>>>"); matrix x_train, x_test; vector y_train, y_test; MatrixExtend::TrainTestSplitMatrices(data,x_train,y_train,x_test,y_test,training_sample_size,random_state_); lda = new CLDA(NULL); matrix x_transformed = lda.fit_transform(x_train, y_train); //Transform the training data classifier_tree = new CDecisionTreeClassifier(); classifier_tree.fit(x_transformed, y_train); //Train the model using the transformed data vector preds = classifier_tree.predict(x_transformed); //Make predictions using the transformed data Print("Train accuracy: ",Metrics::confusion_matrix(y_train, preds).accuracy); x_transformed = lda.transform(x_test); preds = classifier_tree.predict(x_transformed); Print("Test accuracy: ",Metrics::confusion_matrix(y_test, preds).accuracy); delete (classifier_tree); delete (lda); //--- Print("<<<<<<<< PCA Applied >>>>>>>>>"); pca = new CPCA(NULL); x_transformed = pca.fit_transform(x_train); classifier_tree = new CDecisionTreeClassifier(); classifier_tree.fit(x_transformed, y_train); preds = classifier_tree.predict(x_transformed); //Make predictions using the transformed data Print("Train accuracy: ",Metrics::confusion_matrix(y_train, preds).accuracy); x_transformed = pca.transform(x_test); preds = classifier_tree.predict(x_transformed); Print("Test accuracy: ",Metrics::confusion_matrix(y_test, preds).accuracy); delete (classifier_tree); delete(pca); return(INIT_SUCCEEDED); }
LDA 结果:
GM 0 18:23:18.285 LDA Test (EURUSD,H1) <<<<<<<< LDA Applied >>>>>>>>> MR 0 18:23:18.302 LDA Test (EURUSD,H1) JP 0 18:23:18.344 LDA Test (EURUSD,H1) Confusion Matrix FK 0 18:23:18.344 LDA Test (EURUSD,H1) [[39,0,0] CR 0 18:23:18.344 LDA Test (EURUSD,H1) [0,30,5] QF 0 18:23:18.344 LDA Test (EURUSD,H1) [0,2,29]] IS 0 18:23:18.344 LDA Test (EURUSD,H1) OM 0 18:23:18.344 LDA Test (EURUSD,H1) Classification Report KF 0 18:23:18.344 LDA Test (EURUSD,H1) QQ 0 18:23:18.344 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support FF 0 18:23:18.344 LDA Test (EURUSD,H1) 1.0 1.00 1.00 1.00 1.00 39.0 GI 0 18:23:18.344 LDA Test (EURUSD,H1) 2.0 0.94 0.86 0.97 0.90 35.0 ML 0 18:23:18.344 LDA Test (EURUSD,H1) 3.0 0.85 0.94 0.93 0.89 31.0 OS 0 18:23:18.344 LDA Test (EURUSD,H1) FN 0 18:23:18.344 LDA Test (EURUSD,H1) Accuracy 0.93 JO 0 18:23:18.344 LDA Test (EURUSD,H1) Average 0.93 0.93 0.97 0.93 105.0 KJ 0 18:23:18.344 LDA Test (EURUSD,H1) W Avg 0.94 0.93 0.97 0.93 105.0 EQ 0 18:23:18.344 LDA Test (EURUSD,H1) Train accuracy: 0.933 JH 0 18:23:18.344 LDA Test (EURUSD,H1) Confusion Matrix LS 0 18:23:18.344 LDA Test (EURUSD,H1) [[11,0,0] IJ 0 18:23:18.344 LDA Test (EURUSD,H1) [0,13,2] RN 0 18:23:18.344 LDA Test (EURUSD,H1) [0,1,18]] IK 0 18:23:18.344 LDA Test (EURUSD,H1) OE 0 18:23:18.344 LDA Test (EURUSD,H1) Classification Report KN 0 18:23:18.344 LDA Test (EURUSD,H1) QI 0 18:23:18.344 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support LN 0 18:23:18.344 LDA Test (EURUSD,H1) 1.0 1.00 1.00 1.00 1.00 11.0 CQ 0 18:23:18.344 LDA Test (EURUSD,H1) 2.0 0.93 0.87 0.97 0.90 15.0 QD 0 18:23:18.344 LDA Test (EURUSD,H1) 3.0 0.90 0.95 0.92 0.92 19.0 OK 0 18:23:18.344 LDA Test (EURUSD,H1) FF 0 18:23:18.344 LDA Test (EURUSD,H1) Accuracy 0.93 GD 0 18:23:18.344 LDA Test (EURUSD,H1) Average 0.94 0.94 0.96 0.94 45.0 HQ 0 18:23:18.344 LDA Test (EURUSD,H1) W Avg 0.93 0.93 0.96 0.93 45.0 CF 0 18:23:18.344 LDA Test (EURUSD,H1) Test accuracy: 0.933
LDA 产生了一个稳定的模型,在训练和测试中准确率均达到 93%,让我们来看看 PCA。
PCA 结果:
MM 0 18:26:40.994 LDA Test (EURUSD,H1) <<<<<<<< PCA Applied >>>>>>>>>
LS 0 18:26:41.071 LDA Test (EURUSD,H1) Confusion Matrix
LJ 0 18:26:41.071 LDA Test (EURUSD,H1) [[39,0,0]
ER 0 18:26:41.071 LDA Test (EURUSD,H1) [0,34,1]
OE 0 18:26:41.071 LDA Test (EURUSD,H1) [0,4,27]]
KD 0 18:26:41.071 LDA Test (EURUSD,H1)
IL 0 18:26:41.071 LDA Test (EURUSD,H1) Classification Report
MG 0 18:26:41.071 LDA Test (EURUSD,H1)
CR 0 18:26:41.071 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support
DE 0 18:26:41.071 LDA Test (EURUSD,H1) 1.0 1.00 1.00 1.00 1.00 39.0
EH 0 18:26:41.071 LDA Test (EURUSD,H1) 2.0 0.89 0.97 0.94 0.93 35.0
KL 0 18:26:41.071 LDA Test (EURUSD,H1) 3.0 0.96 0.87 0.99 0.92 31.0
ID 0 18:26:41.071 LDA Test (EURUSD,H1)
NO 0 18:26:41.071 LDA Test (EURUSD,H1) Accuracy 0.95
CH 0 18:26:41.071 LDA Test (EURUSD,H1) Average 0.95 0.95 0.98 0.95 105.0
KK 0 18:26:41.071 LDA Test (EURUSD,H1) W Avg 0.95 0.95 0.98 0.95 105.0
NR 0 18:26:41.071 LDA Test (EURUSD,H1) Train accuracy: 0.952
LK 0 18:26:41.071 LDA Test (EURUSD,H1) Confusion Matrix
FR 0 18:26:41.071 LDA Test (EURUSD,H1) [[11,0,0]
FJ 0 18:26:41.072 LDA Test (EURUSD,H1) [0,14,1]
MM 0 18:26:41.072 LDA Test (EURUSD,H1) [0,3,16]]
NL 0 18:26:41.072 LDA Test (EURUSD,H1)
HD 0 18:26:41.072 LDA Test (EURUSD,H1) Classification Report
LO 0 18:26:41.072 LDA Test (EURUSD,H1)
FJ 0 18:26:41.072 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support
KM 0 18:26:41.072 LDA Test (EURUSD,H1) 1.0 1.00 1.00 1.00 1.00 11.0
EP 0 18:26:41.072 LDA Test (EURUSD,H1) 2.0 0.82 0.93 0.90 0.88 15.0
HD 0 18:26:41.072 LDA Test (EURUSD,H1) 3.0 0.94 0.84 0.96 0.89 19.0
HL 0 18:26:41.072 LDA Test (EURUSD,H1)
OG 0 18:26:41.072 LDA Test (EURUSD,H1) Accuracy 0.91
PS 0 18:26:41.072 LDA Test (EURUSD,H1) Average 0.92 0.93 0.95 0.92 45.0
IP 0 18:26:41.072 LDA Test (EURUSD,H1) W Avg 0.92 0.91 0.95 0.91 45.0
PE 0 18:26:41.072 LDA Test (EURUSD,H1) Test accuracy: 0.911 PCA 给出了更准确的模型,训练准确率达 95%,测试准确率达 91.1%。
线性判别分析(LDA)的优点
线性判别分析 (LDA) 具有多种优点,使其成为分类和降维任务中广泛使用的技术:
- 有助于降维。LDA 通过将原始特征转换为低维空间来降低特征空间的维数。这种降低可以简化模型,减轻维数灾难,并提高计算效率。
- 保留类歧视信息。LDA 的目的是寻找最大化类间分离的特征的线性组合。通过关注区分类别的判别信息,LDA 确保转换后的特征保留重要的类相关模式和结构。
- 它一次性提取特征并进行分类。LDA 同时进行特征提取和分类。它学习了原始特征的转换,最大限度地提高了类的可分离性,使其天生适合分类任务。这种集成方法可以产生更高效、更可解释的模型。
- 它对于过度拟合具有很强的鲁棒性。与其他分类算法相比,LDA不太容易过拟合,特别是在样本数量相对于特征数量较少的情况下。通过降低特征空间的维数并专注于最具辨别力的特征,LDA 可以很好地推广到看不见的数据。
- 处理多类分类。LDA 可以自然扩展到具有两个以上类别的多类分类问题。它同时考虑所有类之间的关系,从而在高维特征空间中产生有效的分离边界。
- 计算效率高。LDA 涉及解决特征值问题和矩阵乘法,这些方法计算效率高,并且可以使用内置的 MQL5 方法实现。这使得 LDA 适用于大规模数据集和实时应用程序。
- 容易解释。从 LDA 获得的变换特征是可解释的,可以对其进行分析以了解数据中的潜在模式。LDA 学习到的特征的线性组合可以提供对驱动分类决策的判别因素的见解。
- 它的假设经常会得到满足。LDA 假设数据在每个类内正态分布,协方差矩阵相等。虽然这些假设在实践中可能并不总是成立,但即使假设得到大致满足,LDA 仍然可以表现良好。
虽然线性判别分析(LDA)有这些优点,但它也有一些局限性和缺点:
线性判别分析(LDA)的缺点
- 它假设特征内呈高斯分布。LDA 假设每个类中的数据正态分布,协方差矩阵相等。如果违反了这一假设,LDA 可能会产生次优结果,甚至无法收敛。在实际应用中,真实世界的数据可能呈现非正态分布,这可能会限制 LDA 的有效性。
- 可能对异常值敏感。LDA对异常值很敏感,特别是在从有限数据中估计协方差矩阵时。异常值会显著影响协方差矩阵的估计和由此产生的判别方向,可能导致分类结果有偏差或不可靠。
- 在建模非线性关系时灵活性较低。因为它假设类之间的决策边界是线性的。如果特征和类之间的潜在关系是非线性的,LDA 可能无法有效地捕获这些复杂的模式。在这种情况下,非线性降维技术或非线性分类器可能更合适。
- 维数诅咒是真实存在的。当特征数量远大于样本数量时,LDA 可能会遭受维数灾难。在高维特征空间中,协方差矩阵的估计变得不那么可靠,判别方向可能无法有效地捕捉数据的真实底层结构。
- 不平衡类的性能有限。LDA 在处理不平衡的类分布时可能表现不佳,其中一个或多个类的样本明显少于其他类。在这种情况下,样本较少的类在类均值和协方差矩阵的估计中可能表现不佳,导致分类结果有偏差。
- 几乎无法处理非数字数据。LDA 通常对数值数据进行操作,它可能不直接适用于包含分类或非数值变量的数据集。可能需要预处理步骤,如对分类变量进行编码或将非数字数据转换为数字表示,这可能会带来额外的复杂性和潜在的信息丢失。
交易环境中的 LDA 与 PCA
为了在交易环境中使用这些降维技术,我们必须创建一个函数来训练和测试纸上模型,然后我们可以使用训练好的模型对策略测试器进行预测,这将有助于我们分析它们的表现。
我们将使用数据集中的 5 个指标,我们希望使用这两种方法缩小:
int OnInit() { //--- Trend following indicators indicator_handle[0] = iAMA(Symbol(), PERIOD_CURRENT, 9 , 2 , 30, 0, PRICE_OPEN); indicator_handle[1] = iADX(Symbol(), PERIOD_CURRENT, 14); indicator_handle[2] = iADXWilder(Symbol(), PERIOD_CURRENT, 14); indicator_handle[3] = iBands(Symbol(), PERIOD_CURRENT, 20, 0, 2.0, PRICE_OPEN); indicator_handle[4] = iDEMA(Symbol(), PERIOD_CURRENT, 14, 0, PRICE_OPEN); }
void TrainTest() { vector buffer = {}; for (int i=0; i<ArraySize(indicator_handle); i++) { buffer.CopyIndicatorBuffer(indicator_handle[i], 0, 0, bars); //copy indicator buffer dataset.Col(buffer, i); //add the indicator buffer values to the dataset matrix } //--- vector y(bars); MqlRates rates[]; CopyRates(Symbol(), PERIOD_CURRENT,0,bars, rates); for (int i=0; i<bars; i++) //Creating the target variable { if (rates[i].close > rates[i].open) //if bullish candle assign 1 to the y variable else assign the 0 class y[i] = 1; else y[0] = 0; } //--- dataset.Col(y, dataset.Cols()-1); //add the y variable to the last column //--- matrix x_train, x_test; vector y_train, y_test; MatrixExtend::TrainTestSplitMatrices(dataset,x_train,y_train,x_test,y_test,training_sample_size,random_state_); matrix x_transformed = {}; switch(dimension_reduction) { case LDA: lda = new CLDA(NULL); x_transformed = lda.fit_transform(x_train, y_train); //Transform the training data break; case PCA: pca = new CPCA(NULL); x_transformed = pca.fit_transform(x_train); break; } classifier_tree = new CDecisionTreeClassifier(); classifier_tree.fit(x_transformed, y_train); //Train the model using the transformed data vector preds = classifier_tree.predict(x_transformed); //Make predictions using the transformed data Print("Train accuracy: ",Metrics::confusion_matrix(y_train, preds).accuracy); switch(dimension_reduction) { case LDA: x_transformed = lda.transform(x_test); //Transform the testing data break; case PCA: x_transformed = pca.transform(x_test); break; } preds = classifier_tree.predict(x_transformed); Print("Test accuracy: ",Metrics::confusion_matrix(y_test, preds).accuracy); }
一旦数据经过训练,就需要对其进行测试。以下是从 LDA 开始的两种方法的结果:
JK 0 01:00:24.440 LDA Test (EURUSD,H1) GK 0 01:00:37.442 LDA Test (EURUSD,H1) Confusion Matrix QR 0 01:00:37.442 LDA Test (EURUSD,H1) [[60,266] FF 0 01:00:37.442 LDA Test (EURUSD,H1) [46,328]] DR 0 01:00:37.442 LDA Test (EURUSD,H1) RN 0 01:00:37.442 LDA Test (EURUSD,H1) Classification Report FE 0 01:00:37.442 LDA Test (EURUSD,H1) LP 0 01:00:37.442 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support HD 0 01:00:37.442 LDA Test (EURUSD,H1) 0.0 0.57 0.18 0.88 0.28 326.0 FI 0 01:00:37.442 LDA Test (EURUSD,H1) 1.0 0.55 0.88 0.18 0.68 374.0 RM 0 01:00:37.442 LDA Test (EURUSD,H1) QH 0 01:00:37.442 LDA Test (EURUSD,H1) Accuracy 0.55 KQ 0 01:00:37.442 LDA Test (EURUSD,H1) Average 0.56 0.53 0.53 0.48 700.0 HP 0 01:00:37.442 LDA Test (EURUSD,H1) W Avg 0.56 0.55 0.51 0.49 700.0 KK 0 01:00:37.442 LDA Test (EURUSD,H1) Train accuracy: 0.554 DR 0 01:00:37.443 LDA Test (EURUSD,H1) Confusion Matrix CD 0 01:00:37.443 LDA Test (EURUSD,H1) [[20,126] LO 0 01:00:37.443 LDA Test (EURUSD,H1) [12,142]] OK 0 01:00:37.443 LDA Test (EURUSD,H1) ME 0 01:00:37.443 LDA Test (EURUSD,H1) Classification Report QN 0 01:00:37.443 LDA Test (EURUSD,H1) GI 0 01:00:37.443 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support JM 0 01:00:37.443 LDA Test (EURUSD,H1) 0.0 0.62 0.14 0.92 0.22 146.0 KR 0 01:00:37.443 LDA Test (EURUSD,H1) 1.0 0.53 0.92 0.14 0.67 154.0 MF 0 01:00:37.443 LDA Test (EURUSD,H1) MQ 0 01:00:37.443 LDA Test (EURUSD,H1) Accuracy 0.54 MJ 0 01:00:37.443 LDA Test (EURUSD,H1) Average 0.58 0.53 0.53 0.45 300.0 OI 0 01:00:37.443 LDA Test (EURUSD,H1) W Avg 0.58 0.54 0.52 0.45 300.0 QP 0 01:00:37.443 LDA Test (EURUSD,H1) Test accuracy: 0.54
PCA 在训练期间表现更好,但在测试期间略有下降:
GE 0 01:01:57.202 LDA Test (EURUSD,H1) MS 0 01:01:57.202 LDA Test (EURUSD,H1) Classification Report IH 0 01:01:57.202 LDA Test (EURUSD,H1) OS 0 01:01:57.202 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support KG 0 01:01:57.202 LDA Test (EURUSD,H1) 0.0 0.62 0.28 0.85 0.39 326.0 GL 0 01:01:57.202 LDA Test (EURUSD,H1) 1.0 0.58 0.85 0.28 0.69 374.0 MP 0 01:01:57.202 LDA Test (EURUSD,H1) JK 0 01:01:57.202 LDA Test (EURUSD,H1) Accuracy 0.59 HL 0 01:01:57.202 LDA Test (EURUSD,H1) Average 0.60 0.57 0.57 0.54 700.0 CG 0 01:01:57.202 LDA Test (EURUSD,H1) W Avg 0.60 0.59 0.55 0.55 700.0 EF 0 01:01:57.202 LDA Test (EURUSD,H1) Train accuracy: 0.586 HO 0 01:01:57.202 LDA Test (EURUSD,H1) Confusion Matrix GG 0 01:01:57.202 LDA Test (EURUSD,H1) [[26,120] GJ 0 01:01:57.202 LDA Test (EURUSD,H1) [29,125]] KN 0 01:01:57.202 LDA Test (EURUSD,H1) QJ 0 01:01:57.202 LDA Test (EURUSD,H1) Classification Report MQ 0 01:01:57.202 LDA Test (EURUSD,H1) CL 0 01:01:57.202 LDA Test (EURUSD,H1) _ Precision Recall Specificity F1 score Support QP 0 01:01:57.202 LDA Test (EURUSD,H1) 0.0 0.47 0.18 0.81 0.26 146.0 GE 0 01:01:57.202 LDA Test (EURUSD,H1) 1.0 0.51 0.81 0.18 0.63 154.0 QI 0 01:01:57.202 LDA Test (EURUSD,H1) MD 0 01:01:57.202 LDA Test (EURUSD,H1) Accuracy 0.50 RE 0 01:01:57.202 LDA Test (EURUSD,H1) Average 0.49 0.49 0.49 0.44 300.0 IL 0 01:01:57.202 LDA Test (EURUSD,H1) W Avg 0.49 0.50 0.49 0.45 300.0 PP 0 01:01:57.202 LDA Test (EURUSD,H1) Test accuracy: 0.503
最后,我们可以根据决策树模型提供的信号创建一个简单的交易策略。
void OnTick() { //--- if (!train_once) //call the function to train the model once on the program lifetime { TrainTest(); train_once = true; } //--- vector inputs(indicator_handle.Size()); vector buffer; for (uint i=0; i<indicator_handle.Size(); i++) { buffer.CopyIndicatorBuffer(indicator_handle[i], 0, 0, 1); //copy the current indicator value inputs[i] = buffer[0]; //add its value to the inputs vector } //--- SymbolInfoTick(Symbol(), ticks); if (isnewBar(PERIOD_CURRENT)) // We want to trade on the bar opening { vector transformed_inputs = {}; switch(dimension_reduction) //transform every new data to fit the dimensions selected during training { case LDA: transformed_inputs = lda.transform(inputs); //Transform the new data break; case PCA: transformed_inputs = pca.transform(inputs); break; } int signal = (int)classifier_tree.predict(transformed_inputs); double min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); SymbolInfoTick(Symbol(), ticks); if (signal == -1) { if (!PosExists(MAGICNUMBER, POSITION_TYPE_SELL)) // If a sell trade doesnt exist m_trade.Sell(min_lot, Symbol(), ticks.bid, ticks.bid+stoploss*Point(), ticks.bid - takeprofit*Point()); } else { if (!PosExists(MAGICNUMBER, POSITION_TYPE_BUY)) // If a buy trade doesnt exist m_trade.Buy(min_lot, Symbol(), ticks.ask, ticks.ask-stoploss*Point(), ticks.ask + takeprofit*Point()); } } }
我从 2023 年 1 月到 2024 年 2 月在开盘价模式下进行了测试,两种方法都应用于简单策略:
线性判别分析(LDA)


主成分分析(PCA)测试:


它们的表现几乎相同,LDA 的损失比 PCA 多 8 美元,虽然从数据科学家的角度来看,策略测试器是每个 MQL5 交易员都会关注的东西,但它与这些降维技术的相关性较小,因为它们的主要工作是简化变量,尤其是在处理大数据时。我还必须解释一下,在策略测试器上运行此 EA 时,我遇到了一些计算不一致的问题,这些问题是由矩阵和向量方法中未探索的错误引起的,如果您在此过程中遇到错误和障碍,请多次运行该程序,直到获得有意义的结果。
如果您一直在阅读本系列文章,您可能会问自己为什么我们没有像在前一篇文章中那样缩放从这两种技术获得的转换数据。
PCA 或 LDA 的数据是否需要针对机器学习模型进行归一化,取决于数据集的具体特征、使用的算法和目标。以下是一些需要考虑的事项:
- PCA 变换;这两个操作对原始特征的协方差矩阵进行运算,并找到捕获数据中最大方差的正交分量(主分量)。从这两种方法获得的转换数据由这些主成分组成。
- PCA 或 LDA 之前的归一化;通常的做法是在执行 PCA 或 LDA 之前对原始特征进行归一化,特别是如果特征具有不同的尺度或单位。归一化确保所有特征对协方差矩阵的贡献相等,并防止具有较大尺度的特征主导主成分。
- PCA 或 LDA 后的归一化;是否需要对 PCA 的转换数据进行归一化取决于机器学习算法的具体要求和转换特征的特征。一些算法,如逻辑回归或 k 近邻,对特征尺度的差异很敏感,即使在 PCA 或 LDA 之后,也可能从归一化特征中受益。
- 其他算法,例如我们部署的决策树或随机森林,对特征尺度不太敏感,可能不需要在 PCA 之后进行标准化。
- 归一化对可解释性的影响;PCA 后的归一化可能会影响主成分的可解释性。如果你有兴趣了解原始特征对主成分的贡献,对转换后的数据进行归一化可能会掩盖这些关系。
- 对性能的影响;对归一化和非归一化的转换数据进行实验,以评估对模型性能的影响。在某些情况下,归一化可能会导致更好的收敛、更好的泛化或更快的训练,而在其他情况下,它可能几乎没有效果。
跟踪机器学习模型的开发以及此系列文章中讨论的更多内容,请访问 GitHub repo。
附件:
| 文件 | 描述/用途 |
|---|---|
| tree.mqh | 包含决策树分类器模型 |
| MatrixExtend.mqh | 具有用于矩阵操作的附加函数。 |
| metrics.mqh | 包含用于衡量 ML 模型性能的函数和代码。 |
| preprocessing.mqh | 该库用于预处理原始输入数据,使其适合机器学习模型使用。 |
| base.mqh | PCA 和 LDA 的基础库,具有一些简化这两个库编码的功能 |
| pca.mqh | 主成分分析库 |
| lda.mqh | 线性判别分析库 |
| plots.mqh | 绘制向量和矩阵的库 |
| lda vs pca script.mq5 | 用于展示 PCA 和 LDA 算法的脚本 |
| LDA Test.mq5 | 用于测试大部分代码的主 EA |
| iris.csv | 流行的鸢尾花数据集 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/14128
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
人工藻类算法(Artificial Algae Algorithm,AAA)
重塑经典策略(第六部分):多时间框架分析
数据科学和机器学习(第 27 部分):MetaTrader 5 中训练卷积神经网络(CNN)交易机器人 — 值得吗?