久久久精品视频在线_免费在线a视频_在线看的黄色网址_懂色av粉嫩av浪潮av_艳妇乳肉豪妇荡乳av_国产午夜大地久久_国产在线观看免费播放_天堂视频免费看_久久精品女同亚洲女同13_各处沟厕大尺度偷拍女厕嘘嘘

當前位置: 首頁 >滾動 > 正文

PyTorch 2簡介:卷積神經網絡 當前視點

2023-06-27 11:40:48 來源:維科號

介紹


【資料圖】

在本系列的上一部分中,我們使用了CIFAR-10數據集,并介紹了PyTorch的基礎知識:

張量及其相關操作

數據集和數據加載器

構建基本的神經網絡

基本模型的訓練和評估

我們為CIFAR-10數據集中的圖像分類開發的模型只能在驗證集上達到53%的準確率,并且在一些類別(如鳥類和貓類)的圖像分類上表現非常困難(約33-35%的準確率)。這是預期的,因為我們通常會使用卷積神經網絡進行圖像分類。在本教程系列的這一部分,我們將專注于卷積神經網絡(CNN)并改善在CIFAR-10上的圖像分類性能。

CNN基礎知識

在我們深入代碼之前,讓我們討論卷積神經網絡的基礎知識,以便更好地理解我們的代碼在做什么。如果你已經對CNN的工作原理感到熟悉,可以跳過本節。

與前一部分中開發的前饋網絡相比,卷積神經網絡具有不同的架構,并由不同類型的層組成。在下圖中,我們可以看到典型CNN的一般架構,包括它可能包含的不同類型的層。

卷積網絡中通常包含的三種類型的層是:

卷積層(紅色虛線框)

池化層(藍色虛線框)

全連接層(紅色和紫色實線框)

卷積層

CNN的定義組件和第一層是卷積層,它由以下部分組成:

輸入數據(在本例中為圖像)

濾波器

特征圖

將卷積層與全連接層區分開來的關鍵是卷積運算。我們不會詳細討論卷積的定義,但如果你真的感興趣并想深入了解其數學定義以及一些具體的示例,我強烈推薦閱讀這篇文章,它在解釋數學定義方面做得非常好

https://betterexplained.com/articles/intuitive-convolution/#Part_3_Mathematical_Properties_of_Convolution

卷積相對于密集連接層(全連接層)在圖像數據中的優勢何在?簡而言之,密集連接層會學習輸入中的全局模式,而卷積層具有學習局部和空間模式的優勢。這可能聽起來有些模糊或抽象,所以讓我們看一個例子來說明這是什么意思。

在圖片的左側,我們可以看到一個基本的2D黑白圖像的4是如何在卷積層中表示的。

紅色方框是濾波器/特征檢測器/卷積核,在圖像上進行卷積操作。在右側是相同圖像在一個密集連接層中的表示。你可以看到相同的9個圖像像素被紅色的卷積核框起來。請注意,在左側,像素在空間上是分組的,與相鄰的像素相鄰。然而,在右側,這相同的9個像素不再是相鄰的。

通過這個例子,我們可以看到當圖像被壓平并表示為完全連接/線性層時,空間/位置信息是如何丟失的。這就是為什么卷積神經網絡在處理圖像數據時更強大的原因。輸入數據的空間結構得到保留,圖像中的模式(邊緣、紋理、形狀等)可以被學習。

這基本上是為什么在圖像上使用卷積神經網絡的原因,但現在讓我們討論一下如何實現。讓我們來看看我們的輸入數據的結構,我們一直在談論的那些叫做“濾波器”的東西,以及當我們將它們放在一起時卷積是什么樣子。

輸入數據

CIFAR-10數據集包含60,000個32x32的彩色圖像,每個圖像都表示為一個3D張量。每個圖像將是一個(32,32,3)的張量,其中的維度是32(高度)x 32(寬度)x 3(R-G-B顏色通道)。下圖展示了從數據集中分離出來的飛機全彩色圖像的3個不同的顏色通道(RGB)。

通常將圖像視為二維的,所以很容易忘記它們實際上是以三維表示的,因為它們有3個顏色通道!

濾波器

在卷積層中,濾波器(也稱為卷積核或特征檢測器)是一組權重數組,它以滑動窗口的方式在圖像上進行掃描,計算每一步的點積,并將該點積輸出到一個稱為特征圖的新數組中。這種滑動窗口的掃描稱為卷積。讓我們看一下這個過程的示例,以幫助理解正在發生的事情。

一個3x3的濾波器(藍色)對輸入(紅色)進行卷積,生成一個特征圖(紫色):

在每個卷積步驟中計算點積的示意圖:

需要注意的是,濾波器的權重在每個步驟中保持不變。就像在全連接層中的權重一樣,這些值在訓練過程中進行學習,并通過反向傳播在每個訓練迭代后進行調整。

這些示意圖并不能完全展示所有情況。當訓練一個卷積神經網絡時,模型不僅在卷積層中使用一個濾波器是很常見的。通常在一個卷積層中會有32或64個濾波器,實際上,在本教程中,我們將在一個層中使用多達96個濾波器來構建我們的模型。

最后,雖然濾波器的權重是需要訓練的主要參數,但卷積神經網絡也有一些可以調整的超參數:

層中的濾波器數量

濾波器的維度

步幅(每一步濾波器移動的像素數)

填充(濾波器如何處理圖像邊界)

我們不會詳細討論這些超參數,因為本文不旨在全面介紹卷積神經網絡,但這些是需要注意的重要因素。

池化層

池化層與卷積層類似,都是通過濾波器對輸入數據(通常是從卷積層輸出的特征圖)進行卷積運算。

然而,池化層的功能不是特征檢測,而是降低維度或降采樣。最常用的兩種池化方法是最大池化和平均池化。在最大池化中,濾波器在輸入上滑動,并在每一步選擇具有最大值的像素作為輸出。在平均池化中,濾波器輸出濾波器所經過像素的平均值。

全連接層

最后,在卷積和池化層之后,卷積神經網絡通常會有全連接層,這些層將在圖像分類任務中執行分類,就像本教程中的任務一樣。

現在,我們已經了解了卷積神經網絡的結構和操作方式,讓我們開始進行有趣的部分,在PyTorch中訓練我們自己的CNN模型!

設置

與本教程的第一部分一樣,我建議使用Google Colab進行跟隨,因為你的Python環境已經安裝了PyTorch和其他庫,并且有一個GPU可以用于訓練模型。

