ML.NET案例详解:在.NET下使用机器学习API实现化学分子式数据格式的判定

半年前写过一篇类似的文章,题目是:《在.NET中使用机器学习API(ML.NET)实现化学分子式数据格式的判定》,在该文中,我介绍了化学分子式数据格式的基本知识,同时给出了一个案例,展示了如何在.NET/.NET Core中,使用微软开源的ML.NET框架,通过机器学习,实现化学分子式数据格式的预测。

时隔半年,ML.NET有了很大的发展。在阅读我之前那篇文章的时候,或许还会对给出的案例代码有些疑问,ML.NET经过几个版本的更新之后,API的设计变得更为合理易用,所开放的接口也越来越多(比如,新版本的ML.NET中,对机器学习引擎的OutputSchema进行了完全开放,开发者可以根据自己的需要进行调用),因此,本文就再一次回到这个话题并进行更为详细的介绍,用新版本的ML.NET重新实现化学分子式数据格式的判定。

有关化学分子式的相关知识,在这里也就不多说了,直接看代码实现部分。

准备数据

我们的数据仍然是一个CSV文件,通过逗号分隔,文件包含两个字段:结构式数据(ChemicalStructure),以及该结构式数据的类型(Type),以下是这个文件的部分片段,注意,在这个文件中,我们没有定义CSV头,不过这不重要,只要记得在后面的代码实现中,将这个设置体现出来就可以了。


[O-]C(CCCCCCCCCCCCCCCCC)=O.[Na+],SMILES
O=C(C1)N(C2[C@@]3(CC4)[C@](N4C5)([H])C[C@@]6([H])C5=CCOC1[C@]62[H])C7=C3C=CC=C7.O[N+]([O-])=O,SMILES
O=C1CC2C(C3[C@]45C(C=CC=C6)=C6N31)C(CC4N(CC5)C7)C7=CCO2.OS(O)(=O)=O.O=C8CC9C(C%10[C@@]%11%12C(C=CC=C%13)=C%13N%108)C(CC%11N(CC%12)C%14)C%14=CCO9,SMILES
C=CC1=CC=CC=C1,SMILES
N=C(OC)CCCCCCC(OC)=N.Cl.Cl,SMILES
NC(CCC(N)=O)=O,SMILES
O=C(O)C1(N(CCOC)CCOC)CCC(C)CC1,SMILES
CN(C)C(C)CC(C1=CC=CC=C1)(C(CC)=O)C2=CC=CC=C2,SMILES
NCC1(CCC(CCC)CC1)N(C)CC2=COC=C2,SMILES
AAADceByOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAAAACBThgAYCCAMABAAIAACQCAAAAAAAAAAAAAEIAAACABQAgAAHAAAFIAAQAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==,BASE64_CDX
AAADceByOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAACAAACBThgAYCCAMABgAIAACQCAAAAAAAAAAAAAEIAAACABQAgAAHQAAFIAAQAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==,BASE64_CDX
AAADccBCIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAQCAAACBThgAYCAABAAgAAAAAAAAAAAAAAAAAAAIAAAAACEAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==,BASE64_CDX
AAADccBjgAAAAAAAAAAAAAAAAAAAAWAAAAAsAAAAAAAAAFgB+AAAHAAQAAAACAjBFwQH8L9MEACgAQZhZACAgC0REKABUCAoVBCASABASEAUBAgIAALAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==,BASE64_CDX
AAADceB7uAAAAAAAAAAAAAAAAAAAAAAAAAAwQIAAAAAAAACBAAAAHgAQCAAADCjBmAYxyIPAAgCoAiXS/ACCAAElAgAJiIGIZMiKYDLA1bGUYQhslgLYyce8rwCeCAAAAAAAAAAQAAAAAAAAAAAAAAAAAA==,BASE64_CDX
OC1=C(C2=C(C=C1)C[C@@]3([C@]45[H])[H])O[C@]([C@@]52CCN3C)([C@H](CC4)OC)[H],SMILES
OC1=C(O2)C([C@]([C@]2(C)C(CC3)=O)(CCN4C)[C@]3([H])[C@H]4C5)=C5C=C1,SMILES
........

注意:你不需要将这些数据复制下来,本文结尾会给出源代码,其中包含了这个完整的数据文件。

实现过程

可以基于.NET Framework 4.6.1或者.NET Core创建一个新的控制台应用程序,在这个控制台应用程序上,添加对ML.NET NuGet包的引用。实现的第一步就是定义我们的样本数据对象。根据上面的CSV文件结构,我们可以设计如下的类:


public class ChemicalData
{
     [Column( "0" )]
     public string ChemicalStructure;
 
     [Column( "1" )]
     public string Type;
}

这个类非常简单,仅仅是针对CSV文件两个列的映射。接下来,我们需要定义用于保存预测结果的数据对象,该对象不仅会用来保存预测结果值,而且还会提供基于不同分类的可信度得分(Score):


