歡迎光臨
每天分享高質量文章

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(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的保留欄位名,它指定了計算結果應該輸出到哪個物件欄位中

直接執行程式,可以看到,程式毫無懸念地輸出了正確結果:

可信度得分的獲取

在上面的程式碼中,如果我們將斷點設定在最後一句Console.WriteLine方法上,然後除錯程式,檢視prediction的數值,會發現,各個分類的可信度已經在Score欄位裡了:

可問題是,我如何知道某個得分到底是屬於哪個分類呢?在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 VBufferchar>>();
    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;
}

再次執行程式,可以看到,我們已經可以輸出各個分類的可信度得分了:

預測失誤

現在我們做個試驗,將最後用於測試的資料從SMILES換成INCHI,比如:

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

然後再次執行程式,結果發現,我們本想得到INCHI的輸出,卻仍然得到SMILES的結果,只不過SMILES的可信度降低了,InChi的可信度升高了:

這個問題主要是因為我們所提供的用於訓練的樣本資料還不夠多,如果訓練資料量大,並且幹擾比較小的話,得到的預測結果就會更準確。因此,在實踐機器學習的過程中,保證訓練資料的純凈度和資料量是非常重要的,這也就是為什麼目前機器學習的專案中,在資料清洗這一步中有著相當大的投入。回到我們的案例,讓我們在樣本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):

模型的儲存與使用

我們可以用下麵的程式碼將訓練好的模型儲存到本地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(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/


贊(0)

分享創造快樂