因此,如果你使用的是Colab,請確保使用GPU,方法是轉到“運行時”(Runtime)并點擊“更改運行時類型”。

在對話框中選擇GPU并保存。

現在你可以在Colab中使用GPU了,并且我們可以使用PyTorch驗證你的設備。

因此,首先,讓我們處理導入部分:

importtorch

fromtorchimportnn

fromtorch.utils.dataimportDataLoader

fromtorchvision.utilsimportmake_grid

fromtorchvision.datasetsimportCIFAR10

fromtorchvisionimporttransforms

fromtorchvisionimportutils

fromtorchvision.utilsimportmake_grid

importmatplotlib.pyplotasplt

importnumpyasnp

importseabornassns

importpandasaspd

如果你想檢查你可以訪問的GPU是什么,請鍵入并執行torch.cuda.get_device_name(0),你應該會看到設備輸出。Colab有幾種不同的GPU選項可供選擇,因此你的輸出將根據你所能訪問的內容而有所不同,但只要你在運行此代碼時沒有看到“RuntimeError: No CUDA GPUs are available”錯誤,那么你正在使用GPU!

我們可以將GPU設備設置為device,以便在開發模型時將其分配給GPU,如果沒有CUDA GPU設備可用,我們也可以使用CPU。

device="cuda"iftorch.cuda.is_available()else"cpu"

print(device)

#cuda

接下來,讓我們設置一個隨機種子,以便我們的結果是可重現的,并下載我們的訓練數據并設置一個轉換,將圖像轉換為張量并對數據進行歸一化。

torch.manual_seed(42)

transform=transforms.Compose(

[transforms.ToTensor(),

transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]

)

training_data=CIFAR10(root="cifar",

train=True,

download=True,

transform=transform)

test_data=CIFAR10(root="cifar",

train=False,

download=True,

transform=transform)

一旦下載完成,讓我們查看數據集中的類別:

classes=training_data.classes

classes

#["airplane",

#"automobile",

#"bird",

#"cat",

#"deer",

#"dog",

#"frog",

#"horse",

#"ship",

#"truck"]

最后,讓我們設置訓練和測試數據加載器:

batch_size=24

train_dataloader=DataLoader(training_data,batch_size=batch_size,shuffle=True,num_workers=0)

test_dataloader=DataLoader(test_data,batch_size=batch_size,shuffle=True,num_workers=0)

forX,yintrain_dataloader:

print(f"ShapeofX[N,C,H,W]:{X.shape}")

print(f"Shapeofy:{y.shape}{y.dtype}")

break

#ShapeofX[N,C,H,W]:torch.Size([24,3,32,32])

#Shapeofy:torch.Size([24])torch.int64

現在我們準備構建我們的模型!

構建CNN

在PyTorch中,nn.Conv2d是用于圖像輸入數據的卷積層。Conv2d的第一個參數是輸入中的通道數,在我們的第一層卷積層中,我們將使用3,因為彩色圖像將有3個顏色通道。

在第一個卷積層之后,該參數將取決于前一層輸出的通道數。第二個參數是在該層中卷積操作輸出的通道數。這些通道是卷積層介紹中討論的特征圖。最后,第三個參數將是卷積核或濾波器的大小。這可以是一個整數值,如3表示3x3的卷積核,或者是一個元組,如(3,3)。因此,我們的卷積層將采用nn.Conv2d(in_channels, out_channels, kernel_size)的形式。還可以添加其他可選參數,包括(但不限于)步幅(stride)、填充(padding)和膨脹(dilation)。在我們的卷積層conv4中,我們將使用stride=2。

在一系列卷積層之后,我們將使用一個扁平化層將特征圖扁平化,以便能夠輸入到線性層中。為此,我們將使用nn.Flatten()。我們可以使用nn.BatchNorm1d()應用批量歸一化,并需要將特征數作為參數傳遞。

最后,我們使用nn.Linear()構建線性的全連接層,第一個參數是特征數,第二個參數是指定輸出特征數。

因此,要開始定義我們模型的基本架構,我們將定義一個ConvNet類,該類繼承自PyTorch的nn.Module類。然后,我們可以將每個層定義為類的屬性,并根據需要構建它們。

一旦我們指定了層的架構,我們可以通過創建一個forward()方法來定義模型的流程。我們可以使用激活函數包裝每個層,在我們的情況下,我們將使用relu。我們可以通過傳遞前一層和p(元素被丟棄的概率,缺省值為0.5)在層之間應用dropout。

最后,我們創建模型對象并將其附加到設備上,以便可以在GPU上訓練。

classConvNet(nn.Module):

def__init__(self):

super().__init__()

self.d1=0.1

self.conv1=nn.Conv2d(3,48,3)

self.conv2=nn.Conv2d(48,48,3)

self.conv3=nn.Conv2d(48,96,3)

self.conv4=nn.Conv2d(96,96,3,stride=2)

self.flat=nn.Flatten()

self.batch_norm=nn.BatchNorm1d(96*12*12)

self.fc1=nn.Linear(96*12*12,256)

self.fc2=nn.Linear(256,10)

defforward(self,x):

x=nn.functional.relu(self.conv1(x))

x=nn.functional.relu(self.conv2(x))

x=nn.functional.dropout(x,self.d1)

x=nn.functional.relu(self.conv3(x))

x=nn.functional.relu(self.conv4(x))

x=nn.functional.dropout(x,0.5)

x=self.flat(x)

x=nn.functional.relu(self.batch_norm(x))

x=nn.functional.relu(self.fc1(x))

x=self.fc2(x)

returnx

model=ConvNet().to(device)

訓練和測試函數

如果你完成了本教程的第一部分,我們的訓練和測試函數將與之前創建的函數相同,只是在訓練方法中返回損失,而在測試方法中返回損失和正確數量,以便在調整超參數時使用。

#TrainMethod

deftrain(dataloader,model,loss_fn,optimizer,verbose=True):

size=len(dataloader.dataset)

model.train()

forbatch,(X,y)inenumerate(dataloader):

X,y=X.to(device),y.to(device)

#Computepredictionerror

pred=model(X)

loss=loss_fn(pred,y)

#Backpropagation

optimizer.zero_grad()

loss.backward()

optimizer.step()

ifverbose==True:

ifbatch%50==0:

loss,current=loss.item(),batch*len(X)

print(f"loss:{loss:>7f}[{current:>5d}/{size:>5d}]")

returnloss

