狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

C# 深度學習框架 TorchSharp 原生訓練模型和圖像識別-手寫數字識別

freeflydom
2025年2月10日 10:7 本文熱度 658

教程名稱:使用 C# 入門深度學習

作者:癡者工良

教程地址:https://torch.whuanle.cn

電子書倉庫:https://github.com/whuanle/cs_pytorch

Maomi.Torch 項目倉庫:https://github.com/whuanle/Maomi.Torch

開始使用 Torch

本章內容主要基于 Pytorch 官方入門教程編寫,使用 C# 代碼代替 Python,主要內容包括處理數據、創建模型、優化模型參數、保存模型、加載模型,讀者通過本章內容開始了解 TorchSharp 框架的使用方法。


官方教程:

https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html

準備

創建一個控制臺項目,示例代碼參考 example2.2,通過 nuget 引入以下類庫:

TorchSharp
TorchSharp-cuda-windows
TorchVision
Maomi.Torch

首先添加以下代碼,查找最適合當前設備的工作方式,主要是選擇 GPU 開發框架,例如 CUDA、MPS,CPU,有 GPU 就用 GPU,沒有 GPU 降級為 CPU。

using Maomi.Torch;
Device defaultDevice = MM.GetOpTimalDevice();
torch.set_default_device(defaultDevice);
Console.WriteLine("當前正在使用 {defaultDevice}");

下載數據集

訓練模型最重要的一步是準備數據,但是準備數據集是一個非常繁雜和耗時間的事情,對于初學者來說也不現實,所以 Pytorch 官方在框架集成了一些常見的數據集,開發者可以直接通過 API 使用這些提前處理好的數據集和標簽。

Pytorch 使用 torch.utils.data.Dataset 表示數據集抽象接口,存儲了數據集的樣本和對應標簽;torch.utils.data.DataLoader 表示加載數據集的抽象接口,主要是提供了迭代器。這兩套接口是非常重要的,對于開發者自定義的數據集,需要實現這兩套接口,自定義加載數據集方式。


Pytorch 有三大領域的類庫,分別是 TorchText、TorchVision、TorchAudio,這三個庫都自帶了一些常用開源數據集,但是 .NET 里社區倉庫只提供了 TorchVision,生態嚴重落后于 Pytorch。TorchVision 是一個工具集,可以從 Fashion-MNIST 等下載數據集以及進行一些數據類型轉換等功能。


在本章中,使用的數據集叫 FashionMNIST,Pytorch 還提供了很多數據集,感興趣的讀者參考:https://pytorch.org/vision/stable/datasets.html


現在開始講解如何通過 TorchSharp 框架加載 FashionMNIST 數據集,首先添加引用:

using TorchSharp;
using static TorchSharp.torch;
using datasets = TorchSharp.torchvision.datasets;
using transforms = TorchSharp.torchvision.transforms;

然后通過接口加載訓練數據集和測試數據集:

// 指定訓練數據集
var training_data = datasets.FashionMNIST(
    root: "data",   // 數據集在那個目錄下
    train: true,    // 加載該數據集,用于訓練
    download: true, // 如果數據集不存在,是否下載
    target_transform: transforms.ConvertImageDtype(ScalarType.Float32) // 指定特征和標簽轉換,將標簽轉換為Float32
    );
// 指定測試數據集
var test_data = datasets.FashionMNIST(
    root: "data",   // 數據集在那個目錄下
    train: false,    // 加載該數據集,用于訓練
    download: true, // 如果數據集不存在,是否下載
    target_transform: transforms.ConvertImageDtype(ScalarType.Float32) // 指定特征和標簽轉換,將標簽轉換為Float32
    );

部分參數解釋如下:

  • root 是存放訓練/測試數據的路徑。
  • train 指定訓練或測試數據集。
  • download=True 如果 root 中沒有數據,則從互聯網下載數據。
  • transform 和 target_transform 指定特征和標簽轉換。

注意,與 Python 版本有所差異, Pytorch 官方給出了 ToTensor() 函數用于將圖像轉換為 torch.Tensor 張量類型,但是由于 C# 版本并沒有這個函數,因此只能手動指定一個轉換器。


