- 🍨 本文為🔗365天深度學習訓練營 中的學習記錄博客
- 🍖 原作者:K同學啊
目標
1. 實現pytorch環境配置
2. 實現mnist手寫數字識別
3. 自己寫幾個數字識別試試
具體實現
(一)環境
語言環境:Python 3.10
編 譯 器: PyCharm
框 架:
(二)具體步驟
**1.**配置Pytorch環境
打開官網PyTorch,Get started:
接下來是選擇安裝版本,最難的就是確定Compute Platform的版本,是否要使用GPU。所以先要確定CUDA的版本。
會發現,pytorch官網根本沒有對應12.7的版本,先安裝最新的試試唄,選擇12.4:
安裝命令:pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
安裝完成,我們建立python文件,輸入如下代碼:
import torch
x = torch.rand(5, 3)
print(x) print(torch.cuda.is_available())---------output---------------
tensor([[0.3952, 0.6351, 0.3107],[0.8780, 0.6469, 0.6714],[0.4380, 0.0236, 0.5976],[0.4132, 0.9663, 0.7576],[0.4047, 0.4636, 0.2858]])
True
從輸出來看,成功了。下面開始正式的mnist手寫數字識別
2. 下載數據并加載數據
import torch
import torch.nn as nn
# import matplotlib.pyplot as plt
import torchvision # 第一步:設置硬件設備,有GPU就使用GPU,沒有就使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device) # 第二步:導入數據
# MNIST數據在torchvision.datasets中,自帶的,可以通過代碼在線下載數據。
train_ds = torchvision.datasets.MNIST(root='./data', # 下載的數據所存儲的本地目錄 train=True, # True為訓練集,False為測試集 transform=torchvision.transforms.ToTensor(), # 將下載的數據直接轉換成張量格式 download=True # True直接在線下載,且下載到root指定的目錄中,注意已經下載了,第二次以后就不會再下載了 )
test_ds = torchvision.datasets.MNIST(root='./data', train=False, transform=torchvision.transforms.ToTensor(), download=True ) # 第三步:加載數據
# Pytorch使用torch.utils.data.DataLoader進行數據加載
batch_size = 32
train_dl = torch.utils.data.DataLoader(dataset=train_ds, # 要加載的數據集 batch_size=batch_size, # 批次的大小 shuffle=True, # 每個epoch重新排列數據 # 以下的參數有默認值可以不寫 num_workers=0, # 用于加載的子進程數,默認值為0.注意在windows中如果設置非0,有可能會報錯 pin_memory=True, # True-數據加載器將在返回之前將張量復制到設備/CUDA 固定內存中。 如果數據元素是自定義類型,或者collate_fn返回一個自定義類型的批次。 drop_last=False, #如果數據集大小不能被批次大小整除,則設置為 True 以刪除最后一個不完整的批次。 如果 False 并且數據集的大小不能被批大小整除,則最后一批將保留。 (默認值:False) timeout=0, # 設置數據讀取的超時時間 , 超過這個時間還沒讀取到數據的話就會報錯。(默認值:0) worker_init_fn=None # 如果不是 None,這將在步長之后和數據加載之前在每個工作子進程上調用,并使用工作 id([0,num_workers - 1] 中的一個 int)的順序逐個導入。(默認:None) ) # 取一個批次看一下數據格式,數據的shape為[batch_size, channel, height, weight]
# batch_size是已經設定的32,channel, height和weight分別是圖片的通道數,高度和寬度
images, labels = next(iter(train_dl))
print(images.shape)
看這個圖片的shape是torch.size([32, 1, 28, 28]),可以看圖MNIST的數據集里的圖像我猜應該是單色的(channel=1),28 * 28大小的圖片(height=28, weight=28)。
將圖片可視化展示出來看看:
# 數據可視化
plt.figure(figsize=(20, 5)) # 指定圖片大小 ,圖像大小為20寬,高5的繪圖(單位為英寸)
for i , images in enumerate(images[:20]): # 維度縮減,npimg = np.squeeze(images.numpy()) # 將整個figure分成2行10列,繪制第i+1個子圖 plt.subplot(2, 10, i+1) plt.imshow(npimg, cmap=plt.cm.binary) plt.axis('off')
plt.show()
**3.**構建CNN網絡
num_classes = 10 # MNIST數據集中是識別0-9這10個數字,因此是10個類別。class Model(nn.Module):def __init__(self):super(Model, self).__init__()# 特征提取網絡self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) # 第一層卷積,卷積核大小3*3self.pool1 = nn.MaxPool2d(2) # 池化層,池化核大小為2*2self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 第二層卷積,卷積核大小3*3self.pool2 = nn.MaxPool2d(2)# 分類網絡self.fc1 = nn.Linear(1600, 64)self.fc2 = nn.Linear(64, num_classes)def forward(self, x):x = self.pool1(F.relu(self.conv1(x)))x = self.pool2(F.relu(self.conv2(x)))x = torch.flatten(x, start_dim=1)x = F.relu(self.fc1(x))x = self.fc2(x)return x# 第四步:加載并打印模型
# 將模型轉移到GPU中
model = Model().to(device)
summary(model)>)
4.訓練模型
# 第五步:訓練模型
loss_fn = nn.CrossEntropyLoss() # 創建損失函數
learn_rate = 1e-2 # 設置學習率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate) # 循環訓練
def train(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) # 訓練集的大小 num_batches = len(dataloader) # 批次數目 train_loss, train_acc = 0, 0 # 初始化訓練損失率和正確率都為0 for X, y in dataloader: # 獲取圖片及標簽 X, y = X.to(device), y.to(device) # 將圖片和標準轉換到GPU中 # 計算預測誤差 pred = model(X) # 使用CNN網絡預測輸出pred loss = loss_fn(pred, y) # 計算預測輸出的pred和真實值y之間的差距 # 反向傳播 optimizer.zero_grad() # grad屬性歸零 loss.backward() # 反向傳播 optimizer.step() # 第一步自動更新 # 記錄acc與loss train_acc += (pred.argmax(1) == y).type(torch.float).sum().item() train_loss += loss.item() train_acc /= size train_loss /= num_batches return train_acc, train_loss # 測試函數,注意測試函數不需要進行梯度下降,不進行網絡權重更新,所以不需要傳入優化器
def test(dataloader, model, loss_fn): size = len(dataloader.dataset) num_batches = len(dataloader) test_loss, test_acc = 0, 0 # 當不進行訓練時,停止梯度更新,節省計算內存消耗 with torch.no_grad(): for imgs, targets in dataloader: imgs, target = imgs.to(device), targets.to(device) # 計算 loss target_pred = model(imgs) loss = loss_fn(target_pred, target) test_loss += loss.item() test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item() test_acc /= size test_loss /= num_batches return test_acc, test_loss # 正式訓練
epochs = 5
train_loss, train_acc, test_loss, test_acc = [], [], [], [] for epoch in range(epochs): model.train() epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt) model.eval() epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn) train_acc.append(epoch_train_acc) test_acc.append(epoch_test_acc) train_loss.append(epoch_train_loss) test_loss.append(epoch_test_loss) template = 'Epoch: {:2d}, Train_acc:{:.1f}%, Train_loss: {:.3f}%, Test_acc: {:.1f}%, Test_loss: {:.3f}%' print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))
print('Done')
# 可見化一下訓練結果
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 顯示中文不標簽,不設置會顯示中文亂碼
plt.rcParams['axes.unicode_minus'] = False # 顯示負號
plt.rcParams['figure.dpi'] = 100 # 分辨率 epochs_range = range(epochs) plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1) plt.plot(epochs_range, train_acc, label='訓練正確率')
plt.plot(epochs_range, test_acc, label='測試正確率')
plt.legend(loc='lower right')
plt.title('訓練與測試正確率') plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='訓練損失率')
plt.plot(epochs_range, test_loss, label='測試損失率')
plt.legend(loc='upper right')
plt.title('訓練與測試損失率') plt.show()
四:預測一下自己手寫的數字
準備數據:
再手動將每個數字切割成單獨的一個文件:
注意,這里并沒有將每個圖片的大小切割成一致,理論上切割成要求的28*28是最好。我這里用代碼來重新生成28 * 28大小的圖片。
import torch
import numpy as np
from PIL import Image
from torchvision import transforms
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
import os, pathlib # 第一步:設置硬件設備,有GPU就使用GPU,沒有就使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device) # 定義模型,要把模型搞過來嘛,不然加載模型會出錯。
class Model(nn.Module): def __init__(self): super().__init__() # 特征提取網絡 self.conv1 = nn.Conv2d(1, 32, kernel_size=3 ) # 第一層卷積,卷積核大小3*3 self.pool1 = nn.MaxPool2d(2) # 池化層,池化核大小為2*2 self.conv2 = nn.Conv2d(32, 64, kernel_size=3) # 第二層卷積,卷積核大小3*3 self.pool2 = nn.MaxPool2d(2) # 分類網絡 self.fc1 = nn.Linear(1600, 64) self.fc2 = nn.Linear(64, 10) def forward(self, x): x = self.pool1(F.relu(self.conv1(x))) x = self.pool2(F.relu(self.conv2(x))) x = torch.flatten(x, start_dim=1) x = F.relu(self.fc1(x)) x = self.fc2(x) return x # 加載模型
model = torch.load('./models/cnn.pth')
model.eval() transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))
]) # 導入數據
data_dir = "./mydata/handwrite"
data_dir = pathlib.Path(data_dir)
image_count = len(list(data_dir.glob('*.jpg')))
print("圖片總數量為:", image_count) plt.rcParams['font.sans-serif'] = ['SimHei'] # 顯示中文不標簽,不設置會顯示中文亂碼
plt.rcParams['axes.unicode_minus'] = False # 顯示負號
plt.rcParams['figure.dpi'] = 100 # 分辨率
plt.figure(figsize=(10, 10))
i = 0
for input_file in list(data_dir.glob('*.jpg')): image = Image.open(input_file) image_resize = image.resize((28, 28)) # 將圖片轉換成 28*28 image = image_resize.convert('L') # 轉換成灰度圖 image_array = np.array(image) # print(image_array.shape) # (high, weight) image = Image.fromarray(image_array) image = transform(image) image = torch.unsqueeze(image, 0) # 返回維度為1的張量 image = image.to(device) output = model(image) pred = torch.argmax(output, dim=1) image = torch.squeeze(image, 0) # 返回一個張量,其中刪除了大小為1的輸入的所有指定維度 image = transforms.ToPILImage()(image) plt.subplot(10, 4, i+1) plt.tight_layout() plt.imshow(image, cmap='gray', interpolation='none') plt.title("實際值:{},預測值:{}".format(input_file.stem[:1], pred.item())) plt.xticks([]) plt.yticks([]) i += 1
plt.show()
準確性很低,40張圖片預測準確數量:6,占比:15.0%.。看圖片,感覺resize成28*28和轉換成灰度圖后,圖片本身已經失真比較嚴重了。先把圖片像素翻轉一下,其實就是反色處理,加上這段代碼:
準確率上了一個臺階(40張圖片預測準確數量:30,占比:75.0%).。但是看圖片,還是不清晰。
(三)總結
- epochs=5,預測的準確性達到97%,如果增加迭代的次數到10,準確性提升接近到99%。迭代20次則達到99.3,提升不明顯。
- batch_size如何從32調整到64,準確性差不太多
- 后續研究圖片增強