admin 管理员组

文章数量: 1086934

【Pytorch学习】图像分类从逻辑回归到CNN

【Pytorch学习】图像分类从逻辑回归到CNN

1,未调用torch.nn接口,原始编程实现最小的逻辑回归神经网络

from pathlib import Path
import requestsDATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"PATH.mkdir(parents=True, exist_ok=True)URL = "/"
FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)import pickle
import gzipwith gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")from matplotlib import pyplot
import numpy as nppyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)#PyTorch 使用torch.tensor而不是 numpy 数组,因此我们需要转换数据。
import torchx_train, y_train, x_valid, y_valid = map(torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())#一个最小的逻辑回归神经网络模型
import mathweights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeros(10, requires_grad=True)def log_softmax(x):return x - x.exp().sum(-1).log().unsqueeze(-1)def model(xb):return log_softmax(xb @ weights + bias)bs = 64  # batch sizexb = x_train[0:bs]  # a mini-batch from x
preds = model(xb)  # predictions
preds[0], preds.shape
print(preds[0], preds.shape)def nll(input, target):return -input[range(target.shape[0]), target].mean()loss_func = nllyb = y_train[0:bs]
print(loss_func(preds, yb))def accuracy(out, yb):preds = torch.argmax(out, dim=1)return (preds == yb).float().mean()print(accuracy(preds, yb))from IPython.core.debugger import set_tracelr = 0.5  # learning rate
epochs = 2  # how many epochs to train forfor epoch in range(epochs):for i in range((n - 1) // bs + 1):#         set_trace()start_i = i * bsend_i = start_i + bsxb = x_train[start_i:end_i]yb = y_train[start_i:end_i]pred = model(xb)loss = loss_func(pred, yb)loss.backward()#反向传播的原始实现1with torch.no_grad():weights -= weights.grad * lrbias -= bias.grad * lrweights.grad.zero_()bias.grad.zero_()# 反向传播的原始实现2# with torch.no_grad():#     for p in model.parameters():#         p -= p.grad * lr#     model.zero_grad()print(loss_func(model(xb), yb), accuracy(model(xb), yb))

2,调用torch.nn,重构代码实现最小的逻辑回归神经网络

#数据加载部分from pathlib import Path
import requestsDATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"PATH.mkdir(parents=True, exist_ok=True)URL = "/"
FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)import pickle
import gzipwith gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")from matplotlib import pyplot
import numpy as nppyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)#PyTorch 使用torch.tensor而不是 numpy 数组,因此我们需要转换数据。
import torchx_train, y_train, x_valid, y_valid = map(torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())#模型构建更简洁#损失函数及准确率
import torch.nn.functional as F
from torch import optimloss_func = F.cross_entropy
def accuracy(out, yb):preds = torch.argmax(out, dim=1)return (preds == yb).float().mean()from torch import nnclass Mnist_Logistic(nn.Module):def __init__(self):super().__init__()self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))self.bias = nn.Parameter(torch.zeros(10))def forward(self, xb):return xb @ self.weights + self.biasclass Mnist_Logistic2(nn.Module):def __init__(self):super().__init__()self.lin = nn.Linear(784, 10)def forward(self, xb):return self.lin(xb)model = Mnist_Logistic()
opt = optim.SGD(model.parameters(), lr=lr)print(loss_func(model(xb), yb))#训练迭代
def fit():for epoch in range(epochs):for i in range((n - 1) // bs + 1):start_i = i * bsend_i = start_i + bsxb = x_train[start_i:end_i]yb = y_train[start_i:end_i]pred = model(xb)loss = loss_func(pred, yb)loss.backward()opt.step()opt.zero_grad()fit()
print(loss_func(model(xb), yb))

3,添加批量加载数据,重构代码实现最小的逻辑回归神经网络

#数据加载部分from pathlib import Path
import requestsDATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"PATH.mkdir(parents=True, exist_ok=True)URL = "/"
FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)import pickle
import gzipwith gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")from matplotlib import pyplot
import numpy as nppyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)#PyTorch 使用torch.tensor而不是 numpy 数组,因此我们需要转换数据。
import torchx_train, y_train, x_valid, y_valid = map(torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())#批量数据生成器
from torch.utils.data import DataLoadertrain_ds = TensorDataset(x_train, y_train)
#train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)valid_ds = TensorDataset(x_valid, y_valid)
#valid_dl = DataLoader(valid_ds, batch_size=bs * 2)def get_data(train_ds, valid_ds, bs):return (DataLoader(train_ds, batch_size=bs, shuffle=True),DataLoader(valid_ds, batch_size=bs * 2),)#我们将在每个周期结束时计算并打印验证损失。
#损失函数及准确率
import torch.nn.functional as F
from torch import optimloss_func = F.cross_entropy#批量损失
def loss_batch(model, loss_func, xb, yb, opt=None):loss = loss_func(model(xb), yb)if opt is not None:loss.backward()opt.step()opt.zero_grad()return loss.item(), len(xb)def accuracy(out, yb):preds = torch.argmax(out, dim=1)return (preds == yb).float().mean()from torch import nnclass Mnist_Logistic(nn.Module):def __init__(self):super().__init__()self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))self.bias = nn.Parameter(torch.zeros(10))def forward(self, xb):return xb @ self.weights + self.biasclass Mnist_Logistic2(nn.Module):def __init__(self):super().__init__()self.lin = nn.Linear(784, 10)def forward(self, xb):return self.lin(xb)model = Mnist_Logistic()
opt = optim.SGD(model.parameters(), lr=lr)import numpy as npdef fit(epochs, model, loss_func, opt, train_dl, valid_dl):for epoch in range(epochs):model.train()for xb, yb in train_dl:loss_batch(model, loss_func, xb, yb, opt)model.eval()with torch.no_grad():losses, nums = zip(*[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl])val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)print(epoch, val_loss)#最终执行
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)fit(epochs, model, loss_func, opt, train_dl, valid_dl)

