MNIST手寫數(shù)字識別代碼及部分函數(shù)用法解釋

這是我參考視頻教程copy的代碼,教程里沒有說明每行語句的具體用途,為了理解這段代碼,我查找了一些使我困惑的函數(shù)和語句的具體用途,用注釋和引用塊的方式給出,充當(dāng)備忘。

import torch
# torch.nn用于構(gòu)建神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)
import torch.nn as nn
# torch.nn.functional,一個神經(jīng)網(wǎng)絡(luò)基礎(chǔ)函數(shù)包。
# 包含卷積,池化,非線性激活函數(shù),歸一化函數(shù),線性函數(shù),Dropout函數(shù)等
import torch.nn.functional as F
# torch.optim是一個實現(xiàn)了各種優(yōu)化算法的庫。
# 支持大部分常用的方法,并且接口具備足夠的通用性,使得未來能夠集成更加復(fù)雜的方法。
import torch.optim as potim
# torchvision包含了目前流行的數(shù)據(jù)集,模型結(jié)構(gòu)和常用的圖片轉(zhuǎn)換工具。
# torchvision.datasets為數(shù)據(jù)集
# torchvision.transforms可以對圖像進(jìn)行變換
from torchvision import datasets, transforms
import numpy as np

print("PyTorch Version: ", torch.__version__)
# 上面代碼塊的輸出
PyTorch Version:  1.0.0
# datasets
mnist_data = datasets.MNIST("./mnist_data",
                            train=True,
                            download=True,
                            transform=transforms.Compose(
                                [transforms.ToTensor()]))

mnist_data[0][0].shape
# 上面代碼塊的輸出
torch.Size([1, 28, 28])

torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)
加載MNIST數(shù)據(jù)集并進(jìn)行基本操作
root:數(shù)據(jù)集在本地的保存位置
train(bool, optional):True則該數(shù)據(jù)集為訓(xùn)練集,F(xiàn)alse則是測試集。默認(rèn)為True
transform(callable, optional):一個轉(zhuǎn)換函數(shù),將PIL圖像轉(zhuǎn)換為別的形式輸出
target_transform(callable, optional):一個函數(shù),輸入為target,輸出對其的轉(zhuǎn)換。例子,輸入的是圖片標(biāo)注的string,輸出為word的索引。
download(bool, optional):True = 從互聯(lián)網(wǎng)上下載數(shù)據(jù)集,并把數(shù)據(jù)集放在root目錄下。如果數(shù)據(jù)集之前下載過,將處理過的數(shù)據(jù)(mnist.py中有相關(guān)函數(shù))放在processed文件夾下

torchvision.transforms.Compose(transforms)
對數(shù)據(jù)集進(jìn)行一系列轉(zhuǎn)換的函數(shù)
transforms:由多個transform組成的列表,依次使用這些transform進(jìn)行轉(zhuǎn)換

torchvision.transforms.ToTensor()
把一個取值范圍是[0,255]的PIL.Image或者shape為(H,W,C)的numpy.ndarray,轉(zhuǎn)換成形狀為[C,H,W],取值范圍是[0,1.0]的torch.FloadTensor
為什么ndarray是(H, W, C)而Tensor是[C, H, W]:C-通道數(shù),H-高度,W-寬度。ndarray和Tensor表示的是同一張圖片,但是二者默認(rèn)的表示順序不同。ndarray側(cè)重真實矩陣形式表示,而Tensor面向神經(jīng)網(wǎng)絡(luò)訓(xùn)練時的需要(分通道)。


