日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人工智能 > pytorch >内容正文

pytorch

Lesson 13.1 深度学习建模目标与性能评估理论

發布時間:2025/4/5 pytorch 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Lesson 13.1 深度学习建模目标与性能评估理论 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Lesson 13.1 深度學習建模目標與性能評估理論

??從Lesson 13起,我們講開始系統介紹深度學習建模理論,以及建模過程中的優化方法。

二、機器學習目標與模型評估方法

??在了解深度學習基本模型的概念與實現方法后,接下來,我們將詳細探討深度學習模型優化的常用方法。從上一課的實驗中不難發現,要把一個模型“建好”已是不容易,而要想辦法把模型的效果進行提升,如果沒有基礎理論支持和方法論工具,優化過程無異于盲人摸象。因此,本節課將從建模的根本目標出發,圍繞模型優化的基本概念和核心理論進行全面梳理,并在此基礎之上介紹相關實踐方法,逐漸撥開模型優化面前的迷霧。
??我們經常會對模型的好壞優劣進行評估,Lesson 12中我們也使用準確率、MSE等指標評估建模結果,看起來模型評估是圍繞某項指標在進行評估,指標好模型就好,指標不好模型就不好,其實并不完全如此。要了解模型的性能其實并不簡單,固然我們會使用某些指標去進行模型評估,但其實指標也只是我們了解模型性能的途徑而不是模型性能本身。而要真實、深刻的評判模型性能,就必須首先了解機器學習的建模目標,并在此基礎之上熟悉我們判斷模型是否能夠完成目標的一些方法,當然,只有真實了解的模型性能,我們才能進一步考慮如何提升模型性能。因此,在正式講解模型優化方法之前,我們需要花些時間討論機器學習算法的建模目標、機器學習算法為了能夠達到目標的一般思路,以及評估模型性能的手段,也就是模型評估指標。
??無論是機器學習還是傳統的統計分析模型,核心使命就是探索數字規律,而有監督學習則是希望在探索數字規律的基礎上進一步對未來進行預測,當然,在數字的世界,這個預測未來,也就是預測未來某項事件的某項數值指標,如某地區未來患病人次、具備某種數字特征的圖片上的動物是哪一類,此處的未來也并非指絕對意義上的以后的時間,而是在模型訓練階段暫時未接觸到的數據。正是因為模型有了在未知標簽情況下進行預判的能力,有監督學習才有了存在的價值,但我們知道,基本上所有的模型,都只能從以往的歷史經驗當中進行學習,也就是在以往的、已經知道的數據集上進行訓練(如上述利用已知數據集進行模型訓練,如利用過往股票數據訓練時間序列模型),這里的核心矛盾在于,在以往的數據中提取出來的經驗(也就是模型),怎么證明能夠在接下來的數據中也具備一定的預測能力呢?或者說,要怎么訓練模型,才能讓模型在未知的數據集上也擁有良好的表現呢?
??目的相同,但在具體的實現方法上,傳統的數理統計分析建模和機器學習采用了不同的解決方案。
??首先,在統計分析領域,我們會假設現在的數據和未來的數據其實都屬于某個存在但不可獲得的總體,也就是說,現在和未來的數據都是從某個總體中抽樣而來的,都是這個總體的樣本。而正式因為這些數據屬于同一個總體,因此具備某些相同的規律,而現在挖掘到的數據規律也就在某些程度上可以應用到未來的數據當中去,不過呢,不同抽樣的樣本之間也會有個體之間的區別,另外模型本身也無法完全捕獲規律,而這些就是誤差的來源。
??雖然樣本和總體的概念是統計學概念,但樣本和總體的概念所假設的前后數據的“局部規律一致性”,卻是所有機器學習建模的基礎。試想一下,如果獲取到的數據前后描繪的不是一件事情,那么模型訓練也就毫無價值(比如拿著A股走勢預測的時間序列預測某地區下個季度患病人次)。因此,無論是機器學習所強調的從業務角度出發,要確保前后數據描述的一致性,還是統計分析所強調的樣本和總體的概念,都是建模的基礎。
??在有了假設基礎之后,統計分析就會利用一系列的數學方法和數理統計工具去推導總體的基本規律,也就是變量的分布規律和一些統計量的取值,由于這個過程是通過已知的樣本去推斷未知的總體,因此會有大量的“估計”和“檢驗”,在確定了總體的基本分布規律之后,才能夠進一步使用統計分析模型構建模型(這也就是為什么在數理統計分析領域,構建線性回歸模型需要先進行一系列的檢驗和變換的原因),當然,這些模型都是在總體規律基礎之上、根據樣本具體的數值進行的建模,我們自然有理由相信這些模型對接下來仍然是從總體中抽樣而來的樣本還是會具備一定的預測能力,這也就是我們對統計分析模型“信心”的來源。簡單來說,就是我們通過樣本推斷總體的規律,然后結合總體的規律和樣本的數值構建模型,由于模型也描繪了總體規律,所以模型對接下來從總體當中抽樣而來的數據也會有不錯的預測效果,這個過程我們可以通過下圖來進行表示。
??而對于機器學習來說,并沒有借助“樣本-總體”的基本理論,而是簡單的采用了一種后驗的方法來判別模型有效性,前面說到,我們假設前后獲取的數據擁有規律一致性,但數據彼此之間又略有不同,為了能夠在捕捉規律的同時又能考慮到“略有不同”所帶來的誤差,機器學習會把當前能獲取到的數據劃分成訓練集(trainSet)和測試集(testSet),在訓練集上構建模型,然后帶入測試集的數據,觀測在測試集上模型預測結果和真實結果之間的差異。這個過程其實就是在模擬獲取到真實數據之后模型預測的情況,此前說到,模型能夠在未知標簽的數據集上進行預測,就是模型的核心價值,此時的測試集就是用于模擬未來的未知標簽的數據集。如果模型能夠在測試集上有不錯的預測效果,我們就“簡單粗暴”的認為模型可以在真實的未來獲取的未知數據集上有不錯的表現。其一般過程可以由下圖表示。

