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

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

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

      吳恩達深度學習課程一:神經網絡和深度學習 第二周:神經網絡基礎 課后習題和代碼實踐

      此分類用于記錄吳恩達深度學習課程的學習筆記。
      課程相關信息鏈接如下:

      1. 原課程視頻鏈接:[雙語字幕]吳恩達深度學習deeplearning.ai
      2. github課程資料,含課件與筆記:吳恩達深度學習教學資料
      3. 課程配套練習(中英)與答案:吳恩達深度學習課后習題與答案

      本篇為第一課第二周的課程習題部分的講解。


      1.理論習題

      【中英】【吳恩達課后測驗】Course 1 - 神經網絡和深度學習 - 第二周測驗
      這是本周理論部分的習題和相應解析,博主已經做好了翻譯和解析工作,因此便不再重復,但有一題涉及到代碼中矩陣的兩種乘法,我們把這道題單獨拿出看一下:


      看一下下面的代碼:

      a = np.random.randn(3, 3)
      b = np.random.randn(3, 1)
      c = a * b
      

      請問c的維度會是多少?


      這便是這道題的內容,按照我們的學習的線代知識,我們應該會得到結果為 \((3,1)\) ,但實際上這道題的答案是 \((3,3)\)

      這便涉及到在python代碼中對矩陣的兩類乘法,以題里的量為例:

      c = a * b #Hadamard積(逐元素積)
      c = a @ b #內積
      

      這是兩種不同的乘法,也會帶來不同結果,我們用實際代碼證明一下:

      import numpy as np
      # 定義矩陣 a 和列向量 b
      a = np.array([[1, 1, 1],
                    [1, 1, 1],
                    [1, 1, 1]])
      b = np.array([[1],
                    [2],
                    [3]])
      # 逐元素乘法 (Hadamard積)
      c_hadamard = a * b
      print("Hadamard積結果 (a * b):")
      print(c_hadamard)
      print("Hadamard積的維度:", c_hadamard.shape)
      # 矩陣乘法 (內積)
      c_dot = a @ b
      print("\n矩陣乘法結果 (a @ b):")
      print(c_dot)
      print("矩陣乘法的維度:", c_dot.shape)
      

      直接看結果:

      Pasted image 20251014103116

      現在來解釋一下,我們可以通過結果得知,我們在線代中學習的內積運算在代碼中被定義為 \(@\)
      而直接使用 \(*\) 符號來運算,實際上是對 \(*\) 前的矩陣的每個元素分別做乘法。
      結合廣播機制,我們可以知道 \(c = a * b\) 的實際運算過程是這樣的:

      1. 經過廣播機制, \(b\) 的內容以相同的形式向下復制兩行,讓維度和 \(a\) 一樣變為 \((3,3)\)
      2. \(a和b\) 的相同位置相乘得到結果放入 \(c\) 的相同位置,用公式來說就是:

      \[c_{ij} = a_{ij}*b_{ij} \]

      現在我們可以總結一下兩種乘法的區別如下:

      1. \(@\) 是線代中的內積乘法,當兩個矩陣的維度為\((m,n),(n,k)\) 即前一矩陣的列數等于后一矩陣的行數時才可進行運算,內積會改變矩陣的維度,結果矩陣的維度為 \((m,k)\)
      2. \(*\) 是逐個乘法,實際上是對 \(*\) 前的矩陣的每個元素分別做乘法,當兩個矩陣使用逐個乘法運算時,python會自動將后一矩陣廣播至前一矩陣大小,Hadamard積不會改變矩陣維度大小,結果矩陣和前一矩陣維度相同。

      2.編程題:實現具有神經網絡思維的Logistic回歸

      【中文】【吳恩達課后編程作業】Course 1 - 神經網絡和深度學習 - 第二周作業
      同樣,這是整理了課程習題的博主的編碼答案,其思路邏輯和可視化部分都非常完美,在不借助很多現在流行框架的情況下手動構建線性組合,激活函數等實現邏輯回歸。
      因此我便不再重復,這里給出我的另一個版本以供參考,會更偏向于使用目前普遍使用的框架和內置函數來構建模型,偏向展示一個較為通用的模型訓練流程
      本次構建我們使用比較普遍的pytorch框架來實現這個模型,而配置pytorch環境在不熟悉的情況下可能會比較困難且繁瑣,如果希望動手實操但還沒有配置相關環境,這里我推薦按下面這位up主的視頻過程配置pytorch環境來進行練習:
      【2025年最新版】手把手教你安裝PyTorch,用最簡單的方式教你安裝PyTorch_嗶哩嗶哩_bilibili

      后面的課程筆記部分還會再介紹另一個主流框架:tensorflow

      2.1 數據準備

      使用貓狗二分類數據集,其下載地址為:貓狗圖像分類數據集
      數據集共2400幅圖像,其優點為貓狗各1200幅,做到了樣本均衡。
      但缺點也存在,數據規模不大,且每幅圖像大小不一,需要我們進行一定的預處理。
      我們便以此數據集訓練一個可以分類貓狗的Logistic回歸模型。

      2.2 代碼邏輯

      完整的代碼會附在文末

      2.2.1 導入所需庫

      我們來一個個看一看所需的庫,并介紹它們所起的作用。

      (1)torch
      import torch  # PyTorch 主庫,提供張量計算和自動求導的核心功能
      import torch.nn as nn  # 神經網絡模塊,包含各種層和激活函數
      import torch.optim as optim   # 優化器模塊,用于參數更新
      from torch.utils.data import DataLoader, random_split #數據加載與劃分工具
      

      當我們看到這個導入格式時,可能會產生這樣一個疑問:
      既然已經在第一行已經導入了torch庫,為什么我們還要再顯式地導入torch的子模塊?
      對于這個問題,我們先簡單解釋一下python的導庫語法。

      1. 直接導入庫
      import torch  #import 庫名:直接導入所需庫
      #這樣導入后,我們就可以直接使用 torch.方法名(方法參數)來直接調用torch的方法。
      x = torch.tensor([1, 2, 3]) #創建一個張量,張量同樣是庫定義的一種容納矩陣等內容的數據結構,類似之前的numpy庫。
      
      #同時,如果torch存在子模塊,我們也可以用 torch.子模塊名.子模塊方法名(方法參數)的形式調用。
      y = torch.nn.Sigmoid() #創建一個 Sigmoid 激活函數層,并把它賦值給變量 `y`。
      
      1. 逐層導入庫的子模塊
      # 這種形式實際上是為了簡化代碼,先繼續看上部分:
      import torch
      y = torch.nn.Sigmoid() #這是直接導入的調用方式
      
      import torch.nn as nn # import 庫.模塊名 as 別名
      y = nn.Sigmoid() #這是導入子模塊后的調用方式
      #我們可以直接用 as 后我們為這個模塊起的別名直接調用其方法。
      
      #!注意,如果不使用 as 即:
      import torch.nn #這種方法和 import torch 的調用方式上沒任何區別,相當于直接導入子模塊。
      y = torch.nn.Sigmoid()
      
      1. 直接導入庫的子模塊或方法
      #這樣其實也是進一步簡化代碼,也涉及打包時的優化
      #按照之前的格式,我們先進行下面的導入
      import torch.utils.data as data
      loader = data.DataLoader(dataset, batch_size=32)
      # DataLoader是 PyTorch 中用來加載數據的一個類,它能夠高效地處理數據集的批次(batch)加載,并且支持多種數據加載策略
      # DataLoader(數據集,批次大小):會根據數據集 `dataset` 和指定的 `batch_size` 來加載數據,并返回一個可迭代的對象
      
      #現在我們想進一步簡化,就是這種形式:
      from torch.utils.data import DataLoader
      # from 上層模塊 import 類或方法名
      # 現在我們使用DataLoader的格式就是:
      loader = DataLoader(dataset, batch_size=32)
      

      以這樣的幾種導入格式,可以幫助我們實現較為精準的導入使用的模塊或方法,實際上,這也是目前普遍的使用方法,甚至官方文檔中使用的也是這種格式而非直接導入整個庫。

      (2)torchvision
      from torchvision import datasets, transforms
      # torchvision是一個與PyTorch配合使用的開源計算機視覺工具庫,常用于計算機視覺領域的任務,如圖像分類、目標檢測、圖像分割等
      # datasets模塊,包含了多種常用的計算機視覺數據集,自動下載并加載常見的數據集,并將其轉換為torch.utils.data.Dataset格式
      # transforms用于對圖像進行預處理和數據增強。它提供了很多常見的圖像轉換操作,如縮放、裁剪、歸一化、旋轉等
      
      (3)matplotlib
      import matplotlib.pyplot as plt
      # matplotlib是一個強大的繪圖庫,用于創建靜態、動態和交互式的可視化圖表。
      
      (4) sklearn
      from sklearn.metrics import accuracy_score
      # sklearn 是進行機器學習任務時非常基礎且實用的庫。它支持從數據預處理到模型評估的全流程,涵蓋了各種機器學習任務。
      # metrics這個模塊提供了許多用于評估機器學習模型性能的函數,特別是在分類任務中。
      # accuracy_score用于計算分類模型的準確率(Accuracy),即正確預測的樣本占所有樣本的比例。
      

      2.2.2 數據集預處理和劃分

      如果把訓練完整的模型比作做菜,在導入所有所需庫后,我們已經具備了構建模型的所有“廚具”。
      而數據集,就相當于我們的“原料”。
      這里也補充一下劃分數據集的種類,一般,我們會把數據集劃分為以下三部分:

      1. 訓練集:用來訓練模型的數據,幫助模型學習和調整參數。
      2. 驗證集:用來調整模型超參數和優化模型的性能。
      3. 測試集:用來最終評估模型表現的數據,檢查模型的泛化能力和真實世界的適應性。

      注意,這里出現了一個之前沒有提到過的概念:超參數

      超參數是指在模型訓練開始之前由人工設定的、用于控制模型結構或學習過程的參數。與通過反向傳播算法自動學習得到的模型參數(如權重、偏置)不同,超參數不會在訓練過程中被更新,而是由研究者或工程師在實驗中通過經驗、搜索或驗證集性能調優得到。

      常見的超參數包括學習率、批大小、訓練輪數、網絡層數、正則化系數、激活函數類型等,這些我們定義的量是通過在驗證集上的結果來不斷調優的。

      繼續用做菜的例子來說的話,訓練集讓模型“學會做菜”, 驗證集讓模型“做得更好吃”, 測試集讓我們知道“這道菜到底好不好吃”。

      補充了一些基礎知識后,我們現在繼續實操部分,先看一下數據集:
      Pasted image 20251015155338

      Pasted image 20251015155358

      可以看到,我只是用兩個文件夾分別存放兩類的圖像,并沒有在這里就劃分訓練集,驗證集和測試集。
      而且還有一個問題,那就是圖片的大小不一,深度學習模型要求每個輸入樣本的尺寸、通道數完全一致,如果輸入圖片大小不一致,很多操作無法統一處理,導致特征提取混亂。

      網上對構建模型有這樣一個戲稱:賽博接水管,不無道理,我們在輸入前,一定要對樣本進行預處理,處理完的樣本要和“輸入水管”的管口嚴絲合縫,不能大也不能小,而具體訓練過程中也有其他體現。

      Pytorch自然提供了相應方法:

      # transforms.Compose的作用是將多個圖像變換操作串聯起來,形成一個“流水線”,這樣每張圖片在被加載時會依次執行這些操作。
      transform = transforms.Compose([  
          transforms.Resize((128, 128)), # 統一圖片尺寸為(128,128)  
          transforms.ToTensor() # 轉為Tensor張量 [通道數,高,寬]
          transforms.Normalize((0.5,), (0.5,))  # 標準化,把像素映射到(-1,1)防止梯度消失   
      ])
      #我們可以把transforms.Compose的返回值transform看作一個函數,輸入圖片,輸出圖片經過這些處理后的結果。
      # ToTensor()這一步不能省略,因為pytorch框架只接受它定義的張量結構作為輸入,我們要初步使用框架,就要遵守它定義的規則。
      #沒有 Normalize,輸入在 [0,1],非常大維度的累加后,Linear 輸出可能很大導致輸出接近于1,從而讓梯度消失。
      

      這樣,我們就完成了預處理部分。要說明的是,這里我們僅做了一個大小統一,轉化為張量和標準化的操作,在實際訓練中,可能還有更多預處理的內容,我們遇到再說。
      一般來說,預處理代碼就是跟在導庫后的第一部分內容。

      在其之后的下一步,就是載入和劃分數據集,來繼續看:

      # 加載整個數據集
      dataset = datasets.ImageFolder(root='./cat_dog', transform=transform)
      # ImageFolder是一個圖像數據集加載器,能夠自動讀取一個按照文件夾結構分類的圖像數據集。
      # 兩個參數 root即為數據集存放文件夾路徑,transform即為剛剛的預處理函數,在這里作為參數自動應用于加載的每個樣本。
      # ImageFolder會自動讀取文件夾,并以文件夾名作為分類標簽標注文件夾里的內容,這里就會自動將圖片分為貓狗兩類。
      # 最后我們得到的返回值dataset是一個數據集對象,每一項都是(圖片的張量表示, 標簽)的形式
      
      # 先設置各部分大小
      total_size = len(dataset)
      train_size = int(0.8 * total_size)   # 80% 訓練集
      val_size = int(0.1 * total_size)     # 10% 驗證集
      test_size = total_size - train_size - val_size  # 10% 測試集
      
      # 按設置好的比例隨機劃分
      # 這里的隨機是指對某張圖分入哪部分的隨機,并非比例隨機
      train_dataset, val_dataset, test_dataset = random_split(
          dataset=dataset, 
          lengths=[train_size, val_size, test_size]
      )
      # random_split可以把一個數據集對象隨機拆分成若干個子數據集。
      # 兩個參數 dataset即為待拆分數據集,lengths即為每個子數據集的大小
      # 最后的返回train_dataset, val_dataset, test_dataset就是劃分好的訓練集,驗證集和測試集。
      
      
      # 定義批量數據迭代器
      # 這里干的實際上是我們之前說了很久的向量化。
      train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
      val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
      test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
      # DataLoader 三個參數,第一個即為要處理的數據集,batch_size是批次大小,就是我們向量化里的m
      # shuffle=True 表示每個 epoch 開始前,會隨機打亂數據集的順序,防止模型按固定順序學習
      # 最后我們得到三個迭代器,即可用于各部分的輸入,DataLoader 加載數據時,會把多個樣本堆疊成 batch,即[批次,通道數,高,寬]
      

      要說明的一點是,我們之前在理論部分的講解中的一次處理所有樣本,就是把這里的batch_size設置為訓練集大小,而我們處理完整個訓練集一次是一個輪次(epoch)。
      這樣,一個epoch就會進行一次傳播
      而實際上,batch_size通常小于訓練集大小,而一個批次(batch)便會傳播一次,因此,在這種情況下,一個epoch就會進行多次傳播

      2.2.3 模型構建

      這便是最核心的部分了,我們用一個類來實現模型的架構,如果類的規模較大,我們會單獨創建文件存放模型類。類定義一般會包含兩類方法:

      1. 初始化方法:這個模型里有什么(層級,激活函數等)
      2. 向前傳播方法:輸入進入模型后怎么走
        來看代碼:
      class LogisticRegressionModel(nn.Module): 
      # 類繼承自nn.Module,是 PyTorch 所有模型的基類
          #初始化方法
          def __init__(self):  
              super().__init__() #父類初始化,用于注冊子模塊等,涉及源碼,這里當成固定即可。  
              self.flatten = nn.Flatten() #把張量后三維展平為一維(通道C*高H*寬W)
              self.linear = nn.Linear(128 * 128 * 3, 1) # 輸入是128x128x3,輸出1個加權和
              # nn.Linear接受的是二維輸入[batch_size, features],這里是[32,128 * 128 * 3]
              # 但Linear層不需要在參數里寫 batch 維度,它內部會自動處理批量輸入,只關心每個樣本的特征數和每個樣本輸出的維度,這也是廣播機制的應用。
              self.sigmoid = nn.Sigmoid() #激活函數
          
          #向前傳播方法
          def forward(self, x):  
              # 現在,x的維度是[32,3,128,128]
              x = self.flatten(x)  #1.展平
              # 現在,x的維度是[32,128 * 128 * 3]
              x = self.linear(x)   #2.過線性組合得到加權和
              # 現在,x的維度是[32,1]
              x = self.sigmoid(x)  #3.過激活函數得到輸出
              # 現在,x的維度是[32,1]
              return x
      

      定義完成模型類后,我們便可以將其實例化并加以使用。

      2.2.4 設備選擇

      model = LogisticRegressionModel()  #實例化模型
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  #自動選擇CPU或者GPU 
      model.to(device) #模型的所有參數(權重、偏置)都會存放在對應設備里。
      

      我們都知道CPU就像計算機的“大腦”,但在深度學習的模型訓練領域中,反而GPU更常用,尤其是在較大的模型訓練中,GPU 是深度學習的“加速引擎”,它用大量并行核心,把神經網絡訓練和推理中重復、耗時的矩陣運算做得又快又高效,所謂的“租用服務器讓模型跑快點”,實際上就是利用服務器的較先進的GPU。

      2.2.5 損失函數和優化器

      我們同樣使用內置函數來定義這兩部分:

      criterion = nn.BCELoss()  # 二分類的交叉熵損失。  
      optimizer = optim.SGD(model.parameters(), lr=0.01) #梯度下降法,每個批次傳播一次。
      # SGD 兩個參數 model.parameters()即為模型的所有參數:權重,偏置等。
      # lr即為學習率。
      

      2.2.6 訓練和驗證

      這部分的邏輯較為復雜,主要思路就是遍歷輪次和批次來進行傳播,并記錄相應量用于后續畫圖。

      # 定義訓練的總輪數
      epochs = 10  # 表示訓練整個數據集的次數
      train_losses = []  # 用于記錄每個epoch的訓練損失,便于可視化
      val_accuracies = []  # 用于記錄每個epoch驗證集準確率,便于可視化
      
      # 開始訓練循環,每個epoch表示遍歷完整個訓練集一次
      for epoch in range(epochs):  
          model.train()  # 設置模型為訓練模式
          epoch_train_loss = 0  # 用于累計該epoch的總訓練損失
      
          # 遍歷訓練集DataLoader,每次獲取一個batch
          for images, labels in train_loader:  
              # images: Tensor, 形狀 [32, 3, 128, 128]
              # labels: Tensor, 形狀 [32]
              images, labels = images.to(device), labels.to(device).float().unsqueeze(1)
              # .to(device): 將張量移動到GPU或CPU
              # .float(): 將標簽轉為float類型,因為BCELoss要求輸入為浮點數
              # .unsqueeze(1): 在第1維增加一維,使labels形狀變為 [32, 1],與輸出匹配
      
              # 前向傳播:輸入圖片,計算模型預測輸出
              outputs = model(images)  # 調用模型的forward方法,返回 [32, 1]
              #官方推薦這樣的形式,實際上相當于model.forward(images)
              loss = criterion(outputs, labels)  # 計算二分類交叉熵損失,輸出標量Tensor
      
              # 反向傳播與參數更新
              optimizer.zero_grad()  # 清空上一次梯度,避免梯度累加
              loss.backward()        # 自動求梯度,計算每個參數的梯度
              optimizer.step()       # 根據梯度更新參數,完成一次優化步驟
      
              # 累計損失,用于計算平均訓練損失
              epoch_train_loss += loss.item()  
              # .item():將單元素Tensor轉為Python浮點數,便于記錄
      
          # 計算該epoch的平均訓練損失
          avg_train_loss = epoch_train_loss / len(train_loader)
          train_losses.append(avg_train_loss)  # 保存到列表,用于后續繪圖
      
      
      
          # 驗證集評估準確率
          model.eval()  # 設置模型為評估模式
          val_true, val_pred = [], []  # 用于記錄驗證集真實標簽和預測標簽
      
          with torch.no_grad():  # 禁用梯度計算,節省顯存和計算量
              for images, labels in val_loader:  # 遍歷驗證集
                  images = images.to(device)  # 移動到GPU/CPU
                  outputs = model(images)      # 前向傳播得到預測概率
                  preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()
                  # outputs.cpu().numpy(): 移動到CPU并轉為numpy數組
                  # numpy不用導入,這是PyTorch 內部實現的橋接接口
                  # 要用到scikit-learn、matplotlib 等庫計算或可視化,這些庫只接受 CPU 數據
                  # > 0.5: 將概率轉換為0/1預測
                  # .astype(int): 把布爾值 True/False 轉為整數 1/0
                  # .flatten(): 將二維數組展平成一維
                  val_pred.extend(preds)       # 將預測結果加入列表
                  val_true.extend(labels.numpy())  # 將真實標簽加入列表
      
          # 使用sklearn計算驗證集準確率
          val_acc = accuracy_score(val_true, val_pred)
          val_accuracies.append(val_acc)  # 保存準確率,用于繪圖
      
          # 打印該epoch的訓練損失和驗證集準確率
          print(f"輪次: [{epoch+1}/{epochs}], 訓練損失: {avg_train_loss:.4f}, 驗證準確率: {val_acc:.4f}")
          # {變量:.4f}表示保留4位小數
      
      

      2.2.7 可視化

      plt.rcParams['font.sans-serif'] = ['SimHei']  
      # 設置全局字體為黑體(SimHei),支持中文顯示
      # plt.rcParams 是 Matplotlib 的全局參數字典,可修改默認樣式
      plt.rcParams['axes.unicode_minus'] = False 
      # 設置 False 表示允許正常顯示負號
      
      # 繪制曲線 
      plt.plot(train_losses, label='訓練損失')  
      # 繪制訓練集損失曲線
      # plt.plot(y, label=...) 用于繪制折線圖,label 用于圖例說明
      plt.plot(val_accuracies, label='驗證準確率')
      # 作繪制驗證集準確率曲線
      
      # 設置標題與坐標軸
      plt.title("訓練損失與驗證準確率隨輪次變化圖") # 設置圖表標題
      plt.xlabel("訓練輪次(Epoch)") # 設置橫軸標題
      plt.ylabel("數值") # 設置縱軸標題
      
      plt.legend() # 顯示圖例,用于區分不同折線
      # plt.legend() 會顯示各 plt.plot() 的 label 內容
      plt.grid(True) # 開啟網格顯示
      plt.show() # 顯示繪制的圖形窗口
      

      2.2.8 最終測試

      在最后用測試集進行評估之前,其實應該有根據訓練集對超參數進行調優的過程,但由于目前的篇幅已經較長了,我們先看完流程,我會在最后再附上一個使用方格調優版本的代碼

      
      # 模型評估(測試集)
      model.eval()
      y_true, y_pred = [], []
      # 定義兩個空列表,用于存儲測試集的真實標簽與預測標簽
      with torch.no_grad():
          for images, labels in test_loader:
              images = images.to(device)
              outputs = model(images)
              preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()
              y_pred.extend(preds)
              y_true.extend(labels.numpy())
      acc = accuracy_score(y_true, y_pred)
      print(f"測試準確率: {acc:.4f}")
      

      可以發現,驗證集和測試集的代碼部分幾乎沒有差別,二者的主要差別在于它們起到的作用上。

      2.3 結果分析

      Pasted image 20251015205925

      這是這個模型的結果,可以發現準確率不高,只有一半左右。
      而造成這個結果的因素是多樣的:

      1. 數據集規模不大,圖片大小不一。
      2. 預處理簡單,可能造成失真。
      3. 模型結構簡單,擬合能力不強
      4. 沒有對超參數進行較詳細地調優
        此外,還有其他影響因素。
        而下面便是可視化部分的代碼結果:
        myplot

      我們可能會發現這樣一個問題:按照梯度下降法的思路,損失值應該一直下降才對,為什么反而會有升高的反復現象?
      其實,這是一個非常常見的現象。
      雖然梯度下降的理論目標是不斷讓損失函數下降,但在實際訓練中,損失值并不會嚴格單調遞減,原因主要有以下幾點:

      1. 每次迭代并不是用全部數據計算梯度,而是用一個小批量,不同批次的數據分布略有差異,會導致梯度方向有波動,因此損失可能短暫上升。
      2. 如果學習率偏大,每次更新的步長過長,可能會“越過”最優點,使損失出現震蕩。
      3. 即使是簡單模型,在高維空間中損失函數也可能存在多個局部極小值和鞍點,訓練過程可能會在這些區域間來回波動。
        換句話說,總體趨勢下降才是關鍵,出現輕微的上升是正常現象,不代表模型沒有學習。

      我們本篇的主要目的還是展示模型構建的過程,在之后的課程學習里,會涉及更多更復雜的算法,函數與優化等,我們到時使用其再來試試在貓狗二分類數據集上的分類效果。

      最后,完整代碼如下:

      1. 示例版本
      import torch  
      import torch.nn as nn  
      import torch.optim as optim  
      from torchvision import datasets, transforms  
      from torch.utils.data import DataLoader, random_split  
      import matplotlib.pyplot as plt  
      from sklearn.metrics import accuracy_score  
        
      transform = transforms.Compose([  
          transforms.Resize((128, 128)),        
          transforms.ToTensor(),                 
          transforms.Normalize((0.5,), (0.5,))    
      ])  
      dataset = datasets.ImageFolder(root='./cat_dog', transform=transform)  
        
      train_size = int(0.8 * len(dataset))  
      val_size = int(0.1 * len(dataset))  
      test_size = len(dataset) - train_size - val_size  
      train_dataset, val_dataset, test_dataset = random_split(dataset, [train_size, val_size, test_size])  
      train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)  
      val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)  
      test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)  
        
      class LogisticRegressionModel(nn.Module):  
          def __init__(self):  
              super().__init__()  
              self.flatten = nn.Flatten()  
              self.linear = nn.Linear(128 * 128 * 3, 1)  # 
              self.sigmoid = nn.Sigmoid()  
        
          def forward(self, x):  
              x = self.flatten(x)  
              x = self.linear(x)  
              x = self.sigmoid(x)  
              return x  
        
      model = LogisticRegressionModel()  
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  
      model.to(device)  
      criterion = nn.BCELoss()    
      optimizer = optim.SGD(model.parameters(), lr=0.01)  
        
      epochs = 10  
      train_losses = []  
      val_accuracies = []  
        
      for epoch in range(epochs):  
          model.train()  
          epoch_train_loss = 0  
          for images, labels in train_loader:  
              images, labels = images.to(device), labels.to(device).float().unsqueeze(1)  
        
              outputs = model(images)  
              loss = criterion(outputs, labels)  
            
              optimizer.zero_grad()  
              loss.backward()  
              optimizer.step()  
        
              epoch_train_loss += loss.item()  
          avg_train_loss = epoch_train_loss / len(train_loader)  
          train_losses.append(avg_train_loss)  
            
          model.eval()  
          val_true, val_pred = [], []  
          with torch.no_grad():  
              for images, labels in val_loader:  
                  images = images.to(device)  
                  outputs = model(images)  
                  preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()  
                  val_pred.extend(preds)  
                  val_true.extend(labels.numpy())  
        
          val_acc = accuracy_score(val_true, val_pred)  
          val_accuracies.append(val_acc)  
          print(f"輪次: [{epoch+1}/{epochs}], 訓練損失: {avg_train_loss:.4f}, 驗證準確率: {val_acc:.4f}")  
        
      plt.rcParams['font.sans-serif'] = ['SimHei']    
      plt.rcParams['axes.unicode_minus'] = False  
        
      plt.plot(train_losses, label='訓練損失')  
      plt.plot(val_accuracies, label='驗證準確率')  
      plt.title("訓練損失與驗證準確率隨輪次變化圖")  
      plt.xlabel("訓練輪次(Epoch)")  
      plt.ylabel("數值")  
      plt.legend()  
      plt.grid(True)  
      plt.show()  
        
      model.eval()  
      y_true, y_pred = [], []  
      with torch.no_grad():  
          for images, labels in test_loader:  
              images = images.to(device)  
              outputs = model(images)  
              preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()  
              y_pred.extend(preds)  
              y_true.extend(labels.numpy())  
      acc = accuracy_score(y_true, y_pred)  
      print(f"測試準確率: {acc:.4f}")
      
      1. 加入方格搜索優化超參數的版本:
      import torch  
      import torch.nn as nn  
      import torch.optim as optim  
      from torchvision import datasets, transforms  
      from torch.utils.data import DataLoader, random_split  
      from sklearn.metrics import accuracy_score  
      import itertools  
        
      transform = transforms.Compose([  
          transforms.Resize((128, 128)),  
          transforms.ToTensor(),  
          transforms.Normalize((0.5,), (0.5,))  
      ])  
        
      dataset = datasets.ImageFolder(root='./cat_dog', transform=transform)  
      train_size = int(0.8 * len(dataset))  
      val_size = int(0.1 * len(dataset))  
      test_size = len(dataset) - train_size - val_size  
        
      train_dataset, val_dataset, test_dataset = random_split(dataset, [train_size, val_size, test_size])  
        
      class LogisticRegressionModel(nn.Module):  
          def __init__(self):  
              super().__init__()  
              self.flatten = nn.Flatten()  
              self.linear = nn.Linear(128 * 128 * 3, 1)  
              self.sigmoid = nn.Sigmoid()  
        
          def forward(self, x):  
              x = self.flatten(x)  
              x = self.linear(x)  
              x = self.sigmoid(x)  
              return x  
        
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  
        
      param_grid = {  
          'lr': [0.01, 0.001],  
          'batch_size': [16, 32],  
          'num_epochs': [5, 10]  
      }  
        
      best_acc = 0  
      best_params = None  
        
      for lr, batch_size, num_epochs in itertools.product(param_grid['lr'], param_grid['batch_size'], param_grid['num_epochs']):  
          print(f"當前超參數: 學習率={lr}, 批次大小={batch_size}, 總輪次={num_epochs}")  
        
          train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)  
          val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)  
          test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)  
        
          model = LogisticRegressionModel().to(device)  
          criterion = nn.BCELoss()  
          optimizer = optim.SGD(model.parameters(), lr=lr)  
        
          for epoch in range(num_epochs):  
              model.train()  
              epoch_loss = 0  
              for images, labels in train_loader:  
                  images, labels = images.to(device), labels.to(device).float().unsqueeze(1)  
                  outputs = model(images)  
                  loss = criterion(outputs, labels)  
                  optimizer.zero_grad()  
                  loss.backward()  
                  optimizer.step()  
                  epoch_loss += loss.item()  
              avg_loss = epoch_loss / len(train_loader)  
        
              model.eval()  
              y_val_true, y_val_pred = [], []  
              with torch.no_grad():  
                  for images, labels in val_loader:  
                      images = images.to(device)  
                      outputs = model(images)  
                      preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()  
                      y_val_pred.extend(preds)  
                      y_val_true.extend(labels.numpy())  
              val_acc = accuracy_score(y_val_true, y_val_pred)  
              print(f"輪次 {epoch+1}/{num_epochs}, 損失: {avg_loss:.4f}, 驗證準確率: {val_acc:.4f}")  
          if val_acc > best_acc:  
              best_acc = val_acc  
              best_params = {'lr': lr, 'batch_size': batch_size, 'num_epochs': num_epochs, 'model_state_dict': model.state_dict()}  
      print(f"\n最佳驗證準確率: {best_acc:.4f} 超參數設置: {best_params}")  
      best_model = LogisticRegressionModel().to(device)  
      best_model.load_state_dict(best_params['model_state_dict'])  
      best_model.eval()  
      y_test_true, y_test_pred = [], []  
      with torch.no_grad():  
          for images, labels in test_loader:  
              images = images.to(device)  
              outputs = best_model(images)  
              preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()  
              y_test_pred.extend(preds)  
              y_test_true.extend(labels.numpy())  
        
      test_acc = accuracy_score(y_test_true, y_test_pred)  
      print(f"使用最優超參數的測試準確率: {test_acc:.4f}")
      
      posted @ 2025-10-15 21:23  哥布林學者  閱讀(272)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 欧美亚洲熟妇一区二区三区| 日韩有码中文字幕av| 上蔡县| 精品一区二区三区无码视频| 国产精品亚洲一区二区z| 国产对白老熟女正在播放| 亚洲熟妇熟女久久精品综合| 大香伊蕉在人线国产最新2005| jk白丝喷浆| 女同另类激情在线三区| 久久精品夜色国产亚洲av| 综合久久婷婷综合久久| 欧洲精品一区二区三区久久| 日本视频一两二两三区| 高清破外女出血AV毛片| 成人精品日韩专区在线观看| 丰腴饱满的极品熟妇| 欧美国产成人久久精品| 免费人成黄页在线观看国产| 一区二区福利在线视频| 亚洲AV永久中文无码精品综合| 免费国产黄线在线观看| 中文字幕人妻日韩精品| 不卡一区二区国产在线| 和平县| 午夜精品久久久久久久爽| 亚洲aⅴ男人的天堂在线观看 | 91老熟女老人国产老太| 高清无码在线视频| 精品人妻av区乱码| 亚洲中文字幕伊人久久无码| AV免费播放一区二区三区| 动漫av网站免费观看| 国产成人无码AV片在线观看不卡 | 天堂mv在线mv免费mv香蕉| 国产一区二区av天堂热| 中国国产一级毛片| 国产一区二区三区尤物视频| 欧产日产国产精品精品| 国产又爽又黄的精品视频| 成人一区二区不卡国产|