# torch.nn.Module是所有網(wǎng)絡(luò)的基類
# pytorch下寫的任何神經(jīng)網(wǎng)絡(luò)都應(yīng)該繼承這個基類
class Net(nn.Module):
    # 初始化一些需要用到的層
    def __init__(self):
        # 在__init__方法中首先執(zhí)行父類的__init__():super(Net, self).__init__()
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4 * 4 * 50, 500)
        self.fc2 = nn.Linear(500, 10)

    # torch.nn.Module的一個抽象方法,所有繼承自torch.nn.Module的子類都應(yīng)該實現(xiàn)該方法
    # 用該方法定義網(wǎng)絡(luò)具體結(jié)構(gòu)
    # 網(wǎng)絡(luò)的具體結(jié)構(gòu)為(共6層):
    # 輸入 -> 卷積層(relu)-> 最大值池化 -> 卷積層(relu)-> 最大值池化 -> 全連接層(relu)-> 全連接層(log_softmax)-> 輸出
    # 該方法隱式執(zhí)行。如下兩行語句將會執(zhí)行該方法,并將forward(input_tensor)的輸出賦值給result
    # net = Net()
    # result = net(input_tensor)
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4 * 4 * 50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True)
二維卷積層
in_channels(int):輸入通道數(shù)。手寫數(shù)字圖像是黑白圖像,只有一個通道,所以第一個卷積層的輸入通道數(shù)為1
out_channels(int):輸出通道數(shù)。這個算法預(yù)先設(shè)定好了第一個卷積層輸出通道為20個,第二個輸出通道為50個
kernel_size(int or tuple):卷積核的尺寸。輸入為int時卷積核為方形,輸入為tuple時可以任意指定形狀
stride(int or tuple, optional):卷積的步長。輸入為int時任何方向步長相等,輸入為tuple時,第一維表示行步長,第二維表示列步長。默認(rèn)值是1。教程在這里輸入了1,可能是習(xí)慣吧,習(xí)慣了設(shè)定不同的步長
padding(int or tuple, optional):為輸入圖像的每一維的兩側(cè)填充0的行數(shù)(簡而言之,對圖像進(jìn)行零填充以控制圖像大小)。tuple與stride的效果同
dilation(int or tuple, optional):卷積核元素之間的間距。比如一個3X3卷積核:在dilation=1時,卷積核在原始圖像的3X3的小區(qū)域內(nèi)卷積;在dilation=2時,卷積核的9個元素平均分布在原始圖像的5X5的區(qū)域內(nèi)做一次卷積
groups(int, optional):in_channels和out_channels都需要能被groups整除。默認(rèn)值為1,此時代表每一個輸入通道都通過卷積連接到每一個輸出通道。當(dāng)groups=2時,代表輸入通道和輸出通道平均分成兩組,分別進(jìn)行卷積,然后連接到一起
bias(bool, optional):為True時,在輸出時添加一個可學(xué)習(xí)的偏置

dilation參數(shù)的一個例子

torch.nn.Linear(in_features, out_features, bias=True)
對輸入做一個線性變換,也就是全連接層
in_features:每個樣本輸入的大小
out_features:每個樣本輸出的大小,即該層神經(jīng)元個數(shù)

torch.nn.functional.relu(input, inplace=False)
relu函數(shù),即小于零時置零,大于零時不變
input(Tensor):輸入
inplace(bool, optional):選擇是否就地操作,即是分配一個新的內(nèi)存空間還是直接在input上操作。默認(rèn)為False。

torch.nn.functional.max_pool2d(input, kernel_size, stride=None, padding=0, dilation=1, ceil_mode=False, return_indices=False)
最大值池化
input(Tensor):輸入
kernel_size(int or tuple):最大值池化的窗口大小
stride(int or tuple):移動的步長
padding(int or tuple, optional):為輸入圖像的每一維的兩側(cè)填充0的行數(shù)
dilation(int or tuple, optional):窗口元素之間的間距。
ceil_mode(bool, optional):計算shape時是否使用向上取整,F(xiàn)alse則使用向下取整。默認(rèn)為False
return_indices(bool, optional):是否輸出最大值索引(做上池化時需要這個索引),默認(rèn)為False

torch.nn.functional.log_softmax(input, dim=None, _stacklevel=3, dtype=None)
等效于log(softmax(input))
input(Tensor):輸入
dim(int):用來計算log_softmax的那個維度。dim=0時對每一列操作,dim=1時對每一行操作
_stacklevel(int, optional):出現(xiàn)warnings時的stack追蹤深度,默認(rèn)為3
dtype(torch.dtype, optional):如果指定了,就在操作之前將input中元素強(qiáng)轉(zhuǎn)為dtype類型,常用于防止溢出

torch.nn.functional.nll_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
input:輸入
target:正確分類標(biāo)簽
reduction(string, optional):參數(shù)為'none' | 'mean' | 'sum','none'時不做任何處理,'mean'時輸出平均數(shù),'sum'時輸出總和


def train(model, device, train_loader, optimizer, epoch):
    model.train() # 將模塊設(shè)置為訓(xùn)練模式,默認(rèn)參數(shù)為True即訓(xùn)練模式,F(xiàn)alse則為evaluation模式(測試模式)
    for idx, (data, target) in enumerate(train_loader):
        # torch.Tensor.to()將Tensor轉(zhuǎn)換為對應(yīng)dtype或device版本
        data, target = data.to(device), target.to(device)
        # 將數(shù)據(jù)傳入模型中進(jìn)行一次訓(xùn)練
        pred = model(data)
        # 計算訓(xùn)練結(jié)果與真實值之間的負(fù)對數(shù)似然損失函數(shù)(The negative log likelihood loss)
        loss = F.nll_loss(pred, target)

        # SGD
        # 將梯度設(shè)置為0
        optimizer.zero_grad()
        # 反向傳播
        loss.backward()
        # 進(jìn)行一次優(yōu)化
        optimizer.step()

        if idx % 100 == 0:
            print("Train Epoch: {}, iteration: {} Loss: {}".format(
                epoch, idx, loss.item()))


def test(model, device, test_loader):
    model.eval()  # 將模型設(shè)置為測試模式
    total_loss = 0.0
    correct = 0.0
    # 在不需計算grad的情況下運行
    # 因為在計算過程中可能會出現(xiàn)require_grad = True的情形,此時會給grad分配內(nèi)存空間
    with torch.no_grad():
        for idx, (data, target) in enumerate(test_loader):
            data, target = data.to(device), target.to(device)
            output = model(data)  # 數(shù)據(jù)經(jīng)過訓(xùn)練后的模型的結(jié)果
            total_loss += F.nll_loss(output, target, reduction="sum").item()  # 計算loss
            pred = output.argmax(dim=1)  # 返回output中所有元素最大值的索引,也就是最終被分到第幾類
            correct += pred.eq(target.view_as(pred)).sum().item()  # 計算正確的個數(shù)

    total_loss /= len(test_loader.dataset)
    acc = correct / len(test_loader.dataset) * 100.
    print("Test loss: {}, Accuracy: {}".format(total_loss, acc))

torch.optim.Optimizer.zero_grad(set_to_none: bool = False)
將梯度初始化為0
set_to_none(bool, optional):將梯度設(shè)置為None,節(jié)約空間。但是需要訪問grad時會出錯,建議保持False

optimizer,zero_grad(),backward(),step()各方法的意義
這部分還有很多可以寫,但是個人對這部分的理解有限,所以用的是別人的解釋。建議如果有能力的話,自己查找相關(guān)資料


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size = 32
train_dataloader = torch.utils.data.DataLoader(datasets.MNIST(
    "./mnist_data",
    train=True,
    download=True,
    # 此處的0.1307是樣本均值,0.3081是樣本方差。Normalize進(jìn)行均值歸一化操作
    transform=transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.1307, ), (0.3081, ))])),
                                               batch_size=batch_size,
                                               shuffle=True,
                                               num_workers=1,
                                               pin_memory=True)

test_dataloader = torch.utils.data.DataLoader(datasets.MNIST(
    "./mnist_data",
    train=False,
    download=True,
    transform=transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.1307, ), (0.3081, ))])),
                                              batch_size=batch_size,
                                              shuffle=False,
                                              num_workers=1,
                                              pin_memory=True)

lr = 0.01  # 學(xué)習(xí)率
momentum = 0.5  # 動量
model = Net().to(device)  # 模型
optimizer = torch.optim.SGD(model.parameters(), lr=lr,
                            momentum=momentum)  # 使用隨機(jī)梯度下降
num_epochs = 2  # 訓(xùn)練輪數(shù)

for epoch in range(num_epochs):
    train(model, device, train_dataloader, optimizer, epoch)
    test(model, device, test_dataloader)