??雖然對比起數理統計分析,機器學習的證明模型有效性的過程更加“簡單”,畢竟只要一次“模擬”成功,我們就認為模型對未來的數據也擁有判別效力,但這種“簡單”的處理方式卻非常實用,可以說,這是一種經過長期實踐被證明的行之有效的方法。這也是為什么機器學習很多時候也被認為是實證類的方法,而在以后的學習中,我們也將了解到,機器學習有很多方法都是“經驗總結的結果”。相比數理統計分析,確實沒有“那么嚴謹”,但更易于理解的理論和更通用的方法,卻使得機器學習可以在更為廣泛的應用場景中發揮作用。(當然,負面影響卻是,機器學習在曾經的很長一段時間內并不是主流的算法。)
??據此,我們稱模型在訓練集上誤差稱為訓練誤差,在測試集上的誤差稱為泛化誤差,不過畢竟在測試集上進行測試還只是模擬演習,我們采用模型的泛化能力來描述模型在未知數據上的判別能力,當然泛化能力無法準確衡量(未知的數據還未到來,到來的數據都變成了已知數據),我們只能通過模型在訓練集和測試集上的表現,判別模型泛化能力,當然,就像此前說的一樣,最基本的,我們會通過模型在測試集上的表現來判斷模型的泛化能力。

三、手動實現訓練集和測試集切分

??接下來我們開始實踐模型評估過程,首先是對訓練集和測試集的劃分,我們嘗試創建一個切分訓練集和測試集的函數。

def data_split(features, labels, rate=0.7):"""訓練集和測試集切分函數:param features: 輸入的特征張量:param labels:輸入的標簽張量:param rate:訓練集占所有數據的比例:return Xtrain, Xtest, ytrain, ytest:返回特征張量的訓練集、測試集,以及標簽張量的訓練集、測試集 """num_examples = len(features) # 總數據量indices = list(range(num_examples)) # 數據集行索引random.shuffle(indices) # 亂序調整 num_train = int(num_examples * rate) # 訓練集數量 indices_train = torch.tensor(indices[: num_train]) # 在已經亂序的的indices中挑出前num_train數量的行索引值indices_test = torch.tensor(indices[num_train: ]) Xtrain = features[indices_train] # 訓練集特征ytrain = labels[indices_train] # 訓練集標簽Xtest = features[indices_test] # 測試集特征ytest = labels[indices_test] # 測試集標簽return Xtrain, Xtest, ytrain, ytest

??一般來說,訓練集和測試集可以按照8:2或7:3比例進行劃分。在進行數據劃分的過程中,如果測試集劃分數據過多,參與模型訓練的數據就會相應減少,而訓練數據不足則會導致模型無法正常訓練、損失函數無法收斂、模型過擬合等問題,但如果反過來測試集劃分數據過少,則無法代表一般數據情況測試模型是否對未知數據也有很好的預測作用。因此,根據經驗,我們一般來說會按照8:2或7:3比例進行劃分。
??看到這里,相信肯定有小伙伴覺得根據所謂的“經驗”來定數據集劃分比例不太嚴謹,有沒有一種方法能夠“精準”的確定什么劃分比例最佳呢?例如通過類似最小二乘法或者梯度下降這類優化算法來計算劃分比例?各位同學可以嘗試著進行思考,并給出自己的答案。課程中將在下一節介紹參數和超參數時給出詳細解答。
??值得一提的是,在機器學習領域,充斥著大量的“經驗之談”或者“約定俗成”的規則,一方面這些經驗為建模提供了諸多便捷、也節省了很多算力,但另一方面,通過經驗來決定影響模型效果的一些“超參數”取值的不嚴謹的做法,也被數理統計分析流派所詬病。

