Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # 在最後面有說明怎麼獲得資料並進行整理的,
- # import
- import torch
- import torch.nn as nn
- from torch.utils.data import Dataset
- from torch.utils.data import DataLoader
- from torch.utils.data import random_split
- from torchvision import datasets, transforms
- import matplotlib.pyplot as plt
- import numpy as np
- # 若 CUDA 環境可用,則使用 GPU 計算,否則使用 CPU
- device = "cuda" if torch.cuda.is_available() else "cpu"
- print(f"Using {device=}")
- # 設定圖片轉換器
- transform = transforms.Compose([
- transforms.Resize((224, 224)), # 將圖片大小轉換為 224x224
- transforms.ToTensor() # 轉換成 PyTorch 張量
- ])
- # 讀取資料夾中的圖片資料
- dataset = datasets.ImageFolder(root='pizza_classify', transform=transform)
- # 將資料集分成訓練集和驗證集
- train_size = int(len(dataset) * 0.8) # 設定訓練集佔 80%
- valid_size = len(dataset) - train_size
- # 隨機分布
- train_dataset, valid_dataset = random_split(dataset, [train_size, valid_size])
- # 設定資料讀取器
- batch_size = 16
- train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
- valid_dataloader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=True)
- # 資料類別:
- print(dataset.class_to_idx)
- # 訓練集、驗證集資料筆數
- train_dataset_num = len(train_dataset)
- valid_dataset_num = len(valid_dataset)
- print(f'{train_dataset_num=}')
- print(f'{valid_dataset_num=}')
- # 訓練集batch、驗證集batch數(批次數 即 iterantion)
- print(f'{len(train_dataloader)=}')
- print(f'{len(valid_dataloader)=}')
- # VGG16模型
- class VGG16(nn.Module):
- def __init__(self, num_classes=2):
- super(VGG16, self).__init__()
- self.features = nn.Sequential(
- nn.Conv2d(3, 64, kernel_size=3, padding=1), # 224
- nn.BatchNorm2d(64),
- nn.ReLU(inplace=True),
- nn.Conv2d(64, 64, kernel_size=3, padding=1), # 224
- nn.BatchNorm2d(64),
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=2, stride=2), # 112
- nn.Conv2d(64, 128, kernel_size=3, padding=1), # 112
- nn.BatchNorm2d(128),
- nn.ReLU(inplace=True),
- nn.Conv2d(128, 128, kernel_size=3, padding=1), # 112
- nn.BatchNorm2d(128),
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=2, stride=2), # 56
- nn.Conv2d(128, 256, kernel_size=3, padding=1), # 56
- nn.BatchNorm2d(256),
- nn.ReLU(inplace=True),
- nn.Conv2d(256, 256, kernel_size=3, padding=1), # 56
- nn.BatchNorm2d(256),
- nn.ReLU(inplace=True),
- nn.Conv2d(256, 256, kernel_size=3, padding=1), # 56
- nn.BatchNorm2d(256),
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=2, stride=2), # 28
- nn.Conv2d(256, 512, kernel_size=3, padding=1), # 28
- nn.BatchNorm2d(512),
- nn.ReLU(inplace=True),
- nn.Conv2d(512, 512, kernel_size=3, padding=1), # 28
- nn.BatchNorm2d(512),
- nn.ReLU(inplace=True),
- nn.Conv2d(512, 512, kernel_size=3, padding=1), # 28
- nn.BatchNorm2d(512),
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=2, stride=2), # 14
- nn.Conv2d(512, 512, kernel_size=3, padding=1), # 14
- nn.BatchNorm2d(512),
- nn.ReLU(inplace=True),
- nn.Conv2d(512, 512, kernel_size=3, padding=1), # 14
- nn.BatchNorm2d(512),
- nn.ReLU(inplace=True),
- nn.Conv2d(512, 512, kernel_size=3, padding=1), # 14
- nn.BatchNorm2d(512),
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=2, stride=2) # 7
- )
- self.classifier = nn.Sequential(
- nn.Linear(25088, 4096),
- nn.ReLU(inplace=True),
- nn.Dropout(p=0.5),
- nn.Linear(4096, 4096),
- nn.ReLU(inplace=True),
- nn.Dropout(p=0.5),
- nn.Linear(4096, num_classes)
- )
- def forward(self, x):
- output = self.features(x)
- output = output.view(output.size(0),-1)
- output = self.classifier(output)
- output = torch.softmax(output, dim=1)
- return output
- # 設置停止器
- class Stop_and_Save:
- def __init__(self, patience = 10, path = 'vgg16_pizza.pth'):
- self.patience = patience
- self.path = path
- self.best_val = None
- self.counter = 0
- self.earlystop = False
- self.min_val = np.inf
- def __call__(self,val_loss,model):
- if self.best_val is None:
- self.best_val = val_loss
- elif val_loss >= self.best_val:
- self.counter += 1
- if self.counter == self.patience:
- self.earlystop = True
- else:
- self.best_val = val_loss
- if val_loss <= self.min_val:
- self.save_checkpoint(model)
- self.min_val = val_loss
- self.counter = 0
- def save_checkpoint(self, model):
- torch.save(model.state_dict(), self.path)
- # 建立模型並輸出模型資訊
- model = VGG16(num_classes=2)
- # print(model)
- # 將model移至模型
- model.to(device)
- # 設定損失函數、優化器和學習率
- criterion = nn.CrossEntropyLoss()
- # VGG因為參數過多,不能使用ADAM
- # 我也不知道這是不是真的,但總之改成SGD之後loss有在好好變動
- optimizer = torch.optim.SGD(model.parameters(),lr=0.005,momentum=0.9)
- # 用調度器適當縮小learning rate
- scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer,factor=0.2,patience=5,mode='min',verbose=False)
- # 具體化停止器
- early_stopping = Stop_and_Save(patience=10)
- # 設定最大epochs和訓練過程紀錄
- max_epochs = 200
- # train的部分:
- train_loss_history = []
- train_accuracy_history = []
- train_precision_history = []
- # valid的部分:
- valid_loss_history = []
- valid_accuracy_history = []
- valid_precision_history = []
- # 訓練迴圈
- # 把數據移往gpu必須在迴圈中執行是因為GPU空間太小,最好用mini_batch的方式送入
- for epoch in range(max_epochs):
- # 每個epoch開始時,需要歸零的計數器
- train_loss_total = 0.0
- correct_train_total = 0
- precision_train_total = 0
- guess_train_total = 0
- train_iteration = 0
- valid_loss_total = 0.0
- correct_valid_total = 0
- precision_valid_total = 0
- guess_valid_total = 0
- valid_iteration = 0
- model.train()
- for i,(inputs,labels) in enumerate(train_dataloader):
- # 將資料移到GPU上
- inputs, labels = inputs.to(device), labels.to(device)
- # 清空優化器的梯度
- optimizer.zero_grad()
- # 模型向前計算
- train_outputs = model(inputs)
- # 計算損失
- train_loss = criterion(train_outputs, labels)
- # 反向傳播
- train_loss.backward()
- # 用優化器更新權重
- optimizer.step()
- # 數據紀錄(accuracy和precision必須用累加否則會有剛好全部都選到非披薩的可能)
- # train_loss累加
- train_loss_total += train_loss.item()
- # 獲取預測標籤
- _, preds = torch.max(train_outputs,1)
- # 訓練正確的次數
- correct_train_total += ((preds == labels).sum()).item()
- # 猜pizza且真的為pizza的次數
- precision_train_total += ((torch.logical_and(preds==labels,preds==1)).sum()).item()
- # 猜pizza為真
- guess_train_total += ((preds == 1).sum()).item()
- train_iteration += 1
- model.eval()
- for i,(inputs,labels) in enumerate(valid_dataloader):
- # 即使在eval模式下,pytorch仍會跟蹤梯度占用內存,所以仍要使用no_grad
- with torch.no_grad():
- # 將資料移到GPU上
- inputs, labels = inputs.to(device), labels.to(device)
- valid_outputs = model(inputs)
- # 計算損失
- valid_loss = criterion(valid_outputs,labels)
- # 數據紀錄(同上)
- # valid_loss累加
- valid_loss_total += valid_loss.item()
- # 獲取預測標籤
- _, preds = torch.max(valid_outputs,1)
- # 驗證正確的次數
- correct_valid_total += ((preds == labels).sum()).item()
- # 猜pizza且真的為pizza的次數(驗證)
- precision_valid_total += ((torch.logical_and(preds==labels,preds==1)).sum()).item()
- guess_valid_total += ((preds == 1).sum()).item()
- valid_iteration += 1
- # 每個epoch報告一次
- # train的部分:
- train_loss_avg = train_loss_total/train_iteration
- train_loss_history.append(train_loss_avg)
- train_accuracy = correct_train_total*100/train_dataset_num
- train_accuracy_history.append(train_accuracy)
- train_prec = precision_train_total*100/guess_train_total
- train_precision_history.append(train_prec)
- # valid的部分:
- valid_loss_avg = valid_loss_total/valid_iteration
- valid_loss_history.append(valid_loss_avg)
- valid_accuracy = correct_valid_total*100/valid_dataset_num
- valid_accuracy_history.append(valid_accuracy)
- valid_prec = precision_valid_total*100/guess_valid_total
- valid_precision_history.append(valid_prec)
- # 使用調度器
- scheduler.step(valid_loss_avg)
- # 使用停止器
- early_stopping(valid_loss_avg,model=model)
- if early_stopping.earlystop:
- print(f"early stop triggered by {valid_loss_avg=}! at epochs:{epoch}")
- break
- # if epoch%1 == 0:
- # print(f'epoch:{epoch}')
- # print(f'{train_loss_avg=} ; {train_accuracy=}% ; {train_prec=}%')
- # print(f'{valid_loss_avg=} ; {valid_accuracy=}% ; {valid_prec=}%')
- # 輸出訓練歷史
- plt.plot(train_loss_history, label='train', color = 'deepskyblue')
- plt.plot(valid_loss_history, label='valid', color = 'r')
- plt.title('Loss trend')
- plt.xlabel('epochs')
- plt.ylabel('Loss')
- plt.legend()
- plt.show()
- plt.plot(train_accuracy_history, label='train', color = 'deepskyblue')
- plt.plot(valid_accuracy_history, label='valid', color = 'r')
- plt.title('accuracy')
- plt.xlabel('epochs')
- plt.ylabel('accuracy')
- plt.legend()
- plt.show()
- plt.plot(train_precision_history, label='train', color = 'deepskyblue')
- plt.plot(valid_precision_history, label='valid', color = 'r')
- plt.title('precision')
- plt.xlabel('epochs')
- plt.ylabel('precision')
- plt.legend()
- plt.show()
- # data
- # by food-101, I choose 10 random foods from each foods category
- code that can help you to randomly get 1000 non_pizza foods in food_101
- # remember to delete/move pizza folder from food_101 before use this
- # all import needed is here
- import os
- import numpy as np
- import shutil
- # get information about pizza dataset
- pizza_Path = "pizza_classify"
- category_list = os.listdir(pizza_Path)
- print(category_list)
- pizza_list = os.listdir(pizza_Path+"/pizza")
- print(pizza_list)
- pizzas = len(pizza_list)
- print(pizzas)
- # first calculate number
- img_Path = "food_101/images"
- food_list = os.listdir(img_Path)
- foods = len(food_list)
- print(f'{foods=}')
- divide_foods = pizzas//foods
- remain_foods = pizzas - foods*divide_foods
- print(f'{divide_foods=} and {remain_foods}')
- # make dir
- if not os.path.exists(pizza_Path + "/not_pizza"):
- os.mkdir(pizza_Path + "/not_pizza")
- counts = 0
- for i in range(foods):
- pick_food_path = food_list[i]
- pick_food_path = img_Path + "/" + pick_food_path
- pick_food_list = os.listdir(pick_food_path)
- pick_food_number = len(pick_food_list)
- # print(f'{pick_food_path} have {pick_food_number} picture in it')
- # print(pick_food_list)
- pick_food_list = np.array(pick_food_list) # necessary
- # print(pick_food_number,divide_foods)
- pick_foods = pick_food_list[np.random.choice(pick_food_number,divide_foods,replace=False)]
- # print(pick_foods)
- for j in range(divide_foods):
- shutil.copyfile(f'{pick_food_path}/{pick_foods[j]}',f'{pizza_Path}/not_pizza/not_pizza_{counts:04d}.jpg')
- counts += 1
- print(f'{counts=}')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement