日本xxxx18视频在线观看-日本xxxx1819-日本xxxwww在线观看-日本xxx-日本xx-日本www在线视频

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

C# 深度學(xué)習(xí)框架 TorchSharp 原生訓(xùn)練模型和圖像識別-手寫數(shù)字識別

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

教程名稱:使用 C# 入門深度學(xué)習(xí)

作者:癡者工良

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

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

Maomi.Torch 項(xiàng)目倉庫:https://github.com/whuanle/Maomi.Torch

開始使用 Torch

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


官方教程:

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

準(zhǔn)備

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

TorchSharp
TorchSharp-cuda-windows
TorchVision
Maomi.Torch

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

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

下載數(shù)據(jù)集

訓(xùn)練模型最重要的一步是準(zhǔn)備數(shù)據(jù),但是準(zhǔn)備數(shù)據(jù)集是一個非常繁雜和耗時間的事情,對于初學(xué)者來說也不現(xiàn)實(shí),所以 Pytorch 官方在框架集成了一些常見的數(shù)據(jù)集,開發(fā)者可以直接通過 API 使用這些提前處理好的數(shù)據(jù)集和標(biāo)簽。

Pytorch 使用 torch.utils.data.Dataset 表示數(shù)據(jù)集抽象接口,存儲了數(shù)據(jù)集的樣本和對應(yīng)標(biāo)簽;torch.utils.data.DataLoader 表示加載數(shù)據(jù)集的抽象接口,主要是提供了迭代器。這兩套接口是非常重要的,對于開發(fā)者自定義的數(shù)據(jù)集,需要實(shí)現(xiàn)這兩套接口,自定義加載數(shù)據(jù)集方式。


Pytorch 有三大領(lǐng)域的類庫,分別是 TorchText、TorchVision、TorchAudio,這三個庫都自帶了一些常用開源數(shù)據(jù)集,但是 .NET 里社區(qū)倉庫只提供了 TorchVision,生態(tài)嚴(yán)重落后于 Pytorch。TorchVision 是一個工具集,可以從 Fashion-MNIST 等下載數(shù)據(jù)集以及進(jìn)行一些數(shù)據(jù)類型轉(zhuǎn)換等功能。


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


現(xiàn)在開始講解如何通過 TorchSharp 框架加載 FashionMNIST 數(shù)據(jù)集,首先添加引用:

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

然后通過接口加載訓(xùn)練數(shù)據(jù)集和測試數(shù)據(jù)集:

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

部分參數(shù)解釋如下:

  • root 是存放訓(xùn)練/測試數(shù)據(jù)的路徑。
  • train 指定訓(xùn)練或測試數(shù)據(jù)集。
  • download=True 如果 root 中沒有數(shù)據(jù),則從互聯(lián)網(wǎng)下載數(shù)據(jù)。
  • transform 和 target_transform 指定特征和標(biāo)簽轉(zhuǎn)換。

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


啟動項(xiàng)目,會自動下載數(shù)據(jù)集,接著在程序運(yùn)行目錄下會自動創(chuàng)建一個 data 目錄,里面是數(shù)據(jù)集文件,包括用于訓(xùn)練的數(shù)據(jù)和測試的數(shù)據(jù)集。


文件內(nèi)容如下所示,子目錄 test_data 里面的是測試數(shù)據(jù)集,用于檢查模型訓(xùn)練情況和優(yōu)化。

│   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

顯示圖片

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

在上一節(jié),已經(jīng)編寫好如何加載數(shù)據(jù)集,將訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)分開加載,為了了解 Dataset ,讀者可以通過以下代碼將數(shù)據(jù)集的結(jié)構(gòu)打印到控制臺。

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();
}

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


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

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() 接口通過窗口的形式顯示圖片。


你也可以直接轉(zhuǎn)存為圖片:

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

加載數(shù)據(jù)集

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


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

如下面所示,分批加載數(shù)據(jù)集,批處理大小是 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 內(nèi)部發(fā)生的,我們可以理解為緩沖區(qū)大小,對于開發(fā)者來說,并不需要關(guān)注分批情況。

定義網(wǎng)絡(luò)

接下來定義一個神經(jīng)網(wǎng)絡(luò),神經(jīng)網(wǎng)絡(luò)有多個層,通過神經(jīng)網(wǎng)絡(luò)來訓(xùn)練數(shù)據(jù),通過數(shù)據(jù)的訓(xùn)練可以的出參數(shù)、權(quán)重等信息,這些信息會被保存到模型中,加載模型時,必須要有對應(yīng)的網(wǎng)絡(luò)結(jié)構(gòu),比如神經(jīng)網(wǎng)絡(luò)的層數(shù)要相同、每層的結(jié)構(gòu)一致。

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


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