接下來,測試函數性能

f = torch.arange(10) # 創建特征0-9 f #tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) l = torch.arange(1, 11) # 創建標簽1-10,保持和特征+1的關系 l #tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) data_split(f, l) #(tensor([1, 6, 4, 5, 8, 3, 0]), # tensor([9, 7, 2]), # tensor([2, 7, 5, 6, 9, 4, 1]), # tensor([10, 8, 3]))

接下來,還是在上一節課的內容上,嘗試帶入訓練集進行建模,利用測試集評估模型建模效果

# 設置隨機數種子 torch.manual_seed(420) # 生成回歸類數據集 features, labels = tensorGenReg() #<torch._C.Generator at 0x26f6196e570> features #tensor([[-0.0070, 0.5044, 1.0000], # [ 0.6704, -0.3829, 1.0000], # [ 0.0302, 0.3826, 1.0000], # ..., # [-0.9164, -0.6087, 1.0000], # [ 0.7815, 1.2865, 1.0000], # [ 1.4819, 1.1390, 1.0000]]) torch.manual_seed(420) # 初始化數據 features, labels = tensorGenReg()# 切分訓練集和測試集 Xtrain, Xtest, ytrain, ytest = data_split(features, labels)# 初始化核心參數 batch_size = 10 # 小批的數量 lr = 0.03 # 學習率 num_epochs = 5 # 訓練過程遍歷幾次數據 w = torch.zeros(3, 1, requires_grad = True) # 隨機設置初始權重# 參與訓練的模型方程 net = linreg # 使用回歸方程 loss = MSE_loss # 均方誤差的一半作為損失函數# 模型訓練過程 for epoch in range(num_epochs):for X, y in data_iter(batch_size, Xtrain, ytrain):l = loss(net(X, w), y)l.backward()sgd(w, lr)

查看訓練結果

w #tensor([[ 2.0002], # [-1.0002], # [ 0.9996]], requires_grad=True)

查看模型在訓練集、測試集上的MSE

MSE_loss(torch.mm(Xtrain, w), ytrain) #tensor(0.0001, grad_fn=<DivBackward0>) MSE_loss(torch.mm(Xtest, w), ytest) #tensor(9.9141e-05, grad_fn=<DivBackward0>)

至此,我們就完成了一整個從數據集劃分,到訓練集訓練,再到測試集上測試模型性能的一整個流程。

四、Dataset和DataLoader基本使用方法與數據集切分函數

??接下來,我們嘗試使用PyTorch原生庫來實現上述功能,不過這個實現過程略顯復雜,首先我們需要了解Dataset和DataLoader的基本使用方法。

1.Dataset和DataLoader的基本使用方法

  • random_split隨機切分函數
    ??首先,在PyTorch的torch.utils.data中,提供了random_split函數可用于數據集切分。
from torch.utils.data import random_split

簡單測試函數功能

t = torch.arange(12).reshape(4, 3) t #tensor([[ 0, 1, 2], # [ 3, 4, 5], # [ 6, 7, 8], # [ 9, 10, 11]]) random_split(t, [2, 2]) # 輸入切分的每部分數據集數量 #[<torch.utils.data.dataset.Subset at 0x15872bd9b08>, # <torch.utils.data.dataset.Subset at 0x15872bd9c48>]

根據生成結果可知,random_split函數其實生成了生成器切分結果的生成器,并不是和此前定義的函數一樣,直接切分數據后返回。當然這也符合utils.data模塊主要生成映射式和迭代式對象的一般規定。

train, test = random_split(t, [2, 2])

使用print函數查看生成器內容

for tr, te in random_split(t, [2, 2]):print(tr, te) #tensor([ 9, 10, 11]) tensor([0, 1, 2]) #tensor([3, 4, 5]) tensor([6, 7, 8])
  • Dataset和Dataloader