#TestMethod

deftest(dataloader,model,loss_fn,verbose=True):

size=len(dataloader.dataset)

num_batches=len(dataloader)

model.eval()

test_loss,correct=0,0

withtorch.no_grad():

forX,yindataloader:

X,y=X.to(device),y.to(device)

pred=model(X)

test_loss+=loss_fn(pred,y).item()

correct+=(pred.argmax(1)==y).type(torch.float).sum().item()

test_loss/=num_batches

correct/=size

ifverbose==True:

print(f"TestError:Accuracy:{(100*correct):>0.1f}%,Avgloss:{test_loss:>8f}")

returntest_loss,correct#Forreportingtuningresults/earlystopping

最后,在基本模型訓練之前,我們定義損失函數和優化器。

loss_fn=nn.CrossEntropyLoss()

optimizer=torch.optim.Adam(model.parameters(),lr=0.001)

讓我們訓練模型。

epochs=10

fortinrange(epochs):

print(f"Epoch{t+1}-------------------------------")

train(train_dataloader,model,loss_fn,optimizer)

test(test_dataloader,model,loss_fn)

print("Done!")

僅經過10個epochs,61.7%的性能比我們訓練的全連接模型要好得多!很明顯,CNN更適合用于圖像分類,但我們可以通過延長訓練時間和調整超參數來進一步提高性能。

在進行這些之前,讓我們快速看看模型內部是什么樣子。請記住,濾波器的像素是我們模型中可訓練的參數。這不是訓練圖像分類模型的必要步驟,也不會得到太多有用的信息,但是了解模型內部的情況還是挺有意思的。

可視化濾波器

我們可以編寫一個函數來繪制模型中指定層的濾波器。我們只需要指定要查看的層,并將其傳遞給我們的函數。

defvisualizeTensor(tensor,ch=0,all_kernels=False,nrow=8,padding=1):

n,c,w,h=tensor.shape

ifall_kernels:

tensor=tensor.view(n*c,-1,w,h)

elifc!=3:

tensor=tensor[:,ch,:,:].unsqueeze(dim=1)

rows=np.min((tensor.shape[0]//nrow+1,64))

grid=utils.make_grid(tensor,

nrow=nrow,

normalize=True,

padding=padding)

grid=grid.cpu()#backtocpufornumpyandplotting

plt.figure(figsize=(nrow,rows))

plt.imshow(grid.numpy().transpose((1,2,0)))

讓我們來看看第一個卷積層(conv1)中的濾波器是什么樣子,因為這些濾波器直接應用于圖像。

filter=model.conv1.weight.data.clone()

visualizeTensor(filter)

plt.axis("off")

plt.ioff()

plt.show

下面是輸出,包含了我們的conv1卷積層中48個濾波器的可視化。我們可以看到每個濾波器都是一個不同值或顏色的3x3張量。

如果我們的濾波器是5x5的,我們會在繪圖中看到以下差異。請記住,使用nn.Conv2d我們可以使用第三個參數更改濾波器的大小,因此如果我們想要一個5x5的濾波器,conv1將如下所示:

self.conv1=nn.Conv2d(3,48,5)#NewKernelSize

如果我們用新的5x5濾波器重新訓練模型,輸出將如下所示:

如我之前提到的,這里并沒有太多有用的信息,但還是很有趣可以看到這些。

超參數優化

在本教程中,我們將調整的超參數是卷積層中的濾波器數量以及線性層中的神經元數量。當前這些值在我們的模型中是硬編碼的,所以為了使它們可調整,我們需要使我們的模型可配置。

我們可以在模型的__init__方法中使用參數(c1、c2和l1),并使用這些值創建模型的層,在調整過程中將動態傳遞這些值。

classConfigNet(nn.Module):

def__init__(self,l1=256,c1=48,c2=96,d1=0.1):

super().__init__()

self.d1=d1

self.conv1=nn.Conv2d(3,c1,3)

self.conv2=nn.Conv2d(c1,c1,3)

self.conv3=nn.Conv2d(c1,c2,3)

self.conv4=nn.Conv2d(c2,c2,3,stride=2)

self.flat=nn.Flatten()

self.batch_norm=nn.BatchNorm1d(c2*144)

self.fc1=nn.Linear(c2*144,l1)

self.fc2=nn.Linear(l1,10)

defforward(self,x):

x=nn.functional.relu(self.conv1(x))

x=nn.functional.relu(self.conv2(x))

x=nn.functional.dropout(x,self.d1)

x=nn.functional.relu(self.conv3(x))

x=nn.functional.relu(self.conv4(x))

x=nn.functional.dropout(x,0.5)

x=self.flat(x)

x=nn.functional.relu(self.batch_norm(x))

x=nn.functional.relu(self.fc1(x))

x=self.fc2(x)

returnx

model=ConfigNet().to(device)

當然,我們不僅限于調整這些超參數。事實上,學習率和批量大小通常也包括在要調整的超參數列表中,但由于我們將使用網格搜索,為了保持訓練時間合理,我們必須大大減少可調整的變量數量。

接下來,讓我們為搜索空間定義一個字典,并保存給我們最佳結果的參數。由于我們使用網格搜索進行優化,將使用每個超參數組合的所有組合。

你可以輕松地向每個超參數的列表中添加更多值,但每個額外的值都會大大增加運行時間,因此建議從以下值開始以節省時間。

search_space={

"c1":[48,96],

"c2":[96,192],

"l1":[256,512],

}

best_results={

"c1":None,

"c2":None,

"l1":None,

"loss":None,

"acc":0

}

提前停止

優化過程中一個重要的組成部分是使用提前停止。由于我們將進行多次訓練運行,每次訓練運行時間都很長,如果訓練性能沒有改善,我們將希望提前結束訓練。繼續訓練一個沒有改善的模型是沒有意義的。

實質上,我們將在每個時期之后跟蹤模型產生的最低損失。然后,我們定義一個容差,指定模型必須在多少個時期內達到更好的損失。如果在指定的容差內沒有實現更低的損失,將終止該運行的訓練,并繼續下一個超參數組合。

如果你像我一樣,喜歡檢查訓練過程,可以設置self.verbose = True來記錄控制臺上的更新,并查看提前停止計數器增加的情況。你可以在此處硬編碼到EarlyStopping類中,也可以在優化過程中實例化EarlyStopping對象時更改verbose值。

classEarlyStopping():

def__init__(self,tolerance=5,verbose=False,path="cifar-tune.pth"):

self.tolerance=tolerance

self.counter=0

self.early_stop=False

self.lowest_loss=None

self.verbose=verbose

self.path=path

defstep(self,val_loss):

if(self.lowest_loss==None):

self.lowest_loss=val_loss

torch.save(model.state_dict(),self.path)

elif(val_loss

self.lowest_loss=val_loss

self.counter=0

torch.save(model.state_dict(),self.path)

else:

ifself.verbose:

print("Earlystopcounter:{}".format(self.counter+1))

self.counter+=1

ifself.counter>=self.tolerance:

self.early_stop=True

ifself.verbose:

print("Earlystoppingexecuted.")

圖像增強

在設置超參數優化方法之前,我們還有最后一件事要做,以提取出一些額外的性能并避免在訓練數據上過度擬合。圖像增強是一種將隨機變換應用于圖像的技術,從本質上講,它會創建“新的”人工數據。這些變換可以是以下幾種:

旋轉圖像幾度

水平/垂直翻轉圖像

裁剪

輕微的亮度/色調變化

隨機縮放

包含這些隨機變換將提高模型的泛化能力,因為增強后的圖像將與原始圖像類似,但不同。內容和模式將保持不變,但數組表示將有所不同。

PyTorch通過torchvision.transforms模塊使圖像增強變得很容易。如果我們想要應用多個變換,可以使用Compose將它們鏈接在一起。

需要記住的一點是,圖像增強對每個變換需要一點計算量,并且這些計算量應用于數據集中的每個圖像。將許多不同的隨機變換應用于我們的數據集將增加訓練時間。

因此,現在讓我們限制變換的數量,以便訓練時間不會太長。如果你想添加更多變換,請查看PyTorch關于轉換和增強圖像的文檔,然后將它們添加到Compose列表中。

選擇了增強變換之后,我們可以像應用規范化和將圖像轉換為張量一樣將它們應用于數據集。

#AugmentImagesforthetrainset

augmented=transforms.Compose([

transforms.RandomRotation(20),

transforms.ColorJitter(brightness=0.2,hue=0.1),

transforms.RandomHorizontalFlip(p=0.5),

transforms.ToTensor(),

transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))

])

#Standardtransformationforvalidationset

transform=transforms.Compose([

transforms.ToTensor(),

transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))

])