using TorchSharp.Modules;
using static TorchSharp.torch;
using nn = TorchSharp.torch.nn;
public class NeuralNetwork : nn.Module<Tensor, Tensor>
{
    // 傳遞給基類的參數(shù)是模型的名稱
    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# 版本需要調(diào)用這個函數(shù),將模型的組件注冊到模型中
        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;
    }
}

注意,網(wǎng)絡(luò)中只能定義字段,不要定義屬性;不要使用 _ 開頭定義字段;

然后繼續(xù)在 Program 里繼續(xù)編寫代碼,初始化神經(jīng)網(wǎng)絡(luò),并使用 GPU 來加載網(wǎng)絡(luò)。

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

優(yōu)化模型參數(shù)

為了訓(xùn)練模型,需要定義一個損失函數(shù)和一個優(yōu)化器,損失函數(shù)的主要作用是衡量模型的預(yù)測結(jié)果與真實(shí)標(biāo)簽之間的差異,即誤差或損失,有了損失函數(shù)后,通過優(yōu)化器可以指導(dǎo)模型參數(shù)的調(diào)整,使預(yù)測結(jié)果能夠逐步靠近真實(shí)值,從而提高模型的性能。Pytorch 自帶很多損失函數(shù),這里使用計算交叉熵?fù)p失的損失函數(shù)。

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

同時,優(yōu)化器也很重要,是用于調(diào)整模型參數(shù)以最小化損失函數(shù)的模塊。

因?yàn)閾p失函數(shù)比較多,但是優(yōu)化器就那么幾個,所以這里簡單列一下 Pytorch 中自帶的一些優(yōu)化器。

  • SGD(隨機(jī)梯度下降):通過按照損失函數(shù)的梯度進(jìn)行線性步長更新權(quán)重;
  • Adam(自適應(yīng)矩估計) :基于一階和二階矩估計的優(yōu)化算法,它能自適應(yīng)地調(diào)整學(xué)習(xí)率,對大多數(shù)問題效果較好;
  • RMSprop:適用于處理非平穩(wěn)目標(biāo),能夠自動進(jìn)行學(xué)習(xí)率的調(diào)整;
  • AdamW(帶權(quán)重衰減的 Adam) :在 Adam 的基礎(chǔ)上添加了權(quán)重衰減(weight decay),防止過擬合。

訓(xùn)練模型

接下來講解訓(xùn)練模型的步驟,如下代碼所示。

下面是詳細(xì)步驟:

  • 每讀取一張圖片,就使用神經(jīng)網(wǎng)絡(luò)進(jìn)行識別(.call() 函數(shù)),pred 為識別結(jié)果
  • 通過損失函數(shù)判斷網(wǎng)絡(luò)的識別結(jié)果和標(biāo)簽值的誤差;
  • 通過損失函數(shù)反向傳播,計算網(wǎng)絡(luò)的梯度等;
  • 通過 SGD 優(yōu)化器,按照損失函數(shù)的梯度進(jìn)行線性步長更新權(quán)重,optimizer.step() 會調(diào)整模型的權(quán)重,根據(jù)計算出來的梯度來更新模型的參數(shù),使模型逐步接近優(yōu)化目標(biāo)。
  • 因?yàn)閿?shù)據(jù)是分批處理的,因此計算當(dāng)前批次的梯度后,需要使用 optimizer.zero_grad() 重置當(dāng)前所有梯度。
  • 計算訓(xùn)練成果,即打印當(dāng)前訓(xùn)練進(jìn)度和損失值。
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"];
        // 第一步
        // 訓(xùn)練當(dāng)前圖片
        var pred = model.call(x);
        // 通過損失函數(shù)得出與真實(shí)結(jié)果的誤差
        var loss = loss_fn.call(pred, y);
        // 第二步,反向傳播
        loss.backward();
        // 計算梯度并優(yōu)化參數(shù)
        optimizer.step();
        // 清空優(yōu)化器當(dāng)前的梯度
        optimizer.zero_grad();
        // 每 100 次打印損失值和當(dāng)前訓(xùn)練的圖片數(shù)量
        if (batch % 100 == 0)
        {
            loss = loss.item<float>();
            
            // Pytorch 框架會在 x.shape[0] 存儲當(dāng)前批的位置
            var current = (batch + 1) * x.shape[0];
            
            Console.WriteLine("loss: {loss.item<float>(),7}  [{current,5}/{size,5}]");
        }
        batch++;
    }
}

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