public class ChemicalDataPrediction
{
     [ColumnName( "PredictedLabel" )]
     public string Type;
 
     public float [] Score;
}

OK,到这里我们基本上已经清楚我们的机器学习应用场景了:我们在使用Multi-class Classification对化学结构式数据进行分类。在机器学习的应用过程中,了解应用场景是非常重要的。然后,回到Main函数,实现如下代码:


static void Main( string [] args)
{
     // 创建机器学习上下文实例
     var mlContext = new MLContext();
 
     // 从data.txt读入样本数据
     var dataView = mlContext.Data.ReadFromTextFile( "data.txt" , new TextLoader.Arguments
     {
         Separators = new char [] { ',' }, // 逗号分隔
         HasHeader = false , // 文件中不包含CSV头信息
         Column = new [] {
             new TextLoader.Column( "ChemicalStructure" , DataKind.Text, 0),  // 化学结构式数据字段
             new TextLoader.Column( "Type" , DataKind.Text, 1)  // 化学结构式数据类型字段
         }
     });
 
     // 创建机器学习管道,指定我们需要使用CSV文件中的Type字段进行标记并分类
     var pipeline = mlContext.Transforms.Conversion.MapValueToKey( "Label" , "Type" )
         
         // 指定将由ChemicalStructure字段提供特征信息
         .Append(mlContext.Transforms.Text.FeaturizeText( "Features" , "ChemicalStructure" ))
 
         // 选择机器学习算法
         .Append(mlContext.MulticlassClassification.Trainers.LogisticRegression())
 
         // 计算结果将输出到由PredictedLabel所标记的对象字段上
         .Append(mlContext.Transforms.Conversion.MapKeyToValue( "PredictedLabel" ));
 
     // 基于样本数据和所选择的管道选项,进行模型训练,并返回模型
     var model = pipeline.Fit(dataView);
 
     // 创建预测引擎
     var engine = model.CreatePredictionEngine<ChemicalData, ChemicalDataPrediction>(mlContext);
 
     // 对给定的测试数据进行预测,并输出测试结果
     var sample = new ChemicalData { ChemicalStructure = "NC(C(N)=O)=O" };
     var prediction = engine.Predict(sample);
     Console.WriteLine(prediction.Type);
}

代码非常简单,有几个点说明一下:

  • 新的ML.NET需要创建MLContext对象,所有的机器学习工作都会依赖于这个上下文

  • 通过MapValueToKey方法来指定读入数据的哪个字段是用来进行分类标记的,这个Label是ML.NET的一个保留字段名,在模型训练的时候会找到由Label所标记的字段进行计算

  • Features也是ML.NET的一个保留字段名,它指定了哪个(或哪些)字段将提供特征数据

  • PredictedLabel也是ML.NET的保留字段名,它指定了计算结果应该输出到哪个对象字段中

直接运行程序,可以看到,程序毫无悬念地输出了正确结果:

640?wx_fmt=png

可信度得分的获取

在上面的代码中,如果我们将断点设置在最后一句Console.WriteLine方法上,然后调试程序,查看prediction的数值,会发现,各个分类的可信度已经在Score字段里了:

640?wx_fmt=png

可问题是,我如何知道某个得分到底是属于哪个分类呢?在ML.NET 0.6之前的版本,在训练好的模型对象上,会有一个TryGetScoreLabelNames的扩展方法,它能够返回可信度得分的分类名称,顺序和Score数组的顺序一致。但从ML.NET 0.6开始,这个扩展方法已经没有了,但这并不是说ML.NET变得更弱了,相反,新版本中直接将OutputSchema对象暴露出来,开发者可以自己实现所需的方法。下面的代码展示了如何基于预测引擎的OutputSchema来获取各个分类的名称,以及所对应的可信度得分:


static void Main( string [] args)
{
     // ...
     // 接上文代码
     
     var outputSchema = engine.OutputSchema;
     TryGetScoreLabelNames(outputSchema, out var names);
     var confidences = new Dictionary< string , float >();
     for ( var idx = 0; idx < names.Length; idx++)
     {
         confidences.Add(names[idx], prediction.Score[idx]);
     }
 
     Console.WriteLine(JsonConvert.SerializeObject(
         new
         {
             Label = prediction.Type,
             Confidences = confidences
         },
         Formatting.Indented));
}
 
static bool TryGetScoreLabelNames(Schema outputSchema, out string [] names, string scoreColumnName = DefaultColumnNames.Score)
{
     names = ( string []) null ;
     var scoreColumn = outputSchema.GetColumnOrNull(scoreColumnName);
     var slotNames = new VBuffer<ReadOnlyMemory< char >>();
     scoreColumn.Value.GetSlotNames( ref slotNames);
     names = new string [slotNames.Length];
     var num = 0;
     foreach ( var denseValue in slotNames.DenseValues())
     {
         names[num++] = denseValue.ToString();
     }
     return true ;
}