??由于在大多數調庫建模過程中,我們都是先通過創建Dataset的子類并將數據保存為該子類類型,然后再使用DataLoader進行數據載入,因此更為通用的做法是先利用Dataset和DatasetLoader這兩個類進行數據的讀取、預處理和載入,然后再使用random_split函數進行切分。
??再次強調,Dataset類主要負責數據類的生成,在PyTorch中,所有數據集都是Dataset的子類;而DatasetLoader類則是加載模型訓練的接口,二者基本使用流程如下:

from torch.utils.data import Dataset from torch.utils.data import DataLoader
  • 創建數據類

??根據此前描述,PyTorch中所有的數據都是Dataset的子類,換而言之就是在使用PyTorch建模訓練數據時,需要創建一個和數據集對應的類來表示該數據集,此前我們使用的TensorDataset函數其實就是一個簡單的類型轉化函數,將數據統一轉化為“TensorDataset”類然后帶入模型進行計算。

features, labels = tensorGenReg(bias=False) features #tensor([[ 0.5846, 0.4064, -0.7022], # [ 0.5943, 0.5927, 0.8111], # [-0.4947, 0.2168, 0.0981], # ..., # [-0.4568, -1.1319, -1.7560], # [-1.3112, -0.9356, 1.5156], # [-1.0726, 0.8814, -1.6092]]) data = TensorDataset(features, labels) data #<torch.utils.data.dataset.TensorDataset at 0x17be64eeb48>

而TensorDataset其實使用面較窄,最直接的限制就是該函數只能將張量類型轉化為TensorDataset類

TensorDataset([1,2], 1) #AttributeError: 'list' object has no attribute 'size' TensorDataset? #Init signature: TensorDataset(*args, **kwds) #Docstring: #Dataset wrapping tensors.#Each sample will be retrieved by indexing tensors along the first dimension.#Arguments: # *tensors (Tensor): tensors that have the same size of the first dimension. #File: d:\users\asus\anaconda3\lib\site-packages\torch\utils\data\dataset.py #Type: type #Subclasses:

更加通用的數據讀取方法則是手動創建一個繼承自torch.utils.data.dataset的數據類,用來作為當前數據的表示。例如Lesson 11中的乳腺癌數據,通過如下方式進行讀取

from sklearn.datasets import load_breast_cancer as LBC data = LBC()

簡單查看data數據集

data.data # 返回數據集的特征數組 #array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01, # 1.189e-01], # [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01, # 8.902e-02], # [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01, # 8.758e-02], # ..., # [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01, # 7.820e-02], # [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01, # 1.240e-01], # [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01, # 7.039e-02]]) data.target # 返回數據集的標簽數組 #array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, # 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, # . . . . . . # 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, # 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1]) len(data.data) # 返回數據集總個數 #569

??接下來,創建一個用于表示該數據集的Dataset的子類。在創建Dataset的子類過程中,必須要重寫__getitem__方法和__len__方法,其中__getitem__方法返回輸入索引后對應的特征和標簽,而__len__方法則返回數據集的總數據個數。當然,在必須要進行的__init__初始化過程中,我們也可輸入可代表數據集基本屬性的相關內容,包括數據集的特征、標簽、大小等等,視情況而定。

class LBCDataset(Dataset):def __init__(self,data): # 創建該類時需要輸入sklearn導入的數據集self.features = data.data # features屬性返回數據集特征self.labels = data.target # labels屬性返回數據集標簽self.lens = len(data.data) # lens屬性返回數據集大小def __getitem__(self, index):# 調用該方法時需要輸入index數值,方法最終返回index對應的特征和標簽return self.features[index,:],self.labels[index] def __len__(self):# 調用該方法不需要輸入額外參數,方法最終返回數據集大小return self.lensdata = LBC() LBC_data = LBCDataset(data)LBC_data.features #array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01, # 1.189e-01], # [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01, # 8.902e-02], # [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01, # 8.758e-02], # ..., # [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01, # 7.820e-02], # [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01, # 1.240e-01], # [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01, # 7.039e-02]]) LBC_data.lens #569 # 查看第三條數據 LBC_data.__getitem__(2) #(array([1.969e+01, 2.125e+01, 1.300e+02, 1.203e+03, 1.096e-01, 1.599e-01, # 1.974e-01, 1.279e-01, 2.069e-01, 5.999e-02, 7.456e-01, 7.869e-01, # 4.585e+00, 9.403e+01, 6.150e-03, 4.006e-02, 3.832e-02, 2.058e-02, # 2.250e-02, 4.571e-03, 2.357e+01, 2.553e+01, 1.525e+02, 1.709e+03, # 1.444e-01, 4.245e-01, 4.504e-01, 2.430e-01, 3.613e-01, 8.758e-02]), # 0) LBC_data.features[2] #array([1.969e+01, 2.125e+01, 1.300e+02, 1.203e+03, 1.096e-01, 1.599e-01, # 1.974e-01, 1.279e-01, 2.069e-01, 5.999e-02, 7.456e-01, 7.869e-01, # 4.585e+00, 9.403e+01, 6.150e-03, 4.006e-02, 3.832e-02, 2.058e-02, # 2.250e-02, 4.571e-03, 2.357e+01, 2.553e+01, 1.525e+02, 1.709e+03, # 1.444e-01, 4.245e-01, 4.504e-01, 2.430e-01, 3.613e-01, 8.758e-02]) LBC_data.labels[2] #0