啟動項目,會自動下載數據集,接著在程序運行目錄下會自動創建一個 data 目錄,里面是數據集文件,包括用于訓練的數據和測試的數據集。


文件內容如下所示,子目錄 test_data 里面的是測試數據集,用于檢查模型訓練情況和優化。

│   t10k-images-idx3-ubyte.gz
│   t10k-labels-idx1-ubyte.gz
│   train-images-idx3-ubyte.gz
│   train-labels-idx1-ubyte.gz
│
└───test_data
        t10k-images-idx3-ubyte
        t10k-labels-idx1-ubyte
        train-images-idx3-ubyte
        train-labels-idx1-ubyte

顯示圖片

數據集是 Dataset 類型,繼承了 Dataset<Dictionary<string, Tensor>> 類型,Dataset 本質是列表,我們把 Dataset 列表的 item 稱為數據,每個 item 都是一個字典類型,每個字典由 data、label 兩個 key 組成。

在上一節,已經編寫好如何加載數據集,將訓練數據和測試數據分開加載,為了了解 Dataset ,讀者可以通過以下代碼將數據集的結構打印到控制臺。

for (int i = 0; i < training_data.Count; i++)
{
    var dic = training_data.GetTensor(i);
    var img = dic["data"];
    var label = dic["label"];
    label.print();
}

通過觀察控制臺,可以知道,每個數據元素都是一個字典,每個字典由 data、label 兩個 key 組成,dic["data"] 是一個圖片,而 label 就是表示該圖片的文本值是什么。


Maomi.Torch 框架提供了將張量轉換為圖片并顯示的方法,例如下面在窗口顯示數據集前面的三張圖片:

for (int i = 0; i < training_data.Count; i++)
{
    var dic = training_data.GetTensor(i);
    var img = dic["data"];
    var label = dic["label"];
    if (i > 2)
    {
        break;
    }
    img.ShowImage();
}

使用 Maomi.ScottPlot.Winforms 庫,還可以通過 img.ShowImageToForm() 接口通過窗口的形式顯示圖片。


你也可以直接轉存為圖片:

img.SavePng("data/{i}.png");

加載數據集

由于 FashionMNIST 數據集有 6 萬張圖片,一次性加載所有圖片比較消耗內存,并且一次性訓練對 GPU 的要求也很高,因此我們需要分批處理數據集。


torch.utils.data 中有數據加載器,可以幫助我們分批加載圖片集到內存中,開發時使用迭代器直接讀取,不需要關注分批情況。

如下面所示,分批加載數據集,批處理大小是 64 張圖片。

// 分批加載圖像,打亂順序
var train_loader = torch.utils.data.DataLoader(training_data, batchSize: 64, shuffle: true, device: defaultDevice);
// 分批加載圖像,不打亂順序
var test_loader = torch.utils.data.DataLoader(test_data, batchSize: 64, shuffle: false, device: defaultDevice);

注意,分批是在 DataLoader 內部發生的,我們可以理解為緩沖區大小,對于開發者來說,并不需要關注分批情況。

定義網絡

接下來定義一個神經網絡,神經網絡有多個層,通過神經網絡來訓練數據,通過數據的訓練可以的出參數、權重等信息,這些信息會被保存到模型中,加載模型時,必須要有對應的網絡結構,比如神經網絡的層數要相同、每層的結構一致。

該網絡通過接受 28*28 大小的圖片,經過處理后輸出 10 個分類值,每個分類結果都帶有其可能的概率,概率最高的就是識別結果。


將以下代碼存儲到 NeuralNetwork.cs 中。

using TorchSharp.Modules;
using static TorchSharp.torch;
using nn = TorchSharp.torch.nn;
public class NeuralNetwork : nn.Module<Tensor, Tensor>
{
    // 傳遞給基類的參數是模型的名稱
    public NeuralNetwork() : base(nameof(NeuralNetwork))
    {
        flatten = nn.Flatten();
        linear_relu_stack = nn.Sequential(
            nn.Linear(28 * 28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10));
        // C# 版本需要調用這個函數,將模型的組件注冊到模型中
        RegisterComponents();
    }
    Flatten flatten;
    Sequential linear_relu_stack;
    public override Tensor forward(Tensor input)
    {
        // 將輸入一層層處理并傳遞給下一層
        var x = flatten.call(input);
        var logits = linear_relu_stack.call(x);
        return logits;
    }
}