再次执行程序,可以看到,我们已经可以输出各个分类的可信度得分了:

640?wx_fmt=png

预测失误

现在我们做个试验,将最后用于测试的数据从SMILES换成INCHI,比如:

1
var sample = new ChemicalData { ChemicalStructure = "InChI=1S/ClH/h1H/p-1" };

然后再次运行程序,结果发现,我们本想得到INCHI的输出,却仍然得到SMILES的结果,只不过SMILES的可信度降低了,InChi的可信度升高了:

640?wx_fmt=png

这个问题主要是因为我们所提供的用于训练的样本数据还不够多,如果训练数据量大,并且干扰比较小的话,得到的预测结果就会更准确。因此,在实践机器学习的过程中,保证训练数据的纯净度和数据量是非常重要的,这也就是为什么目前机器学习的项目中,在数据清洗这一步中有着相当大的投入。回到我们的案例,让我们在样本CSV文件中多加一些InChi数据,来帮助机器学习得到更精确的结果:


"InChI=1/C2H6O/c1-2-3/h3H,2H2,1H3",InChi
"InChI=1/C6H8O6/c7-1-2(8)5-3(9)4(10)6(11)12-5/h2,5,7-10H,1H2/t2-,5+/m0/s1",InChi
"InChI=1S/C6H8O6/c7-1-2(8)5-3(9)4(10)6(11)12-5/h2,5,7-10H,1H2/t2-,5+/m0/s1",InChi
"InChI=1S/CH4/h1H4",InChi
"InChI=1S/C2H6/c1-2/h1-2H3",InChi
"InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3",InChi
"InChI=1S/C3H7NO2/c1-2(4)3(5)6/h2H,4H2,1H3,(H,5,6)/t2-/m0/s1",InChi
"InChI=1S/ClH/h1H/p-1",InChi
"InChI=1S/C6H7NO/c1-5-3-2-4-7-6(5)8/h2-4H,1H3,(H,7,8)",InChi
"InChI=1S/CH2N2/c1-3-2/h1H2",InChi
"InChI=1S/C7H5N3O/c11-7-5-3-1-2-4-6(5)8-10-9-7/h1-4H,(H,8,9,11)",InChi
"InChI=1S/C8H6N2O/c11-8-6-3-1-2-4-7(6)9-5-10-8/h1-5H,(H,9,10,11)",InChi
"InChI=1S/C2H6N2O/c1-4(2)3-5/h1-2H3",InChi
"InChI=1S/C9H8N2O/c1-6-10-8-5-3-2-4-7(8)9(12)11-6/h2-5H,1H3,(H,10,11,12)",InChi
"InChI=1S/C6H8O/c1-2-3-4-5-6-7/h2-6H,1H3/b3-2+,5-4+",InChi

再次运行程序,我们已经可以得到正确的输出了(虽然它仍然认为有31%的可能性是SMILES):

640?wx_fmt=png

模型的保存与使用

我们可以用下面的代码将训练好的模型保存到本地ZIP文件中,以便今后直接在项目中使用:


using ( var fs = new FileStream( "ml_model.zip" , FileMode.Create, FileAccess.Write, FileShare.Write))
{
     mlContext.Model.Save(model, fs);
}

然后使用下面的代码,读入保存的模型,并进行新的预测:


var mlContext2 = new MLContext();
ITransformer loadedModel;
using ( var stream = new FileStream( "ml_model.zip" , FileMode.Open, FileAccess.Read, FileShare.Read))
{
     loadedModel = mlContext2.Model.Load(stream);
     var engine2 = loadedModel.CreatePredictionEngine<ChemicalData, ChemicalDataPrediction>(mlContext2);
     var pred = engine2.Predict( new ChemicalData { ChemicalStructure = "c1ccccc1" });
     Console.WriteLine(pred.Type);
}

总结

本文再一次介绍了如何使用微软开源的ML.NET框架,实现化学结构式数据格式的预测和判定。本文对使用ML.NET的整个流程进行了详细完整的介绍,但只演示了Multi-class Classification的应用场景。其它应用场景其实也大同小异,开发人员需要根据实际情况进行选择。通过ML.NET产生的训练模型是可以序列化到ZIP文件的,因此,模型可以方便地重用。ML.NET支持.NET Core,因此,基于docker和ASP.NET Core实现机器学习的RESTful API也是轻而易举的事情,本文就不继续深入了。

源代码下载

请 下载本文案例的源代码http://sunnycoding.cn/archives/ML_ChemStructure_Demo.zip

原文地址:http://sunnycoding.cn/2019/02/22/categorize-chemical-structure-using-ml-net-advanced/

 
 

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
640?wx_fmt=jpeg


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值