图像基础知识

图像概念

  • 计算机中图像分类表示
    • 二值图像 1通道(1个二维矩阵) 像素值:0或1
    • 灰度图像 1通道 像素值:0-255
    • 索引图像 1通道 索引值->RGB二维矩阵行下标 彩色图像 像素值:0-255
    • RGB真彩色图像(最常用) 3通道(3个二维矩阵) R G B三个二维矩阵 像素值:0-255

图像加载

import numpy as np
import matplotlib.pyplot as plt
import torch


# 创建全黑和全白图片
def dm01():
	# 全黑图片
	# 创建3通道二维矩阵, 黑色 0像素点
	# H W C: 200, 200, 3
	# 高 宽 通道
	img1 = np.zeros(shape=(200, 200, 3))
	print('img1->', img1)
	print('img1.shape->', img1.shape)
	# 展示图像
	plt.imshow(img1)
	plt.show()

	# 全白图片
    # 放到全连接层就是 200*200*3=120000个特征值的一维向量
	img2 = torch.full(size=(200, 200, 3), fill_value=255)
	print('img2->', img2)
	print('img2.shape->', img2.shape)
	# 展示图像
	plt.imshow(img2)
	plt.show()


def dm02():
	# 加载图片
	img1 = plt.imread(fname='data/img.jpg')
	print('img1->', img1)
	print('img1.shape->', img1.shape)
	# 保存图像
	plt.imsave(fname='data/img1.png', arr=img1)
	# 展示图像
	plt.imshow(img1)
	plt.show()


if __name__ == '__main__':
	# dm01()
	dm02()

卷积神经网络(CNN)介绍

什么是CNN

  • 包含卷积层,池化层以及全连接层的神经网络计算模型
  • 组成
    • 卷积层: 提取图像特征图
    • 池化层: 降维, 减少特征图的特征值, 减少模型参数
    • 全连接层: 进行预测, 只能接受二维数据集, 1个样本就是1维向量
      • 将池化层的特征图(1张图像)转换成一维 200*200*3->120000个特征值

卷积层

作用: 提取特征图

卷积计算

卷积计算等同于线性层加权求和计算

  • 通过带有权重的卷积核和图像的特征值进行点乘运算, 得到新特征图上的一个特征值

  • 卷积核/滤波器 -> 带有权重参数的神经元

  • w1x1 + w2x2 + … + b

    • w1->卷积核一个权重参数
    • x1->特征图的一个特征值(像素点)

在这里插入图片描述

Padding(填充)

  • 在原图像特征图周围补充特征值(默认补0)
  • 作用
    • 使新特征图和原特征图形状保持一致
    • 减少边缘特征值信息丢失问题
      • 未padding, 边缘特征值只参与一次计算, 经过padding后, 边缘特征值参与多次计算,从而解决卷积操作时边缘信息丢失问题
  • 实现方式
    • 不进行padding处理: 新特征图比原图像特征图小
    • same padding: 原图像特征图形状和新特征图形状一致
    • full padding: 新特征图形状比原图像特征图大, 新增特征

Stride(步长)

  • stride指卷积核(神经元)在特征图上滑动的步伐 默认1
  • 作用:
    • 减少计算量
    • 减少特征, 新特征图特征值减少(降维)
  • 一般默认1, 可以设置2或4
  • 原图像特征图5*5, stride=1->新特征图3*3, stride=2->新特征图2*2

多通道卷积计算

在这里插入图片描述

  • RGB彩色图像是由3个通道组成 -> 3个二维矩阵, 每个矩阵分别代表R/G/B
  • 卷积核通道数和原图像通道数一致
  • 卷积计算 -> 对应通道二维矩阵进行卷积计算, 将每个通道卷积计算的结果加到一起, 得到新特征图的一个特征值
  • 新特征图是1个二维矩阵, 不是3个二维矩阵

多卷积核卷积计算

在这里插入图片描述

  • 有多少个卷积核就是有多少个神经元, 就会提取到多少个二维的特征图

特征图大小计算

  • N = (W-F+2P)/S + 1
  • N: 新特征图高或宽
  • W: 原特征图高或宽
  • F: 卷积核高或宽
  • P: padding值
  • S: stride值
  • N如果为小数, 向下取整, 内部封装floor函数

卷积层API使用

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

"""
in_channels:原图像的通道数,RGB彩色图像是3
out_channels:卷积核/神经元个数 输出的新图像是由n个通道的二维矩阵组成
kernel_size:卷积核形状 (3,3) (3,5)
stride:步长 默认为1
padding:填充圈数 默认为0  1  same->stride=1  2,3...
nn.Conv2d(in_channels=,out_channels=,kernel_size=,stride=,padding=)
"""