注意,網絡中只能定義字段,不要定義屬性;不要使用 _ 開頭定義字段;

然后繼續在 Program 里繼續編寫代碼,初始化神經網絡,并使用 GPU 來加載網絡。

var model = new NeuralNetwork();
model.to(defaultDevice);

優化模型參數

為了訓練模型,需要定義一個損失函數和一個優化器,損失函數的主要作用是衡量模型的預測結果與真實標簽之間的差異,即誤差或損失,有了損失函數后,通過優化器可以指導模型參數的調整,使預測結果能夠逐步靠近真實值,從而提高模型的性能。Pytorch 自帶很多損失函數,這里使用計算交叉熵損失的損失函數。

// 定義損失函數、優化器和學習率
var loss_fn = nn.CrossEntropyLoss();
var optimizer = torch.optim.SGD(model.parameters(), learningRate : 1e-3);

同時,優化器也很重要,是用于調整模型參數以最小化損失函數的模塊。

因為損失函數比較多,但是優化器就那么幾個,所以這里簡單列一下 Pytorch 中自帶的一些優化器。

  • SGD(隨機梯度下降):通過按照損失函數的梯度進行線性步長更新權重;
  • Adam(自適應矩估計) :基于一階和二階矩估計的優化算法,它能自適應地調整學習率,對大多數問題效果較好;
  • RMSprop:適用于處理非平穩目標,能夠自動進行學習率的調整;
  • AdamW(帶權重衰減的 Adam) :在 Adam 的基礎上添加了權重衰減(weight decay),防止過擬合。

訓練模型

接下來講解訓練模型的步驟,如下代碼所示。

下面是詳細步驟:

  • 每讀取一張圖片,就使用神經網絡進行識別(.call() 函數),pred 為識別結果;
  • 通過損失函數判斷網絡的識別結果和標簽值的誤差;
  • 通過損失函數反向傳播,計算網絡的梯度等;
  • 通過 SGD 優化器,按照損失函數的梯度進行線性步長更新權重,optimizer.step() 會調整模型的權重,根據計算出來的梯度來更新模型的參數,使模型逐步接近優化目標。
  • 因為數據是分批處理的,因此計算當前批次的梯度后,需要使用 optimizer.zero_grad() 重置當前所有梯度。
  • 計算訓練成果,即打印當前訓練進度和損失值。
static void Train(DataLoader dataloader, NeuralNetwork model, CrossEntropyLoss loss_fn, SGD optimizer)
{
    var size = dataloader.dataset.Count;
    model.train();
    int batch = 0;
    foreach (var item in dataloader)
    {
        var x = item["data"];
        var y = item["label"];
        // 第一步
        // 訓練當前圖片
        var pred = model.call(x);
        // 通過損失函數得出與真實結果的誤差
        var loss = loss_fn.call(pred, y);
        // 第二步,反向傳播
        loss.backward();
        // 計算梯度并優化參數
        optimizer.step();
        // 清空優化器當前的梯度
        optimizer.zero_grad();
        // 每 100 次打印損失值和當前訓練的圖片數量
        if (batch % 100 == 0)
        {
            loss = loss.item<float>();
            
            // Pytorch 框架會在 x.shape[0] 存儲當前批的位置
            var current = (batch + 1) * x.shape[0];
            
            Console.WriteLine("loss: {loss.item<float>(),7}  [{current,5}/{size,5}]");
        }
        batch++;
    }
}

torch.Tensor 類型的 .shape 屬性比較特殊,是一個數組類型,主要用于存儲當前類型的結構,要結合上下文才能判斷,例如在當前訓練中,x.shape 值是 [64,1,28,28],shape[1] 是圖像的通道,1 是灰色,3 是彩色(RGB三通道);shape[2]、shape[3] 分別是圖像的長度和高度。