封裝好的數據可以直接進行索引,并且能夠返回實體結果

LBC_data[1] #(array([2.057e+01, 1.777e+01, 1.329e+02, 1.326e+03, 8.474e-02, 7.864e-02, # 8.690e-02, 7.017e-02, 1.812e-01, 5.667e-02, 5.435e-01, 7.339e-01, # 3.398e+00, 7.408e+01, 5.225e-03, 1.308e-02, 1.860e-02, 1.340e-02, # 1.389e-02, 3.532e-03, 2.499e+01, 2.341e+01, 1.588e+02, 1.956e+03, # 1.238e-01, 1.866e-01, 2.416e-01, 1.860e-01, 2.750e-01, 8.902e-02]), # 0) LBC_data.__getitem__(1) #(array([2.057e+01, 1.777e+01, 1.329e+02, 1.326e+03, 8.474e-02, 7.864e-02, # 8.690e-02, 7.017e-02, 1.812e-01, 5.667e-02, 5.435e-01, 7.339e-01, # 3.398e+00, 7.408e+01, 5.225e-03, 1.308e-02, 1.860e-02, 1.340e-02, # 1.389e-02, 3.532e-03, 2.499e+01, 2.341e+01, 1.588e+02, 1.956e+03, # 1.238e-01, 1.866e-01, 2.416e-01, 1.860e-01, 2.750e-01, 8.902e-02]), # 0) LBC_data[:] #(array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01, # 1.189e-01], # [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01, # 8.902e-02], # [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01, # 8.758e-02], # ..., # [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01, # 7.820e-02], # [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01, # 1.240e-01], # [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01, # 7.039e-02]]), # array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, # 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, # 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, # . . . . . . # 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, # 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, # 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1]))

另外,我們可以使用random_split方法對其進行切分

# 確定訓練集、測試集大小,此處以7:3劃分訓練集和測試集 num_train = int(LBC_data.lens * 0.7) num_test = LBC_data.lens - num_trainnum_train # 訓練集個數 num_test # 測試集個數 #398 #171 LBC_train, LBC_test = random_split(LBC_data, [num_train, num_test])

注,此時切分的結果是一個映射式的對象,只有dataset和indices兩個屬性,其中dataset屬性用于查看原數據集對象,indices屬性用于查看切分后數據集的每一條數據的index(序號)。

LBC_train? #Type: Subset #String form: <torch.utils.data.dataset.Subset object at 0x0000017B8803D648> #Length: 398 #File: d:\users\asus\anaconda3\lib\site-packages\torch\utils\data\dataset.py #Docstring: #Subset of a dataset at specified indices. # #Arguments: # dataset (Dataset): The whole Dataset # indices (sequence): Indices in the whole set selected for subset LBC_train.dataset #<__main__.LBCDataset at 0x7fb8ac3adb50>

通過切分結果還原原始數據集

LBC_train.dataset == LBC_data # 還原原數據集 #True

在原始數據集中查找切分數據集

LBC_train.indices[:10] # 抽取的訓練集數據的index #[384, 475, 64, 490, 496, 136, 123, 300, 175, 342]

當然,無論是迭代式生成數據還是映射式生成數據,都可以使用print查看數據