def dm01():
	# todo:1-加载RGB彩色图像 (H,W,C)
	img = plt.imread(fname='data/img.jpg')
	print('img->', img)
	print('img.shape->', img.shape)
	# todo:2-将图像的形状(H,W,C)转换成(C,H,W)  permute()方法
	img2 = torch.tensor(data=img, dtype=torch.float32).permute(dims=(2, 0, 1))
	print('img2->', img2)
	print('img2.shape->', img2.shape)
	# todo:3-将这张图像保存到数据集中 (batch_size,C,H,W)  unsqueeze()方法
	# 数据集只有一个样本
	img3 = img2.unsqueeze(dim=0)
	print('img3->', img3)
	print('img3.shape->', img3.shape)
	# todo:4-创建卷积层对象, 提取特征图
	conv = nn.Conv2d(in_channels=3,
					 out_channels=4,
					 kernel_size=(3, 3),
					 stride=2,
					 padding=0)
	conv_img = conv(img3)
	print('conv_img->', conv_img)
	print('conv_img.shape->', conv_img.shape)



	img4 = conv_img[0] #取出第一个batch
	# 转换形状 (H,W,C)
	img5 = img4.permute(1, 2, 0)
	print('img5->', img5)
	print('img5.shape->', img5.shape)
	# img5->(H,W,C)
	# img5[:, :, 0]->第1个通道的二维矩阵特征图 第一个特征图
	feature1 = img5[:, :, 0].detach().numpy()
	plt.imshow(feature1)
	plt.show()


if __name__ == '__main__':
	dm01()

池化层

池化层没有神经元参与, 只是实现降维, 不进行特征提取

池化计算

  • 卷积层提取到的特征图进行降维操作
  • 最大池化 -> 二维矩阵中最大的特征作为输出特征
  • 平均池化 -> 二维矩阵中平均特征值作为输出特征

多通道池化计算

  • 池化只在高和宽维度计算, 通道维度不参与池化
  • 卷积层提取到的特征图像有多少通道, 经过池化后还是多少通道

池化层API使用

池化层的输出维度计算公式与卷积层类似

import torch
import torch.nn as nn
"""
最大池化
kernel_size:窗口形状大小, 不是神经元形状大小, 池化层没有神经元参与
nn.MaxPool2d(kernel_size=, stride=, padding=)
平均池化
nn.AVGPool2d(kernel_size=, stride=, padding=)
"""

# 单通道卷积层特征图池化
def dm01():
	# 创建1通道的3*3二维矩阵, 一张特征图
	inputs = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]]], dtype=torch.float)
	print('inputs->', inputs)
	print('inputs.shape->', inputs.shape) # inputs.shape-> torch.Size([1, 3, 3])
	# 创建池化层
	# kernel_size: 窗口的形状大小
	pool1 = nn.MaxPool2d(kernel_size=(2, 2), stride=1, padding=0)
	outputs = pool1(inputs)
	print('outputs->', outputs)
	print('outputs.shape->', outputs.shape) #outputs.shape-> torch.Size([1, 2, 2])
	pool2 = nn.AvgPool2d(kernel_size=(2, 2), stride=1, padding=0)
	outputs = pool2(inputs)
	print('outputs->', outputs)
	print('outputs.shape->', outputs.shape)


# 多通道卷积层特征图池化
def dm02():
	# size(3,3,3)
	inputs = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
						   [[10, 20, 30], [40, 50, 60], [70, 80, 90]],
						   [[11, 22, 33], [44, 55, 66], [77, 88, 99]]], dtype=torch.float)
    print('inputs->', inputs)
    print('inputs.shape->', inputs.shape) # inputs.shape-> torch.Size([3, 3, 3])
	# 创建池化层
	# kernel_size: 窗口的形状大小
	pool1 = nn.MaxPool2d(kernel_size=(2, 2), stride=1, padding=0) #
	outputs = pool1(inputs)
	print('outputs->', outputs)
	print('outputs.shape->', outputs.shape) # outputs.shape-> torch.Size([3, 2, 2])
	pool2 = nn.AvgPool2d(kernel_size=(2, 2), stride=1, padding=0)
	outputs = pool2(inputs)
	print('outputs->', outputs)
	print('outputs.shape->', outputs.shape)


if __name__ == '__main__':
	# dm01()
	dm02()

图像分类案例

