贝塞尔曲线机器学习演示
使用 ALGLIB 进行贝塞尔曲线分类训练和验证模型
访问第一部分: 数据可视化与贝塞尔曲线
引言
我喜欢处理纵向数据。这是关于使用贝塞尔曲线来平滑大型数据点波动并提高不断展现的模式可见性的一系列文章中的第二篇。本篇文章侧重于使用机器学习算法来训练网络模型,以识别学生学业表现的贝塞尔曲线轨迹中反映出的模式和趋势。
背景
本次演示使用了ALGLIB,这是C#程序员可用的一个较好的数值分析库,它提供了几种易于使用的机器学习方法。(在本系列的后续文章中,我们将考察用于C#例程的MS CNKT。)ALGLIB for C# 可用,并根据个人实验和使用情况,以免费的单线程版本或付费的商业多线程版本进行许可。在本演示中,我将详细解释如何下载和构建免费版本,将其作为类库包含在引用中,以使演示程序能够正常工作。供参考,您可以在ALGLIB维基百科页面上找到描述和历史,在ALGLIB网站上找到下载信息和极佳的在线用户指南,以及在下载本身中查找详细的.html格式的用户手册。
使用ALGLIB
首先,访问ALGLIB网站,查看新闻和用户指南(特别是关于“数据分析:分类、回归、其他任务”的章节),并下载ALGLIB 3.14.0 for C#(发布于2018年6月16日)的免费版本(ZIP文件)。解压缩并打开应用程序文件夹,该文件夹应命名为“csharp”。在此文件夹的内容列表中,有一个名为“manual.csharp.html”的文件。它包含详细的叙述、方法描述和示例代码片段,对于任何希望使用该库的人都很有用。在csharp文件夹中,另一个值得关注的项目是一个名为“net-core”的子文件夹,其中包含alglibnet2.dll库,还有一个名为“src”的子文件夹,其中包含源代码。ALGLIB的各个部分可以提取出来直接包含在程序中(以节省空间等)。我更喜欢从源代码构建自己的完整库。要做到这一点,请遵循以下步骤:
- 打开Visual Studio,并创建一个新的类库(.NET Framework) C#项目。
- 将项目命名为ALGLIB314;选择一个保存项目文件夹的位置;将框架设置为.NET4.7;勾选为解决方案创建目录复选框;然后单击创建按钮。
- 项目打开后,在解决方案资源管理器中,右键单击项目名称,然后选择添加:现有项选项。
- 在添加现有项窗口中,找到下载的csharp文件夹;打开它;打开net-core子文件夹及其src子文件夹。在那里,选择列出的所有.cs文件,然后单击添加,将这些文件的副本导入到您的新
ALGLIB314
项目中。 - 接下来,在Visual Studio中,选择Release和Any CPU,然后使用菜单栏选择生成:生成解决方案选项,以构建完整的ALGLIB314.dll库。
- 注意:上一步将失败,在输出的错误列表窗格中会显示两条错误,指向项目AssemblyInfo.cs文件的第11行和第12行。要解决此问题,请单击其中一条错误行以打开该文件,然后简单地注释掉这两行并保存该文件。(这是因为alglib_info.cs文件已经提供了相同的信息)。
- 现在,再次单击生成:重新生成解决方案菜单选项,以创建ALGIB314.dll文件的发布版本。这次,过程将成功。
- 最后,将Release更改为Debug,然后再次单击生成解决方案。
- 现在,您的项目BIN文件夹中同时拥有了完整ALGLIB314.dll库的发布和调试版本(以及用于调试的相关.pdb文件)。稍后可以将此库作为引用添加到其他项目中。
- 保存您的类库项目并关闭解决方案。
- 此时,也可以将csharp文件夹中的manual.csharp.html文件复制并粘贴到新ALGLIB314项目文件夹的顶层,以备将来参考。
数据特征
本数据可视化系列文章中的上一篇文章,题为数据可视化与贝塞尔曲线,是关于使用贝塞尔曲线对数据进行建模的。我们研究了曲线拟合、时域点评估和绘图以及微分。您可能想回顾一下那篇文章。再次强调,我们讨论的是纵向数据,它从起始点(沿着时间或X轴)移动到终点,没有回环、尖点或回溯。再次以学生在6至12年级的学业表现为例。在本篇文章中,我们将讨论构建机器学习分类模型,并利用这些模型来识别各种模式,例如预示着学生表现良好或可能处于风险中的轨迹。
在这里,我们将使用另一组小样本(N=500)的学生在核心课程中的学期绩点(MPgpa)表现历史。MPgpa在评分等级中介于0.00到4.00之间。这是从一个大型多州、多学校区、完全去识别化的研究数据库的蒙特卡洛版本中提取的模拟样本。(请参阅上一篇文章以更全面地描述数据来源。)在该研究中,大量的学生历史(从进入初中到八年级结束)使用课程时间表经过了单独的贝塞尔曲线建模、检查和分类,形成了一个用于机器学习的大型数据集。
每条曲线被分类为
- 在课程时间表的估计状态点(9.0,初中结束)之前,表示一个相对成功的学业历史;
- 可能处于风险之中,因为MPgpa模式下降到评分等级2.0以下;
- 仍处于风险之中,但表现出MPgpa模式上升,接近或略高于2.0;或
- 严重处于学业失败的风险中,模式呈下降趋势低于1.0。
我们的小型演示数据集是通过随机选择预先分类的学生历史来构建的,同时确保了代表每个状态组的均衡演示数据。(实际数据并非这样“均衡”。幸运的是,在公立学校中,通常“学业成功”的学生数量远多于“处于风险中”的学生。)
Using the Code
在这个使用Visual Studio 2017和.NET 4.7编写的C#项目“BezierCurveMachineLearningDemo
”中,我们首先读取一个数据文件并构建学生历史,定义为DataPoint
元组(time
,MPgpa
)的列表。然后对数据记录的集合进行随机打乱。
从每个学生历史中,提取一个子历史(从进入初中的时间点到9.0这个标记初中结束的状态估计点)。每个子历史都与一个预先分类的状态值相关联。
接下来,使用贝塞尔曲线来拟合模型以描述每个学生子历史。这条平滑的贝塞尔曲线本身通过从起始点到终点以相等的间隔提取24个MPgpa值来建模,包括起始点和终点。24个点似乎足以描述这些学业表现的贝塞尔曲线,因为在课程时间范围内,它们很少表现出超过两到三个频率周期。
ALGLIB机器学习方法需要数据以double[,]
数组的形式提供,其中行代表特征,然后是标签。描述贝塞尔曲线模式的24个点构成了本演示中的特征。预先分类的状态值(0、1、2或3)成为标签(因为在ALGLIB中,标签从0开始)。然后,数据被分成训练集和验证集(基于用户定义的分配百分比),以便于访问。
下载项目并在Visual Studio中打开解决方案文件。在解决方案资源管理器中,右键单击项目引用文件夹,删除对ALGLIB314.dll库的任何现有引用。然后使用添加引用选项找到并添加您上面创建的ALGLIB314.dll库的发布版本到项目中。然后单击开始以生成并运行应用程序。该解决方案需要三个包(MSTest.TestAdapter.1.2.1
、MSTest.TestFramework.1.2.1
和System.ValueTuple.4.3.1
),它们应该会自动下载和恢复。
将打开一个WinForm,其中包含一个列表框、一个图表、一个数据网格视图和几个按钮。列表框将显示机器学习过程的进展。图表可视化地显示了这个进程。当网络训练完成后,列表框中会提供一个简要的摘要,并且在datagridview
中会呈现更详细的训练结果交叉制表。对于本次演示,提供了菜单选项来选择要训练的网络模型类型(神经网络或决策森林)、用于训练的数据百分比,以及标签是二进制(0、1)还是三元(0、1、2、3)。窗体上的其他控件用于启动训练过程、运行验证测试、保存窗体图像、保存训练模型和关闭应用程序。
关注点
该应用程序提供了一个演示菜单,包含两个选项:一个选择神经网络作为分类模型进行训练。另一个演示了为同一目的训练决策森林模型。选择一个模型,然后单击开始按钮运行新的分析。
代码的大部分(如果不是绝大部分)都很普通,用于构建窗体和演示用户界面,以及创建和操作数据集。然而,最重要的方法是那些调用ALGLIB实现的实际机器学习过程的方法。ALGLIB API对于这两种模型非常相似。例如,这是演示中的NeuralNet代码块,它定义并实现了一个(24:12:6:4)的神经网络,该网络已被证明足以对代表学业表现的贝塞尔曲线进行分类。
class NeuralNet
{
private static alglib.multilayerperceptron network;
private static alglib.mlptrainer trainer;
…
private static double relclserror;
private static double accuracy;
public static void RunExample1()
{
int Nfeatures = Form1.rdr.NofFeatures;
int Nlabels = Form1.LabelCategories == LABEL.Binary ? 2 : 4;
…
// First, create trainer function
alglib.mlpcreatetrainercls(Nfeatures, Nlabels, out trainer);
alglib.mlpsetdataset(trainer, Form1.rdr.GetTrainingData, Form1.rdr.NofTrainingCases);
// Create neural network with two hidden layers
alglib.mlpcreatec2(Nfeatures, 12, 6, Nlabels, out network);
// Set the regularization
alglib.mlpsetdecay(trainer, 1.0E-3);
// Run the trainer
// RunAutoTrainer();
RunStepTrainer();
TrainingSummary();
PredictStatus(Form1.rdr.GetTrainingData);
}
…
}
正如您所见,训练器由特征的数量和标签值的数量定义,其中一个可能出现在每个案例记录中。一组训练记录构成了呈现给训练器的dataset
。网络被定义,在本例中,它具有一个24个节点的输入层,用于描述贝塞尔曲线的24个点,两个隐藏层——第一个有12个节点,第二个有6个节点,以及一个输出层,有4个或2个节点用于三元或二进制分类。ALGLIB提供几种运行模式。其中一种模式简单地“自动运行”直到完成,然后产生输出。
/// <summary>
/// This neural network trainer runs to completion without producing intermediate results
/// </summary>
private static void RunAutoTrainer()
{
…
alglib.mlptrainnetwork(trainer, network, 1, out alglib.mlpreport rep);
accuracy = 1.0 - rep.relclserror; // set global accuracy variable
ShowReport(rep);
}
还有一个“分步”运行模式,会产生中间输出。这是本演示用于更新列表框中的表格信息和训练进度图的可视化的运行模式。
/// <summary>
/// This neural network Trainer produces intermediate step-wise results
/// </summary>
private static void RunStepTrainer()
{
int epoch = 0;
alglib.mlpstarttraining(trainer, network, true);
while (alglib.mlpcontinuetraining(trainer, network))
{
avgce = alglib.mlpavgce(network,
Form1.rdr.GetTrainingData, Form1.rdr.NofTrainingCases);
ssqerror = alglib.mlperror(network,
Form1.rdr.GetTrainingData, Form1.rdr.NofTrainingCases);
rmserror = alglib.mlprmserror(network,
Form1.rdr.GetTrainingData, Form1.rdr.NofTrainingCases);
relclserror = alglib.mlprelclserror(network,
Form1.rdr.GetTrainingData, Form1.rdr.NofTrainingCases);
accuracy = 1.0 - relclserror; // set global accuracy variable
Charts.ChartAddaPoint(epoch++, (float)accuracy); // update chart
…
}
}
模型训练完成后,将其保存为磁盘文件,并在另一个程序中重新加载该模型以分类新数据记录就非常简单了。ALGLIB为此目的提供了网络序列化和反序列化函数,例如:
public alglib.multilayerperceptron network;
…
/// <summary>
/// Write the trained serialized neural network to a disk data file
/// </summary>
/// <param name="pathname"></param>
public static void SaveTrainedNeuralNetwork(string pathname)
{
alglib.mlpserialize(network, out string s_out);
System.IO.File.WriteAllText(pathname, s_out);
}
和
/// <summary>
/// Read and deserialize a saved trained serialized neural network
/// </summary>
/// <param name="pathname"></param>
public static void SaveTrainedNeuralNetwork(string networkPathName)
{
if (!File.Exists(networkPathName))
throw new FileNotFoundException("Neural Network Classifier file not found.");
string text = System.IO.File.ReadAllText(networkPathName);
alglib.mlpunserialize(text, out network);
}
在演示中,然而,我们已经在内存中拥有了训练好的网络,并可以首先将其应用于训练集,然后在验证集上应用,如下所示:
/// <summary>
/// Use an in-memory training or validation data array
/// and predict status with trained neural network
/// </summary>
/// <param name="rundata"> a training or validation data array</param>
/// <param name="validationFlag"> True: add Validation summary info to listbox</param>
/// <remarks>This routine concludes by producing the Crosstabulation DataGridView</remarks>
public static void PredictStatus(double[,] rundata, bool validationFlag = false)
{
double[] results = new double[4];
List<int> status = new List<int>();
List<int> predict = new List<int>();
int NofMatches = 0;
for (int i = 0; i < rundata.GetLength(0); i++)
{
alglib.mlpprocess(network,
Support.VectorRow(ref rundata, i, 1), ref results); // get probabilities
int pred = Array.IndexOf(results, results.Max()) + 1; // get max prob index + 1
int actual = Convert.ToInt32(
Math.Round(rundata[i, rundata.GetLength(1) - 1], 0)) + 1; // get actual status label
status.Add(actual);
predict.Add(pred);
if (actual == pred) NofMatches++;
}
if (validationFlag == true)
{
accuracy = (double)NofMatches / rundata.GetLength(0);
ValidationSummary();
}
Support.RunCrosstabs(predict, status);
}
crosstabs.cs模块包含在WinForm
的数据网格视图控件中以“混淆”矩阵格式显示实际和预测分类结果的方法,以及各种精确率、召回率和准确率统计信息。有兴趣将其用于自己目的的读者可以参考我以前写过的另一篇CodeProject文章,题为Crosstabs/Confusion Matrix for AI Classification Projects。
使用ALGLIB训练和使用决策森林的过程与上述代码非常相似,并且演示包括一个DecisionForest
类模块来实现该模型。(对于这个小型训练集,DecisionForest
模型使用24个特征、2或4个标签、10棵树以及60%的r值用于节点拆分。)演示还提供了菜单选项,可以将分配给模型训练和验证的数据百分比从50%更改到100%。另一个菜单选项将预先分类的状态Label
变量重新编码为二进制分类(即,(0)明显成功或(1)可能处于风险中),而不是三元分类。操作这些选项会产生预期的效果。更多的训练案例和更少的标签值可以带来更好的结果。在之前的研究中,使用更大的数据集(N>14,000)和60%/40%的训练/验证比例,使用这些分类模型通常可以实现稳定且大于0.98的贝塞尔曲线验证准确率。
结论
本系列第一篇演示得出的主要结论是,贝塞尔曲线可以成为对在不同时间或X轴点收集的嘈杂纵向数据非常有用的模型。第二个推论是,如果模型是好的,那么关于这些模型的推断也可能很好。该演示说明了许多不同的学生表现轨迹,贯穿他们各自的公立学校课程。提出一个问题:这些是终点或“当前”或中间兴趣点(如从八年级中学到九年级和高中的9.0过渡点)的相对成功的指标吗?
通过查看实际数据或贝塞尔曲线平滑后的数据,教育工作者可以就特定学生是否可能在学业上“处于风险之中”(从而可能受益于额外的支持服务)做出决定。这就是为本演示构建预分类数据集的方式。但是,假设您需要实时地(例如,在每个学期)对数百甚至数千名学生做出类似的决定,在他们各自完成课程的过程中?学校辅导员每天都在做这种事情,但大多没有这种数据的可用性或使用。一个能够以各种方式对表现轨迹进行分类的机器学习模型无疑将是数据驱动决策的有用工具。
本文展示了如何使用机器学习算法来训练分类网络模型,以识别反映学生学业表现趋势的贝塞尔曲线轨迹模式。重点是ALGLIB提供的数值方法,但同样的事情也可以使用MS CNTK for C#,或者使用Python的TensorFlow或KERAS来完成。我认为这种跨模型验证/确认总是一个重要且有用的步骤。本系列的下一篇文章将探讨这一点。
学校和学区不断使用商业学生信息系统(SIS)收集数据。但是这些“原始”信息很少能获得。一旦获得了训练好的模型,仪表板和其他可视化方法就可以访问这些数据,对其进行分析,并为教育工作者提供实时分析个人或聚合队列的决策工具。这些或许是本系列未来文章的主题。
除此之外,本项目还展示了处理贝塞尔曲线和训练机器学习模型以识别此类曲线所反映的模式和趋势的各种有用技术和方法,这些可以应用于其他类似的项目。
历史
- 2018年8月22日:版本1.0