for i in LBC_train:print(i)break #(array([1.328e+01, 1.372e+01, 8.579e+01, 5.418e+02, 8.363e-02, 8.575e-02, # 5.077e-02, 2.864e-02, 1.617e-01, 5.594e-02, 1.833e-01, 5.308e-01, # 1.592e+00, 1.526e+01, 4.271e-03, 2.073e-02, 2.828e-02, 8.468e-03, # 1.461e-02, 2.613e-03, 1.424e+01, 1.737e+01, 9.659e+01, 6.237e+02, # 1.166e-01, 2.685e-01, 2.866e-01, 9.173e-02, 2.736e-01, 7.320e-02]), 1) LBC_data.__getitem__(384) # 驗證是否是LBC_train的第一條數據 #(array([1.328e+01, 1.372e+01, 8.579e+01, 5.418e+02, 8.363e-02, 8.575e-02, # 5.077e-02, 2.864e-02, 1.617e-01, 5.594e-02, 1.833e-01, 5.308e-01, # 1.592e+00, 1.526e+01, 4.271e-03, 2.073e-02, 2.828e-02, 8.468e-03, # 1.461e-02, 2.613e-03, 1.424e+01, 1.737e+01, 9.659e+01, 6.237e+02, # 1.166e-01, 2.685e-01, 2.866e-01, 9.173e-02, 2.736e-01, 7.320e-02]), # 1) LBC_data[LBC_train.indices][1] #array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, # 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, # 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, # 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, # 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, # 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, # 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, # 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, # 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, # 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, # 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, # 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, # 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, # 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, # 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, # 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, # 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, # 0, 1])

還是需要強調,雖然PyTorch的數據表示形式會略顯復雜,但這是應對復雜大規模數據計算之必須,面對海量、非結構化數據,我們很難去查看一條條數據,而只能通過一些數據集的特性來探索數據信息。

??然后使用DataLoader函數進行數據轉化,由一般數據狀態轉化為“可建模”的狀態。所謂“可建模”狀態,指的是經過DataLoader處理的數據,不僅包含數據原始的數據信息,還包含數據處理方法信息,如調用幾個線程進行訓練、分多少批次等,DataLoader常用參數如下:

  • batch_size:每次迭代輸入多少數據,如果是小批量梯度下降,則輸入的數據量就是小批量迭代過程中“小批”的數量
  • shuffle:是否需要先打亂順序然后再進行小批量的切分,一般訓練集需要亂序,而測試集亂序沒有意義
  • num_worker:啟動多少線程進行計算

其他更多參數,將隨著我們介紹的深入逐步進行介紹

DataLoader?train_loader = DataLoader(LBC_train, batch_size=10, shuffle=True)test_loader = DataLoader(LBC_test, batch_size=10, shuffle=False)

此處需要注意,對于測試集來說,數據裝載并不是一定要進行的,如果測試集只是用于檢測模型效果,有時可以不用裝載直接帶入計算。

同樣,經過DataLoader處理后的數據也可以使用dataset屬性查看原數據

train_loader.dataset #<torch.utils.data.dataset.Subset at 0x15819156e88> LBC_train #<torch.utils.data.dataset.Subset at 0x15819156e88> train_loader.dataset == LBC_train #True

這里值得一提的是,市面上有很多教材在介紹PyTorch深度學習建模過程中的數據集劃分過程,會推薦使用scikit-learn中的train_test_split函數。該函數是可以非常便捷的完成數據集切分,但這種做法只能用于單機運行的數據,并且切分之后還要調用Dataset、DataLoader模塊進行數據封裝和加載,切分過程看似簡單,但其實會額外占用非常多的存儲空間和計算資源,當進行超大規模數據訓練時,所造成的影響會非常明顯(當然,也有可能由于數據規模過大,本地無法運行)。因此,為了更好的適應深度學習真實應用場景,在使用包括數據切分等常用函數時,函數使用優先級是

Pytorch原生函數和類>依據張量及其常用方法手動創建的函數>Scikit-Learn函數

2.建模及評估過程

??接下來,我們嘗試通過調庫實現完整的數據切分、訓練、查看建模結果一整個流程。

  • 數據準備過程
# 生成數據 features, labels = tensorGenReg() features = features[:, :-1] # 剔除最后全是1的列# 創建一個針對手動創建數據的數據類 class GenData(Dataset):def __init__(self, features, labels): # 創建該類時需要輸入的數據集self.features = features # features屬性返回數據集特征self.labels = labels # labels屬性返回數據集標簽self.lens = len(features) # lens屬性返回數據集大小def __getitem__(self, index):# 調用該方法時需要輸入index數值,方法最終返回index對應的特征和標簽return self.features[index,:],self.labels[index] def __len__(self):# 調用該方法不需要輸入額外參數,方法最終返回數據集大小return self.lens# 實例化對象/數據封裝 data = GenData(features, labels)# 切分數據集 num_train = int(data.lens * 0.7) num_test = data.lens - num_train data_train, data_test = random_split(data, [num_train, num_test])# 加載數據 train_loader = DataLoader(data_train, batch_size=10, shuffle=True) test_loader = DataLoader(data_test, batch_size=10, shuffle=False)
  • 構建模型