通過上面步驟可以看出,“訓(xùn)練” 是一個字面意思,跟人類的學(xué)習(xí)不一樣,這里是先使用模型識別一個圖片,然后計算誤差,更新模型參數(shù)和權(quán)重,然后進(jìn)入下一次調(diào)整。


訓(xùn)練模型的同時,我們還需要評估模型的準(zhǔn)確率等信息,評估時需要使用測試圖片來驗(yàn)證訓(xùn)練結(jié)果。


static void Test(DataLoader dataloader, NeuralNetwork model, CrossEntropyLoss loss_fn)
{
    var size = (int)dataloader.dataset.Count;
    var num_batches = (int)dataloader.Count;
    // 將模型設(shè)置為評估模式
    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"];
            // 使用已訓(xùn)練的參數(shù)預(yù)測測試數(shù)據(jù)
            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");
}

下圖是后面訓(xùn)練打印的日志,可以看出準(zhǔn)確率是逐步上升的。


在 Program 中添加訓(xùn)練代碼,我們使用訓(xùn)練數(shù)據(jù)集進(jìn)行五輪訓(xùn)練,每輪訓(xùn)練都輸出識別結(jié)果。

// 訓(xùn)練的輪數(shù)
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!");

保存和加載模型

經(jīng)過訓(xùn)練后的模型,可以直接保存和加載,代碼很簡單,如下所示:

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

使用模型識別圖片

要使用模型識別圖片,只需要使用 var pred = model.call(x); 即可,但是因?yàn)槟P筒⒉荒苤苯虞敵鲎R別結(jié)果,而是根據(jù)網(wǎng)絡(luò)結(jié)構(gòu)輸出到每個神經(jīng)元中,每個神經(jīng)元都表示當(dāng)前概率。在前面定義的網(wǎng)絡(luò)中,nn.Linear(512, 10)) 會輸出 10 個分類結(jié)果,每個分類結(jié)果都帶有概率,那么我們將概率最高的一個結(jié)果拿出來,就相當(dāng)于圖片的識別結(jié)果了。

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

  • 因?yàn)槟P秃途W(wǎng)絡(luò)并不使用字符串表示每個分類結(jié)果,所以需要手動配置分類表。
  • 然后從測試數(shù)據(jù)集中選取第一個圖片和標(biāo)簽,識別圖片并獲得序號。
  • 從分類字符串中通過序號獲得分類名稱。
var classes = new string[] {
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
};
// 設(shè)置為評估模式
model.eval();
// 加載測試數(shù)據(jù)中的第一個圖片以及其標(biāo)簽
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}\"");
}

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

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

轉(zhuǎn)自https://www.cnblogs.com/whuanle/p/18700127


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

主站蜘蛛池模板: 国产精品三级国语在线看 | 成年人免费的 | 国产精品丝袜亚洲熟女 | 337p西西人体大胆瓣开下部 | 国产午夜场免费视频在线播放 | 在线观看视频在线播放9 | 精品国产美女一级a爱 | 日韩精品欧美激情亚洲综合 | 欧美日韩一区二区三区不卡在线 | 精品字幕亚洲一区二区三区 | 国产卡二卡三卡四卡 | 欧美三级伦理 | 国产在线原创七七欠欠色综 | 国产在线观看一区二区三区精品 | 国产五月综合网 | 男人的天堂国产综合 | 亚洲精品免费日日日夜夜夜夜 | 女女同性一区二区三区在线 | 国产美腿91肉丝袜在线播放 | 制服丝袜中文字 | 国产午夜亚洲精品午夜鲁丝片 | 动漫h片在线播放免费高清 国产精品合集一区二区 | 国产自产精品一区 | 另类亚洲欧美视频在线观看 | 99精品免费 | 国产乱子伦一区二区三区黑人 | 免费国产乱码一二三区 | 国产视频最新地址发布 | 成人国产一区二区精品小说 | 97se国产在线 | 欧美在线观看网站 | 国产又滑又嫩又白又爽 | 欧洲影视 | 蜜芽tv| 精品国产自在在线在线观看 | 中文字幕亚洲综合小综合在线 | 欧美a视频 | 殴美在线观看乱操 | 日韩欧美一区二区三区国产 | 日韩欧美亚洲综合久 | 日本三级在线播放线观看免 |