CIFAR10数据集介绍

  • cifar数据是torchvision第三方包提供的数据集

  • 训练集5w 测试集1w

  • y标签 10个类别 10分类问题

  • 一张图形状 (32, 32, 3)

    import torch
    import torch.nn as nn
    from torchvision.datasets import CIFAR10
    from torchvision.transforms import ToTensor
    import torch.optim as optim
    from torch.utils.data import DataLoader
    import time
    import matplotlib.pyplot as plt
    from torchsummary import summary
    
    # 每批次样本数
    BATCH_SIZE = 8
    
    
    # todo: 1-加载数据集转换成张量数据集
    def create_dataset():
    	# root: 文件夹所在目录路径
    	# train: 是否加载训练集
    	# ToTensor(): 将图片数据转换成张量数据
    	train_dataset = CIFAR10(root='./data', train=True, transform=ToTensor())
    	valid_dataset = CIFAR10(root='./data', train=False, transform=ToTensor())
    	return train_dataset, valid_dataset
    
    
    # todo: 2-构建卷积神经网络分类模型
    # todo: 3-模型训练
    # todo: 4-模型评估
    if __name__ == '__main__':
    	train_dataset, valid_dataset = create_dataset()
    	print('图片类别对应关系->', train_dataset.class_to_idx)
    	print('train_dataset->', train_dataset.data[0])
    	print('train_dataset.data.shape->', train_dataset.data.shape)
    	print('valid_dataset.data.shape->', valid_dataset.data.shape)
    	print('train_dataset.targets->', train_dataset.targets[0])
    	# 图像展示
    	plt.figure(figsize=(2, 2))
    	plt.imshow(train_dataset.data[1])
    	plt.title(train_dataset.targets[1])
    	plt.show()
    

构建分类神经网络模型

# todo: 2-构建卷积神经网络分类模型
class ImageModel(nn.Module):
	# todo:2-1 构建init构造函数, 实现搭建神经网络
	def __init__(self):
		super().__init__()
		# 第1层卷积层
		# 输入通道3 一张RGB图像就是3通道
		# 输出通道6 6个神经元提取出6张特征图
		# 卷积核大小3
		self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3)
		# 第1层池化层
		# 窗口大小2*2
		# 步长2
		self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
		# 第2层卷积层
		self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=3)
		# 第2层池化层
		# 池化层输出的特征图 16*6*6
		self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
		# 第1层隐藏层
		# in_features: 将最后池化层的16*6*6三维矩阵转换为一维矩阵
		# 一维矩阵就是池化层这图像
		self.linear1 = nn.Linear(in_features=16 * 6 * 6, out_features=120)
		# 第2层隐藏层
		self.linear2 = nn.Linear(in_features=120, out_features=84)
		# 输出层
		# out_features: 10, 10分类问题
		self.out = nn.Linear(in_features=84, out_features=10)

	# todo:2-2 构建forward函数, 实现前向传播
	def forward(self, x):
		# 第1层 卷积+激活+池化 计算
		x = self.pool1(torch.relu(self.conv1(x)))
		# 第2层 卷积+激活+池化 计算
		# x->(8, 16, 6, 6) 8个样本, 每个样本是16*6*6
		x = self.pool2(torch.relu(self.conv2(x)))
		# 第1层隐藏层  只能接收二维数据集
		# 四维数据集转换成二维数据集
		# x.shape[0]: 每批样本数, 最后一批可能不够8个, 所以不是写死8
		# -1*8=8*16*6*6 -1=16*6*6=576
		x = x.reshape(shape=(x.shape[0], -1))
		x = torch.relu(self.linear1(x))
		# 第2层隐藏层
		x = torch.relu(self.linear2(x))
		# 输出层 没有使用softmax激活函数, 后续多分类交叉熵损失函数会自动进行softmax激活
		x = self.out(x)
		return x
	# todo: 3-模型训练


if __name__ == '__main__':
	train_dataset, valid_dataset = create_dataset()
	# print('图片类别对应关系->', train_dataset.class_to_idx)
	# print('train_dataset->', train_dataset.data[0])
	# print('train_dataset.data.shape->', train_dataset.data.shape)
	# print('valid_dataset.data.shape->', valid_dataset.data.shape)
	# print('train_dataset.targets->', train_dataset.targets[0])
	# # 图像展示
	# plt.figure(figsize=(2, 2))
	# plt.imshow(train_dataset.data[1])
	# plt.title(train_dataset.targets[1])
	# plt.show()
	model = ImageModel()
	summary(model, input_size=(3, 32, 32))

模型训练

