在之前的学习中,我们都是使用下载好的手写数字数据来学习,那些数据都是处理好的,都已经转化为tensor数据了,那我们自己的数据该怎么处理才能导入训练呢?
在此之前,我们先观察一下我们下载的手写数字的数据,
MNIST
数据类型与结构
training_data
和 test_data
的类型
- 核心类型:
torchvision.datasets.MNIST
对象- 继承自
torch.utils.data.Dataset
,是PyTorch的标准数据集类。 - 支持索引访问(如
dataset[i]
)和长度查询(len(dataset)
)。
- 继承自
数据内容
元素 | 数据类型 | 格式说明 |
---|---|---|
图像数据 | torch.Tensor | 形状为 [1, 28, 28] ,即单通道(灰度)、28×28像素。 |
数据范围 | 像素值归一化到 [0, 1] | 由 transform=ToTensor() 自动完成(原始像素值0~255缩放到0~1)。 |
关键转换:
ToTensor()
将PIL图像转为张量,并自动调整维度顺序为(C, H, W)
(通道×高×宽)
转化方向
根据这个数据类型,我们有了一个方向
1 把我们的图片加上标签。
2 把我们的图片和标签都转化为tensor型数据,并且图像的尺寸都一样大小。
3 支持索引访问(如 dataset[i]
)和长度查询(len(dataset)
)。
具体实现
下面我们来一步一步的实现。
1 图片加上标签
先观察我们现有的数据
思路
把每一个图片的地址和对应标签写入一个txt中,其中标签就取每一类在文件中排列的顺序。方便后期直接调用。
代码
def transfoem_tp(path1,path2):
data=open(path1,'w',encoding='utf-8')
import os
for root,files,file in os.walk(path2):
if file==[]:
dic=files
print(dic)
else:
for f in file:
path=os.path.join(root,f)
lei=root.split('\\')[-1]
c=dic.index(lei)
data.write(path+' '+str(dic.index(lei))+'\n')
transfoem_tp('train_data',r'food_dataset\train')
transfoem_tp('test_data',r'food_dataset\test')
解释
我们创建了一个txt文件,如何开始读取文件,这里用到了os.walk(),这个的作用就是在文件夹中遍历,先遍历最上层,如何得到这一层的文件夹名,这一层的下一次所含的文件夹名,这一层的下一次所含的文件名。
这样我们就可以直接传入我们train数据,然后判断当前是否存在图片,如果不存在,这肯定就是我们的全部类的文件夹,这个时候就可以把所有文件夹名字保存下来,用于后面添加标签。
下面就开始遍历每一个文件夹了,保存其中每一个文件地址,文件地址就是当前文件名+root地址。然后标签就是当前类在dir中存在的index。
结果
2 转化数据
创建转化词典
我们要把数据转化为同尺寸的图片,因为如果不同图片在全连接层会出现尺寸不对应然后就会报错,转化为tensor是为了为了传入到cuda中计算。
data_transfoem={
'train': transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor()
]),
'test': transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor()
])
}
这里把图片全部统一为255*255的图片,然后也全部转化为了tensor数据。
创建加载食物图像数据集的类
在这个类中我们要完成,tensor数据类型的转化,索引方法。
这里继承了Dataset这个类。如何初始化__init__()方法。如何定义了几个属性,image用于保存图片,labels用于保存标签。transform就是前面创建的转化词典。这里并没有转化,只是干了一些准备工作,定义了转化规则。
class food_dataset(Dataset):
def __init__(self, root, transform=None):
super().__init__()
self.root = root
self.transform = transform
self.images = []
self.labels = []
with open(root,encoding='utf-8') as f:
samples = [i.strip().split() for i in f.readlines()]
for img_path,label in samples:
self.images.append(img_path)
self.labels.append(label)
结果就是self.images保存了所有文件的地址,self.label保存了所有标签。
转化数据和定义索引
注意这里的__getitem__方法这个就是定义索引的。然后再利用PIL中的Image.opne方法来读取图片(这个就像使用opencv库中的cv2.imread方法差不多的)。然后在按照转化字典转化为我们要的255*255的大小和tensor型数据。
然后再处理label,由于tensor数据要先转化为numpy型数据再转化为tensor型数据,所以要先转化为了numpy数据,这里转化为了np.int64是因为类只能是整型的,然后在返回的时候进行转化为tensor型数据。
def __getitem__(self, index):
image=Image.open(self.images[index]).convert('RGB')
if transform:
image=self.transform(image)
label = self.labels[index]
label = torch.from_numpy(np.array(label,dtype=np.int64))
return image, torch.tensor(label, dtype=torch.long)
然后还有一个定义可以进行长度查询的方法
def __len__(self):
return len(self.images)
好了上面就是对于定义这个类的全部。下面我们开始使用。
示例化
我们先对前面定义的类进行实例化,传入数据和转化词典。
train_data=food_dataset('train_data',transform=(data_transfoem['train']))
test_data=food_dataset('test_data',transform=(data_transfoem['test']))
下面就是进行正常的数据加载工作了, 第一次出现的shuffle=True,是设置批次抽取的数据是否是乱序的。
from torch.utils.data import DataLoader
train_dataloader = DataLoader(train_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
测试一张图片
while 1:
pa=input('请输入图片地址:')
# pa='hlg.jpeg'
image = Image.open(pa).convert('RGB')
input_image = data_transfoem['train'](image).unsqueeze(0) # 添加batch维度
input_image = input_image.to(device)
from torch.utils.data import DataLoader
with torch.no_grad():
output = model(input_image)
# print(output)
probabilities = torch.nn.functional.softmax(output[0], dim=0)
# print(probabilities)
confidence, predicted_idx = torch.max(probabilities, 0)
# print(predicted_idx.item())
predicted_class = dic[predicted_idx.item()]
# confidence = confidence.item()
print('识别到的',predicted_class)
# img_火龙果_180.jpeg
image.show()
完整代码
下面是完整代码,只包含数据处理部分,具体神经网络搭建和训练可是直接使用前面几篇文章讲述的模型和代码。
import os
import numpy as np
from PIL import Image
from sklearn.datasets import images
from torch.optim.lr_scheduler import ReduceLROnPlateau
def transfoem_tp(path1,path2):
data=open(path1,'w',encoding='utf-8')
import os
for root,files,file in os.walk(path2):
if file==[]:
dic=files
# print(dic)
else:
for f in file:
path=os.path.join(root,f)
lei=root.split('\\')[-1]
c=dic.index(lei)
data.write(path+' '+str(dic.index(lei))+'\n')
return dic
dic=transfoem_tp('train_data',r'food_dataset\train')
transfoem_tp('test_data',r'food_dataset\test')
from torchvision import transforms
# data_transform={
# 'train': transforms.Compose([
# transforms.Resize((256, 256)),
# transforms.ToTensor(),
# transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
# ]),
# 'test': transforms.Compose([
# transforms.Resize((256, 256)),
# transforms.ToTensor(),
# transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
# ])
# }
data_transform={'train': transforms.Compose([
# 调整图像大小为300x300像素
transforms.Resize([300, 300]),
# 随机旋转:-45到45度之间随机选择角度
transforms.RandomRotation(45),
#
# # 从中心裁剪出256x256的区域
transforms.CenterCrop([256, 256]),
# 随机水平翻转:以50%的概率进行水平镜像
transforms.RandomHorizontalFlip(p=0.5),
# 随机垂直翻转:以50%的概率进行垂直镜像
transforms.RandomVerticalFlip(p=0.5),
# # 颜色抖动:随机调整亮度、对比度、饱和度和色调
transforms.ColorJitter(
brightness=0.2, # 亮度变化幅度为20%
contrast=0.1, # 对比度变化幅度为10%
saturation=0.1, # 饱和度变化幅度为10%
hue=0.1 # 色调变化幅度为10%
),
#
# # 随机灰度化:以10%的概率将图像转换为灰度图
transforms.RandomGrayscale(p=0.1),
# 将PIL图像转换为PyTorch张量,并自动归一化到[0,1]范围
transforms.ToTensor(),
# 标准化:使用ImageNet数据集的均值和标准差进行标准化
transforms.Normalize(
[0.485, 0.456, 0.406], # 均值(R, G, B通道)
[0.229, 0.224, 0.225] # 标准差(R, G, B通道)
)
]),
# 验证/测试数据的预处理(通常不需要数据增强)
'test': transforms.Compose([
transforms.Resize([256, 256]),
# transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
}
import torch
print(torch.__version__)
import torch
from torch import nn
from torch.utils.data import DataLoader, Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
from PIL import Image
import torch
from torch.utils.data import Dataset
from PIL import Image
import numpy as np
class food_dataset(Dataset):
def __init__(self, root, transform=None):
super().__init__()
self.root = root
self.transform = transform
self.images = []
self.labels = []
with open(root,encoding='utf-8') as f:
samples = [i.strip().split() for i in f.readlines()]
for img_path,label in samples:
self.images.append(img_path)
self.labels.append(label)
def __len__(self):
return len(self.images)
def __getitem__(self, index):
image=Image.open(self.images[index]).convert('RGB')
if self.transform:
image=self.transform(image)
label = self.labels[index]
# print(label)
label = torch.from_numpy(np.array(label,dtype=np.int64))
# print(label)
return image, label
from matplotlib import pyplot as plt, figure
# figure = plt.figure()
# for i in range(9):
# image,label = training_data[i+59000]
#
# figure.add_subplot(3,3,i+1)
# plt.title(label)
# plt.axis('off')
# plt.imshow(image.squeeze(),cmap='gray')
# a=image.squeeze()
# plt.show()
train_data=food_dataset('train_data',transform=(data_transform['train']))
test_data=food_dataset('test_data',transform=(data_transform['test']))
from torch.utils.data import DataLoader
train_dataloader = DataLoader(train_data, batch_size=16, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=16, shuffle=True)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# device='cpu'
print(device)
# for X, y in test_dataloader:
# print(f"Shape of X [N, C, H, W]: {X.shape}") # 批次大小、通道、高度、宽度
# print(f"Shape of y: {y.shape}, Type of y: {y.dtype}")
# break
class Net(nn.Module):
def __init__(self, num_classes=20):
super().__init__()
# 输入层适配: 3通道RGB图像 (原代码为1通道)
self.conv1 = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2)
)
# 加深的第二卷积块
self.conv2 = nn.Sequential(
nn.Conv2d(32, 64, 5, 1, 2),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 64, 5, 1, 2),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2)
)
# 新增的第三卷积块
self.conv3 = nn.Sequential(
nn.Conv2d(64, 128, 5, 1, 2),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.Conv2d(128, 128, 5, 1, 2),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2)
)
# 新增的第四卷积块
self.conv4 = nn.Sequential(
nn.Conv2d(128, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.Conv2d(256, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2)
)
# 自适应全局平均池化,确保不同尺寸输入都能适配
self.global_pool = nn.AdaptiveAvgPool2d((7, 7))
# 全连接层 - 输出20个类别
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(256 * 7 * 7, 1024),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(1024, 512),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(512, 20)
)
def forward(self, x):
x = self.conv1(x) # 256×256 → 128×128
x = self.conv2(x) # 128×128 → 64×64
x = self.conv3(x) # 64×64 → 32×32
x = self.conv4(x) # 32×32 → 16×16
x = self.global_pool(x) # 16×16 → 7×7 (确保尺寸统一)
x = x.view(x.size(0), -1) # 展平
x = self.fc(x)
return x
model = Net()
model.to(device)
loss_fn = nn.CrossEntropyLoss()
# optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
scheduler = ReduceLROnPlateau(optimizer,
mode='min', # 监控验证损失,越小越好
factor=0.1, # 每次降低10倍
patience=5, # 给5个epoch的机会
verbose=True, # 打印更新信息
min_lr=1e-6) # 最低学习率
best_acc=0
accuracy_list=[]
def train(train_dataloader,model,loss_fn,optimizer):
model.train()
# batch_size_num=1
# correct=0
size = len(train_dataloader.dataset)
for X, y in train_dataloader:
X,y=X.to(device),y.to(device)
output = model(X) #model和model.forward一样效果
loss = loss_fn(output,y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
a=loss.item()
# if batch_size_num %10 == 0:
# print(f"Batch size: {batch_size_num}, Loss: {a}")
# batch_size_num+=1
# print(model)
return loss
def test(dataloader,model,loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval()
batch_size_num=1
loss,correct=0,0
with torch.no_grad():
for X, y in test_dataloader:
X,y=X.to(device),y.to(device)
pred = model(X)
loss = loss_fn(pred,y)+loss
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
loss/=num_batches
correct/=size
print(f'Test result: \n Accuracy: {(100*correct)}%,Avg loss: {loss}')
return correct
print(dic)
train(train_dataloader,model,loss_fn,optimizer)
test(test_dataloader,model,loss_fn)
epochs=20
for i in range(epochs):
print(f"Epoch {i+1}")
loss=train(train_dataloader,model,loss_fn,optimizer)
correct = test(test_dataloader,model,loss_fn)
accuracy_list.append(correct)
if correct>best_acc:
print(f"Best Accuracy: {correct}%")
best_acc=correct
#第一种
# torch.save(model.state_dict(),'best_model.pth')
#第二种
torch.save(model,'best1.pth')
scheduler.step(loss)
test(train_dataloader,model,loss_fn)
import matplotlib.pyplot as plt
# 假设这是你的准确率数据,请替换为你的实际数据
# accuracy_list 应为一个列表,包含每个epoch后的准确率值
epochs = list(range(1, len(accuracy_list) + 1)) # 横坐标,从1开始
# 创建图表
plt.figure(figsize=(10, 6)) # 设置图表大小
# 绘制准确率曲线
plt.plot(epochs, accuracy_list, marker='o', linestyle='-', linewidth=2, markersize=6, label='Training Accuracy')
# 设置图表标题和坐标轴标签
plt.title('Model Accuracy over Epochs', fontsize=15)
plt.xlabel('Epoch', fontsize=12)
plt.ylabel('Accuracy', fontsize=12)
# 设置坐标轴范围,可以根据你的数据调整
plt.xlim(1, len(accuracy_list))
plt.ylim(min(accuracy_list) - 0.05, max(accuracy_list) + 0.05) # 留一些边距
# 添加网格线以便于读取数值
plt.grid(True, alpha=0.3)
# 显示图例
plt.legend()
# 显示图表
plt.tight_layout()
plt.show()
# while 1:
# pa=input('请输入图片地址:')
# if pa=='q':
# break
# # pa='hlg.jpeg'
# image = Image.open(pa).convert('RGB')
# input_image = data_transform['train'](image).unsqueeze(0) # 添加batch维度
# input_image = input_image.to(device)
# from torch.utils.data import DataLoader
#
# pa=input_image.to(device)
# # test_pa=model(x=pa)
# # print(dic[test_pa])
#
# with torch.no_grad():
# output = model(input_image)
# # print(output)
# probabilities = torch.nn.functional.softmax(output[0], dim=0)
# # print(probabilities)
# confidence, predicted_idx = torch.max(probabilities, 0)
# # print(predicted_idx.item())
# predicted_class = dic[predicted_idx.item()]
# # confidence = confidence.item()
# print('识别到的',predicted_class)
# # img_火龙果_180.jpeg
# image.show()
'''
如何实现一个神经网络
1 对数据进行处理 要定义一个类,目的是为了后面可以对我们的数据进行索引读取
如何就要先进行实例化咱们的类,如何在加载数据就可以了。
data=Data_set(features,labels)
dataloader=DataLoader(data,batch_size=4,shuffle=True)
(这里只是我们训练集进行了处理,如果还有测试集,还要进行相同处理)
2 定义我们的网络 包含网络层本身,还有前向传播
基本上就是几个卷积层如何再加上一个全连接层用于输出
3 进行训练train函数和test函数
其中train函数流程
先说明模式 model.train()还是model.eval(),第一个的权重w是可读可写的,第二个的权重只可读,不可改
batch_size这里只是为了后面100次打印一次,其实没有也是可以的
for循环遍历数据,使用 for X,y in dataloader(这个数据就是我们上面对dataloader进行实例化的数据):
a=model(X)计算得到预测值
使用loss_fu(a,y)计算出损失值
optimizer.zero_grad()进行梯度初始化
loss.backward()反向传播,进行参数改变
optimizer.step()
4 图像增强
5 保存参数模型
6 动态梯度
'''