天天看點

使用 .NET 5 體驗大資料和機器學習

.NET 5 旨在提供統一的運作時和架構,使其在各平台都有統一的運作時行為和開發體驗。微軟釋出了與 .NET 協作的大資料(.NET for Spark)和機器學習(ML.NET)工具,這些工具共同提供了富有成效的端到端體驗。在本文中,我們将介紹 .NET for Spark、大資料、ML.NET 和機器學習的基礎知識,我們将研究其 API 和功能,向你展示如何開始建構和消費你自己的 Spark 作業和 ML.NET 模型。

翻譯:精緻碼農-王亮

原文:http://dwz.win/XnM

什麼是大資料

大資料是一個幾乎不言自明的行業術語。該術語指的是大型資料集,通常涉及 TB 甚至 PB 級的資訊,這些資料集被用作分析的輸入,以揭示資料中的模式和趨勢。大資料與傳統工作負載之間的關鍵差別在于,大資料往往過于龐大、複雜或多變,傳統資料庫和應用程式無法處理。一種流行的資料分類方式被稱為 "3V"(譯注:即3個V,Volume 容量、Velocity 速度、Variety 多樣性)。

大資料解決方案是為适應高容量、處理複雜多樣的資料結構而定制的,并通過批處理(靜态)和流處理(動态)來管理速度。

大多數大資料解決方案都提供了在資料倉庫中存儲資料的方式,資料倉庫通常是一個為快速檢索和為并行處理而優化的分布式叢集。處理大資料往往涉及多個步驟,如下圖所示:

.NET 5 開發人員如果需要基于大型資料集進行分析和洞察,可以使用基于流行的大資料解決方案 Apache Spark 的 .NET 實作:.NET for Spark。

.NET for Spark

.NET for Spark 基于 Apache Spark,這是一個用于處理大資料的開源分析引擎。它被設計為在記憶體中處理大量資料,以提供比其他依賴持久化存儲的解決方案更好的性能。它是一個分布式系統,并行處理工作負載。它為加載資料、查詢資料、處理資料和輸出資料提供支援。

Apache Spark 支援 Java、Scala、Python、R 和 SQL。微軟建立了 .NET for Spark 以增加對 .NET 的支援。該解決方案提供了免費、開放、跨平台的工具,用于使用 .NET 所支援的語言(如 C#和 F#)建構大資料應用程式,這樣你就可以使用現有的 .NET 庫,同時利用 SparkSQL 等 Spark 特性。

以下代碼展示了一個小而完整的 .NET for Spark 應用程式,它讀取一個文本檔案并按降序輸出字數。

using Microsoft.Spark.Sql;

namespace MySparkApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a Spark session.
            SparkSession spark = SparkSession.Builder().AppName("word_count_sample").GetOrCreate();

            // Create initial DataFrame.
            DataFrame dataFrame = spark.Read().Text("input.txt");

            // Count words.
            DataFrame words = dataFrame.Select(Functions.Split(Functions.Col("value"), " ").Alias("words"))
                .Select(Functions.Explode(Functions .Col("words"))
                .Alias("word"))
                .GroupBy("word")
                .Count()
                .OrderBy(Functions.Col("count").Desc());

            // Show results.
            words.Show();

            // Stop Spark session.
            spark.Stop();
        }
    }
}
           