# todo: 3-模型训练
def train(train_dataset):
	# 创建数据加载器
	dataloader = DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True)
	# 创建模型对象
	model = ImageModel()
	# model.to(device='cuda')
	# 创建损失函数对象
	criterion = nn.CrossEntropyLoss()
	# 创建优化器对象
	optimizer = optim.Adam(model.parameters(), lr=1e-3)
	# 循环遍历epoch
	# 定义epoch变量
	epoch = 10
	for epoch_idx in range(epoch):
		# 定义总损失变量
		total_loss = 0.0
		# 定义预测正确样本个数变量
		total_correct = 0
		# 定义总样本数据变量
		total_samples = 0
		# 定义开始时间变量
		start = time.time()
		# 循环遍历数据加载器 min-batch
		for x, y in dataloader:
			# print('y->', y)
			# 切换训练模式
			model.train()
			# 模型预测y
			output = model(x)
			# print('output->', output)
			# 计算损失值 平均损失值
			loss = criterion(output, y)
			# print('loss->', loss)
			# 梯度清零
			optimizer.zero_grad()
			# 梯度计算
			loss.backward()
			# 参数更新
			optimizer.step()
			# 统计预测正确的样本个数
			# tensor([9, 9, 9, 9, 9, 9, 9, 9])
			# print(torch.argmax(output, dim=-1))
			# tensor([False, False, False, False, False, False, False, False])
			# print(torch.argmax(output, dim=-1) == y)
			# tensor(0)
			# print((torch.argmax(output, dim=-1) == y).sum())
			total_correct += (torch.argmax(output, dim=-1) == y).sum()
			# 统计当前批次的总损失值
			# loss.item(): 当前批次平均损失值
			total_loss += loss.item() * len(y)
			# 统计当前批次的样本数
			total_samples += len(y)
		end = time.time()
		print('epoch:%2s loss:%.5f acc:%.2f time:%.2fs' % (
			epoch_idx + 1, total_loss / total_samples, total_correct / total_samples, end - start))
	# 保存训练模型
	torch.save(obj=model.state_dict(), f='model/imagemodel.pth')


# todo: 4-模型评估
if __name__ == '__main__':
	train_dataset, valid_dataset = create_dataset()
	# 模型训练
	train(train_dataset)

模型评估

# todo: 4-模型评估
def test(valid_dataset):
	# 创建测试集数据加载器
	dataloader = DataLoader(dataset=valid_dataset, batch_size=BATCH_SIZE, shuffle=False)
	# 创建模型对象, 加载训练模型参数
	model = ImageModel()
	model.load_state_dict(torch.load('model/imagemodel.pth'))
	# 定义统计预测正确样本个数变量 总样本数据变量
	total_correct = 0
	total_samples = 0
	# 遍历数据加载器
	for x, y in dataloader:
		# 切换推理模型
		model.eval()
		# 模型预测
		output = model(x)
		# 将预测分值转成类别
		y_pred = torch.argmax(output, dim=-1)
		print('y_pred->', y_pred)
		# 统计预测正确的样本个数
		total_correct += (y_pred == y).sum()
		# 统计总样本数
		total_samples += len(y)

	# 打印精度
	print('Acc: %.2f' % (total_correct / total_samples))

if __name__ == '__main__':
	train_dataset, valid_dataset = create_dataset()
	# 模型训练
	# train(train_dataset)
	# 模型预测
	test(valid_dataset)

网络性能优化

  • 增加卷积层的卷积核数据量

  • 增加全连接层神经元数量

  • 减小学习率

  • 增加dropout随机失活层

    class ImageClassification(nn.Module):
    	def __init__(self):
    		super(ImageClassification, self).__init__()
    		self.conv1 = nn.Conv2d(3, 32, stride=1, kernel_size=3)
    		self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
    		self.conv2 = nn.Conv2d(32, 128, stride=1, kernel_size=3)
    		self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
    
    		self.linear1 = nn.Linear(128 * 6 * 6, 2048)
    		self.linear2 = nn.Linear(2048, 2048)
    		self.out = nn.Linear(2048, 10)
    		# Dropout层,p表示神经元被丢弃的概率
    		self.dropout = nn.Dropout(p=0.5)
    
    	def forward(self, x):
    		x = torch.relu(self.conv1(x))
    		x = self.pool1(x)
    		x = torch.relu(self.conv2(x))
    		x = self.pool2(x)
    		# 由于最后一个批次可能不够 32,所以需要根据批次数量来 flatten
    		x = x.reshape(x.size(0), -1)
    		x = torch.relu(self.linear1(x))
    		# dropout正则化
    		# 训练集准确率远远高于测试准确率,模型产生了过拟合
    		x = self.dropout(x)
    		x = torch.relu(self.linear2(x))
    		x = self.dropout(x)
    		return self.out(x)
    
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