1,資料加載
PyTorch開發了與資料互動的標準約定,所以能一致地處理資料,而不論處理影像、文本還是音頻,與資料互動的兩個主要約定是資料集(dataset)和資料加載器(dataloader),資料集是一個Python類,使我們能獲得提供給神經網路的資料,資料加載器則從資料集向網路提供資料,
PyTorch通過torch.utils.data對一般常用的資料加載進行了封裝,可以很容易地實作多執行緒資料預讀和批量加載, 并且torchvision已經預先實作了常用影像資料集,包括前面使用過的CIFAR-10,ImageNet、COCO、MNIST、LSUN等資料集,可通過torchvision.datasets方便的呼叫,
1.1,Dataset
Dataset是一個抽象類,為了能夠方便的讀取,需要將要使用的資料包裝為Dataset類, 自定義的Dataset需要繼承它并且實作兩個成員方法:
__getitem__()該方法定義用索引(0到len(self))獲取一條資料或一個樣本,__len__()該方法回傳資料集的總長度,from torch.utils.data import Dataset import pandas as pd # 定義一個資料集 class BulldozerDataset(Dataset): """ 資料集演示 """ def __init__(self, csv_file): """實作初始化方法,在初始化的時候將資料讀載入""" self.df = pd.read_csv(csv_file) def __len__(self): ''' 回傳df的長度 ''' return len(self.df) def __getitem__(self, idx): ''' 根據 idx 回傳一行資料 ''' return self.df.iloc[idx].Title if __name__ == '__main__': ds_demo = BulldozerDataset('Highest Holywood Grossing Movies.csv') print(len(ds_demo)) print(ds_demo[0]) ================================================= 918 Star Wars: Episode VII - The Force Awakens (2015)
1.2,Dataloader
DataLoader為我們提供了對Dataset的讀取操作,常用引數有:
- dataset(Dataset):傳入的資料集,
- batch_size:每個batch的大小,
- shuffle:在每個epoch開始的時候,對資料進行重新排序,
- num_workers:加載資料的時候使用幾個子行程,windows 下執行緒引數設為 0 安全,
dl = torch.utils.data.DataLoader(ds_demo, batch_size=10, shuffle=True, num_workers=0)DataLoader回傳的是一個可迭代物件,我們可以使用迭代器分次獲取資料:
idata=iter(dl) print(next(idata)) ==================== ['Cars 3 (2017)', 'Dick Tracy (1990)', 'Click (2006)', 'Star Trek: The Motion Picture (1979)', 'Apocalypse Now (1979)', 'The Devil Wears Prada (2006)', 'The Divergent Series: Insurgent (2015)', 'As Good as It Gets (1997)', 'Safe House (2012)', 'Puss in Boots (2011)']常見的用法是使用for回圈對其進行遍歷:
ds_demo = BulldozerDataset('Highest Holywood Grossing Movies.csv') dl = torch.utils.data.DataLoader(ds_demo, batch_size=10, shuffle=True, num_workers=0) for i, data in enumerate(dl): print(i, data) =================================== 0 ['Catch Me If You Can (2002)', 'Cast Away (2000)', "Miss Peregrine's Home for Peculiar Children (2016)", 'Django Unchained (2012)', 'xXx (2002)', 'Iron Man 2 (2010)', 'The Meg (2018)', 'Despicable Me 2 (2013)', 'Peter Rabbit (2018)', 'Bridesmaids (2011)'] ...
1.3,torchvision
torchvision 是PyTorch中專門用來處理影像的庫,torchvision.datasets 可以理解為PyTorch團隊自定義的dataset,這些dataset幫我們提前處理好了很多的圖片資料集,我們拿來就可以直接使用: - MNIST - COCO - Captions - Detection - LSUN - ImageFolder - Imagenet-12 - CIFAR - STL10 - SVHN - PhotoTour 我們可以直接使用,
torchvision的安裝:pip install torchvision,
import torchvision.datasets as datasets trainset = datasets.CIFAR10(root='./data', # 表示 MNIST 資料的加載的目錄 train=True, # 表示是否加載資料庫的訓練集,false的時候加載測驗集 download=True, # 表示是否自動下載 MNIST 資料集 transform=None) # 表示是否需要對資料進行預處理,none為不進行預處理
torchvision.models:torchvision不僅提供了常用圖片資料集,還提供了訓練好的模型,可以加載之后,直接使用,或者在進行遷移學習 torchvision.models模塊的 子模塊中包含以下模型結構, - AlexNet - VGG - ResNet - SqueezeNet - DenseNet,
#我們直接可以使用訓練好的模型,當然這個與datasets相同,都是需要從服務器下載的 import torchvision.models as models resnet18 = models.resnet18(pretrained=True)#True:加載模型,并設為預訓練模式
torchvision.transforms:transforms 模塊提供了一般的影像轉換操作類,用作資料處理和資料增強,
rom torchvision import transforms as transforms transform = transforms.Compose([ transforms.RandomCrop(32, padding=4), #先四周填充0,在把影像隨機裁剪成32*32 transforms.RandomHorizontalFlip(), #影像一半的概率翻轉,一半的概率不翻轉 transforms.RandomRotation((-45,45)), #隨機旋轉 transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.229, 0.224, 0.225)), #R,G,B每層的歸一化用到的均值和方差 ])(0.485, 0.456, 0.406), (0.2023, 0.1994, 0.2010) :根據ImageNet訓練的歸一化引數,可以直接使用,認為這個是固定值就可以,也可以使用其他的值,
2,數學原理
2.1,損失函式
損失函式(loss function)是用來估量模型的預測值(我們例子中的output)與真實值(例子中的y_train)的不一致程度,它是一個非負實值函式,損失函式越小,模型的魯棒性就越好, 我們訓練模型的程序,就是通過不斷的迭代計算,使用梯度下降的優化演算法,使得損失函式越來越小,損失函式越小就表示演算法達到意義上的最優,
機器學習:概念_燕雙嚶-CSDN博客1,機器學習概述1.1,機器學習概念機器學習即Machine Learning,涉及概率論、統計學、逼近論、凸分析、演算法復雜度理論等多門學科,目的是讓計算機模擬或實作人類的學習行為,以獲取新的知識或技能,重新組織已有的知識結構使之不斷完善自身的性能,簡單來講,機器學習就是人們通過提供大量的相關資料來訓練機器,DataAnalysis:基本概念,環境介紹,環境搭建,大資料問題_燕雙嚶-CSDN博客1,概述1.1,資料的性質所謂資料就是描述事物的符號,是對客觀事物的性質、狀態和相互關系等進行記載的
https://shao12138.blog.csdn.net/article/details/120507206#t8因為PyTorch是使用mini-batch來進行計算的,所以損失函式的計算出來的結果已經對mini-batch取了平均,
nn.L1Loss(和方誤差):輸入x和目標y之間差的絕對值,要求 x 和 y 的維度要一樣(可以是向量或者矩陣),得到的 loss 維度也是對應一樣的,
nn.NLLLoss:用于多分類的負對數似然損失函式,
nn.CrossEntropyLoss:多分類用的交叉熵損失函式,
nn.BCELoss:計算 x 與 y 之間的二進制交叉熵,
2.2,梯度下降
機器學習:梯度下降_燕雙嚶-CSDN博客_機器學習梯度下降
https://shao12138.blog.csdn.net/article/details/121306952
在微積分里面,對多元函式的引數求
偏導數,把求得的各個引數的偏導數以向量的形式寫出來,就是梯度, 例如函式
, 分別對
求偏導數,求得的梯度向量就是
,簡稱
或者
,
幾何上講,梯度就是函式變化增加最快的地方,沿著梯度向量的方向,更加容易找到函式的最大值,反過來說,沿著梯度向量相反的方向梯度減少最快,也就是更加容易找到函式的最小值,
我們需要最小化損失函式,可以通過梯度下降法來一步步的迭代求解,得到最小化的損失函式,和模型引數值,
梯度下降法直觀解釋:梯度下降法就好比下山,我們并不知道下山的路,于是決定走一步算一步,每走到一個位置的時候,求解當前位置的梯度,沿著梯度的負方向,也就是當前最陡峭的位置向下走一步,然后繼續求解當前位置梯度,向這一步所在位置沿著最陡峭最易下山的位置走一步,這樣一步步的走下去,一直走到覺得我們已經到了山腳,
這樣走下去,有可能我們不能走到山腳,而是到了某一個區域的山峰低處(區域最優解),
這個問題在以前的機器學習中可能會遇到,因為機器學習中的特征比較少,所以導致很可能陷入到一個區域最優解中出不來,但是到了深度學習,動輒百萬甚至上億的特征,出現這種情況的概率幾乎為0,所以我們可以不用考慮這個問題,
2.3,Mini-batch的梯度下降法
對整個訓練集進行梯度下降法的時候,必須處理整個訓練資料集,然后才能進行一步梯度下降,即每一步梯度下降法需要對整個訓練集進行一次處理,如果訓練資料集很大的時候處理速度會很慢,而且也不可能一次的載入到記憶體或者顯存中,所以我們會把大資料集分成小資料集,一部分一部分的訓練,這個訓練子集即稱為Mini-batch,
在PyTorch中就是使用這種方法進行的訓練,可以看看關于dataloader的介紹里面的batch_size就是我們一個Mini-batch的大小,
對于普通的梯度下降法,一個epoch只能進行一次梯度下降;而對于Mini-batch梯度下降法,一個epoch可以進行Mini-batch的個數次梯度下降,
普通的batch梯度下降法和Mini-batch梯度下降法代價函式的變化趨勢,如下圖所示:
- 如果訓練樣本的大小比較小時,能夠一次性的讀取到記憶體中,那我們就不需要使用Mini-batch,
- 如果訓練樣本的大小比較大時,一次讀入不到記憶體或者現存中,那我們必須要使用 Mini-batch來分批的計算 - Mini-batch size的計算規則如下,在記憶體允許的最大情況下使用2的N次方個size,
torch.optim 是一個實作了各種優化演算法的庫,大部分常用優化演算法都有實作,我們直接呼叫即可,
torch.optim.SGD
隨機梯度下降演算法,帶有動量(momentum)的演算法作為一個可選引數可以進行設定,樣例如下:
#lr引數為學習率,對于SGD來說一般選擇0.1 0.01.0.001,如何設定會在后面實戰的章節中詳細說明 ##如果設定了momentum,就是帶有動量的SGD,可以不設定 optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)torch.optim.RMSprop
除了以上的帶有動量Momentum梯度下降法外,RMSprop(root mean square prop)也是一種可以加快梯度下降的演算法,利用RMSprop演算法,可以減小某些維度梯度更新波動較大的情況,使其梯度下降的速度變得更快,
#我們的課程基本不會使用到RMSprop所以這里只給一個實體 optimizer = torch.optim.RMSprop(model.parameters(), lr=0.01, alpha=0.99)torch.optim.Adam
Adam 優化演算法的基本思想就是將 Momentum 和 RMSprop 結合起來形成的一種適用于不同深度學習結構的優化演算法,
# 這里的lr,betas,還有eps都是用默認值即可,所以Adam是一個使用起來最簡單的優化方法 optimizer = torch.optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08)
2.4,方差&偏差
偏差度量了學習演算法的期望預測與真實結果的偏離程式,即刻畫了學習演算法本身的擬合能力,
方差度量了同樣大小的訓練集的變動所導致的學習性能的變化,即模型的泛化能力,
高偏差(high bias)的情況:一般稱為欠擬合(underfitting),即我們的模型并沒有很好的去適配現有的資料,擬合度不夠,
高方差(high variance)的情況:一般稱作過擬合(overfitting),即模型對于訓練資料擬合度太高了,失去了泛化的能力,
欠擬合解決方案:
- 增加網路結構,如增加隱藏層數目;
- 訓練更長時間;
- 尋找合適的網路架構,使用更大的NN結構;
過擬合解決方案:
- 使用更多的資料;
- 正則化( regularization);
- 尋找合適的網路結構;
#計算出偏差: print (5-w.data.item(),7-b.data.item())
3,貓魚影像分類
3.1,建立訓練集
貓,魚分類資料集,訓練集,測驗集和驗證集-機器學習檔案類資源-CSDN下載
接下來就需要把它們轉換成PyTorch能理解的一種格式,torchvision包中有一個名為ImageFolder的類,它能很好地為我們完成一切,只要我們的影像在一個適當的目錄結構中,其中每個目錄分別是一個標簽(例如所有的貓都在一個名為cat的目錄中):
import torchvision from torchvision import transforms train_data_path = "/train/" transforms = transforms.Compose([ transforms.Resize(64), #將每個影像縮放為相同解析度64*64 transforms.ToTensor(), #將影像轉換成一個張量 transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) #根據特定的均值進行歸一化處理,超引數, ]) train_data = torchvision.datasets.ImageFolder(root=train_data_path, transforms=transforms)將影像大小調整為64*64,這是隨意調整的,目的是讓接下來第一個網路能很快地計算,一般來講,大多數現有的框架都使用224*224或299*299的影像輸入,而輸入尺寸越大,網路學習的資料就越多,但通常GPU記憶體中職能容納很小批量的影像,
torchvision允許指定一個轉換串列,將影像輸入到神經網路之前可以應用這些轉換,默認轉換是將影像轉換為一個張量(transforms.ToTensor()方法),另外還可以做一些可能看起來不太明顯的作業,
歸一化很重要,因為輸入通過神經網路層時會完成大量乘法;保證到來的值在0~1之間可以防止訓練階段中值變得過大(梯度爆炸),這里使用的神奇引數是整個ImageNet資料集的均值和標準差,可以專門計算整個魚和貓子集的均值和標準差,不過ImageNet資料集的這些引數已經足夠了(如果你處理一個完全不同的資料集,就必須計算均值和標準差,直接使用ImageNet常量也是可接受的),
3.2,建立驗證集和測驗集
深度學習面臨的一個危險是過擬合:你的模型確實能很好地識別所訓練的資料,不過不能泛化到它沒見過的例子上面,它看到一個貓的影像后,除非其他貓的影像都與這個影像非常相似,否則模型不會認為那是貓,盡管它們是貓,為了防止網路出現這個問題,因此準備驗證集,這是未出現在訓練集中的一系列貓和魚的影像,在每個訓練周期(epoch)的最后,通過與這個驗證集的比較來確保我們的網路沒有做錯,
除了一個驗證集,還需要創建一個測驗集,這個資料集要在所有訓練完成之后用來測驗模型,
train_data_path = "/train/" val_data_path = "/val/" test_data_path = "/test/" train_data = torchvision.datasets.ImageFolder(root=train_data_path, transforms=transforms) val_data = torchvision.datasets.ImageFolder(root=val_data_path, transforms=transforms) test_data = torchvision.datasets.ImageFolder(root=test_data_path, transforms=transforms)建立資料加載器:
import torchvision.datasets as data batch_size = 64 train_data_loader = data.DataLoader(train_data, batch_size=batch_size) val_data_loader = data.DataLoader(val_data, batch_size=batch_size) test_data_loader = data.DataLoader(test_data, batch_size=batch_size)batch_size:在訓練和更新網路之前將為這個網路提供多少影像,理論上講,可以把batch_size設定為測驗和訓練集中的影像數,使網路在更新之前會看到每一個影像,但在實際中,我們并不會這么做,因為與存盤資料集中每一個影像的所有相關資訊相比,較小批量需要的記憶體更少,而且更小的批量會使訓練速度更快,以便我們可以更快地更新網路,
PyTorch的資料加載器將batch_size默認設定為1,你幾乎一定會改變這個,你還可以指定資料集如何采樣,每次運行時是否將資料集打亂,另外從資料集取資料需要使用多少個作業行程,
3.3,搭建神經網路
神經網路結構:一個輸入層,它要處理輸入張量(我們的影像);另外一個輸出層,其大小為輸出的類別的個數(2);介于這兩者之間的一個隱藏層;層與層之間采用全連接,
圖中的神經網路,它有一個包含3個節點的輸入層,一個包含3個節點的隱藏層和一個包含2個節點的輸出層,可以看出,在全連接中每一個節點都會影響下一層中各個節點,而且每個連接有一個權重,它確定了信號從這個節點進入下一層的強度(訓練的主要目的就是更新這些權重,通常會隨機化開始),一個輸入傳入網路時,可以簡單地對權重與這一層對輸入的偏置完成矩陣乘法,
在PyTorch中創建神經網路需要繼承一個名為torch.nn.Network的類,填寫__init__和forward方法:
import torch.nn as nn import torch.nn.functional as F class SimpleNet(nn.Module): def __init__(self): super(SimpleNet, self).__init__() #建立3個全連接層 self.fc1 = nn.Linear(12288, 84) self.fc2 = nn.Linear(84, 50) self.fc3 = nn.Linear(50, 2) def forward(self, x): #將影像中的3維向量轉換為1維向量,從而能輸入到第一個全連接層 x = x.view(-1, 12288) #呼叫激活函式 x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = F.softmax(self.fc3(x)) #回傳softmax激活函式的結果作為分類結果 return x simplenet = SimpleNet()隱藏層的結點數可以任意,不過要求最后輸出層節點數為2,這與我們的分類結果相對應,一般來講,你會希望層中的資料向下傳遞時進行壓縮,比如,如果一個層有50個輸入對100個輸出,那么網路在學習時,只需要把50個連接傳遞到100個輸出中的50個,就認為它的作業完成了,根據輸入減少輸出的大小,就能要求這部分網路用更少的資源學習原始輸入的一個表示,這往往意味著它會提取影像中對于我們要解決的問題很重要的一些特征,
3.4,損失函式和優化器
損失函式是有效深度學習解決方案的關鍵環節之一,PyTorch使用損失函式來確定如何更新網路從而達到期望的結果,根據你的需求,損失函式可以很復雜,也可以很簡單,PyTorch提供了一個完備的損失函式集合,涵蓋了你可能遇到的大多數應用,當然你也可以自定義損失函式,
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(simplenet.parameters(), lr=0.001)Adam(以及RMSProp和AdaGrad)相較于SGD所做的重要改進之一是對每個引數使用一個學習率,并根據這些引數的變化率來調整學習率,它會維護一個指數衰減的梯度串列和這些梯度的平方,并用它們來調整Adam使用的全域學習率,根據經驗,對于深度學習網路,Adam遠勝于大多數其他優化器,不過你也可以把Adam換成SGD或RMSProp,
3.5,模型的訓練
def train(model, optimizer, loss_fn, train_loader, val_loader, epochs=20, device=torch.device("cuda")): for epoch in range(1, epochs + 1): training_loss = 0.0 valid_loss = 0.0 model.train() for batch in train_loader: optimizer.zero_grad() inputs, targets = batch inputs = inputs.to(device) targets = targets.to(device) output = model(inputs) loss = loss_fn(output, targets) loss.backward() optimizer.step() training_loss += loss.data.item() * inputs.size(0) training_loss /= len(train_loader.dataset) model.eval() num_correct = 0 num_examples = 0 for batch in val_loader: inputs, targets = batch inputs = inputs.to(device) output = model(inputs) targets = targets.to(device) loss = loss_fn(output, targets) valid_loss += loss.data.item() * inputs.size(0) correct = torch.eq(torch.max(F.softmax(output, dim=1), dim=1)[1], targets) num_correct += torch.sum(correct).item() num_examples += correct.shape[0] valid_loss /= len(val_loader.dataset) print( 'Epoch: {}, Training Loss: {:.2f}, Validation Loss: {:.2f}, accuracy = {:.2f}'.format(epoch, training_loss, valid_loss, num_correct / num_examples)) if torch.cuda.is_available(): device = torch.device("cuda") print("cuda") else: device = torch.device("cpu") print("cpu") simplenet.to(device) train(simplenet, optimizer,torch.nn.CrossEntropyLoss(), train_data_loader,val_data_loader, epochs=10, device=device)
3.6,預測
simplenet = torch.load("simplenet") labels = ['cat','fish'] import os i =0 j =0 for root, dirs, files in os.walk("test/cat"): for file in files: j+=1 img = Image.open("test/cat/"+file) img = transforms(img).to(device) img = torch.unsqueeze(img, 0) simplenet.eval() prediction = F.softmax(simplenet(img), dim=1) prediction = prediction.argmax() if(labels[prediction]=="cat"): i+=1 print(i/j)重用了前面的轉換流水線,將影像準換為適用于這個神經網路的正確形式,不過,由于我們的網路使用了批次,實際上它希望得到一個4維張量,第一個維度指示一個批次中的不同影像,我們沒有批次,不過可以使用unsqueeze(0)創建長度為1的一個批次,這回在張量最前面增加一個維度,
得到預測結果很簡單,只需要把我們的批次傳入模型,然后找出較大概率的類,在這里,可以簡單地將張量轉換為一個陣列,并比較這兩個元素,不過通常會有更多元素,PyTorch提供了argmax()函式,這很有用,它會回傳張量中最大值的索引,然后使用這個索引訪問我們的標簽陣列,列印結果,
我們可以使用save保存整個模型,也可以僅使用state_dict保存引數,使用state_dict通常更可取,因為它允許您重用引數,即使模型的結構發生變化(或將引數從一個模型應用到另一個模型),
torch.save(simplenet, "/tmp/simplenet") simplenet = torch.load("/tmp/simplenet")torch.save(simplenet.state_dict(), "/tmp/simplenet") simplenet = SimpleNet() simplenet_state_dict = torch.load("/tmp/simplenet") simplenet.load_state_dict(simplenet_state_dict)
4,卷積神經網路
class CNNNet(nn.Module): def __init__(self, num_classes=2): super(CNNNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(64, 192, kernel_size=5, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(192, 384, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(256, 256, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), ) self.avgpool = nn.AdaptiveAvgPool2d((6, 6)) self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(), nn.Dropout(), nn.Linear(4096, 4096), nn.ReLU(), nn.Linear(4096, num_classes) ) def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.classifier(x) return x
4.1,卷積:Conv2d
Conv2d層是一個二維卷積,如果有一個灰度影像,它會包含一個陣列,寬度為x像素,高度為y像素,陣列中的每個元素有一個值,指示這是黑色還是白色,或者介于兩者之間(假設是一個8位影像,那么每個值可以在0~255之間),例如:一個高度和寬度都是4像素的影像:
卷積核:這是另外一個矩陣,可能更小,我們會在影像上拖動這個過濾器,例如:一個2*2卷積核:
卷積結果:要生成輸出,使用卷積核滑過原始輸入,就想一個放大鏡滑過一張紙一樣,例如:從左上角開始,我們第一個計算機結果為:
nn.Conv2d(in_channels,out_channel, kernel_size, stride, padding) in_channels:是這一層要接收的輸入通道個數,開始時,網路接受RGB影像作為輸入,所以輸入通道數為3, out_channels:是這一層輸出通道個數,對應卷積層中卷積核的個數, kernel_size:對應卷積核的大小, stride:表示將過濾器調整到一個新位置時要在輸入上移動多少步,可以按1來移動,這樣可以得到與輸入大小相同的特征圖輸出,還可以傳入一個元組(a,b),這允許每一步水平移動a并且向下移動b, padding:如果stride使用元組,可能出現數值丟失,padding就是用來指定缺失值,如果沒有設定stride,則最后一列會被直接丟棄,
4.2,池化:MaxPool2d
與卷積層一起,通常還會看到池化層,這些層會降低前一個輸入層的網路解析度,是的更低層有更少的引數,首先這種壓縮可以讓計算更快,而且這有助于避免網路中的過擬合,
在上述模型中,我們使用了MaxPool2d,內核大小為3,步長為2,例如:一個5*3的輸入:
使用內核大小為3*3和步長為2,可以通過池化得到兩個3*3的張量:
在MaxPool中,我們分別從這兩個張量中取最大值,這樣就得到一個輸出張量
,與卷積層一樣,MaxPool也有一個padding選項,可以在張量上創建一個0值組成的邊框,以防跨出到張量視窗之外,
除了從內核取得最大值,還可以用其他函式池化,一個很受歡迎的函式是取張量平均值,這就使得所有張量資料都會對池化做出貢獻,而不像max中那樣只是一個值所貢獻,
另外,PyTorch還提供了AdaptiveMaxPool和AdaptiveAvgPool層,它們的作業獨立于接收的輸入張量的維度,建議在你構造的模型架構中使用這些層而不是標準的MaxPool或AvgPool層,因為它們允許你創建可以處理不同輸入維度的架構;在處理不同的資料集時,這回很方便,
4.3,Dropout
對于神經網路,一個反復出現的問題是它們很容易對訓練資料過擬合,為此神經網路領域做了大量作業來尋找適當的方法,希望能夠使網路學習和泛化到非訓練資料,而不只是學習如何簡單地應對訓練輸入,為了做到這一點,Droput(隨機失活)層是一個極其簡單的方法,好處是很容易理解而且很有效:在一個訓練周期中,如果對網路中隨機一組節點不做訓練怎么樣?因為他們不會更新,所以沒有機會與輸入資料過擬合,而且由于這是隨機的,所以每個訓練周期會忽略輸入中不同的資料,這有助于進一步泛化,
默認地,在上述CNN網路中,Droupout層初始化為0.5,這表示50%的輸入張量會隨機地置為0,如果你想把它修改為20%,可以為初始化呼叫增加p引數:Dropout(p=0.2),
4.4,訓練模型
import torchvision.models as models import torch.nn as nn import torchvision import torchvision.models as models import torch from torchvision import transforms from PIL import Image import torch.nn.functional as F import torch.optim as optim class CNNNet(nn.Module): def __init__(self, num_classes=2): super(CNNNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(64, 192, kernel_size=5, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(192, 384, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(256, 256, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), ) self.avgpool = nn.AdaptiveAvgPool2d((6, 6)) self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(), nn.Dropout(), nn.Linear(4096, 4096), nn.ReLU(), nn.Linear(4096, num_classes) ) def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.classifier(x) return x def train(model, optimizer, loss_fn, train_loader, val_loader, epochs=20, device="cpu"): for epoch in range(1, epochs + 1): training_loss = 0.0 valid_loss = 0.0 model.train() for batch in train_loader: optimizer.zero_grad() inputs, targets = batch inputs = inputs.to(device) targets = targets.to(device) output = model(inputs) loss = loss_fn(output, targets) loss.backward() optimizer.step() training_loss += loss.data.item() * inputs.size(0) training_loss /= len(train_loader.dataset) model.eval() num_correct = 0 num_examples = 0 for batch in val_loader: inputs, targets = batch inputs = inputs.to(device) output = model(inputs) targets = targets.to(device) loss = loss_fn(output, targets) valid_loss += loss.data.item() * inputs.size(0) correct = torch.eq(torch.max(F.softmax(output, dim=1), dim=1)[1], targets) num_correct += torch.sum(correct).item() num_examples += correct.shape[0] valid_loss /= len(val_loader.dataset) print( 'Epoch: {}, Training Loss: {:.2f}, Validation Loss: {:.2f}, accuracy = {:.2f}'.format(epoch, training_loss, valid_loss, num_correct / num_examples)) img_transforms = transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_data_path = "./train/" train_data = torchvision.datasets.ImageFolder(root=train_data_path, transform=img_transforms) val_data_path = "./val/" val_data = torchvision.datasets.ImageFolder(root=val_data_path, transform=img_transforms) batch_size = 64 train_data_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True) val_data_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, shuffle=True) if torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") cnnnet = CNNNet() cnnnet.to(device) optimizer = optim.Adam(cnnnet.parameters(), lr=0.001) train(cnnnet, optimizer, torch.nn.CrossEntropyLoss(), train_data_loader, val_data_loader, epochs=10, device=device)
5,預訓練
5.1,預訓練模型
顯然,如果每次都要使用一個模型都必須定義模型,這會很麻煩,特別是如果你的起點是AlexNet,PyTorch默認地在torchvision庫中提供了很多流行的模型,對于AlexNet,所做的只是:
import torchvision.models as models alexnet = models.alexnet(num_classes=1000, pretrained=True) print(alexnet)另外還提供了不同版本的VGG,ResNet,Inception,DenseNet和SqueezeNet的定義,這里提供了模型定義,不過你還可以更進一步呼叫models.alexnet(pretrained=True)為AlexNet下載一個預訓練的權重集,這樣你就能立即用它進行分類而不需要額外的訓練(大部分情況下還需要做一些訓練,來提高特定資料集上的準確度),
PyTorchHub:PyTorch提供了另外一個途徑來獲取模型:PyTorchHub,未來這應該會成為所有發布模型的中心,無論是用于處理影像、文本、音頻視頻還是其他型別的資料,要以這種方式得到一個模型,需要使用torch.hub模塊:
resnet50 = torch.hub.load('pytorch/vision', 'resnet50') print(resnet50)第一個引數只是一個GitHub所有者和存盤庫(這個字串還可以有一個可選的tag/branc識別符號);第二個引數是所請求的模型(resnet50);第三個引數指示是否下載預訓練的權重,還刻意使用torch.hub.list('pytorch/vision')發現存盤庫可以下載的所有模塊,
5.2,BatchNorm
BatchNorm:是批歸一化,這是只有一個任務的簡單層:使用兩個學習引數(這意味著它將隨其余網路進行訓練),努力確保通過網路的每個小批次(minibatch)均值以0為中心(零均值化),并且方差為1,
【問題】之前我們通過轉換鏈隊輸入進行歸一化了,為啥還需要這個作業呢?
【答案】對于較小的網路,BatchNorm確實沒有太大用處,但是隨著網路越來越大,由于反復相乘,任意一層對另一層的影響是極大的,最終可能會遭遇梯度消失或梯度爆炸,這兩個問題對于訓練程序是致命的,BatchNorm層可以確保即使使用類似ResNet-152的模型,網路中的乘法也不會失控,
【問題】如果我們網路中有BatchNorm,為什么還要在訓練回圈的轉換連中對輸入歸一化呢?難道BatchNorm不能為我們做這個作業嗎?
【答案】完全可以!不過這會使得網路花更長時間來學習如何控制輸入,因為它們必須自己發現初始轉換,這會讓訓練時間更長,
6,遷移學習???????
6.1,用ResNet遷移學習
創建ResNet模型,然后進行訓練,這完全可以,ResNet模型并沒有什么神奇的地方:它同樣由你已見過的構建模塊組成,不過,這是一個很大的模型,另外,盡管用你的資料對一個基準ResNet做一些改進,但它還是需要大量資料來確保訓練信號達到架構的所有部分,并針對新的分類任務進行訓練,因此我們想避免在這個方法中使用大量資料,
現在的情況是:我們處理的架構不是用隨機引數初始化,這與我們以前的做法不同,預訓練的ResNet模型已經編碼了一組資訊來滿足影像識別和分類需要,所以沒必要進行重新訓練,實際上,我們會微調(fine-tune)這個網路,對這個架構稍作修改,在最后包括一個新的網路模塊,來取代正常情況下完成ImageNet分類(1000個類別)的標準線性層,然后凍結(freeze)所有現有的ResNet層,訓練時,只更新新層中的引數,不過還使用要從凍結層得到激活值,這使得我們可以快速地訓練新層,同時保留預訓練已經包含的資訊,
1,創建一個預訓練的ResNet-50模型,
import torchvision.models as models transfer_model = models.resnet50(pretrained=True)2,接下來需要凍結這些層,做法很簡單:通過使用requires_grad()讓它們停止累計梯度,需要對網路中的每一個引數都這么做,不過好在PyTorch提供了一個parameters()方法,可以很容易地完成這個作業,
for name, param in transfer_model.named_parameters(): param.requires_grad = False你可能不想凍結模型中的BatchNorm層,因為這些層訓練為逼近模型原先訓練資料集的均值和標準差,而不是你想要微調的那個資料集,BatchNorm修正你的輸入時,最后可能會丟失資料的一些信號,可以查看模型結構,只凍結不是BatchNorm的層:
for name, param in transfer_model.named_parameters(): if("bn" not in name): param.requires_grad = False3,把最后的分類模塊替換為一個新模塊,就是我們要訓練來檢驗是貓還是魚的模塊,在這個例子中,我們把她替換為兩個Linear層、一個ReLU和Dropout,不過這里也可以有額外的CNN層,PyTorch中ResNet實作的定義把最后的分類器模塊存盤為一個實體變數fc,所以我們需要做的就是把它替換為我們的新結構:
transfer_model.fc = nn.Sequential(nn.Linear(transfer_model.fc.in_features,500), nn.ReLU(), nn.Dropout(), nn.Linear(500,2))上面代碼中,我們利用in_features變數,這可以用來得到進入一層的激活值數(這里是2048),還可以使用out_features發現輸出的激活值,想打磚塊一樣組合網路時,這些函式會很方便,如果一層的輸入特征與前一層的輸出特征不一致,程式會在運行時報錯,
4,訓練網路,應該可以看到,只用幾個epoch,準確率就會大幅度提升,
import torchvision.models as models import torch.nn as nn import torchvision import torchvision.models as models import torch from torchvision import transforms from PIL import Image import torch.nn.functional as F import torch.optim as optim transfer_model = models.resnet50(pretrained=True) for name, param in transfer_model.named_parameters(): if("bn" not in name): param.requires_grad = False transfer_model.fc = nn.Sequential(nn.Linear(transfer_model.fc.in_features,500), nn.ReLU(), nn.Dropout(), nn.Linear(500,2)) def train(model, optimizer, loss_fn, train_loader, val_loader, epochs=20, device="cpu"): for epoch in range(1, epochs + 1): training_loss = 0.0 valid_loss = 0.0 model.train() for batch in train_loader: optimizer.zero_grad() inputs, targets = batch inputs = inputs.to(device) targets = targets.to(device) output = model(inputs) loss = loss_fn(output, targets) loss.backward() optimizer.step() training_loss += loss.data.item() * inputs.size(0) training_loss /= len(train_loader.dataset) model.eval() num_correct = 0 num_examples = 0 for batch in val_loader: inputs, targets = batch inputs = inputs.to(device) output = model(inputs) targets = targets.to(device) loss = loss_fn(output, targets) valid_loss += loss.data.item() * inputs.size(0) correct = torch.eq(torch.max(F.softmax(output), dim=1)[1], targets).view(-1) num_correct += torch.sum(correct).item() num_examples += correct.shape[0] valid_loss /= len(val_loader.dataset) print( 'Epoch: {}, Training Loss: {:.2f}, Validation Loss: {:.2f}, accuracy = {:.2f}'.format(epoch, training_loss, valid_loss, num_correct / num_examples)) img_transforms = transforms.Compose([ transforms.Resize((64,64)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) ]) train_data_path = "./train/" val_data_path = "./val/" train_data = torchvision.datasets.ImageFolder(root=train_data_path,transform=img_transforms) val_data = torchvision.datasets.ImageFolder(root=val_data_path,transform=img_transforms) batch_size=64 train_data_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True) val_data_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, shuffle=True) if torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") print(len(val_data_loader.dataset)) transfer_model.to(device) optimizer = optim.Adam(transfer_model.parameters(), lr=0.001) train(transfer_model, optimizer,torch.nn.CrossEntropyLoss(), train_data_loader, val_data_loader, epochs=5, device=device)==================遷移學習=================== Epoch: 1, Training Loss: 0.51, Validation Loss: 0.19, accuracy = 0.90 Epoch: 2, Training Loss: 0.20, Validation Loss: 0.17, accuracy = 0.93 Epoch: 3, Training Loss: 0.11, Validation Loss: 0.24, accuracy = 0.92 Epoch: 4, Training Loss: 0.05, Validation Loss: 0.19, accuracy = 0.93 Epoch: 5, Training Loss: 0.01, Validation Loss: 0.19, accuracy = 0.93 ==================卷積神經網路================ Epoch: 1, Training Loss: 1.05, Validation Loss: 0.63, accuracy = 0.81 Epoch: 2, Training Loss: 0.70, Validation Loss: 0.69, accuracy = 0.71 Epoch: 3, Training Loss: 0.65, Validation Loss: 0.60, accuracy = 0.69 Epoch: 4, Training Loss: 0.53, Validation Loss: 0.39, accuracy = 0.83 Epoch: 5, Training Loss: 0.49, Validation Loss: 0.34, accuracy = 0.84 ==================全連接神經網路=============== Epoch: 1, Training Loss: 0.63, Validation Loss: 0.56, accuracy = 0.74 Epoch: 2, Training Loss: 0.54, Validation Loss: 0.51, accuracy = 0.79 Epoch: 3, Training Loss: 0.49, Validation Loss: 0.51, accuracy = 0.81 Epoch: 4, Training Loss: 0.46, Validation Loss: 0.59, accuracy = 0.71 Epoch: 5, Training Loss: 0.44, Validation Loss: 0.51, accuracy = 0.81
6.2,查找學習率
學習率是你能修改的最重要的超引數之一,建議使用一個相當小的數,通過自己試驗得到不同的值,很多人就是以這種方式來為他們的架構發現最優學習率,通常會采用一種稱為網格搜索的技術,就是窮盡搜索一個學習率值的子集,將結果與一個驗證資料集比較,這實在是很耗費時間,從而導致大部分人會選擇相信從業者的經驗,
利用fast.ai技術可以快速找到適合的學習率:在一個epoch期間,首先從一個小的學習率開始,每一個小批次增加到一個更大的學習率,直到這個epoch結束時得到一個很大的學習率,計算每個學習率的回應損失,然后得到一個圖,選擇出使損失下降最大的學習率,
在這里大約為
之間,因為在這中間梯度下降最大,
import torchvision.models as models import torch.nn as nn import torchvision import torchvision.models as models import torch from matplotlib import pyplot as plt from torchvision import transforms from PIL import Image import torch.nn.functional as F import torch.optim as optim transfer_model = models.resnet50(pretrained=True) for name, param in transfer_model.named_parameters(): if("bn" not in name): param.requires_grad = False transfer_model.fc = nn.Sequential(nn.Linear(transfer_model.fc.in_features,500), nn.ReLU(), nn.Dropout(), nn.Linear(500,2)) img_transforms = transforms.Compose([ transforms.Resize((64,64)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) ]) train_data_path = "./train/" val_data_path = "./val/" train_data = torchvision.datasets.ImageFolder(root=train_data_path,transform=img_transforms) val_data = torchvision.datasets.ImageFolder(root=val_data_path,transform=img_transforms) batch_size=64 train_data_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True) val_data_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, shuffle=True) if torch.cuda.is_available(): device = torch.device("cuda") else: device = torch.device("cpu") print(len(val_data_loader.dataset)) transfer_model.to(device) optimizer = optim.Adam(transfer_model.parameters(), lr=0.001) def find_lr(model, loss_fn, optimizer, train_loader, init_value=1e-8, final_value=10.0, device="cpu"): number_in_epoch = len(train_loader) - 1 update_step = (final_value / init_value) ** (1 / number_in_epoch) lr = init_value optimizer.param_groups[0]["lr"] = lr best_loss = 0.0 batch_num = 0 losses = [] log_lrs = [] for data in train_loader: batch_num += 1 inputs, targets = data inputs = inputs.to(device) targets = targets.to(device) optimizer.zero_grad() outputs = model(inputs) loss = loss_fn(outputs, targets) # Crash out if loss explodes if batch_num > 1 and loss > 4 * best_loss: if(len(log_lrs) > 20): return log_lrs[10:-5], losses[10:-5] else: return log_lrs, losses # Record the best loss if loss < best_loss or batch_num == 1: best_loss = loss # Store the values losses.append(loss.item()) log_lrs.append((lr)) # Do the backward pass and optimize loss.backward() optimizer.step() # Update the lr for the next step and store lr *= update_step optimizer.param_groups[0]["lr"] = lr if(len(log_lrs) > 20): return log_lrs[10:-5], losses[10:-5] else: return log_lrs, losses (lrs, losses) = find_lr(transfer_model, torch.nn.CrossEntropyLoss(),optimizer, train_data_loader,device=device) plt.plot(lrs, losses) plt.xscale("log") plt.xlabel("Learning rate") plt.ylabel("Loss") plt.show()這里所做的就是迭代各個批次,基本上照常訓練,將輸入傳入模型,然后得到這個批次的損失,記錄到目前為止的best_loss,將新損失值與它比較,如果新損失值大于best_loss的4倍,就退出函式,回傳目前得到的結果(因為損失可能趨近于無窮大),否則,繼續追加這個損失以及當前學習率的對數,再在下一步更新學習率,直到回圈結束時達到最大學習率,然后使用matplotlib plt函式繪圖,
注意這里只回傳了學習率對數和損失的片段,這樣做只是因為開始和最后的幾個訓練往往不會告訴我們太多資訊(特別是如果學習率很快變得非常大的情況下),
fast.ai庫中的實作還包含加權平滑,所以你會在圖中得到一條平滑曲線,但這個代碼段生成的輸出是不平滑的,最后,要記住,因為這個函式確實會訓練模型,會改變優化器的學習率設定,所以應該提前保存并重新加載你的模型,恢復到find_lr()之前的狀態,還要重新初始化你選擇的優化器,
6.3,差分學習率
在目前為止的訓練中,對整個模型只應用一個學習率,從頭開始訓練一個模型時,這可能是有道理的,不過對于遷移學習,正常情況下如果嘗試不同的做法可能得到更好的準確率,就是用不同的學習率訓練不同的層組,
在前面我們凍結了模型中的所有預訓練層,而只訓練新的分類器,但我們也可能想要微調模型中的某些層,比如ResNet模型,對分類器前面的幾層增加一些訓練可能會讓我們的模型更加準確一些,不過,由于前面這些層已經在ImageNet資料集上經過訓練,與新的層相比,它們可能只需很少的一點訓練,
optimizer = optim.Adam(transfer_model.parameters(), lr=0.001) #改為 optimizer = optim.Adam([ {'params': transfer_model.layer4.parameters(), 'lr': found_lr / 3}, {'params': transfer_model.layer3.parameters(), 'lr': found_lr / 9} ], lr=found_lr)這會把layer4(分類器之前的那一層)的學習率設定為當前找到的學習率(found_lr)的1/3,并把layer3的學習率設定為found_lr學習率的1/9,從經驗來看這種組合效果最好,當然你可以自由嘗試,
我們凍結了所有這些預訓練的層,為它們提供了一個不同的學習率也是可以的,不過,就目前而言,模型訓練根本不涉及它們,因為它們并不累積梯度,
unfreeze_layers = [transfer_model.layer3,transfer_model.layer4] for layer in unfreeze_layers: for param in layer.parameters(): param.requires_grad=True現在這些層中的引數再次接受梯度,微調模型時將會應用差分學習率,
6.4,資料增強
為了防止過擬合的出現,傳統的做法是提供大量資料,通過查看更多資料,模型會對所要解決的問題有一個更一般的認知,如果把這種情況看作是一個壓縮問題,通過避免模型存盤所有答案(由于資料過多,超出了其存盤容量,所以無法全部存盤),就會要求壓縮輸入,相應地可以生成一個更泛化的解決方案,而不只是存盤答案,
可以使用的一種方法是資料增強,如果我們有一個影像,可以考慮對這個影像做很多種不同的處理,這樣就能避免過擬合,并使模型更泛化,
torchvision提供了一個豐富的轉換集合,包含可以用于資料增強的大量轉換,另外還提供了兩種構造新轉換的方法,
改變影像的亮度、對比度、飽和度和色調,對于亮度、對比度和飽和度,可以提供一個float或一個float元組,都是0~1范圍之內的非負數,隨機性將介于0到所提供的float之間,或者使用元組來生成隨機性將介于所提供的float對之間,對于色調,要求提供介于-0.5~0.5之間的一個float或一個float元組,會生成[-hue,hue]或[min,max]之間的隨機色度調整,
import torchvision import torch import numpy as np from matplotlib import pyplot as plt from torchvision import transforms img_transforms = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), transforms.ColorJitter(brightness=0.1, contrast=0, saturation=0, hue=0) ]) train_data_path = "./train/" train_data = torchvision.datasets.ImageFolder(root=train_data_path, transform=img_transforms) batch_size = 1 train_data_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True) def imshow(img): img = img/2 + 0.5 npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() dataiter = iter(train_data_loader) images, labels = dataiter.next() # imshow(torchvision.utils.make_grid(images, nrow=10))
翻轉影像
transforms.RandomHorizontalFlip(p=0.9) transforms.RandomVerticalFlip(p=0.5)灰度化
transforms.RandomGrayscale(p=0.8)裁剪:RandomCrop和RandomResizeCrop會根據size對影像完成隨機的裁剪,這個size可以是一個表示高度和寬度的int,也可以是有不同高度和寬的一個元組,
這里需要注意一點,因為如果裁剪得過小,就有可能剪去影像中重要的部分,這可能導致模型在錯誤的資料上進行訓練,例如,如果一個影像中有一只貓在桌子上玩耍,假如裁剪將貓去掉而保留了桌子的部分,把分類為貓就不合適了,
RandomResizeCrop會調整裁剪以滿足給定的大小,而RandomCrop會按這個大小裁剪,可能會在影像之外加入填充,默認地,會使用constant填充模式,也就是將影像之外的像素(否則為空)填充為fill中給定的值,不過,建議使用reflect填充模式,以你為從經驗看,這種填充效果比簡單地放入空常量要好一些,
RandomResizeCrop使用雙線性插值,不過也可以修改interpolation引數選擇最近鄰或雙三次插值,
transforms.RandomCrop(size,padding=None,pad_if_needed=False,fill=0,padding_mode='constant') transforms.RandomResizedCrop(size,scale=(0.08,1.0),ratio=(0.75,1.3333333),interpolation=2)旋轉:如果想要隨機地旋轉一個影像若degrees是一個float或int,RandomRotation會在[-degrees, degrees]之間變化,如果degrees是一個元組,RandomRotation就在(min, max)之間變化,
如果expand設定為True,這個函式會擴展輸出影像,使它能包含整個影像;默認地,會設定為裁剪到輸入維度以內,可以指定一個PIL重采用過濾器,還可以為旋轉中心提供一個(x,y)元組,否則就會在影像中心旋轉,
transforms.RandomRotation(degrees=50, resample=False, expand=False, center=None)填充:Pad是一個通用的填充操作,會在影像邊框上增加填充(額外的高度和寬度),
如果padding只有一個值,會在所有方向上應用這個指定長度的填充,如果padding是一個包含兩個值的元素,會生成長度為(left/right, top/bottom)填充,默認地,填充設定為constant模式,這回把fill的值復制到填充槽,填充模式還有另外一些選擇,包括edge(這會用影像邊緣的最后一個值填充指定的長度);reflect(這會將影像的值(除了邊緣)反射到邊框);symmetric(這也是一種reflection,不過包括影像邊緣的最后一個值),
transforms.Pad(padding=100,fill=0,padding_mode=symmetric)仿射:RandomAffine允許指定影像的隨即仿射轉換(縮放、旋轉、平移和/或切變或者它們的任意組合),
degress引數是一個foloat或int,或者是一個元組,如果是單個值,會生成(-degrees,degress)之間的一個隨機轉換,如果是一個元組,則會生成(min, max)之間的隨機旋轉,如果要避免出現旋轉,必須顯示設定degrees,對此沒有默認設定,
transforms.RandomAffine(degrees,translate=None,scale=None,shear=None,resample=False,fillcolor=0)
另外,cv2也可以完成資料增強的操作,
import numpy as np import cv2 import os j = 0 for info in os.listdir(r'D:\t'): j += 1 domain = os.path.abspath( r'D:\t') # 獲取檔案夾的路徑,此處其實沒必要這么寫,目的是為了熟悉os的檔案夾操作 info1 = os.path.join(domain, info) # 將路徑與檔案名結合起來就是每個檔案的完整路徑 img = cv2.imread(info1) # cv2.imshow("original", img) cv2.waitKey(1000) # 水平鏡像,垂直鏡像,水平垂直鏡像 h_flip = cv2.flip(img, 1) v_flip = cv2.flip(img, 0) hv_flip = cv2.flip(img, -1) cv2.imwrite("D://data/A" + str(j) + "//" + info + '_h_flip.jpg', h_flip) cv2.imwrite("D://data/A" + str(j) + "//" + info + '_v_flip.jpg', v_flip) cv2.imwrite("D://data/A" + str(j) + "//" + info + 'hv_flip.jpg', hv_flip) # 平移矩陣[[1,0,-100],[0,1,-12]] # M = np.array([[1, 0, -100], [0, 1, -12]], dtype=np.float32) # img_change = cv2.warpAffine(img, M, (300, 300)) # cv2.imshow("translation", img_change) # cv2.imwrite(save_path+info+'img_change.jpg', img_change) # 角度 for i in range(1, 360): rows, cols = img.shape[:2] M = cv2.getRotationMatrix2D((cols / 2, rows / 2), i, 1) dst_90 = cv2.warpAffine(img, M, (cols, rows)) # cv2.imshow("dst_90", dst_90) cv2.imwrite("D://data/A" + str(j) + "//" + info + 'dst_' + str(i) + '.jpg', dst_90) # 縮放 # height, width = img.shape[:2] # print(height,width) # res = cv2.resize(img, (2 * width, 2 * height)) # # cv2.imshow("large", res) # cv2.imwrite("D://" + 'res' + str(j) + '.jpg', res) # 仿射變換 # 對影像進行變換(三點得到一個變換矩陣) # 我們知道三點確定一個平面,我們也可以通過確定三個點的關系來得到轉換矩陣 # 然后再通過warpAffine來進行變換 # point1 = np.float32([[50, 50], [300, 50], [50, 200]]) # point2 = np.float32([[10, 100], [300, 50], [100, 250]]) # M = cv2.getAffineTransform(point1, point2) # dst1 = cv2.warpAffine(img, M, (cols, rows), borderValue=(255, 255, 255)) # # cv2.imshow("affine transformation", dst1) # cv2.imwrite("D://data/A" + str(j) + "//" + info + 'dst1.jpg', dst1) cv2.waitKey(0)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/438676.html
標籤:AI