training_data=CIFAR10(root="cifar",

train=True,

download=True,

transform=augmented)

test_data=CIFAR10(root="cifar",

train=False,

download=True,

transform=transform)

現在我們已經在訓練數據上設置了圖像增強,我們準備設置我們的超參數優化方法。

定義優化方法

我們可以創建一個類(HyperSearch),其中包含超參數值配置、詳細報告設置、報告列表(以便在優化完成后查看每個配置的表現)的屬性,以及一個變量來存儲具有最佳性能的配置。

classHyperSearch():

def__init__(self,config,verbose=True):

self.config=config

self.verbose=verbose

self.report_list=[]

self.best_results={"c1":None,

"c2":None,

"l1":None,

"loss":None,

"acc":0

}

接下來,我們可以創建一個方法(仍在HyperSearch類中),以執行網格搜索,并對每個超參數組合進行訓練運行。首先,我們將使用tolerance=3配置EarlyStopping,并設置它保存每個超參數組合的權重。如果我們將self.verbose設置為True,我們可以在控制臺中看到當前正在訓練的超參數組合。

之后,我們使用我們設計的CoinfigNet模型定義我們的模型,并傳遞l1、c1和c2的值,同時選擇損失函數和優化器,并設置我們的訓練和驗證DataLoader。由于我們沒有時間也沒有意愿完全訓練每個組合,所以我們將保持較低的時期數。目標是了解哪種組合在對數據集進行分類時效果最好,然后我們可以將該模型完全訓練,以查看它在完整的訓練周期中的性能。

#OptimizationMethod

defoptimize(self):

forl1inself.config["l1"]:

forc1inself.config["c1"]:

forc2inself.config["c2"]:

early_stopping=EarlyStopping(tolerance=3,verbose=False,path="{}-{}-{}.pth".format(c1,c2,l1))

ifself.verbose==True:

print("Conv1:{}|Conv2:{}|Lin1:{}".format(str(c1),str(c2),str(l1)))

model=ConfigNet(l1=l1,c1=c1,c2=c2).to(device)

loss_fn=nn.CrossEntropyLoss()

optimizer=torch.optim.Adam(model.parameters(),lr=lrate)

train_dataloader=DataLoader(training_data,batch_size=batch_sz,shuffle=True,num_workers=0)

test_dataloader=DataLoader(test_data,batch_size=batch_sz,shuffle=True,num_workers=0)

現在,我們定義訓練循環,大部分與之前相同,只是現在我們將保存train和test方法的損失,以便early_stopping可以跟蹤訓練進展(或缺乏進展)。最后,在每個時期之后,將結果保存到報告中,并更新最佳損失的值。

epochs=10

fortinrange(epochs):

ifself.verbose==True:

print(f"Epoch{t+1}-------------------------------")

train_loss=train(train_dataloader,model,loss_fn,optimizer,verbose=self.verbose)

test_loss,test_acc=test(test_dataloader,model,loss_fn,verbose=self.verbose)

#EarlyStopping

early_stopping.step(test_loss)

ifearly_stopping.early_stop:

break

print("Done!")

self.append_to_report(test_acc,test_loss,c1,c2,l1)

ifself.best_results["loss"]==Noneortest_loss

ifself.verbose==True:

print("UPDATE:Bestlosschangedfrom{}to{}".format(self.best_results["loss"],test_loss))

self.best_results.update({

"c1":c1,

"c2":c2,

"loss":test_loss,

"l1":l1,

"acc":test_acc

})

self.report()

我們可以將整個超參數優化周期的結果輸出到一個漂亮的表格中,在表格中,我們可以看到每次運行的超參數配置,以及相應的損失和準確率。

defreport(self):

print("""

|-----------------------------------------------------------------------------------------------------|

||

|Reportforhyperparameteroptimization|

||

|-----------------------------------------------------------------------------------------------------|

|RUN|PERFORMANCE|CONFIGURATION|

|------------|--------------------------------------|-------------------------------------------------|""")

foridx,iteminenumerate(self.report_list):

print("|Run{:02d}|Accuracy:{:.2f}%|Loss:{:.2f}|Conv-1:{}|Conv-2:{:3}|Linear-1:{:>4}|".format(idx,

item[0]*100,

item[1],

item[2],

item[3],

item[4]))

print("|------------|---------------------|----------------|--------------|---------------|------------------|")

print("BestResults|Accuracy:{:.2f}%|Loss:{:.2f}|Conv-1:{}|Conv-2:{}|Linear-1:{:>4}|".format(self.best_results["acc"]*100,

self.best_results["loss"],

self.best_results["c1"],

self.best_results["c2"],

self.best_results["l1"]))

defappend_to_report(self,acc,loss,c1,c2,l1):

list_set=(acc,loss,c1,c2,l1)

self.report_list.append(list_set)

因此,將所有這些代碼放在一起,我們的HyperSearch類應該如下所示:

classHyperSearch():

def__init__(self,config,verbose=True):

self.config=config

self.verbose=verbose

self.report_list=[]

self.best_results={"c1":None,

"c2":None,

"l1":None,

"loss":None,

"acc":0

#"d1":None,

#"lr":None,

#"bsz":None,

}

#OptimizationMethod

defoptimize(self):

forl1inself.config["l1"]:

forc1inself.config["c1"]:

forc2inself.config["c2"]:

early_stopping=EarlyStopping(tolerance=3,verbose=False,path="{}-{}-{}.pth".format(c1,c2,l1))

ifself.verbose==True:

print("Conv1:{}|Conv2:{}|Lin1:{}".format(str(c1),str(c2),str(l1)))

model=ConfigNet(l1=l1,c1=c1,c2=c2).to(device)

loss_fn=nn.CrossEntropyLoss()

optimizer=torch.optim.Adam(model.parameters(),lr=lrate)

train_dataloader=DataLoader(training_data,batch_size=batch_sz,shuffle=True,num_workers=0)

test_dataloader=DataLoader(test_data,batch_size=batch_sz,shuffle=True,num_workers=0)

epochs=10

fortinrange(epochs):

ifself.verbose==True:

print(f"Epoch{t+1}-------------------------------")

train_loss=train(train_dataloader,model,loss_fn,optimizer,verbose=self.verbose)

test_loss,test_acc=test(test_dataloader,model,loss_fn,verbose=self.verbose)

#EarlyStopping

early_stopping.step(test_loss)

ifearly_stopping.early_stop:

break

print("Done!")

self.append_to_report(test_acc,test_loss,c1,c2,l1)

ifself.best_results["loss"]==Noneortest_loss

ifself.verbose==True:

print("UPDATE:Bestlosschangedfrom{}to{}".format(self.best_results["loss"],test_loss))

self.best_results.update({

"c1":c1,

"c2":c2,

"loss":test_loss,

"l1":l1,

"acc":test_acc

})

self.report()

defreport(self):

print("""

|-----------------------------------------------------------------------------------------------------|

||

|Reportforhyperparameteroptimization|

||

|-----------------------------------------------------------------------------------------------------|

|RUN|PERFORMANCE|CONFIGURATION|

|------------|--------------------------------------|-------------------------------------------------|""")

foridx,iteminenumerate(self.report_list):

print("|Run{:02d}|Accuracy:{:.2f}%|Loss:{:.2f}|Conv-1:{}|Conv-2:{:3}|Linear-1:{:>4}|".format(idx,

item[0]*100,

item[1],

item[2],

item[3],

item[4]))

print("|------------|---------------------|----------------|--------------|---------------|------------------|")

print("BestResults|Accuracy:{:.2f}%|Loss:{:.2f}|Conv-1:{}|Conv-2:{}|Linear-1:{:>4}|".format(self.best_results["acc"]*100,

self.best_results["loss"],

self.best_results["c1"],

self.best_results["c2"],

self.best_results["l1"]))

defappend_to_report(self,acc,loss,c1,c2,l1):

list_set=(acc,loss,c1,c2,l1)

self.report_list.append(list_set)

調整

現在我們可以調整超參數了!通過使用%%time,在整個調整過程執行完成后,我們可以看到整個過程花費的時間。讓我們保持學習率lrate=0.001和批量大小batch_sz=512,用我們之前定義的search_space實例化HyperSearch類,將verbose設置為True或False(根據你的喜好),然后調用optimize()方法開始調優。

注意:在我的機器上(NVIDIA RTX 3070),完成這個過程大約需要50分鐘,所以如果你使用的是Colab上提供的GPU,可能需要大致相同的時間。

%%time

lrate=0.001

batch_sz=512

hyper_search=HyperSearch(search_space,verbose=True)

hyper_search.optimize()

完成整個優化周期后,你應該得到一個如下所示的表格:

結果

從表格中可以看出,最佳結果來自于Run 00,它具有c1=48、c2=96和l1=256。損失為0.84,準確率為71.24%,這是一個不錯的改進,尤其是考慮到只有10個時期!

因此,現在我們已經找到了在10個時期內性能最佳的超參數,讓我們對這個模型進行微調!我們可以在更多的時期內訓練它,并稍微降低學習率,以嘗試獲得更好的性能。

所以首先,讓我們定義我們想要使用的模型,并設置批量大小和學習率:

classConfigNet(nn.Module):

def__init__(self,l1=256,c1=48,c2=96,d1=0.1):

super().__init__()

self.d1=d1

self.conv1=nn.Conv2d(3,c1,3)

self.conv2=nn.Conv2d(c1,c1,3)

self.conv3=nn.Conv2d(c1,c2,3)

self.conv4=nn.Conv2d(c2,c2,3,stride=2)

self.flat=nn.Flatten()

self.batch_norm=nn.BatchNorm1d(c2*144)

self.fc1=nn.Linear(c2*144,l1)

self.fc2=nn.Linear(l1,10)

defforward(self,x):

x=nn.functional.relu(self.conv1(x))

x=nn.functional.relu(self.conv2(x))

x=nn.functional.dropout(x,self.d1)

x=nn.functional.relu(self.conv3(x))

x=nn.functional.relu(self.conv4(x))

x=nn.functional.dropout(x,0.5)

x=self.flat(x)

x=nn.functional.relu(self.batch_norm(x))

x=nn.functional.relu(self.fc1(x))

x=self.fc2(x)

returnx

model=ConfigNet().to(device)

model=ConfigNet(l1=256,c1=48,c2=96,d1=0.1).to(device)

batch_sz=512

lrate=0.0008

最后,我們可以將時期數設置為50,并更改保存權重的路徑。讓訓練周期運行起來,如果進展停滯,early stopping將終止訓練。

%%time

early_stopping=EarlyStopping(tolerance=6,verbose=True,path="cifar-optimized-test.pth")

loss_fn=nn.CrossEntropyLoss()

optimizer=torch.optim.Adam(model.parameters(),lr=lrate)

train_dataloader=DataLoader(training_data,batch_size=batch_sz,shuffle=True,num_workers=0)

test_dataloader=DataLoader(test_data,batch_size=batch_sz,shuffle=True,num_workers=0)

epochs=50

fortinrange(epochs):

print(f"Epoch{t+1}-------------------------------")

train_loss=train(train_dataloader,model,loss_fn,optimizer)

test_loss,test_acc=test(test_dataloader,model,loss_fn)

#EarlyStopping

early_stopping.step(test_loss)

ifearly_stopping.early_stop:

break

print("Done!")

Early stopping應該在達到50個時期之前終止訓練,并且應該達到約77%的準確率。

現在,我們已經調整了超參數,找到了最佳配置,并對該模型進行了微調,現在是對模型的性能進行更深入評估的時候了。

模型評估

在這種情況下,我們的測試數據集實際上是我們的驗證數據。我們將重復使用我們的驗證數據來評估模型,但通常在超參數調整之后,你將希望使用真實的測試數據進行模型評估。

讓我們加載優化后的模型,準備沒有應用任何圖像增強的test_dataloader,并運行test()來進行評估。

model=ConfigNet(l1=256,c1=48,c2=96,d1=0.1).to(device)

model.load_state_dict(torch.load("cifar-optimized-test.pth"))

loss_fn=nn.CrossEntropyLoss()

batch_sz=512

test_dataloader=DataLoader(test_data,batch_size=batch_sz,shuffle=False,num_workers=0)

classes=test_data.classes

test_loss,test_acc=test(test_dataloader,model,loss_fn)

這應該會輸出準確率和損失:

總體性能不錯,但每個類別的性能對我們更有用。以下代碼將輸出數據集中每個類別的模型準確率:

correct_pred={classname:0forclassnameinclasses}

total_pred={classname:0forclassnameinclasses}

withtorch.no_grad():

fordataintest_dataloader:

images,labels=data

outputs=model(images.to(device))

_,predictions=torch.max(outputs,1)

forlabel,predictioninzip(labels,predictions):

iflabel==prediction:

correct_pred[classes[label]]+=1

total_pred[classes[label]]+=1

forclassname,correct_countincorrect_pred.items():

accuracy=100*float(correct_count)/total_pred[classname]

print(f"Accuracyforclass{classname:5s}:{accuracy:.1f}%")

執行此代碼塊將給出以下輸出:

我們的模型在飛機、汽車、青蛙、船和卡車類別上表現非常好。有趣的是,它在狗和貓這兩個類別上遇到了最大的困難,這也是前面這個系列中完全連接模型面臨的最棘手的類別。

混淆矩陣

我們可以通過混淆矩陣進一步了解模型的性能。讓我們設置一個混淆矩陣,并進行可視化。

num_classes=10

confusion_matrix=torch.zeros(num_classes,num_classes)

withtorch.no_grad():

fori,(inputs,classes)inenumerate(test_dataloader):

inputs=inputs.to(device)

classes=classes.to(device)

outputs=model(inputs)

_,preds=torch.max(outputs,1)

fort,pinzip(classes.view(-1),preds.view(-1)):

confusion_matrix[t.long(),p.long()]+=1

通過定義混淆矩陣,我們可以使用Seaborn庫來幫助我們可視化它。

plt.figure(figsize=(15,10))

cf_dataframe=pd.DataFrame(np.array(confusion_matrix,dtype="int"),index=test_data.classes,columns=test_data.classes)

heatmap=sns.heatmap(cf_dataframe,annot=True,fmt="g")

這個表格的兩個維度是“實際”和“預測”值。我們希望大部分數據都在中心對角線上對齊,即實際和預測屬于同一類別。從錯誤的預測中,我們可以看到模型經常混淆貓和狗,這兩個類別的準確率最低。

總數看起來不錯,但每個類別的精確度和召回率將為我們提供更有意義的數據。讓我們首先看一下每個類別的召回率。

每個類別的召回率cf=np.array(confusion_matrix)

norm_cf=cf/cf.astype(float).sum(axis=1)

plt.figure(figsize=(15,10))

cf_dataframe=pd.DataFrame(np.array(norm_cf,dtype="float64"),index=test_data.classes,columns=test_data.classes).astype(float)

heatmap=sns.heatmap(cf_dataframe,annot=True)

每個類別的精確度cf=np.array(confusion_matrix)

norm_cf=cf/cf.astype(float).sum(axis=0)

plt.figure(figsize=(15,10))

cf_dataframe=pd.DataFrame(np.array(norm_cf,dtype="float64"),index=test_data.classes,columns=test_data.classes).astype(float)

heatmap=sns.heatmap(cf_dataframe,annot=True)

樣本模型預測

最后,讓我們給模型提供幾張圖像,并檢查它的預測結果。讓我們創建一個函數來準備我們的圖像數據以供查看:

defimshow(img):

img=img/2+.05#revertnormalizationforviewing

npimg=img.numpy()

plt.imshow(np.transpose(npimg,(1,2,0)))

plt.show()

現在,我們可以準備我們的測試數據,并創建另一個函數來獲取n個樣本預測。

test_data=CIFAR10(root="cifar",

train=False,

transform=transforms.ToTensor())

classes=test_data.classes

defsample_predictions(n=4):

test_dataloader=DataLoader(test_data,batch_size=n,shuffle=True,num_workers=0)

dataiter=iter(test_dataloader)

images,labels=dataiter.next()

outputs=model(images.to(device))

_,predicted=torch.max(outputs,1)

imshow(make_grid(images))

print("[GroundTruth|Predicted]:","".join(f"[{classes[labels[j]]:5s}|{classes[predicted[j]]:5s}]"forjinrange(n)))

調用該函數,傳遞你想要采樣的圖像數量。輸出將給出每個圖像的實際類別和預測類別,從左到右。

利用經過超參數調優和圖像增強的卷積網絡,我們成功提高了在CIFAR-10數據集上的性能!感謝你的閱讀,希望你對PyTorch和用于圖像分類的卷積神經網絡有所了解。這里提供了包含所有代碼的完整筆記本在GitHub上可用。

https://github.com/florestony54/intro-to-pytorch-2/blob/main/pytorch2_2.ipynb

標簽:

返回頂部
a级网站在线观看| 久久人人爽人人爽人人av| 奇米网一区二区| 国产免费毛卡片| 免费观看a级片| 精品国产伦一区二区三| 青娱乐av在线| 成人公开免费视频| 少妇高潮在线观看| 天天操精品视频| 亚洲国产高清av| 国产奶头好大揉着好爽视频| www精品久久| 国产污在线观看| 国产伦理一区二区| 91激情在线观看| 男人插女人下面免费视频| 女人18毛片毛片毛片毛片区二 | 久久人妻少妇嫩草av蜜桃| 中文字幕一区2区3区| 免费在线观看黄视频| 欧美 日韩 国产 激情| 欧美一级特黄aaaaaa在线看片| 蜜桃av免费看| 日本道中文字幕| 别急慢慢来1978如如2| 中文字幕在线观看成人| 无码少妇一区二区| 朝桐光av一区二区三区| 制服下的诱惑暮生| 性猛交xxxx乱大交孕妇印度| 中文字幕91爱爱| 精品免费囯产一区二区三区 | 人妻无码一区二区三区四区| 99久久精品国产一区二区成人| 国产精品第5页| 老司机午夜性大片| 日韩免费高清在线| 青青国产在线视频| 日本a在线天堂| www插插插无码视频网站| 真实的国产乱xxxx在线91| xxxx 国产| 亚洲制服在线观看| 69堂免费视频| 免费高清一区二区三区| 国产成人三级视频| 艳母动漫在线免费观看| 黄色一级片中国| 日韩在线不卡av| 一本色道久久88| 亚洲精品自拍视频在线观看| 蜜桃av乱码一区二区三区| 中文字幕被公侵犯的漂亮人妻| 女性生殖扒开酷刑vk| 动漫美女无遮挡免费| 大桥未久恸哭の女教师| 亚洲日本久久久| av黄色一级片| 女人被狂躁c到高潮| 日本一卡二卡在线| 少妇久久久久久久久久| 中文字幕第24页| 日本综合在线观看| www.黄色com| 亚洲 欧美 国产 另类| 国产人与禽zoz0性伦| 日本福利视频导航| 你真棒插曲来救救我在线观看| 无码人妻精品一区二区三区在线| 国产成人a亚洲精v品无码| 欧美日韩怡红院| 在线看免费毛片| 日本三级网站在线观看| 超碰在线观看91| 91激情在线观看| 天天躁日日躁狠狠躁伊人| 中文字幕第3页| 亚洲综合欧美综合| 欧美aaa在线观看| 无码播放一区二区三区| 亚洲精品无码久久久| 色婷婷av一区二区三| 国产伦精品一区二区免费| 日本少妇高潮喷水xxxxxxx| 欧美激情精品久久久久久免费| 麻豆中文字幕在线观看| 国产成人精品视频免费看| 肉色超薄丝袜脚交| 波多野结衣一区二区三区四区| www.97av| 受虐m奴xxx在线观看| 熟妇熟女乱妇乱女网站| 韩国一区二区av| 青青草av在线播放| www.色视频| 香蕉视频黄色在线观看| 国产乱国产乱老熟300| 99福利在线观看| 国产无遮无挡120秒| 国产日韩免费视频| 中文字幕在线播放一区| 99精品一区二区三区的区别| 午夜视频在线瓜伦| 精品免费囯产一区二区三区| 亚洲国产剧情在线观看| 精品无人区无码乱码毛片国产 | 亚洲精品mv在线观看| 樱花视频在线免费观看| 久久久精品人妻一区二区三区| 免费黄色国产视频| 亚洲黄色a v| 无码人妻黑人中文字幕| 国产吃瓜黑料一区二区| 国产女人18水真多毛片18精品| 免费激情视频在线观看| 久久精品视频2| 午夜男人的天堂| 国产成a人亚洲精v品在线观看| 在线观看免费视频黄| www.av视频| 一起操在线视频| 国产免费一区二区三区免费视频| 亚洲人人夜夜澡人人爽| 日本a在线免费观看| 日本网站免费观看| 爱情岛论坛亚洲自拍| 久久99国产精品一区| 欧美成人片在线观看| 性做久久久久久久久久| 免费看一级大片| 国产毛片久久久久久| 亚洲国产精品二区| a级黄色片免费看| 久久午夜无码鲁丝片| 五月婷婷久久久| 91国在线高清视频| av中文在线播放| 手机在线成人av| 狠狠爱免费视频| 91尤物国产福利在线观看| 亚洲ⅴ国产v天堂a无码二区| 日本人视频jizz页码69| www.成人精品| 国产免费xxx| 久久久久久久久久久影院| 少妇精品一区二区| 999在线免费视频| 精品国产无码一区二区| 日本一本草久p| 欧美一级视频免费观看| 黄色正能量网站| 2025韩国理伦片在线观看| 久久久99精品视频| 国精产品一区一区| 粉色视频免费看| 99在线无码精品入口| 乱h高h女3p含苞待放| 久久午夜无码鲁丝片午夜精品| 天天操天天干天天插| 青青草国产免费| 亚洲天堂狠狠干| 欧美老熟妇一区二区三区| 日本在线小视频| 久久人妻一区二区| 免费看污污网站| 久久久久久国产精品日本| 凹凸国产熟女精品视频| 精品免费久久久| www.av91| 99热这里只有精品在线| 天堂8在线天堂资源bt| 久久撸在线视频| 男生操女生视频在线观看| 天天操天天操天天| 亚洲国产成人无码av在线| 日韩精品 欧美| 中文字幕人妻色偷偷久久| 久久久久麻豆v国产| 国产 日韩 欧美 在线| 91禁男男在线观看| 精品国产九九九| 国产精品玖玖玖| 久久最新免费视频| 一级一级黄色片| www亚洲国产| 国产一区二区小视频| 国产一区二区视频免费在线观看| 在线观看中文字幕码| 先锋影音男人资源| 97人妻精品一区二区三区动漫| 91传媒免费视频| 国产成人精品a视频| 国产综合av在线| 欧美熟妇另类久久久久久多毛 | 美女福利视频在线观看| 中文字幕一区2区3区| 黄色一级片在线看| 欧美视频在线观看一区二区三区| 国产无套粉嫩白浆内谢的出处| 中文字幕1区2区| 国产一级片中文字幕| 一本在线免费视频| 中文字幕在线视频第一页| 自拍日韩亚洲一区在线| 91精品人妻一区二区三区蜜桃2| 亚洲一区二区三区四区精品| 国产精品视频看看| 国产精品无码天天爽视频| 成人毛片视频网站| 日本一级片在线播放| 精品国产鲁一鲁一区二区三区| 国产高清不卡视频| 欧美三级午夜理伦三级| 人妻无码中文久久久久专区| 国产在线视频在线观看| 中文字幕制服丝袜在线| 蜜臀久久久久久999| 亚洲欧美一区二区三区不卡| 91麻豆精品成人一区二区| 午夜精品久久久久久久第一页按摩| 91淫黄看大片| 国产jizz18女人高潮| 午夜精品久久久久久久第一页按摩 | 亚洲熟妇无码av在线播放| 色综合久久久无码中文字幕波多| 久久老司机精品视频| 欧美日韩视频免费| 日本黄色录像片| 一二三四区视频| 羞羞的视频在线| 天堂av免费看| 又黄又色的网站| 亚洲 小说区 图片区| 男人插女人下面免费视频| 国产人与禽zoz0性伦| 国产综合无码一区二区色蜜蜜| 久草国产在线观看| 拔插拔插海外华人免费| 亚洲欧洲久久久| 国产成人精品一区二三区四区五区| 欧美一级特黄aaa| 欧美与动交zoz0z| 五级黄高潮片90分钟视频| 国产片在线播放| 伊人成人免费视频| 日韩av在线播放不卡| 日本精品在线观看视频| 人妻精品无码一区二区| 性色av一区二区三区四区| 午夜剧场在线免费观看| youjizz.com在线观看| 性欧美一区二区| 原创真实夫妻啪啪av| 一级爱爱免费视频| 国产一级特黄视频| 一级黄色香蕉视频| 日本a在线天堂| www中文在线| 中文文字幕文字幕高清| 殴美一级特黄aaaaaa| 一区二区视频免费| 国产精品美女毛片真酒店| 牛夜精品久久久久久久| 国产片侵犯亲女视频播放| 色偷偷www8888| 女人被狂躁c到高潮| 污污网站免费在线观看| 国产情侣一区二区| 99精品在线播放| 欧美成人精品欧美一级| 成人性做爰aaa片免费看不忠| www.亚洲成人网| 爱爱视频免费在线观看| 亚洲第一综合网| 成人做爰www看视频软件| 男人天堂手机在线观看| 99精品视频在线播放免费| 国产真人无遮挡作爱免费视频| 久久婷婷综合国产| 亚洲网中文字幕| 全部免费毛片在线播放一个| 天天干天天干天天干天天| 欧美精品色视频| av免费一区二区| 乌克兰美女av| 白嫩少妇丰满一区二区| 青青艹视频在线| 800av在线免费观看| 路边理发店露脸熟妇泻火| 国产高潮呻吟久久| 丝袜熟女一区二区三区| 真实乱偷全部视频| 婷婷久久久久久| 日韩一区二区三区不卡| 男人天堂手机在线观看| 性做久久久久久久| www久久久com| 亚洲国产精品久久久久久6q| 成 人 免费 黄 色| 亚洲国产精品欧美久久| 精品人妻aV中文字幕乱码色欲| 99精品在线视频观看| av在线免费观看国产| 大桥未久一区二区| 手机看片日韩国产| 粉嫩av一区二区三区天美传媒 | 在线观看亚洲免费视频| 久久黄色一级视频| 黄色激情在线观看| 亚洲一区二区乱码| 久久国产柳州莫菁门| 91视频免费看片| 四虎影院一区二区| bt天堂新版中文在线地址| 国产精品专区在线| 欧美丰满熟妇bbbbbb百度| 免费无码av片在线观看| 五月婷婷六月丁香激情| 国产5g成人5g天天爽| 国产成人啪精品午夜在线观看| 五月天激情国产综合婷婷婷| 香蕉影院在线观看| 一区二区www| 欧美一区二区三区黄片| 国产高清成人久久| 极品尤物一区二区| 好吊色这里只有精品| 欧美 国产 日本| 中文字幕一区久久| 欧美一区二区激情视频| 在线观看中文字幕av| 黑人精品一区二区| 中文在线观看免费视频| 黄色国产在线播放| 国产精品国产三级国产专区51| 亚洲成熟丰满熟妇高潮xxxxx| 91香蕉视频在线观看视频| 亚洲 欧美 日韩 综合| 99久久精品日本一区二区免费| 天堂成人在线视频| 国产精品一二三区在线观看| 在线免费看av网站| 日韩精品 欧美| www.久久com| 亚洲一区二区人妻| 亚洲精品一区二区18漫画| 色哟哟精品观看| 国产日韩亚洲欧美在线| 肉色超薄丝袜脚交| 中文字幕观看在线| 欧美熟妇精品一区二区| 女同久久另类69精品国产| 欧美视频免费看欧美视频| 久久成年人网站| 免费看av在线| 日本美女视频网站| 无码人妻精品一区二区三区夜夜嗨| 免费观看日韩毛片| 91精品国产高潮对白| 丰满少妇被猛烈进入| 欧美日韩高清丝袜| 大伊香蕉精品视频在线| 九九视频免费看| 国产日韩欧美一区二区东京热 | 免费看日b视频| 在线免费黄色网| 国产喷水福利在线视频| 日韩人妻无码精品综合区| 日本欧美黄色片| 日本一区二区不卡在线| 神马一区二区三区| 午夜成人亚洲理伦片在线观看| 丰满少妇被猛烈进入高清播放| 久久青青草视频| 国产精品嫩草69影院| 日韩不卡一二区| 久久黄色免费视频| 日本高清视频网站| 国产精品九九九九九九| 中文字幕一区二区三区四| 亚洲精品国产一区二| 91ts人妖另类精品系列| 狠狠操狠狠干视频| 国产av无码专区亚洲a∨毛片| 日本一区二区视频在线播放| 白嫩少妇丰满一区二区| 亚洲一级特黄毛片| 99久久99久久精品免费看小说.| 天堂在线资源视频| 国产精品视频一区二区三区,| 国产调教在线观看| 亚洲久久中文字幕| 国产91免费在线观看| 麻豆精品一区二区三区视频| 久久久久亚洲av无码专区| 色综合久久久无码中文字幕波多| 免费网站在线观看视频| 蜜臀99久久精品久久久久小说| 中文字幕高清视频|