# 410921216賴柏諺_深度學習作業二 ## 零. 程式碼: :::spoiler 程式碼 (將資料依照標籤分類) ```PYTHON #處理資料 import os import csv import shutil # 設定圖片和 CSV 檔案的路徑 image_dir = "./music_train" csv_file = "./train_truth.csv" # 逐行讀取 CSV 檔案 with open(csv_file, 'r') as file: #'r': 讀取模式 reader = csv.reader(file) next(reader) # 跳過標題行 for row in tqdm(reader): # 讀取檔案名稱和標籤 filename, label = row # 將標籤轉換成整數 label = int(label) # 確定標籤資料夾是否存在,若不存在就創建一個 label_dir = os.path.join(image_dir, str(label)) if not os.path.exists(label_dir): os.makedirs(label_dir) # 複製圖片到對應的標籤資料夾中 src_path = os.path.join(image_dir, filename) dst_path = os.path.join(label_dir, filename) shutil.copy(src_path, dst_path) ``` ::: :::spoiler 程式碼 (實作) ```PYTHON import torch #PyTorch import torch.nn as nn #神經網路 import torch.optim as optim #優化器 from torch.utils.data import random_split #數據集切割 import torchvision.datasets as datasets #數據集 import torchvision.transforms as transforms #圖片轉換 from torch.utils.data import DataLoader #數據加載器 import pandas as pd #處理CSV檔案 from tqdm.auto import tqdm #進度條 import os #處理資料 from PIL import Image #圖片處理 # 定義數據轉換 data_transform = transforms.Compose([ transforms.Resize((224, 224)), #將圖片轉換成224*224 transforms.ToTensor(), #將圖片轉換成Tensor transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) #數據標準化 ]) # 加載數據集 dataset = datasets.ImageFolder('./music_classified', transform=data_transform) # 將dataset切割成train和test train_size = int(0.85 * len(dataset)) #訓練集佔85% test_size = len(dataset) - train_size #測試集佔15% train_dataset, test_dataset = random_split(dataset, [train_size, test_size]) # 定義數據加載器 train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) #BATCH大小32,打亂數據 test_loader = DataLoader(test_dataset, batch_size=32, shuffle=True) #BATCH大小32,打亂數據 # 定義模型 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.fc1 = nn.Linear(128 * 28 * 28, 128) self.fc2 = nn.Linear(128, 88) self.pool = nn.MaxPool2d(kernel_size=2, stride=2) def forward(self, x): x = self.pool(nn.functional.relu(self.conv1(x))) x = self.pool(nn.functional.relu(self.conv2(x))) x = self.pool(nn.functional.relu(self.conv3(x))) x = x.view(-1, 128 * 28 * 28) x = nn.functional.relu(self.fc1(x)) x = self.fc2(x) return x # 初始化模型和損失函數 model = Net() criterion = nn.CrossEntropyLoss() # 配置GPU device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f'Using device: {device}') model = model.to(device) criterion = criterion.to(device) # 定義優化器 optimizer = optim.AdamW(model.parameters()) # 訓練模型 model.train() #模型設為訓練模式 num_epochs = 10 #訓練10個epoch for epoch in tqdm(range(num_epochs)): running_loss = 0.0 for i, (inputs, labels) in enumerate(train_loader): #每個batch的loss inputs, labels = inputs.to(device), labels.to(device) #將資料放入GPU optimizer.zero_grad() #梯度歸零 outputs = model(inputs) #跑模型 loss = criterion(outputs, labels) #計算loss loss.backward() #反向傳播 optimizer.step() #更新參數 running_loss += loss.item() #計算每個epoch的loss epoch_loss = running_loss / len(train_loader) #計算每個epoch的loss print('Epoch {} loss: {:.4f}'.format(epoch+1, epoch_loss)) # 評估模型 model.eval() #模型設為評估模式 with torch.no_grad(): #不計算梯度(純預測) correct, total = 0, 0 for images, labels in test_loader: #每個batch的準確率 images = images.to(device) #將資料放入GPU labels = labels.to(device) #將資料放入GPU outputs = model(images) #跑模型 _, predicted = torch.max(outputs.data, 1) #取得預測結果 total += labels.size(0) #計算總數 correct += (predicted == labels).sum().item() #計算正確數 # 計算測試集的準確率 accuracy = 100 * correct / total print('Test accuracy: {:.2f}%'.format(accuracy)) #加載music_test資料夾 exam_dir = './music_test' exam_imgs = os.listdir(exam_dir) #預測music_test的label,並將結果存入results model.eval() #模型設為評估模式 results = [] #存放結果 with torch.no_grad(): #不計算梯度(純預測) for img_name in exam_imgs: #每張圖片的預測結果 img_path = os.path.join(exam_dir, img_name) #圖片路徑 img = Image.open(img_path).convert('RGB') #讀取圖片 inputs = data_transform(img).unsqueeze(0).to(device) outputs = model(inputs) #跑模型 _, predicted = torch.max(outputs.data, 1) #取得預測結果 predicted = predicted.cpu().numpy().tolist() #轉成list results.append([img_name[:], dataset.classes[predicted[0]]]) #將結果存入results #將結果存入ans.csv df = pd.DataFrame(results, columns=['filename', 'category']) df.to_csv('ans.csv', index=False) ``` ::: ## 一. 遇到的問題: 1. 資料龐大,不好放到COLAB 2. 為訓練圖片分配標籤 3. 資料預處理 ## 二. 怎麼解決問題: 1. 架設環境是本次作業在開始做之前的一大難題,上次的作業只要上傳csv即可,但這次的作業有大量圖片要上傳,而且實作之前不知道程式會跑多久,萬一跑到一半colab壞掉還要重傳圖片,很麻煩,所以這次的作業完全是在本機上的vscode實作完成。 首先第一步就是安裝pytorch,由於見識過gpu的美好,所以直接走困難路線,安裝gpu版本。在看了網路上不少的教學,默默地註冊cudnn,安裝cuda 11.8,核對python版本後重裝3.10.10,一切都裝好後實際運行,順跑。 但接下來嚴重的問題是pip裝不了東西,嘗試了網路上的方法也都會跳出錯誤訊息,不斷地將錯誤訊息一遍又一遍的查詢網路,是座很難跨過的高牆,最後只好聽室友的意見,用conda去安裝。 用conda安裝花的時間比我整晚查資料問網路還少的多了,簡單打幾條指令,等它跑完就好,最後成功在電腦上裝好gpu版本的pytorch。 2. 為訓練資料分配標籤,也就是人工標註的部分,也是一大難題,一開始在想要不要乾脆邊讀csv邊標註就好了,但感覺直接分類進對應標籤名的資料夾感覺更快。 問題是2000多張,用人工一張一張分類感覺又不切實際,所以打算用程式來解決這個問題,這時候chatgpt就幫了我很多忙。 我請它幫我寫一個簡易的架構,我再手動更改檔案位置,並且學習一些我不會用的語法,加上我的註解,最後成功將其分類。 3. 對於如何將圖片轉換為pytorch的tensor,又請來gpt大神來為我解惑,於是得到了以下的程式碼: ```python data_transform = transforms.Compose([ transforms.Resize((224, 224)), #將圖片轉換成224*224 transforms.ToTensor(), #將圖片轉換成Tensor transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) #數據標準化 ]) ``` 再看完程式碼後,前兩行都看得懂,也自己加上了註解,但最後一行normalize後面那一串就不知道在幹嘛了。 通過又一次的詢問,得知mean與std分別是指在通道(R, G, B)上的平均值與標準差,通過將像素值減去均值,然後除以標準差,可以將數據轉換為以零為中心的分佈,有助於模型更好地學習特徵並提高訓練的穩定性。 而數據標準化是使用imagenet數據集中的平均值與標準差,由於本次圖片並非特殊或手繪圖片,所以適合使用這組數據,根據先人嘗試的結果,可以使一般的圖像類模型訓練效果良好。 ## 三. 模型設計思路(個人創意): 1. 我只使用三通道(rgb),三層捲基層(conv),兩個全連階層(fc)。捲機大小的分配採逐步放大,輸出到到全連接層再轉小,使用128因為大於88且又是2的次方。 2. kernel size就依上課聽過的選擇3*3,機活函數選用reRU。 3. 為了防止特徵地圖越捲越小,使用padding的技術在每次卷完後在外圍填1圈。 4. 為了減少計算量,將移動步數設為2(stride=2),相較一次動一步減少了約4倍的計算量(H*K) --> (H/2 * K/2) = 1/4(H*K)。 ## 四. 學到了什麼: 1. 資料處理的方法: 這次的作業是初次實作cnn網路,初次學習如何處理圖片檔案,更重要的是學習對資料夾做讀寫,並使用with正確釋放資源。 對於圖像的預處理,知道要如何將其轉換為tensor的資料格式,並且使用對一般圖像預訓練過的標準化參數,讓訓練的模型表現更好。 2. CNN實作: 這次的網路實作用了很多cnn常用的模型設計,與許多課堂教受的技術,像是pooling和padding,實際跑起來速度是蠻快的,一開始把epoch調到100,結果跑到epoch 5時,loss就停在0.0001了(我有設定顯示到第4位)到第10步loss就0.0000了,於是馬上暫停,改為訓練10個epoch,最後結果還不錯。