# 保存
torch.save(model.state_dict(), "mnist_cnn.pt")
# 上面代碼塊的輸出
Train Epoch: 0, iteration: 0 Loss: 2.31730055809021
Train Epoch: 0, iteration: 100 Loss: 0.47498029470443726
Train Epoch: 0, iteration: 200 Loss: 0.21783362329006195
Train Epoch: 0, iteration: 300 Loss: 0.14298346638679504
Train Epoch: 0, iteration: 400 Loss: 0.12032605707645416
Train Epoch: 0, iteration: 500 Loss: 0.2093348652124405
Train Epoch: 0, iteration: 600 Loss: 0.07504405081272125
Train Epoch: 0, iteration: 700 Loss: 0.09818609058856964
Train Epoch: 0, iteration: 800 Loss: 0.0559423565864563
Train Epoch: 0, iteration: 900 Loss: 0.01989009976387024
Train Epoch: 0, iteration: 1000 Loss: 0.03885611891746521
Train Epoch: 0, iteration: 1100 Loss: 0.1089647114276886
Train Epoch: 0, iteration: 1200 Loss: 0.01422014832496643
Train Epoch: 0, iteration: 1300 Loss: 0.0961468443274498
Train Epoch: 0, iteration: 1400 Loss: 0.05515772104263306
Train Epoch: 0, iteration: 1500 Loss: 0.16151171922683716
Train Epoch: 0, iteration: 1600 Loss: 0.08142450451850891
Train Epoch: 0, iteration: 1700 Loss: 0.12128034234046936
Train Epoch: 0, iteration: 1800 Loss: 0.02070571482181549
Test loss: 0.07619362027645112, Accuracy: 97.55
Train Epoch: 1, iteration: 0 Loss: 0.01667921245098114
Train Epoch: 1, iteration: 100 Loss: 0.051861390471458435
Train Epoch: 1, iteration: 200 Loss: 0.0019468367099761963
Train Epoch: 1, iteration: 300 Loss: 0.004166126251220703
Train Epoch: 1, iteration: 400 Loss: 0.05624380707740784
Train Epoch: 1, iteration: 500 Loss: 0.05534198880195618
Train Epoch: 1, iteration: 600 Loss: 0.01171034574508667
Train Epoch: 1, iteration: 700 Loss: 0.01633147895336151
Train Epoch: 1, iteration: 800 Loss: 0.06951265037059784
Train Epoch: 1, iteration: 900 Loss: 0.055513106286525726
Train Epoch: 1, iteration: 1000 Loss: 0.03756368160247803
Train Epoch: 1, iteration: 1100 Loss: 0.05086361616849899
Train Epoch: 1, iteration: 1200 Loss: 0.02783718705177307
Train Epoch: 1, iteration: 1300 Loss: 0.056053027510643005
Train Epoch: 1, iteration: 1400 Loss: 0.11548101156949997
Train Epoch: 1, iteration: 1500 Loss: 0.011726364493370056
Train Epoch: 1, iteration: 1600 Loss: 0.1298655867576599
Train Epoch: 1, iteration: 1700 Loss: 0.07312324643135071
Train Epoch: 1, iteration: 1800 Loss: 0.0066553205251693726
Test loss: 0.04361509647369385, Accuracy: 98.67

torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False)
dataset(Dataset):加載數(shù)據(jù)的數(shù)據(jù)集。
batch_size(int, optional):每個batch加載多少個樣本(默認(rèn): 1)。
shuffle(bool, optional):設(shè)置為True時會在每個epoch重新打亂數(shù)據(jù)(默認(rèn): False).
sampler(Sampler, optional):定義從數(shù)據(jù)集中提取樣本的策略。如果指定,則忽略shuffle參數(shù)。
num_workers(int, optional):用多少個子進(jìn)程加載數(shù)據(jù)。0表示數(shù)據(jù)將在主進(jìn)程中加載(默認(rèn): 0)
collate_fn(callable, optional):沒看懂,默認(rèn)就行,不影響
pin_memory(bool, optional):如果為True,則DataLoader在將tensor返回之前將其復(fù)制到CUDA固定的內(nèi)存中。
drop_last(bool, optional):如果數(shù)據(jù)集大小不能被batch size整除,則設(shè)置為True后可刪除最后一個不完整的batch。如果設(shè)為False并且數(shù)據(jù)集的大小不能被batch size整除,則最后一個batch將更小。(默認(rèn): False)

torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
params(iterable):待優(yōu)化參數(shù)的iterable或者是定義了參數(shù)組的dict
lr(float):學(xué)習(xí)率
momentum(float, optional):動量因子(默認(rèn):0)
weight_decay(float, optional):權(quán)重衰減(L2懲罰)(默認(rèn):0)
dampening(float, optional):動量的抑制因子(默認(rèn):0)
nesterov(bool, optional):使用Nesterov動量(默認(rèn):False)
關(guān)于動量方面,查看Adam算法

torch.save(obj, f, pickle_module=<module 'pickle' from '/home/jenkins/miniconda/lib/python3.5/pickle.py'>, pickle_protocol=2)
將模型保存為硬盤文件
obj:想要保存的對象。此處若是module則保存整個網(wǎng)絡(luò)狀態(tài),加載時使用model=torch.load(mymodel.pt)
此處若是module.state_dict()則只保存參數(shù),不保存網(wǎng)絡(luò)結(jié)構(gòu),加載時需先定義好網(wǎng)絡(luò)結(jié)構(gòu),然后再load,也即
model = My_model(*args, **kwargs)
model.load_state_dict(torch.load(mymodel.pt))
f:類文件對象 (返回文件描述符)或一個保存文件名的字符串
pickle_module:用于pickling元數(shù)據(jù)和對象的模塊
pickle_protocol:指定pickle protocal 可以覆蓋默認(rèn)參數(shù)
保存的文件有三種文件名:.pth,.pt,.pkl,三者除了文件名后綴之外無任何區(qū)別。pytorch約定使用.pt文件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容