文章目录
读取数据,构建数据
项目1 创建自己的dataset类
transform 对图片进行处理
碎片
- 对图片进行数据增强处理
self.transform = transforms.Compose(
[
transforms.Resize(size = (224,224)),#尺寸规范 #将图片设置为224*224
transforms.RandomResizedCrop(200), #随机 随机长宽比裁剪
transforms.ToTensor(), #转化为tensor #那transform.Normalize()是怎么工作的呢?以上面代码为例,ToTensor()能够把灰度范围从0-255变换到0-1之间,
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),#而后面的transform.Normalize()则把0-1变换到(-1,1).具体地说,对每个通道而言,Normalize执行以下操作:image=(image-mean)/std 其中mean和std分别通过(0.5,0.5,0.5)和(0.5,0.5,0.5)进行指定。原来的0-1最小值0则变成(0-0.5)/0.5=-1,而最大值1则变成(1-0.5)/0.5=1.
])
对参数进行解释
函数 | 解释 |
---|---|
Resize | 改变大小 |
RandomResizedCrop | 随便改变比例,并随机选取中心,设定大小为200v200 |
ToTensor | 图像转tensor |
Normalize | 对图像标准化 |
修改__getitem__
- 将测试集(不带标签的数据图像)label设定为文件名(名字必须是数字)
这里对__getitem__
进行重写
def __getitem__(self, idx: int):
# img to tensor and label to tensor
print(idx)
img_path = self.path_list[idx]# __getitem__给定 self和一个key
print(img_path)
if self.train_flag is True:
if img_path.split('.')[0] == 'dog' :
label = 1
else:
label = 0
else:
label = int(img_path.split('.')[0]) # split 的是str类型要转换为int
label = torch.as_tensor(label, dtype=torch.int64) # 必须使用long 类型数据,否则后面训练会报错 expect long
img_path = os.path.join(self.data_path, img_path)
img = Image.open(img_path).convert('RGB')
img = self.transform(img)
return img, label
总览
class MyDataset(Dataset):#创建自己的dataset类 ,继承dataset
def __init__(self, data_path:str, train=True, transform=None):#init初始化方案 指定path的类型是str train默认是True transform 默认是无
self.data_path = data_path #设定位置
self.train_flag = train #train的参数
if transform is None:
self.transform = transforms.Compose(
[
transforms.Resize(size = (224,224)),#尺寸规范 #将图片设置为224*224
transforms.RandomResizedCrop(200), #随机 随机长宽比裁剪
transforms.ToTensor(), #转化为tensor #那transform.Normalize()是怎么工作的呢?以上面代码为例,ToTensor()能够把灰度范围从0-255变换到0-1之间,
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),#而后面的transform.Normalize()则把0-1变换到(-1,1).具体地说,对每个通道而言,Normalize执行以下操作:image=(image-mean)/std 其中mean和std分别通过(0.5,0.5,0.5)和(0.5,0.5,0.5)进行指定。原来的0-1最小值0则变成(0-0.5)/0.5=-1,而最大值1则变成(1-0.5)/0.5=1.
])
else:
self.transform = transform
self.path_list = os.listdir(data_path)#给出一个文件夹所有的文件
def __getitem__(self, idx: int):
# img to tensor and label to tensor
print(idx)
img_path = self.path_list[idx]# __getitem__给定 self和一个key
print(img_path)
if self.train_flag is True:
if img_path.split('.')[0] == 'dog' :
label = 1
else:
label = 0
else:
label = int(img_path.split('.')[0]) # split 的是str类型要转换为int
label = torch.as_tensor(label, dtype=torch.int64) # 必须使用long 类型数据,否则后面训练会报错 expect long
img_path = os.path.join(self.data_path, img_path)
img = Image.open(img_path).convert('RGB')
img = self.transform(img)
return img, label
def __len__(self) -> int: #没有这个指定,就没有len(class) 也就是 没有len(MyDataset)
return len(self.path_list)
把测试集与训练集的文件路径输入
train_path =r'E:\machine_learning\mcm\new_net\Math-Modeling-2021-master\data\pic\big_train'
test_path = r'E:\machine_learning\mcm\new_net\Math-Modeling-2021-master\data\pic\test_N'
train_ds =MyDataset(train_path)
test_ds = MyDataset(test_path,train=False)
将数据集输入DataLoader
train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32,
shuffle=True, pin_memory=True, num_workers=0)
test_loader = torch.utils.data.DataLoader(test_ds, batch_size=32,
shuffle=True, pin_memory=True, num_workers=0)
#pin_memory If True, the data loader will copy tensors into CUDA pinned memory before returning them. 也就是一个数据拷贝的问题
## numworkers设置不为0 会报错 Broken pipe Error 网上说是win10上的pytorch bug
pin_memory
拷贝数据
项目2 使用自带的
ImageFolder
如果使用torch自带的数据集整理功能,那就用ImageFolder
transform = transforms.Compose([transforms.RandomResizedCrop(200),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=(.5, .5, .5),
std=(.5, .5, .5))])
trainData = dsets.ImageFolder(train_path, transform=transform)
testData = dsets.ImageFolder(test_path, transform=transform)
防止样本不均衡,使用sample
进行权重分配
使用ImageFolder
的targets
获取一部分数据对应的标签:
class_sample_count
表示每种种类的个数
samples_weight
表示样本的权重
target = np.array(trainData.targets)
class_sample_count = np.array([len(np.where(target == t)[0]) for t in np.unique(target)])
# print(ctest_sample_count)
samples_weight = np.array([(1. / class_sample_count)[t] for t in target])
samples_weight = torch.from_numpy(samples_weight).double()
sampler = WeightedRandomSampler(samples_weight, len(samples_weight))
trainLoader = DataLoader(dataset=trainData, batch_size=BATCH_SIZE, sampler=sampler)
testLoader = DataLoader(dataset=testData, batch_size=BATCH_SIZE, shuffle=False)
创建网络
import torch.nn.functional as F
class MyCNN(nn.Module):
def __init__(self):
super(MyCNN,self).__init__()# 这是对继承自父类的属性进行初始化,而且是用父类的初始化方法来初始化继承的属性.也就是说,子类继承了父类的所有属性和方法,
self.conv1 = nn.Conv2d(3,8,kernel_size=3,stride=1,padding=1) # 按照公式计算后经过卷积层不改变尺寸 #对由多个输入平面组成的输入信号进行二维卷积 3通道 卷积产生8通道 卷积核尺寸为3v3矩阵1步长padding 填充
self.pool = nn.MaxPool2d(2,2) # 2*2的池化 池化后size 减半
self.conv2 = nn.Conv2d(8,16,kernel_size=3,stride=1,padding=1)
self.fc1 = nn.Linear(16*56*56,256)#两个池化,所以是224/2/2=56
self.fc2 = nn.Linear(256,64)
self.fc3 = nn.Linear(64,2)
# self.dp = nn.Dropout(p=0.5)
def forward(self,x):
# print("input:", x)
x = self.pool(F.relu(self.conv1(x)))
# print("first conv:", x)
x = self.pool(F.relu(self.conv2(x)))
# print("second conv:", x)
x = x.view(-1, 16 * 56* 56)#将数据平整为一维的
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
# x = F.log_softmax(x,dim=1) NLLLoss()才需要,交叉熵不需要
return x
性能评估
criterion = nn.CrossEntropyLoss()
# criterion = nn.BCELoss() #二分类交叉熵损失函数
# criterion = nn.BCEWithLogitsLoss() #二分类交叉熵损失函数 带log loss
# criterion = nn.MSELoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
#也可以选择Adam优化方法
# optimizer = torch.optim.Adam(net.parameters(),lr=1e-2)
计算函数
class AvgrageMeter(object):
def __init__(self):
self.reset()
def reset(self):
self.avg = 0
self.sum = 0
self.cnt = 0
def update(self, val, n=1):
self.sum += val * n
self.cnt += n
self.avg = self.sum / self.cnt
准确率函数
topk=(1,)
这个参数设置,是在输入的时候还会收入一个数,然后在maxk=max(topk)
中会比对大小,选最大的假设咱做top2准确率,那就输入个2
output.topk(maxk,1,True,True)
输入参数对应output.topk(k=maxk,dim=1,largest=True,sorted=True)
output.topk
输出是值value
和索引index
value, index= output.topk()
pred.eq()
比对label通过索引pred
,对两个张量Tensor进行逐元素的比较,若相同位置的两个元素相同,则返回True;若不同,返回False
## topk的准确率计算
def accuracy(output, label, topk=(1,)):#topk还可以再输入一个数
maxk = max(topk)
batch_size = label.size(0)
# 获取前K的索引
_, pred = output.topk(maxk, 1, True, True) #使用topk来获得前k个的索引
pred = pred.t() # 进行转置
# eq按照对应元素进行比较 view(1,-1) 自动转换到行为1,的形状, expand_as(pred) 扩展到pred的shape
# expand_as 执行按行复制来扩展,要保证列相等
correct = pred.eq(label.view(1, -1).expand_as(pred)) # 与正确标签序列形成的矩阵相比,生成True/False矩阵
# print(correct)
rtn = []
for k in topk:
correct_k = correct[:k].contiguous().view(-1).float().sum(0) # 前k行的数据 然后平整到1维度,来计算true的总个数
rtn.append(correct_k.mul_(100.0 / batch_size)) # mul_() ternsor 的乘法 正确的数目/总的数目 乘以100 变成百分比
return rtn
训练模型
训练函数
def train( epoch, train_loader, device, model, criterion, optimizer,tensorboard_path):
model = model.to(device)
for e in range(epoch):
model.train()
top1 = AvgrageMeter()
train_loss = 0.0
train_loader = tqdm(train_loader) #转换成tqdm类型 以方便增加日志的输出
train_loader.set_description('[%s%04d/%04d %s%f]' % ('Epoch:', e + 1, epoch, 'lr:', 0.001))
for i, data in enumerate(train_loader, 0): # 0是下标起始位置默认为0
inputs, labels = data[0].to(device), data[1].to(device)
# 初始为0,清除上个batch的梯度信息
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
# topk 准确率计算
prec1, prec2 = accuracy(outputs, labels, topk=(1, 2))
n = inputs.size(0)
top1.update(prec1.item(), n)
train_loss += loss.item()
postfix = {'train_loss': '%.6f' % (train_loss / (i + 1)), 'train_acc': '%.6f' % top1.avg}
train_loader.set_postfix(log=postfix)
# ternsorboard 曲线绘制
writer = SummaryWriter(tensorboard_path)
writer.add_scalar('Train/Loss', loss.item(), epoch)
writer.add_scalar('Train/Accuracy', top1.avg, epoch)
writer.flush()
print('Finished Training')
验证函数
def validate( epoch, validate_loader, device, model, criterion):
val_acc = 0.0
model = model.to(device)
for e in range(epoch):
model.eval()
with torch.no_grad(): # 进行评测的时候网络不更新梯度
val_top1 = AvgrageMeter()
validate_loader = tqdm(validate_loader)
validate_loss = 0.0
for i, data in enumerate(validate_loader, 0): # 0是下标起始位置默认为0
inputs, labels = data[0].to(device), data[1].to(device)
# inputs,labels = data[0],data[1]
outputs = model(inputs)
loss = criterion(outputs, labels)
prec1, prec2 = accuracy(outputs, labels, topk=(1, 2))
n = inputs.size(0)
val_top1.update(prec1.item(), n)
validate_loss += loss.item()
postfix = {'validate_loss': '%.6f' % (validate_loss / (i + 1)), 'validate_acc': '%.6f' % val_top1.avg}
validate_loader.set_postfix(log=postfix)
writer = SummaryWriter(tensorboard_tpath)
writer.add_scalar('Validate/Loss', loss.item(), epoch)
writer.add_scalar('Validate/Accuracy', val_top1.avg, epoch)
writer.flush()
val_acc = val_top1.avg
return val_acc
保存
def submission(csv_path,test_loader, device, model):
result_list = []
model = model.to(device)
test_loader = tqdm(test_loader)
with torch.no_grad(): # 进行评测的时候网络不更新梯度
for i, data in enumerate(test_loader, 0):
images, labels = data[0].to(device), data[1].to(device)
# print(images)
# print(labels)
outputs = model(images)
softmax_func = nn.Softmax(dim=1) # dim=1表示行的和为1
soft_output = softmax_func(outputs)
predicted = soft_output[:, 1]
for i in range(len(predicted)):
result_list.append({
"id": labels[i].item(),
"label": predicted[i].item()
})
# 从list转成 dataframe 然后保存为csv文件
columns = result_list[0].keys()
result_dict = {col: [anno[col] for anno in result_list] for col in columns}
result_df = pd.DataFrame(result_dict)
result_df = result_df.sort_values("id")
result_df.to_csv(csv_path, index=None)
# net = MyCNN()
net =resnet18
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
criterion = nn.CrossEntropyLoss()
# criterion = nn.BCELoss() #二分类交叉熵损失函数
# criterion = nn.BCEWithLogitsLoss() #二分类交叉熵损失函数 带log loss
# criterion = nn.MSELoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
#也可以选择Adam优化方法
# optimizer = torch.optim.Adam(net.parameters(),lr=1e-2)
# train( 1, train_loader, device,net, criterion, optimizer,tensorboard_path) # 完整的训练数据集
train( 30, new_train_loader, device,net, criterion, optimizer,tensorboard_path) # 划分80%后的训练数据集