4,实现CNN神经网络,只需要将模型改为CNN即可

使用 Pytorch 的预定义Conv2d类作为我们的卷积层。 我们定义了具有 3 个卷积层的 CNN。 每个卷积后跟一个 ReLU。 最后,我们执行平均池化。
 

#数据加载部分from pathlib import Path
import requestsDATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"PATH.mkdir(parents=True, exist_ok=True)URL = "/"
FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)import pickle
import gzipwith gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")from matplotlib import pyplot
import numpy as nppyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)#PyTorch 使用torch.tensor而不是 numpy 数组,因此我们需要转换数据。
import torchx_train, y_train, x_valid, y_valid = map(torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())#批量数据生成器
from torch.utils.data import DataLoadertrain_ds = TensorDataset(x_train, y_train)
#train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)valid_ds = TensorDataset(x_valid, y_valid)
#valid_dl = DataLoader(valid_ds, batch_size=bs * 2)def get_data(train_ds, valid_ds, bs):return (DataLoader(train_ds, batch_size=bs, shuffle=True),DataLoader(valid_ds, batch_size=bs * 2),)###数据增强与预处理部分可扩展
# def preprocess(x, y):
#     return x.view(-1, 1, 28, 28), y
#     #return x.view(-1, 1, 28, 28).to(dev), y.to(dev)##GPU设置
#
# class WrappedDataLoader:
#     def __init__(self, dl, func):
#         self.dl = dl
#         self.func = func
#
#     def __len__(self):
#         return len(self.dl)
#
#     def __iter__(self):
#         batches = iter(self.dl)
#         for b in batches:
#             yield (self.func(*b))
#
# train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
# train_dl = WrappedDataLoader(train_dl, preprocess)
# valid_dl = WrappedDataLoader(valid_dl, preprocess)#我们将在每个周期结束时计算并打印验证损失。
#损失函数及准确率
import torch.nn.functional as F
from torch import optimloss_func = F.cross_entropy#批量损失
def loss_batch(model, loss_func, xb, yb, opt=None):loss = loss_func(model(xb), yb)if opt is not None:loss.backward()opt.step()opt.zero_grad()return loss.item(), len(xb)def accuracy(out, yb):preds = torch.argmax(out, dim=1)return (preds == yb).float().mean()from torch import nnclass Mnist_CNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1)self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1)def forward(self, xb):xb = xb.view(-1, 1, 28, 28)xb = F.relu(self.conv1(xb))xb = F.relu(self.conv2(xb))xb = F.relu(self.conv3(xb))xb = F.avg_pool2d(xb, 4)return xb.view(-1, xb.size(1))lr = 0.1model = Mnist_CNN()
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)import numpy as np#GPU设置部分
print(torch.cuda.is_available())
dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(dev)def fit(epochs, model, loss_func, opt, train_dl, valid_dl):for epoch in range(epochs):model.train()for xb, yb in train_dl:loss_batch(model, loss_func, xb, yb, opt)model.eval()with torch.no_grad():losses, nums = zip(*[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl])val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)print(epoch, val_loss)#最终执行
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)fit(epochs, model, loss_func, opt, train_dl, valid_dl)

5,扩展:自定义层与nn.Sequentia

class Lambda(nn.Module):def __init__(self, func):super().__init__()self.func = funcdef forward(self, x):return self.func(x)def preprocess(x):return x.view(-1, 1, 28, 28)model = nn.Sequential(Lambda(preprocess),nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.AvgPool2d(4),Lambda(lambda x: x.view(x.size(0), -1)),
)opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)fit(epochs, model, loss_func, opt, train_dl, valid_dl)

6,迁移学习