通過上面步驟可以看出,“訓練” 是一個字面意思,跟人類的學習不一樣,這里是先使用模型識別一個圖片,然后計算誤差,更新模型參數和權重,然后進入下一次調整。


訓練模型的同時,我們還需要評估模型的準確率等信息,評估時需要使用測試圖片來驗證訓練結果。


static void Test(DataLoader dataloader, NeuralNetwork model, CrossEntropyLoss loss_fn)
{
    var size = (int)dataloader.dataset.Count;
    var num_batches = (int)dataloader.Count;
    // 將模型設置為評估模式
    model.eval();
    var test_loss = 0F;
    var correct = 0F;
    using (var n = torch.no_grad())
    {
        foreach (var item in dataloader)
        {
            var x = item["data"];
            var y = item["label"];
            // 使用已訓練的參數預測測試數據
            var pred = model.call(x);
            // 計算損失值
            test_loss += loss_fn.call(pred, y).item<float>();
            correct += (pred.argmax(1) == y).type(ScalarType.Float32).sum().item<float>();
        }
    }
    test_loss /= num_batches;
    correct /= size;
    Console.WriteLine("Test Error: \n Accuracy: {(100 * correct):F1}%, Avg loss: {test_loss:F8} \n");
}

下圖是后面訓練打印的日志,可以看出準確率是逐步上升的。


在 Program 中添加訓練代碼,我們使用訓練數據集進行五輪訓練,每輪訓練都輸出識別結果。

// 訓練的輪數
var epochs = 5;
foreach (var epoch in Enumerable.Range(0, epochs))
{
    Console.WriteLine("Epoch {epoch + 1}\n-------------------------------");
    Train(train_loader, model, loss_fn, optimizer);
    Test(train_loader, model, loss_fn);
}
Console.WriteLine("Done!");

保存和加載模型

經過訓練后的模型,可以直接保存和加載,代碼很簡單,如下所示:

model.save("model.dat");
Console.WriteLine("Saved PyTorch Model State to model.dat");
model.load("model.dat");

使用模型識別圖片

要使用模型識別圖片,只需要使用 var pred = model.call(x); 即可,但是因為模型并不能直接輸出識別結果,而是根據網絡結構輸出到每個神經元中,每個神經元都表示當前概率。在前面定義的網絡中,nn.Linear(512, 10)) 會輸出 10 個分類結果,每個分類結果都帶有概率,那么我們將概率最高的一個結果拿出來,就相當于圖片的識別結果了。

代碼如下所示,步驟講解如下:

  • 因為模型和網絡并不使用字符串表示每個分類結果,所以需要手動配置分類表。
  • 然后從測試數據集中選取第一個圖片和標簽,識別圖片并獲得序號。
  • 從分類字符串中通過序號獲得分類名稱。
var classes = new string[] {
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
};
// 設置為評估模式
model.eval();
// 加載測試數據中的第一個圖片以及其標簽
var x = test_data.GetTensor(0)["data"];
var y = test_data.GetTensor(0)["label"];
using (torch.no_grad())
{
    x = x.to(defaultDevice);
    var pred = model.call(x);
    var predicted = classes[pred[0].argmax(0).ToInt32()];
    var actual = classes[y.ToInt32()];
    Console.WriteLine("Predicted: \"{predicted}\", Actual: \"{actual}\"");
}

當然,使用 Maomi.Torch 的接口,可以很方便讀取圖片使用模型識別:

var img = MM.LoadImage("0.png");
using (torch.no_grad())
{
    img = img.to(defaultDevice);
    var pred = model.call(img);
    // 轉換為歸一化的概率
    var array = torch.nn.functional.softmax(pred, dim: 0);
    var max = array.ToFloat32Array().Max();
    var predicted = classes[pred[0].argmax(0).ToInt32()];
    
    Console.WriteLine("識別結果 {predicted},概率 {max * 100}%");
}

轉自https://www.cnblogs.com/whuanle/p/18700127


該文章在 2025/2/10 10:07:55 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved