我們將繼續計算機視覺內容的講解。
我們已經知道了計算機視覺,用在什么地方,如何用Pytorch來處理數據,設定一些基礎的設置以及模型。下面,我們將要解釋剩下的部分,包括以下內容:
主題 | 內容 |
---|---|
Model 1 :加入非線性 | 實驗是機器學習的很大一部分,讓我們嘗試通過添加非線性層來改進我們的基線模型。 |
Model 2:卷積神經網絡(CNN) | 是時候讓計算機視覺具體化,并介紹強大的卷積神經網絡架構。 |
比較我們的模型 | 我們已經建立了三種不同的模型,讓我們來比較一下。 |
評估我們的最佳模型 | 讓我們對隨機圖像做一些預測,并評估我們的最佳模型。 |
制作混淆矩陣 | 混淆矩陣是評估分類模型的好方法,讓我們看看如何制作一個。 |
保存和載入最佳性能模型 | 由于我們可能希望稍后使用我們的模型,讓我們保存它并確保它正確地加載回來。 |
下面開始正文:
6 Model 1:建立一個更好的非線性模型
我們在第二篇文章里學過非線性的力量。
看看我們一直在處理的數據,你認為它需要非線性函數嗎?
記住,線性意味著直線,非線性意味著非直線。
讓我們來看看。
我們將通過重新創建與之前類似的模型來實現這一點,只不過這次我們將在每個線性層之間放置非線性函數(nn.ReLU())。
# Create a model with non-linear and linear layers
class FashionMNISTModelV1(nn.Module):def __init__(self, input_shape: int, hidden_units: int, output_shape: int):super().__init__()self.layer_stack = nn.Sequential(nn.Flatten(), # flatten inputs into single vectornn.Linear(in_features=input_shape, out_features=hidden_units),nn.ReLU(),nn.Linear(in_features=hidden_units, out_features=output_shape),nn.ReLU())def forward(self, x: torch.Tensor):return self.layer_stack(x)
看起來不錯。
現在讓我們用之前使用的相同設置實例化它。
我們需要input_shape=784(等于圖像數據的特征數量),hidden_units=10(開始時較小,與基線模型相同)和output_shape=len(class_names)(每個類一個輸出單元)。
[!TIP]
注意:注意我們如何保持我們的模型的大部分設置相同,除了一個變化:添加非線性層。這是運行一系列機器學習實驗的標準做法,改變一件事,看看會發生什么,然后再做一次,一次,一次。
torch.manual_seed(42)
model_1 = FashionMNISTModelV1(input_shape=784, # number of input featureshidden_units=10,output_shape=len(class_names) # number of output classes desired
).to(device) # send model to GPU if it's available
print(next(model_1.parameters()).device) # check model device )
輸出為:
cuda:0
6.1設置損耗、優化器和評估指標
像往常一樣,我們將設置一個損失函數、一個優化器和一個評估指標(我們可以設置多個評估指標,但現在我們將堅持準確性)。
from helper_functions import accuracy_fn
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=model_1.parameters(), lr=0.1)
6.2訓練和測試回路的功能化
到目前為止,我們已經一遍又一遍地編寫train和test循環。
我們再寫一遍,但是這次我們把它們放在函數里,這樣它們就可以被反復調用了。
因為我們現在使用的是與設備無關的代碼,我們將確保在特征(X)和目標(y)張量上調用.to(device)。
對于訓練循環,我們將創建一個名為train_step()的函數,它接受一個模型、一個數據加載器(DataLoader)、一個損失函數和一個優化器。
測試循環將是類似的,但它將被稱為test_step(),它將接受一個模型、一個DataLoader、一個損失函數和一個求值函數。
[!TIP]
注意:由于這些都是函數,您可以以任何喜歡的方式自定義它們。我們在這里所做的可以看作是針對我們的特定分類用例的基本訓練和測試功能。
def train_step(model: torch.nn.Module,data_loader: torch.utils.data.DataLoader,loss_fn: torch.nn.Module,optimizer: torch.optim.Optimizer,accuracy_fn,device: torch.device = device):train_loss, train_acc = 0, 0model.to(device)for batch, (X, y) in enumerate(data_loader):# Send data to GPUX, y = X.to(device), y.to(device)# 1. Forward passy_pred = model(X)# 2. Calculate lossloss = loss_fn(y_pred, y)train_loss += losstrain_acc += accuracy_fn(y_true=y,y_pred=y_pred.argmax(dim=1)) # Go from logits -> pred labels# 3. Optimizer zero gradoptimizer.zero_grad()# 4. Loss backwardloss.backward()# 5. Optimizer stepoptimizer.step()# Calculate loss and accuracy per epoch and print out what's happeningtrain_loss /= len(data_loader)train_acc /= len(data_loader)print(f"Train loss: {train_loss:.5f} | Train accuracy: {train_acc:.2f}%")def test_step(data_loader: torch.utils.data.DataLoader,model: torch.nn.Module,loss_fn: torch.nn.Module,accuracy_fn,device: torch.device = device):test_loss, test_acc = 0, 0model.to(device)model.eval() # put model in eval mode# Turn on inference context managerwith torch.inference_mode(): for X, y in data_loader:# Send data to GPUX, y = X.to(device), y.to(device)# 1. Forward passtest_pred = model(X)# 2. Calculate loss and accuracytest_loss += loss_fn(test_pred, y)test_acc += accuracy_fn(y_true=y,y_pred=test_pred.argmax(dim=1) # Go from logits -> pred labels)# Adjust metrics and print outtest_loss /= len(data_loader)test_acc /= len(data_loader)print(f"Test loss: {test_loss:.5f} | Test accuracy: {test_acc:.2f}%\n")
哦吼!
現在我們有了一些用于訓練和測試模型的函數,讓我們運行它們。
我們將在每個epoch的另一個循環中這樣做。
這樣,對于每個epoch,我們都要經歷一個訓練步驟和一個測試步驟。
[!TIP]
注意:您可以自定義執行測試步驟的頻率。有時人們每隔5個時期或10個時期做一次,在我們的例子中,每個時期做一次。
我們還可以計時,看看代碼在GPU上運行需要多長時間。
# Import tqdm for progress bar
from tqdm.auto import tqdmtorch.manual_seed(42)# Measure time
from timeit import default_timer as timer
train_time_start_on_gpu = timer()epochs = 3
for epoch in tqdm(range(epochs)):print(f"Epoch: {epoch}\n---------")train_step(data_loader=train_dataloader, model=model_1, loss_fn=loss_fn,optimizer=optimizer,accuracy_fn=accuracy_fn)test_step(data_loader=test_dataloader,model=model_1,loss_fn=loss_fn,accuracy_fn=accuracy_fn)train_time_end_on_gpu = timer()
total_train_time_model_1 = print_train_time(start=train_time_start_on_gpu,end=train_time_end_on_gpu,device=device)
輸出為:
0%| | 0/3 [00:00<?, ?it/s]Epoch: 0
---------
Train loss: 1.09199 | Train accuracy: 61.34%
Test loss: 0.95636 | Test accuracy: 65.00%33%|████████████████████████████ | 1/3 [00:05<00:11, 5.79s/it]Epoch: 1
---------
Train loss: 0.78101 | Train accuracy: 71.93%
Test loss: 0.72227 | Test accuracy: 73.91%67%|████████████████████████████████████████████████████████ | 2/3 [00:12<00:06, 6.26s/it]Epoch: 2
---------
Train loss: 0.67027 | Train accuracy: 75.94%
Test loss: 0.68500 | Test accuracy: 75.02%100%|████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:19<00:00, 6.39s/it]
Train time on cuda: 19.161 seconds
太好了!
我們的模型訓練了,但是訓練時間更長?
[!TIP]
注意:CUDA vs CPU的訓練時間很大程度上取決于你使用的CPU/GPU的質量。請繼續閱讀,以獲得更詳細的答案。
問題:“我用了GPU,但我的模型訓練得并不快,這是為什么呢?”
答:嗯,一個原因可能是因為你的數據集和模型都很小(就像我們正在使用的數據集和模型),使用GPU的好處被實際傳輸數據所需的時間所抵消。
在將數據從CPU內存(默認)復制到GPU內存之間存在一個小瓶頸。
因此,對于較小的模型和數據集,CPU實際上可能是進行計算的最佳位置。
但對于更大的數據集和模型,GPU提供的計算速度通常遠遠超過獲取數據的成本。
然而,這在很大程度上取決于您使用的硬件。通過練習,您將習慣訓練模型的最佳地點。
讓我們使用eval_model()函數對訓練好的model_1求值,看看結果如何。
# Note: This will error due to `eval_model()` not using device agnostic code
model_1_results = eval_model(model=model_1, data_loader=test_dataloader,loss_fn=loss_fn, accuracy_fn=accuracy_fn)
print(model_1_results )
輸出為:
...
RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu! (when checking argument for argument mat1 in method wrapper_CUDA_addmm)
噢,不!
看起來我們的eval_model()函數出錯了:
RuntimeError:期望所有張量都在同一設備上,但發現至少有兩個設備,cuda:0和cpu!(當檢查wrapper_addmm方法中參數mat1的參數時)
這是因為我們已經將數據和模型設置為使用與設備無關的代碼,而不是我們的求值函數。
我們通過將目標設備參數傳遞給eval_model()函數來解決這個問題怎么樣?
然后我們再試著計算結果。
# Move values to device
torch.manual_seed(42)
def eval_model(model: torch.nn.Module, data_loader: torch.utils.data.DataLoader, loss_fn: torch.nn.Module, accuracy_fn, device: torch.device = device):"""Evaluates a given model on a given dataset.Args:model (torch.nn.Module): A PyTorch model capable of making predictions on data_loader.data_loader (torch.utils.data.DataLoader): The target dataset to predict on.loss_fn (torch.nn.Module): The loss function of model.accuracy_fn: An accuracy function to compare the models predictions to the truth labels.device (str, optional): Target device to compute on. Defaults to device.Returns:(dict): Results of model making predictions on data_loader."""loss, acc = 0, 0model.eval()with torch.inference_mode():for X, y in data_loader:# Send data to the target deviceX, y = X.to(device), y.to(device)y_pred = model(X)loss += loss_fn(y_pred, y)acc += accuracy_fn(y_true=y, y_pred=y_pred.argmax(dim=1))# Scale loss and accloss /= len(data_loader)acc /= len(data_loader)return {"model_name": model.__class__.__name__, # only works when model was created with a class"model_loss": loss.item(),"model_acc": acc}# Calculate model 1 results with device-agnostic code
model_1_results = eval_model(model=model_1, data_loader=test_dataloader,loss_fn=loss_fn, accuracy_fn=accuracy_fn,device=device
)
print(model_1_results)
輸出為:
{'model_name': 'FashionMNISTModelV1', 'model_loss': 2.302107095718384, 'model_acc': 10.75279552715655}
# Check baseline results
print(model_0_results)
輸出為:
{'model_name': 'FashionMNISTModelV0', 'model_loss': 2.3190648555755615, 'model_acc': 10.85263578