# 初始化核心參數 batch_size = 10 # 小批的數量 lr = 0.03 # 學習率 num_epochs = 3 # 訓練過程遍歷幾次數據# Stage 1.定義模型 class LR(nn.Module):def __init__(self, in_features=2, out_features=1): # 定義模型的點線結構super(LR, self).__init__()self.linear = nn.Linear(in_features, out_features)def forward(self, x): # 定義模型的正向傳播規則out = self.linear(x) return out# 實例化模型 LR_model = LR()# Stage 2.定義損失函數 criterion = nn.MSELoss()# Stage 3.定義優化方法 optimizer = optim.SGD(LR_model.parameters(), lr = 0.03)# Stage 4.模型訓練與測試 def fit(net, criterion, optimizer, batchdata, epochs=3):for epoch in range(epochs):for X, y in batchdata:yhat = net.forward(X)loss = criterion(yhat, y)optimizer.zero_grad()loss.backward()optimizer.step()
  • 模型訓練與測試
fit(net = LR_model,criterion = criterion,optimizer = optimizer,batchdata = train_loader,epochs = num_epochs )# 查看訓練模型 LR_model #LR( # (linear): Linear(in_features=2, out_features=1, bias=True) #) # 查看模型參數 list(LR_model.parameters()) #[Parameter containing: # tensor([[ 1.9997, -0.9996]], requires_grad=True), # Parameter containing: # tensor([1.0005], requires_grad=True)]

查看模型在訓練集上表現,首先我們可以通過dataset和indices方法還原訓練數據集

data_train.indices # 返回訓練集索引 #[705, # 223, # 553, # 386, # ... data[data_train.indices] # 返回訓練集 #(tensor([[ 1.1406, 0.4082], # [ 0.4877, 0.8226], # [-0.2876, 0.8771], # ..., # [ 2.0659, 0.6420], # [-0.5856, 1.1246], # [-0.3630, -1.5988]]), # tensor([[ 2.8812e+00], # [ 1.1609e+00], # [-4.6634e-01], # [ 3.9260e-01], # ........ data[data_train.indices][0] # 返回訓練集的特征 #tensor([[ 1.1406, 0.4082], # [ 0.4877, 0.8226], # [-0.2876, 0.8771], # ..., # [ 2.0659, 0.6420], # [-0.5856, 1.1246], # [-0.3630, -1.5988]]) # 計算訓練集MSE F.mse_loss(LR_model(data[data_train.indices][0]), data[data_train.indices][1]) #tensor(9.4741e-05, grad_fn=<MseLossBackward>) # 計算測試集MSE F.mse_loss(LR_model(data[data_test.indices][0]), data[data_test.indices][1]) #tensor(0.0001, grad_fn=<MseLossBackward>)

至此,即完成了整個從數據集切分到模型訓練,再到查看模型在不同數據集上表現的全過程。

五、實用函數補充

??結合上述過程,我們可以補充一些實用函數,方便簡化后續建模流程。

  • 數據封裝、切分和加載函數

??該函數可以直接將輸入的特征和標簽直接進行封裝、切分和加載。該函數可以直接處理此前定義的數據生成器創建的數據。

def split_loader(features, labels, batch_size=10, rate=0.7):"""數據封裝、切分和加載函數::param features:輸入的特征 :param labels: 數據集標簽張量:param batch_size:數據加載時的每一個小批數據量 :param rate: 訓練集數據占比:return:加載好的訓練集和測試集"""data = GenData(features, labels) num_train = int(data.lens * 0.7)num_test = data.lens - num_traindata_train, data_test = random_split(data, [num_train, num_test])train_loader = DataLoader(data_train, batch_size=batch_size, shuffle=True)test_loader = DataLoader(data_test, batch_size=batch_size, shuffle=False)return(train_loader, test_loader)

測試函數性能

# 設置隨機數種子 torch.manual_seed(420) # 創建數據集 features, labels = tensorGenReg() features = features[:, :-1] # 進行數據加載 train_loader, test_loader = split_loader(features, labels) #<torch._C.Generator at 0x17be1691530> # 查看第一條訓練集數據 train_loader.dataset[0] #(tensor([-1.4463, -0.6221]), tensor([-1.2863])) len(train_loader.dataset[:][0]) #700
  • 模型訓練函數

??模型訓練函數并不是新的函數,此處正式對其進行定義并寫入自定義模塊中,方便后續調用。

def fit(net, criterion, optimizer, batchdata, epochs=3, cla=False):"""模型訓練函數:param net:待訓練的模型 :param criterion: 損失函數:param optimizer:優化算法:param batchdata: 訓練數據集:param cla: 是否是分類問題:param epochs: 遍歷數據次數"""for epoch in range(epochs):for X, y in batchdata:if cla == True:y = y.flatten().long() # 如果是分類問題,需要對y進行整數轉化yhat = net.forward(X)loss = criterion(yhat, y)optimizer.zero_grad()loss.backward()optimizer.step()
  • MSE計算函數