在開發機器上配置 .NET for Spark 需要安裝幾個依賴,包括 Java SDK 和 Apache Spark。你可以在這裡(https://aka.ms/go-spark-net)檢視手把手的入門指南。

Spark for .NET 可在多種環境中運作,并可部署到雲中運作。可部署目标包括 Azure HDInsight、Azure Synapse、AWS EMR Spark 和 Databricks 等。如果資料作為項目可用的一部分,你可以将其與其他

project

檔案一起送出。

大資料通常與機器學習一起使用,以獲得關于資料的洞察。

什麼是機器學習

首先,我們先來介紹一下人工智能和機器學習的基本知識。

人工智能(AI)是指計算機模仿人類智慧和能力,如推理和尋找意義。典型的人工智能技術通常是從規則或邏輯系統開始的。作為一個簡單的例子,想一想這樣的場景:你想把某樣東西分類為“面包”或“不是面包”。當你開始時,這似乎是一個簡單的問題,例如“如果它有眼睛,它就不是面包”。然而,你很快就會開始意識到,有很多不同的特征可以将某物定性為面包與非面包,而且特征越多,一系列的 if 語句就會越長越複雜,如下圖所示:

從上圖中的例子可以看出,傳統的、基于規則的人工智能技術往往難以擴充。這就是機器學習的作用。機器學習(ML)是人工智能的一個子集,它能在過去的資料中找到模式,并從經驗中學習,以對新資料采取行動。ML 允許計算機在沒有明确的邏輯規則程式設計的情況下進行預測。是以,當你有一個難以(或不可能)用基于規則的程式設計解決的問題時,你可以使用 ML。你可以把 ML 看作是 "對不可程式設計的程式設計"。

為了用 ML 解決“面包”與“非面包”的問題,你提供面包的例子和非面包的例子(如下圖所示),而不是實作一長串複雜的 if 語句。你将這些例子傳遞給一個算法,該算法在資料中找到模式,并傳回一個模型,然後你可以用這個模型來預測尚未被模型“看到”的圖像是“面包”還是“不是面包”。

上圖展示了 AI 與 ML 的另一種思考方式。AI 将規則和資料作為輸入,預期輸出基于這些規則的答案。而 ML 則是将資料和答案作為輸入,輸出可用于對新資料進行歸納的規則。

AI 将規則和資料作為輸入,并根據這些規則輸出預期的答案。ML 将資料和答案作為輸入,并輸出可用于概括新資料的規則。

ML.NET

微軟在 2019 年 5 月的 Build 上釋出了 ML.NET,這是一個面向.NET 開發人員的開源、跨平台 ML 架構。在過去的九年裡,微軟的團隊已經廣泛使用該架構的内部版本來實作流行的 ML 驅動功能;一些例子包括 Dynamics 365 欺詐檢測、PowerPoint 設計理念和 Microsoft Defender 防病毒威脅保護。

ML.NET 允許你在.NET 生态系統中建構、訓練和消費 ML 模型,而不需要 ML 或資料科學的背景。ML.NET 可以在任何.NET 運作的地方運作。Windows、Linux、macOS、on-prem、離線場景(如 WinForms 或 WPF 桌面應用)或任何雲端(如 Azure)中。你可以将 ML.NET 用于各種場景,如表 1 所述。

ML.NET 使用自動機器學習(或稱 AutoML)來自動建構和訓練 ML 模型的過程,以根據提供的場景和資料找到最佳模型。你可以通過 AutoML.NET API 或 ML.NET 工具來使用 ML.NET 的 AutoML,其中包括 Visual Studio 中的 Model Builder 和跨平台的 ML.NET CLI,如圖 6 所示。除了訓練最佳模型外,ML.NET 工具還生成在最終使用者.NET 應用程式中消費模型所需的檔案和 C#代碼,該應用程式可以是任何.NET 應用程式(桌面、Web、控制台等)。所有 AutoML 方案都提供了本地訓練選項,圖像分類也允許你利用雲的優勢,使用 Model Builder 中的 Azure ML 進行訓練。

你可以在 Microsoft Docs 中了解更多關于 ML.NET 的資訊,網址是:https://aka.ms/mlnetdocs。

ML 和大資料結合

大資料和 ML 可以很好地結合在一起。讓我們建構一個同時使用 Spark for .NET 和 ML.NET 的管道,以展示大資料和 ML 如何一起工作。Markdown 是一種用于編寫文檔和建立靜态網站的流行語言,它使用的文法不如 HTML 複雜,但提供的格式控制比純文字更多。這是從 .NET 文檔庫中的摘取一段 markdown 檔案内容:

---
title: Welcome to .NET
description: Getting started with the .NET
family of technologies.
ms.date: 12/03/2019
ms.custom: "updateeachrelease"
---

# Welcome to .NET

See [Get started with .NET Core](core/get-started.md) to learn how to create .NET Core apps.

Build many types of apps with .NET, such as cloud ,IoT, and games using free cross-platform tools...
           

破折号之間的部分稱為前頁(front matter),是使用 YAML 描述的有關文檔的中繼資料。以井号(#)開頭的部分是标題。兩個哈希(##)表示二級标題。“ .NET Core 入門”是一個超連結。

我們的目标是處理大量文檔,添加諸如字數和估計的閱讀時間之類的中繼資料,并将相似的文章自動分組在一起。

這是我們将建構的管道:

  • 為每個文檔建立字數統計;
  • 估計每個文檔的閱讀時間;
  • 根據“ TF-IDF”或“術語頻率/反向文檔頻率”為每個文檔建立前 20 個單詞的清單(這将在後面說明)。

第一步是拉取文檔存儲庫和需引用的應用程式。你可以使用任何包含 Markdown 檔案的存儲庫及檔案夾結構。本文使用的示例來自 .NET 文檔存儲庫,可從 https://aka.ms/dot-net-docs 克隆。

為.NET 和 Spark 準備本地環境之後,可以從https://aka.ms/spark-ml-example拉取項目。

解決方案檔案夾包含一個批處理指令(在倉庫中有提供),你可以使用該指令來運作所有步驟。

處理 Markdown

DocRepoParser 項目以遞歸方式周遊存儲庫中的子檔案夾,以收集各文檔有關的中繼資料。Common 項目包含幾個幫助程式類。例如,

FilesHelper

用于所有檔案 I/O。它跟蹤存儲檔案和檔案名的位置,并提供諸如為其他項目讀取檔案的服務。構造函數需要一個标簽(一個唯一辨別工作流的數字)和包含文檔的 repo 或頂級檔案夾的路徑。預設情況下,它在使用者的本地應用程式資料檔案夾下建立一個檔案夾。如有必要,可以将其覆寫。

MarkdownParser

利用

Microsoft.Toolkit.Parsers

解析 Markdown 的庫。該庫有兩個任務:首先,它必須提取标題和子标題;其次,它必須提取單詞。Markdown 檔案以 "塊 "的形式暴露出來,代表标題、連結和其他 Markdown 特征。塊又包含承載文本的“Inlines”。例如,這段代碼通過疊代行和單元格來解析一個 TableBlock,以找到 Inlines。

case TableBlock table:
    table.Rows.SelectMany(r => r.Cells)
        .SelectMany(c => c.Inlines)
        .ForEach(i => candidate = RecurseInline(i, candidate, words, titles));
        break;
           

此代碼提取超連結的文本部分:

case HyperlinkInline hyper:
    if (!string.IsNullOrWhiteSpace(hyper.Text))
    {
        words.Append(hyper.Text.ExtractWords());
    }
    break;
           

結果是一個 CSV 檔案,如下圖所示:

第一步隻是準備要處理的資料。下一步使用 Spark for .NET 作業确定每個文檔的字數,閱讀時間和前 20 個術語。

建構 Spark Job

SparkWordsProcessor

項目用來運作 Spark 作業。雖然該應用程式是一個控制台項目,但它需要 Spark 來運作。

runjob.cmd

批處理指令将作業送出到正确配置的 Windows 計算機上運作。典型作業的模式是建立一個會話或“應用程式”,執行一些邏輯,然後停止會話。

var spark = SparkSession.Builder()
    .AppName(nameof(SparkWordsProcessor))
    .GetOrCreate();
RunJob();
spark.Stop();
           

通過将其路徑傳遞給 Spark 會話,可以輕松讀取上一步的檔案。

var docs = spark.Read().HasHeader().Csv(filesHelper.TempDataFile);
docs.CreateOrReplaceTempView(nameof(docs));
var totalDocs = docs.Count();
           

docs

變量解析為一個

DataFrame

。Data Frame 本質上是一個帶有一組列和一個通用接口的表,用于與資料互動,而不管其底層來源是什麼。可以從其他 data frame 中引用一個 data frame。SparkSQL 也可以用來查詢 data frame。你必須建立一個臨時視圖,該視圖為 data frame 提供别名,以便從 SQL 中引用它。通過

CreateOrReplaceTempView

方法,可以像這樣從 data frame 中查詢行:

SELECT * FROM docs
           

totalDocs

變量檢索文檔中所有行的計數。Spark 提供了一個名為

Split

的将字元串分解為數組的函數。

Explode

函數将每個數組項變成一行:

var words = docs.Select(fileCol,
    Functions.Split(nameof(FileDataParse.Words)
    .AsColumn(), " ")
    .Alias(wordList))
    .Select(fileCol, Functions.Explode(wordList.AsColumn())
    .Alias(word));
           

該查詢為每個單詞或術語生成一行。這個 data frame 是生成術語頻率(TF)或者說每個文檔中每個詞的計數的基礎。

var termFrequency = words
    .GroupBy(fileCol, Functions.Lower(word.AsColumn()).Alias(word))
    .Count()
    .OrderBy(fileCol, count.AsColumn().Desc());
           

Spark 有内置的模型,可以确定“術語頻率/反向文檔頻率”。在這個例子中,你将手動确定術語頻率來示範它是如何計算的。術語在每個文檔中以特定的頻率出現。一篇關于 wizard 的文檔可能有很高的“wizard”一詞計數。同一篇文檔中,"the "和 "is "這兩個詞的出現次數可能也很高。對我們來說,很明顯,“wizard”這個詞更重要,也提供了更多的語境。另一方面,Spark 必須經過訓練才能識别重要的術語。為了确定什麼是真正重要的,我們将總結文檔頻率(document frequency),或者說一個詞在 repo 中所有文檔中出現的次數。這就是“按不同出現次數分組”:

var documentFrequency = words
    .GroupBy(Functions.Lower(word.AsColumn())
    .Alias(word))
    .Agg(Functions.CountDistinct(fileCol)
    .Alias(docFrequency));
           

現在是計算的時候了。一個特殊的方程式可以計算出所謂的反向文檔頻率(inverse document frequency),即 IDF。将總文檔的自然對數(加一)輸入方程,然後除以該詞的文檔頻率(加一)。

static double CalculateIdf(int docFrequency, int totalDocuments) =>
    Math.Log(totalDocuments + 1) / (docFrequency + 1);
           

在所有文檔中出現的詞比出現頻率較低的詞指派低。例如,給定 1000 個文檔,一個在每個文檔中出現的詞與一個隻在少數文檔中出現的詞(約 1 個)相比,IDF 為 0.003。Spark 支援使用者定義的函數,你可以這樣注冊。

spark.Udf().Register<int, int, double>(nameof(CalculateIdf), CalculateIdf);
           

接下來,你可以使用該函數來計算 data frame 中所有單詞的 IDF:

var idfPrep = documentFrequency.Select(word.AsColumn(),
    docFrequency.AsColumn())
        .WithColumn(total, Functions.Lit(totalDocs))
        .WithColumn(inverseDocFrequency,
            Functions.CallUDF(nameof(CalculateIdf), docFrequency.AsColumn(), total.AsColumn()
        )
    );
           

使用文檔頻率 data frame,增加兩列。第一列是文檔的單詞總數量,第二列是調用你的 UDF 來計算 IDF。還有一個步驟,就是确定“重要詞”。重要詞是指在所有文檔中不經常出現,但在目前文檔中經常出現的詞,用 TF-IDF 表示,這隻是 IDF 和 TF 的産物。考慮“is”的情況,IDF 為 0.002,在文檔中的頻率為 50,而“wizard”的 IDF 為 1,頻率為 10。相比頻率為 10 的“wizard”,“is”的 TF-IDF 計算結果為 0.1。這讓 Spark 對重要性有了更好的概念,而不僅僅是原始字數。

到目前為止,你已經使用代碼來定義 data frame。讓我們嘗試一下 SparkSQL。為了計算 TF-IDF,你将文檔頻率 data frame 與反向文檔頻率 data frame 連接配接起來,并建立一個名為

termFreq_inverseDocFreq

的新列。下面是 SparkSQL:

var idfJoin = spark.Sql($"SELECT t.File, d.word, d.{docFrequency}, d.{inverseDocFrequency}, t.count, d.{inverseDocFrequency} * t.count as {termFreq_inverseDocFreq} from {nameof(documentFrequency)} d inner join {nameof(termFrequency)} t on t.word = d.word");
           

探索代碼,看看最後的步驟是如何實作的。這些步驟包括:

到目前為止所描述的所有步驟都為 Spark 提供了一個模闆或定義。像 LINQ 查詢一樣,實際的處理在結果被具體化之前不會發生(比如計算出總文檔數時)。最後一步調用 Collect 來處理和傳回結果,并将其寫入另一個 CSV。然後,你可以使用新檔案作為 ML 模型的輸入,下圖是該檔案的一部分:

Spark for .NET 使你能夠查詢和塑造資料。你在同一個資料源上建立了多個 data frame,然後添加它們以獲得關于重要術語、字數和閱讀時間的洞察。下一步是應用 ML 來自動生成類别。

預測類别

最後一步是對文檔進行分類。

DocMLCategorization

項目包含了 ML.NET 的

Microsoft.ML

包。雖然 Spark 使用的是 data frame,但 data view 在 ML.NET 中提供了類似的概念。

這個例子為 ML.NET 使用了一個單獨的項目,這樣就可以将模型作為一個獨立的步驟進行訓練。對于許多場景,可以直接從你的.NET for Spark 項目中引用 ML.NET,并将 ML 作為同一工作的一部分來執行。

首先,你必須對類進行标記,以便 ML.NET 知道源資料中的哪些列映射到類中的屬性。在

FileData

類使用

LoadColumn

注解,就像這樣:

[LoadColumn(0)]
public string File { get; set; }

[LoadColumn(1)]
public string Title { get; set; }
           

然後,你可以為模型建立上下文,并從上一步中生成的檔案中加載 data view:

var context = new MLContext(seed: 0);
var dataToTrain = context.Data
    .LoadFromTextFile<FileData>(path: filesHelper.ModelTrainingFile, hasHeader: true, allowQuoting: true, separatorChar: ',');
           

ML 算法對數字的處理效果最好,是以文檔中的文本必須轉換為數字向量。ML.NET 為此提供了

FeaturizeText

方法。在一個步驟中,模型分别:

  • 檢測語言
  • 将文本标記為單個單詞或标記
  • 規範化文本,以便對單詞的變體進行标準化和大小寫相似化
  • 将這些術語轉換為一緻的數值或準備處理的“特征向量”

以下代碼将列轉換為特征,然後建立一個結合了多個特征的“Features”列。

var pipeline = context.Transforms.Text.FeaturizeText(
    nameof(FileData.Title).Featurized(),
    nameof(FileData.Title)).Append(context.Transforms.Text.FeaturizeText(nameof(FileData.Subtitle1).Featurized(),
    nameof(FileData.Subtitle1))).Append(context.Transforms.Text.FeaturizeText(nameof(FileData.Subtitle2).Featurized(),
    nameof(FileData.Subtitle2))).Append(context.Transforms.Text.FeaturizeText(nameof(FileData.Subtitle3).Featurized(),
    nameof(FileData.Subtitle3))).Append(context.Transforms.Text.FeaturizeText(nameof(FileData.Subtitle4).Featurized(),
    nameof(FileData.Subtitle4))).Append(context.Transforms.Text.FeaturizeText(nameof(FileData.Subtitle5).Featurized(),
    nameof(FileData.Subtitle5))).Append(context.Transforms.Text.FeaturizeText(nameof(FileData.Top20Words).Featurized(),
    nameof(FileData.Top20Words))).Append(context.Transforms.Concatenate(features, nameof(FileData.Title).Featurized(),
    nameof(FileData.Subtitle1).Featurized(),
    nameof(FileData.Subtitle2).Featurized(),
    nameof(FileData.Subtitle3).Featurized(),
    nameof(FileData.Subtitle4).Featurized(),
    nameof(FileData.Subtitle5).Featurized(),
    nameof(FileData.Top20Words).Featurized())
);
           

此時,資料已經為訓練模型做了适當的準備。訓練是無監督的,這意味着它必須用一個例子來推斷資訊。你沒有将樣本類别輸入到模型中,是以算法必須通過分析特征如何聚類來找出資料的互相關聯。你将使用k-means 聚類算法。該算法使用特征計算文檔之間的“距離”,然後圍繞分組後的文檔“繪制”邊界。該算法涉及随機化,是以兩次運作結果會是不相同的。主要的挑戰是确定訓練的最佳聚類大小。不同的文檔集最好有不同的最佳類别數,但算法需要你在訓練前輸入類别數。

代碼在 2 到 20 個簇之間疊代,以确定最佳大小。對于每次運作,它都會擷取特征資料并應用算法或訓練器。然後,它根據預測模型對現有資料進行轉換。對結果進行評估,以确定每個簇中文檔的平均距離,并選擇平均距離最小的結果。

var options = new KMeansTrainer.Options
{
    FeatureColumnName = features,
    NumberOfClusters = categories,
};

var clusterPipeline = pipeline.Append(context.Clustering.Trainers.KMeans(options));
var model = clusterPipeline.Fit(dataToTrain);
var predictions = model.Transform(dataToTrain);
var metrics = context.Clustering.Evaluate(predictions);
distances.Add(categories, metrics.AverageDistance);
           

經過教育訓練和評估後,你可以儲存最佳模型,并使用它對資料集進行預測。将生成一個輸出檔案以及一個摘要,該摘要顯示有關每個類别的一些中繼資料并在下面列出标題。标題隻是幾個功能之一,是以有時需要仔細研究細節才能使類别有意義。在本地測試中,教程之類的文檔歸于一組,API 文檔歸于另一組,而例外歸于它們自己的組。

ML zip 檔案可與 Prediction Engine 一起用于其他項目中的新資料。

機器學習模型另存為單個 zip 檔案。該檔案可以包含在其他項目中,與 Prediction Engine 一起使用以對新資料進行預測。例如,你可以建立一個 WPF 應用程式,該應用程式允許使用者浏覽目錄,然後加載并使用經過訓練的模型對文檔進行分類,而無需先對其進行訓練。

下一步是什麼

Spark for .NET 計劃與.NET 5 同時在 GA(譯注:GA=General Availability,正式釋出的版本)釋出。請通路 https://aka.ms/spark-net-roadmap 閱讀路線圖和推出功能的計劃。(譯注:.NET 5 正式釋出時間已過,Spark for .NET 已随 .NET 5 正式釋出)

本文着重于本地開發體驗,為了充分利用大資料的力量,你可以将 Spark 作業送出到雲中。有各種各樣的雲主機可以容納 PB 級資料,并為你的工作負載提供數十個核的計算能力。Azure Synapse Analytics 是一項 Azure 服務,旨在承載大量資料,提供用于運作大資料作業的群集,并允許通過基于圖表的儀表盤進行互動式探索。若要了解如何将 Spark for .NET 作業送出到 Azure Synapse,請閱讀官方文檔(https://aka.ms/spark-net-synapse)。

下面這張表列舉了 ML.NET 機器學習的常見任務和場景:

任務 示例場景
分類(基于文本) Classification 将郵件資訊分類為垃圾郵件或非垃圾郵件,或根據内容将調查評論分為不同的組别。
回歸 Regression 根據二手車的品牌、型号、裡程數來預測二手車的價格,或者根據廣告預算來預測産品的銷量。
預測 Forecasting 根據過去的銷售情況來預測未來産品的銷售情況,或天氣預報。
異常檢測 Anomaly detection 檢測産品在一段時間内的銷售高峰或檢測斷電情況。
排名 Ranking 預測搜尋引擎結果的最佳顯示順序,或為使用者的新聞排序。
聚類 Clustering 對客戶進行細分。
推薦 Recommendation 根據使用者之前看的電影向使用者推薦電影,或者推薦經常一起購買的産品。
圖像分類 Image classification 對機器零件的圖像進行分類。
對象檢測 Object detection 檢測汽車圖像上的車牌。

作者:精緻碼農-王亮

出處:http://cnblogs.com/willick

聯系:[email protected]

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。

繼續閱讀