两个主要的迁移学习方案:

  • 卷积网络的微调:代替随机初始化,我们使用经过预训练的网络初始化网络,例如在 imagenet 1000 数据集上进行训练的网络。 其余的训练照常进行。
  • 作为固定特征提取器的 ConvNet:在这里,我们将冻结除最终全连接层之外的所有网络的权重。 最后一个全连接层将替换为具有随机权重的新层,并且仅训练该层。

 完整代码如下,复制即用:

# License: BSD
# Author: Sasank Chilamkurthyfrom __future__ import print_function, divisionimport torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copyplt.ion()   # interactive mode
'''
加载数据
我们将使用torchvision和torch.utils.data包来加载数据。我们今天要解决的问题是训练一个模型来对蚂蚁和蜜蜂进行分类。 我们为蚂蚁和蜜蜂提供了大约 120 张训练图像。 每个类别有 75 个验证图像。 通常,如果从头开始训练的话,这是一个非常小的数据集。 由于我们正在使用迁移学习,因此我们应该能够很好地概括。该数据集是 imagenet 的很小一部分。注意从的下载数据,并将其提取到当前目录。'''
# Data augmentation and normalization for training
# Just normalization for validation
data_transforms = {'train': transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),'val': transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),
}data_dir = 'data/hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),data_transforms[x])for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,shuffle=True, num_workers=4)for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classesdevice = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")'''
可视化一些图像
让我们可视化一些训练图像,以了解数据扩充。
'''
def imshow(inp, title=None):"""Imshow for Tensor."""inp = inp.numpy().transpose((1, 2, 0))mean = np.array([0.485, 0.456, 0.406])std = np.array([0.229, 0.224, 0.225])inp = std * inp + meaninp = np.clip(inp, 0, 1)plt.imshow(inp)if title is not None:plt.title(title)plt.pause(0.001)  # pause a bit so that plots are updated# Get a batch of training data
inputs, classes = next(iter(dataloaders['train']))# Make a grid from batch
out = torchvision.utils.make_grid(inputs)imshow(out, title=[class_names[x] for x in classes])'''
训练模型
现在,让我们编写一个通用函数来训练模型。 在这里,我们将说明:安排学习率
保存最佳模型
以下,参数scheduler是来自torch.optim.lr_scheduler的 LR 调度器对象。'''
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):since = time.time()best_model_wts = copy.deepcopy(model.state_dict())best_acc = 0.0for epoch in range(num_epochs):print('Epoch {}/{}'.format(epoch, num_epochs - 1))print('-' * 10)# Each epoch has a training and validation phasefor phase in ['train', 'val']:if phase == 'train':model.train()  # Set model to training modeelse:model.eval()   # Set model to evaluate moderunning_loss = 0.0running_corrects = 0# Iterate over data.for inputs, labels in dataloaders[phase]:inputs = inputs.to(device)labels = labels.to(device)# zero the parameter gradientsoptimizer.zero_grad()# forward# track history if only in trainwith torch.set_grad_enabled(phase == 'train'):outputs = model(inputs)_, preds = torch.max(outputs, 1)loss = criterion(outputs, labels)# backward + optimize only if in training phaseif phase == 'train':loss.backward()optimizer.step()# statisticsrunning_loss += loss.item() * inputs.size(0)running_corrects += torch.sum(preds == labels.data)if phase == 'train':scheduler.step()epoch_loss = running_loss / dataset_sizes[phase]epoch_acc = running_corrects.double() / dataset_sizes[phase]print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))# deep copy the modelif phase == 'val' and epoch_acc > best_acc:best_acc = epoch_accbest_model_wts = copy.deepcopy(model.state_dict())print()time_elapsed = time.time() - sinceprint('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))print('Best val Acc: {:4f}'.format(best_acc))# load best model weightsmodel.load_state_dict(best_model_wts)return model'''
可视化模型预测
通用函数,显示一些图像的预测
'''def visualize_model(model, num_images=6):was_training = model.trainingmodel.eval()images_so_far = 0fig = plt.figure()with torch.no_grad():for i, (inputs, labels) in enumerate(dataloaders['val']):inputs = inputs.to(device)labels = labels.to(device)outputs = model(inputs)_, preds = torch.max(outputs, 1)for j in range(inputs.size()[0]):images_so_far += 1ax = plt.subplot(num_img//2, 2, images_so_far)ax.axis('off')ax.set_title('predicted: {}'.format(class_names[preds[j]]))imshow(inputs.cpu().data[j])if images_so_far == num_images:model.train(mode=was_training)returnmodel.train(mode=was_training)
'''
微调 ConvNet
加载预训练的模型并重置最终的全连接层。
训练和评估'''
model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
# Here the size of each output sample is set to 2.
# Alternatively, it can be generalized to nn.Linear(num_ftrs, len(class_names)).
model_ft.fc = nn.Linear(num_ftrs, 2)model_ft = model_ft.to(device)criterion = nn.CrossEntropyLoss()# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,num_epochs=25)

本文标签: Pytorch学习图像分类从逻辑回归到CNN