<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

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

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

      作者:癡者工良

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

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

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

      開始使用 Torch

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


      官方教程:

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

      準備

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

      TorchSharp
      TorchSharp-cuda-windows
      TorchVision
      Maomi.Torch
      

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

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

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

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

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


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


      在本章中,使用的數(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;
      

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

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

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

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

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


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

      image-20241202120839339


      文件內容如下所示,子目錄 test_data 里面的是測試數(shù)據(jù)集,用于檢查模型訓練情況和優(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 本質是列表,我們把 Dataset 列表的 item 稱為數(shù)據(jù),每個 item 都是一個字典類型,每個字典由 data、label 兩個 key 組成

      在上一節(jié),已經編寫好如何加載數(shù)據(jù)集,將訓練數(shù)據(jù)和測試數(shù)據(jù)分開加載,為了了解 Dataset ,讀者可以通過以下代碼將數(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"];
          label.print();
      }
      

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


      Maomi.Torch 框架提供了將張量轉換為圖片并顯示的方法,例如下面在窗口顯示數(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() 接口通過窗口的形式顯示圖片。


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

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

      image-20250204215615584

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

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


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

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

      定義網絡

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

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


      將以下代碼存儲到 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# 版本需要調用這個函數(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;
          }
      }
      

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

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

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

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

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

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

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

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

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

      訓練模型

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

      下面是詳細步驟:

      • 每讀取一張圖片,就使用神經網絡進行識別(.call() 函數(shù)),pred識別結果
      • 通過損失函數(shù)判斷網絡的識別結果和標簽值的誤差;
      • 通過損失函數(shù)反向傳播,計算網絡的梯度等;
      • 通過 SGD 優(yōu)化器,按照損失函數(shù)的梯度進行線性步長更新權重,optimizer.step() 會調整模型的權重,根據(jù)計算出來的梯度來更新模型的參數(shù),使模型逐步接近優(yōu)化目標。
      • 因為數(shù)據(jù)是分批處理的,因此計算當前批次的梯度后,需要使用 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);
      
              // 通過損失函數(shù)得出與真實結果的誤差
              var loss = loss_fn.call(pred, y);
      
              // 第二步,反向傳播
              loss.backward();
      
              // 計算梯度并優(yōu)化參數(shù)
              optimizer.step();
      
              // 清空優(yōu)化器當前的梯度
              optimizer.zero_grad();
      
              // 每 100 次打印損失值和當前訓練的圖片數(shù)量
              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 屬性比較特殊,是一個數(shù)組類型,主要用于存儲當前類型的結構,要結合上下文才能判斷,例如在當前訓練中,x.shape 值是 [64,1,28,28]shape[1] 是圖像的通道,1 是灰色,3 是彩色(RGB三通道);shape[2]shape[3] 分別是圖像的長度和高度。


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


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


      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"];
      
                  // 使用已訓練的參數(shù)預測測試數(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");
      }
      

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

      image-20250205090040316


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

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

      保存和加載模型

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

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

      使用模型識別圖片

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

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

      • 因為模型和網絡并不使用字符串表示每個分類結果,所以需要手動配置分類表。
      • 然后從測試數(shù)據(jù)集中選取第一個圖片和標簽,識別圖片并獲得序號。
      • 從分類字符串中通過序號獲得分類名稱。
      var classes = new string[] {
          "T-shirt/top",
          "Trouser",
          "Pullover",
          "Dress",
          "Coat",
          "Sandal",
          "Shirt",
          "Sneaker",
          "Bag",
          "Ankle boot",
      };
      
      // 設置為評估模式
      model.eval();
      
      // 加載測試數(shù)據(jù)中的第一個圖片以及其標簽
      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}%");
      }
      
      posted @ 2025-02-06 08:36  癡者工良  閱讀(4478)  評論(10)    收藏  舉報
      主站蜘蛛池模板: 日韩精品一区二区高清视频| 亚洲狠狠婷婷综合久久久久图片| 久久无码中文字幕免费影院蜜桃| 精品在免费线中文字幕久久| 麻豆亚洲精品一区二区| 97人妻人人揉人人躁人人| 国产v综合v亚洲欧美大天堂| 亚洲一区二区三区在线| 亚洲色欲色欱WWW在线| 制服丝袜中文字幕在线| 亚洲精品入口一区二区乱| 亚洲区成人综合一区二区| 日韩不卡无码精品一区高清视频| 欧美另类精品xxxx人妖| 免费观看国产女人高潮视频| 成人午夜av在线播放| 青青草无码免费一二三区| 亚洲国产成人久久77| 黄色一级片一区二区三区| 日本亚洲一区二区精品| 国产午夜福利免费入口| 亚洲线精品一区二区三八戒 | 人妻精品动漫H无码中字| av中文无码乱人伦在线观看| 久久精品丝袜高跟鞋| 91亚洲国产成人久久精品| 色伦专区97中文字幕| 另类 专区 欧美 制服| 久久精品国产亚洲av品| 国产午夜91福利一区二区| 一区二区三区精品不卡| 亚洲AV日韩AV激情亚洲| 另类图片亚洲人妻中文无码 | 国产一区二区在线观看粉嫩| 国内精品久久久久影视| 女人腿张开让男人桶爽| 精品国产乱来一区二区三区| 熟妇的奶头又大又长奶水视频| 欧美人与动牲交A免费观看| 亚洲一区二区三区自拍高清| 99久久免费精品色老|