??接下來,我們借助F.mse_loss,定義一個可以直接根據模型輸出結果和加載后的數據計算MSE的函數。

def mse_cal(data_loader, net):"""mse計算函數:param data_loader:加載好的數據:param net: 模型:return:根據輸入的數據,輸出其MSE計算結果"""data = data_loader.dataset # 還原Dataset類X = data[:][0] # 還原數據的特征y = data[:][1] # 還原數據的標簽yhat = net(X)return F.mse_loss(yhat, y)train_loader.dataset[:][0] #tensor([[-1.4463, -0.6221], # [-0.4742, -0.2939], # [ 1.9870, 0.1949], # ..., # [-1.6366, -2.1399], # [-1.8178, -1.4618], # [ 0.2646, 2.3555]])

接下來,測試函數性能。借助上述建模實驗中構建的回歸模型,測試函數能否順利執行。

# 設置隨機數種子 torch.manual_seed(420) # 實例化模型 LR_model = LR()# Stage 2.定義損失函數 criterion = nn.MSELoss()# Stage 3.定義優化方法 optimizer = optim.SGD(LR_model.parameters(), lr = 0.03)# Stage 4.訓練模型 fit(net = LR_model,criterion = criterion,optimizer = optimizer,batchdata = train_loader,epochs = 3 ) #<torch._C.Generator at 0x17be1691530> LR_model #LR( # (linear): Linear(in_features=2, out_features=1, bias=True) #) mse_cal(train_loader, LR_model) # 計算訓練誤差 #tensor(0.0001, grad_fn=<MseLossBackward>) mse_cal(test_loader, LR_model) # 計算測試誤差 #tensor(8.8412e-05, grad_fn=<MseLossBackward>)

和F.mse_loss對比

F.mse_loss(LR_model(train_loader.dataset[:][0]), train_loader.dataset[:][1]) #tensor(0.0001, grad_fn=<MseLossBackward>) F.mse_loss(LR_model(test_loader.dataset[:][0]), test_loader.dataset[:][1]) #tensor(8.8412e-05, grad_fn=<MseLossBackward>)
  • 準確率計算函數

??類似的,定義一個分類問題的準確率計算函數,同樣要求輸入是加載后的數據集和訓練完成的模型。

def accuracy_cal(data_loader, net):"""準確率:param data_loader:加載好的數據:param net: 模型:return:根據輸入的數據,輸出其準確率計算結果"""data = data_loader.dataset # 還原Dataset類X = data[:][0] # 還原數據的特征y = data[:][1] # 還原數據的標簽zhat = net(X) # 默認是分類問題,并且輸出結果是未經softmax轉化的結果soft_z = F.softmax(zhat, 1) # 進行softmax轉化acc_bool = torch.argmax(soft_z, 1).flatten() == y.flatten()acc = torch.mean(acc_bool.float())return acc t = torch.arange(9).reshape(3, 3).float() t #tensor([[0., 1., 2.], # [3., 4., 5.], # [6., 7., 8.]]) F.softmax(t, 1) #tensor([[0.0900, 0.2447, 0.6652], # [0.0900, 0.2447, 0.6652], # [0.0900, 0.2447, 0.6652]])

接下來,測試函數性能:

# 設置隨機數種子 torch.manual_seed(420) # 創建分類數據集 features, labels = tensorGenCla()# 進行數據加載 train_loader, test_loader = split_loader(features, labels)class softmaxR(nn.Module):def __init__(self, in_features=2, out_features=3, bias=False): # 定義模型的點線結構super(softmaxR, self).__init__()self.linear = nn.Linear(in_features, out_features)def forward(self, x): # 定義模型的正向傳播規則out = self.linear(x) return out# 實例化模型和 softmax_model = softmaxR()# 定義損失函數 criterion = nn.CrossEntropyLoss()# 定義優化算法 optimizer = optim.SGD(softmax_model.parameters(), lr = lr)# 執行模型訓練 fit(net = softmax_model, criterion = criterion, optimizer = optimizer, batchdata = train_loader, epochs = num_epochs, cla=True)accuracy_cal(train_loader, softmax_model) #tensor(0.8571) accuracy_cal(test_loader, softmax_model) #tensor(0.8533)

至此,完成本階段實用函數的添加。

總結

以上是生活随笔為你收集整理的Lesson 13.1 深度学习建模目标与性能评估理论的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。