【pytorch易错点复习笔记】

本文汇总了PyTorch手动设置随机种子、列表切片技巧及Keras模型训练方法,包括fit_generator与fit的区别,旨在帮助开发者高效地进行深度学习实验与模型训练。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章目录


一、torch.manual_seed()用法详解

描述
设置CPU生成随机数的种子,方便下次复现实验结果。

语法

torch.manual_seed(seed) → torch._C.Generator

参数
seed (int) – CPU生成随机数的种子。取值范围为[-0x8000000000000000, 0xffffffffffffffff],十进制是[-9223372036854775808, 18446744073709551615],超出该范围将触发 RuntimeError报错。

返回
返回一个torch.Generator对象。

示例
设置随机种子

# test.py
import torch
torch.manual_seed(0)
print(torch.rand(1)) 
# 返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数

每次运行test.py的输出结果都是一样:

tensor([0.4963])

没有随机种子

# test.py
import torch

print(torch.rand(1)) # 返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数

每次运行test.py的输出结果都不相同:

tensor([0.2079])
----------------------------------
tensor([0.6536])
----------------------------------
tensor([0.2735])

注意
设置随机种子后,是每次运行test.py文件的输出结果都一样,而不是每次随机函数生成的结果一样:

# test.py
import torch
torch.manual_seed(0)
print(torch.rand(1))
print(torch.rand(1))

输出:

tensor([0.4963])
tensor([0.7682])

可以看到两次打印torch.rand(1)函数生成的结果是不一样的,但如果你再运行test.py,还是会打印:

tensor([0.4963])
tensor([0.7682])

但是,如果你就是想要每次运行随机函数生成的结果都一样,那你可以在每个随机函数前都设置一模一样的随机种子:

# test.py
import torch
torch.manual_seed(0)
print(torch.rand(1))
torch.manual_seed(0)
print(torch.rand(1))

输出:

tensor([0.4963])
tensor([0.4963])

二、torch.manual_seed()用法

torch.manual_seed()一般和torch.rand()、torch.randn()等函数搭配使用。通过指定seed值,可以令每次生成的随机数相同 直接上代码理解:

  • 如果不指定seed值,则每次生成的随机数会因时间的差异而有所不同

输入:

import torch
 
print(torch.randn(1, 2))
print(torch.randn(1, 2))

输出:

tensor([[ 0.1604, -0.6065]])
tensor([[-0.7831,  1.0622]])

通过指定seed值,可以保证每次执行 torch.manual_seed()后生成的随机数都相同。需注意,不是执行一次 torch.manual_seed()语句后,所有随机函数的生成结果就都相同了;而是每次执行设有相同seed值的torch.manual_seed()语句后,各随机函数生成的结果都能和上一次执行torch.manual_seed()语句后生成的结果相同。

输入:

import torch
 
torch.manual_seed(0)
print(torch.randn(1, 2))
print(torch.randn(1, 2))
 
torch.manual_seed(0)
print(torch.randn(1, 2))
print(torch.randn(1, 2))

输出:

tensor([[ 1.5410, -0.2934]])
tensor([[-2.1788,  0.5684]])
 
tensor([[ 1.5410, -0.2934]])
tensor([[-2.1788,  0.5684]])
  • 需注意,必须使用相同的生成随机数函数才能保证每次执行 torch.manual_seed()语句后生成相同随机数,否则无效。

输入:

import torch
 
torch.manual_seed(0)
print(torch.rand(1, 2))
 
torch.manual_seed(0)
print(torch.randn(1, 2))
输出:

tensor([[0.4963, 0.7682]])
 
tensor([[ 1.5410, -0.2934]])
  • random.seed()是用于控制random模块的随机数种子的,与random.random()搭配使用

输入:

import random
 
random.seed(1)
print(random.random())
 
random.seed(1)
print(random.random())

输出:

0.13436424411240122
 
0.13436424411240122

最后,总结一下,torch.manual_seed()为CPU设置随机数种子,torch.cuda.manual_seed()为GPU设置随机数种子,torch.cuda.manual_seed_all()为所有的GPU设置随机数种子,random.seed()为random模块的随机数种子。

三、list列表切片方法汇总

python为list列表提供了强大的切片功能,下面是一些常见功能的汇总

"""
使用模式: [start:end:step]
    其中start表示切片开始的位置,默认是0
    end表示切片截止的位置(不包含),默认是列表长度
    step表示切片的步长,默认是1
    当start是0时,可以省略;当end是列表的长度时,可以省略.
    当step是1时,也可以省略,并且省略步长时可以同时省略最后一个冒号.
    此外,当step为负数时,表示反向切片,这时start值应该比end值大.
    注意:切片操作创建了一个新的列表.
"""

alist = [1, 2, 3, 4, 5, 6, 7, 8, 9]

print(alist[::])  # 返回包含原列表所有元素的新列表
print(alist[::-1])  # 返回原列表的一个逆序列表
print(alist[::2])  # [1, 3, 5, 7, 9] .取列表下标偶数位元素
print(alist[1::2])  # [2, 4, 6, 8]  取列表下标奇数位元素
print(alist[3:6])  # [4, 5, 6]  #取列表中下标3到6的值,步长是1
print(alist[3:6:2])  # [4, 6] #取列表中下标3到6的值,步长是2

print(alist[:10])  # [1, 2, 3, 4, 5, 6, 7, 8, 9]  . end大于列表长度时,取列表中所有元素,省略了步长1.
print(alist[10:]) # []  . 表示从列表的第10位开始取,一直取到列表结果,步长是1.

alist[len(alist):] = [10]  # 在列表的未尾添加一个元素
print(alist) #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

alist[:0] = [-1,0]  # 在列表的头部插入元素. alist[:0] 相当于 alist[0:0] 再相当于 alist[0:0:1]
print(alist) #[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

alist[5:5] = [-6]  # 把列表的第6个位置替换成-6. 相当于alist[5:5:1] = [-6]
print(alist) #[-1, 0, 1, 2, 3, -6, 4, 5, 6, 7, 8, 9, 10]

alist = [1, 2, 3, 4, 5, 6, 7, 8, 9]
alist[:3] = ['A','B','C','D']  # 把alist的前3个元素换成['A','B','C','D']
print(alist)  #['A', 'B', 'C', 'D', 4, 5, 6, 7, 8, 9]

alist[3:] = ['E','F','G']  # 把列表第3个元素之后的元素替换成['E','F','G']
print(alist) # ['A', 'B', 'C', 'E', 'F', 'G']

alist = [1, 2, 3, 4, 5, 6]
alist[::2] = [0]*3 # 替换列表下标是偶数的前三个元素,注意使用该方法时,列表下标是偶数的元素个数必须与后面替换的元素个数相等,否则报错
print(alist) #[0, 2, 0, 4, 0, 6]

alist[::2] = ['a','b','c'] # 跟上面一样的道理,只是替换的内容不一样
print(alist) # ['a', 2, 'b', 4, 'c', 6]

alist = [1, 2, 3, 4, 5, 6]
alist[:3] = [] # 删除列表中前3个元素
print(alist) # [4, 5, 6]


alist = [1, 2, 3, 4, 5, 6]
del alist[:3]  # 使用del关键字删除列表前3个元素
print(alist) #[4, 5, 6]

alist = [1, 2, 3, 4, 5, 6]
del alist[::2]  # 删除列表元素,下标是偶数位的元素
print(alist) #[2, 4, 6]

四、PyTorch中view的用法

相当于numpy中resize()的功能,但是用法可能不太一样。我的理解是:

把原先tensor中的数据按照行优先的顺序排成一个一维的数据(这里应该是因为要求地址是连续存储的),然后按照参数组合成其他维度的tensor。比如说是不管你原先的数据是[[[1,2,3],[4,5,6]]]还是[1,2,3,4,5,6],因为它们排成一维向量都是6个元素,所以只要view后面的参数一致,得到的结果都是一样的。 比如,

a=torch.Tensor([[[1,2,3],[4,5,6]]])
b=torch.Tensor([1,2,3,4,5,6])

print(a.view(1,6))
print(b.view(1,6))

得到的结果都是 tensor([[1., 2., 3., 4., 5., 6.]])

再看一个例子:

a=torch.Tensor([[[1,2,3],[4,5,6]]])
print(a.view(3,2))

将会得到:

tensor([[1., 2.],
        [3., 4.],
        [5., 6.]])

相当于就是从1,2,3,4,5,6顺序的拿数组来填充需要的形状。但是如果您想得到如下的结果:

tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])

就需要使用另一个函数了:permute()。用法参见我的另一篇博客:PyTorch中permute的用法

另外,参数不可为空。参数中的-1就代表这个位置由其他位置的数字来推断,只要在不致歧义的情况的下,view参数就可以推断出来,也就是人可以推断出形状的情况下,view函数也可以推断出来。比如a tensor的数据个数是6个,如果view(1,-1),我们就可以根据tensor的元素个数推断出-1代表6。而如果是view(-1,-1,2),人不知道怎么推断,机器也不知道。还有一种情况是人可以推断出来,但是机器推断不出来的:view(-1,-1,6),人可以知道-1都代表1,但是机器不允许同时有两个负1。

如果没有-1,那么所有参数的乘积就要和tensor中元素的总个数一致了,否则就会出现错误。

五、Pytorch生成随机数Tensor的方法汇总

在使用PyTorch做实验时经常会用到生成随机数Tensor的方法

  • torch.rand()
  • torch.randn()
  • torch.normal()
  • torch.linespace()

均匀分布

torch.rand(*sizes, out=None) → Tensor

返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。张量的形状由参数sizes定义。

参数:

  • sizes (int…) - 整数序列,定义了输出张量的形状
  • out (Tensor, optinal) - 结果张量
torch.rand(2, 3)
[[0.0836 0.6151 0.6958],
 [0.6998 0.2560 0.0139]]
[torch.FloatTensor of size 2x3]

标准正态分布

torch.randn(*sizes, out=None) → Tensor

返回一个张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。张量的形状由参数sizes定义。

参数:

  • sizes (int…) - 整数序列,定义了输出张量的形状
  • out (Tensor, optinal) - 结果张量
torch.randn(2, 3)
0.5419 0.1594 -0.0413
-2.7937 0.9534 0.4561
[torch.FloatTensor of size 2x3]

离散正态分布

torch.normal(means, std, out=None) → → Tensor

返回一个张量,包含了从指定均值means和标准差std的离散正态分布中抽取的一组随机数。

标准差std是一个张量,包含每个输出元素相关的正态分布标准差。

参数:

  • means (float, optional) - 均值
  • std (Tensor) - 标准差
  • out (Tensor) - 输出张量
torch.normal(mean=0.5, std=torch.arange(1, 6))
-0.1505
-1.2949
-4.4880
-0.5697
-0.8996
[torch.FloatTensor of size 5]

线性间距向量

torch.linspace(start, end, steps=100, out=None) → Tensor

返回一个1维张量,包含在区间start和end上均匀间隔的step个点。

输出张量的长度由steps决定。

参数:

  • start (float) - 区间的起始点
  • end (float) - 区间的终点
  • steps (int) - 在start和end间生成的样本数
  • out (Tensor, optional) - 结果张量
torch.linspace(3, 10, steps=5)
3.0000
4.7500
6.5000
8.2500
10.0000
[torch.FloatTensor of size 5]

六、pytorch 状态字典:state_dict使用详解

pytorch 中的 state_dict 是一个简单的python的字典对象,将每一层与它的对应参数建立映射关系.(如model的每一层的weights及偏置等等)

(注意,只有那些参数可以训练的layer才会被保存到模型的state_dict中,如卷积层,线性层等等)

优化器对象Optimizer也有一个state_dict,它包含了优化器的状态以及被使用的超参数(如lr, momentum,weight_decay等)

备注:

1.state_dict是在定义了model或optimizer之后pytorch自动生成的,可以直接调用.常用的保存state_dict的格式是".pt"或’.pth’的文件,即下面命令的 PATH=“./***.pt”

torch.save(model.state_dict(), PATH)

2.load_state_dict 也是model或optimizer之后pytorch自动具备的函数,可以直接调用

model = TheModelClass(*args, **kwargs)

model.load_state_dict(torch.load(PATH))

model.eval()

注意:model.eval() 的重要性,在2)中最后用到了model.eval(),是因为,只有在执行该命令后,"dropout层"及"batch normalization层"才会进入 evalution 模态. 而在"训练(training)模态"与"评估(evalution)模态"下,这两层有不同的表现形式.

模态字典(state_dict)的保存(model是一个网络结构类的对象)

1.1)仅保存学习到的参数,用以下命令

torch.save(model.state_dict(), PATH)

1.2)加载model.state_dict,用以下命令

model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()

备注:model.load_state_dict的操作对象是 一个具体的对象,而不能是文件名

2.1)保存整个model的状态,用以下命令

torch.save(model,PATH)

2.2)加载整个model的状态,用以下命令:

# Model class must be defined somewhere
model = torch.load(PATH)
model.eval()

state_dict 是一个python的字典格式,以字典的格式存储,然后以字典的格式被加载,而且只加载key匹配的项

如何仅加载某一层的训练的到的参数(某一层的state)

If you want to load parameters from one layer to another, but some keys do not match, simply change the name of the parameter keys in the state_dict that you are loading to match the keys in the model that you are loading into.

conv1_weight_state = torch.load('./model_state_dict.pt')['conv1.weight']

加载模型参数后,如何设置某层某参数的"是否需要训练"(param.requires_grad)

for param in list(model.pretrained.parameters()):

 param.requires_grad = False

注意: requires_grad的操作对象是tensor.

疑问:能否直接对某个层直接之用requires_grad呢?例如:model.conv1.requires_grad=False

回答:经测试,不可以.model.conv1 没有requires_grad属性.

全部测试代码:

#-*-coding:utf-8-*-
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim  

# define model
class TheModelClass(nn.Module):
 def __init__(self):
  super(TheModelClass,self).__init__()
  self.conv1 = nn.Conv2d(3,6,5)
  self.pool = nn.MaxPool2d(2,2)
  self.conv2 = nn.Conv2d(6,16,5)
  self.fc1 = nn.Linear(16*5*5,120)
  self.fc2 = nn.Linear(120,84)
  self.fc3 = nn.Linear(84,10)
  
 def forward(self,x):
  x = self.pool(F.relu(self.conv1(x)))
  x = self.pool(F.relu(self.conv2(x)))
  x = x.view(-1,16*5*5)
  x = F.relu(self.fc1(x))
  x = F.relu(self.fc2(x))
  x = self.fc3(x)
  return x

# initial model
model = TheModelClass()

#initialize the optimizer
optimizer = optim.SGD(model.parameters(),lr=0.001,momentum=0.9)

# print the model's state_dict
print("model's state_dict:")
for param_tensor in model.state_dict():
 print(param_tensor,'\t',model.state_dict()[param_tensor].size())
 
print("\noptimizer's state_dict")
for var_name in optimizer.state_dict():
 print(var_name,'\t',optimizer.state_dict()[var_name])
 
print("\nprint particular param")
print('\n',model.conv1.weight.size())
print('\n',model.conv1.weight)
print("------------------------------------")

torch.save(model.state_dict(),'./model_state_dict.pt')
# model_2 = TheModelClass()
# model_2.load_state_dict(torch.load('./model_state_dict'))
# model.eval()
# print('\n',model_2.conv1.weight)
# print((model_2.conv1.weight == model.conv1.weight).size())

## 仅仅加载某一层的参数
conv1_weight_state = torch.load('./model_state_dict.pt')['conv1.weight']
print(conv1_weight_state==model.conv1.weight)

model_2 = TheModelClass()
model_2.load_state_dict(torch.load('./model_state_dict.pt'))
model_2.conv1.requires_grad=False
print(model_2.conv1.requires_grad)
print(model_2.conv1.bias.requires_grad)

七、pytorch中feature map的可视化

分为四步:
1, 单个图像导入
2, 建立模型
3, 提取特征层
4, 进行可视化

可视化代码如下:

import os
import torch
import torchvision as tv
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import argparse
import skimage.data
import skimage.io
import skimage.transform
import numpy as np
import matplotlib.pyplot as plt

# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Load training and testing datasets.
pic_dir = '01_4_full.jpg'

# 定义数据预处理方式(将输入的类似numpy中arrary形式的数据转化为pytorch中的张量(tensor))
transform = transforms.ToTensor()


# 单张图像送入
# 构建网络
# 提取中间层
# 可视化特征图

def get_picture(picture_dir, transform):
    '''
    该算法实现了读取图片,并将其类型转化为Tensor
    '''
    img = skimage.io.imread(picture_dir)
    img256 = skimage.transform.resize(img, (256, 256))
    img256 = np.asarray(img256)
    img256 = img256.astype(np.float32)

    return transform(img256)


def get_picture_rgb(picture_dir):
    '''
    该函数实现了显示图片的RGB三通道颜色
    '''
    img = skimage.io.imread(picture_dir)
    img256 = skimage.transform.resize(img, (256, 256))
    skimage.io.imsave('new4.jpg', img256)

    # 取单一通道值显示
    # for i in range(3):
    #     img = img256[:,:,i]
    #     ax = plt.subplot(1, 3, i + 1)
    #     ax.set_title('Feature {}'.format(i))
    #     ax.axis('off')
    #     plt.imshow(img)

    # r = img256.copy()
    # r[:,:,0:2]=0
    # ax = plt.subplot(1, 4, 1)
    # ax.set_title('B Channel')
    # # ax.axis('off')
    # plt.imshow(r)

    # g = img256.copy()
    # g[:,:,0]=0
    # g[:,:,2]=0
    # ax = plt.subplot(1, 4, 2)
    # ax.set_title('G Channel')
    # # ax.axis('off')
    # plt.imshow(g)

    # b = img256.copy()
    # b[:,:,1:3]=0
    # ax = plt.subplot(1, 4, 3)
    # ax.set_title('R Channel')
    # # ax.axis('off')
    # plt.imshow(b)

    # img = img256.copy()
    # ax = plt.subplot(1, 4, 4)
    # ax.set_title('image')
    # # ax.axis('off')
    # plt.imshow(img)

    img = img256.copy()
    ax = plt.subplot()
    ax.set_title('new-image')
    # ax.axis('off')
    plt.imshow(img)

    plt.show()


class LeNet(nn.Module):
    '''
    该类继承了torch.nn.Modul类
    构建LeNet神经网络模型
    '''
    def __init__(self):
        super(LeNet, self).__init__()

        # 第一层神经网络,包括卷积层、线性激活函数、池化层
        self.conv1 = nn.Sequential( 
            nn.Conv2d(3, 32, 5, 1, 2),   # input_size=(3*256*256),padding=2
            nn.ReLU(),                  # input_size=(32*256*256)
            nn.MaxPool2d(kernel_size=2, stride=2),  # output_size=(32*128*128)
        )

        # 第二层神经网络,包括卷积层、线性激活函数、池化层
        self.conv2 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2),  # input_size=(32*128*128)
            nn.ReLU(),            # input_size=(64*128*128)
            nn.MaxPool2d(2, 2)    # output_size=(64*64*64)
        )

        # 全连接层(将神经网络的神经元的多维输出转化为一维)
        self.fc1 = nn.Sequential(
            nn.Linear(64 * 64 * 64, 128),  # 进行线性变换
            nn.ReLU()                    # 进行ReLu激活
        )

        # 输出层(将全连接层的一维输出进行处理)
        self.fc2 = nn.Sequential(
            nn.Linear(128, 84),
            nn.ReLU()
        )

        # 将输出层的数据进行分类(输出预测值)
        self.fc3 = nn.Linear(84, 62)

    # 定义前向传播过程,输入为x
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        # nn.Linear()的输入输出都是维度为一的值,所以要把多维度的tensor展平成一维
        x = x.view(x.size()[0], -1)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x

# 中间特征提取
class FeatureExtractor(nn.Module):
    def __init__(self, submodule, extracted_layers):
        super(FeatureExtractor, self).__init__()
        self.submodule = submodule
        self.extracted_layers = extracted_layers
 
    def forward(self, x):
        outputs = []
        print('---------',self.submodule._modules.items())
        for name, module in self.submodule._modules.items():
            if "fc" in name:
                x = x.view(x.size(0), -1)
            print(module)
            x = module(x)
            print('name', name)
            if name in self.extracted_layers:
                outputs.append(x)
        return outputs


def get_feature():  # 特征可视化
    # 输入数据
    img = get_picture(pic_dir, transform) # 输入的图像是【3,256,256】
    # 插入维度
    img = img.unsqueeze(0)  # 【1,3,256,256】
    img = img.to(device)

    # 特征输出
    net = LeNet().to(device)
    # net.load_state_dict(torch.load('./model/net_050.pth'))
    exact_list = ['conv1']
    myexactor = FeatureExtractor(net, exact_list)  # 输出是一个网络
    x = myexactor(img)

    # 特征输出可视化
    for i in range(32):  # 可视化了32通道
        ax = plt.subplot(6, 6, i + 1)
        ax.set_title('Feature {}'.format(i))
        ax.axis('off')
        ax.set_title('new—conv1-image')

        plt.imshow(x[0].data.cpu().numpy()[0,i,:,:],cmap='jet')

    plt.show()  # 图像每次都不一样,是因为模型每次都需要前向传播一次,不是加载的与训练模型

# 训练
if __name__ == "__main__":
    #get_picture_rgb(pic_dir)
    get_feature()
    

在这里插入图片描述

八、Pytorch 查看每一层 feature map 的大小size

源码:https://github.com/sksq96/pytorch-summary
在这里插入图片描述
注:netD是网络

效果图:
在这里插入图片描述

九、one hot编码torch.Tensor.scatter_()函数用法详解

torch.Tensor.scatter_()是torch.gather()函数的方向反向操作。两个函数可以看成一对兄弟函数。gather用来解码one hot,scatter_用来编码one hot。

scatter_(dim, index, src) → Tensor

  • dim (python:int) – 用来寻址的坐标轴
  • index (LongTensor) – 索引
  • src(Tensor) –用来scatter的源张量,以防value未被指定。
  • value(python:float) – 用来scatter的源张量,以防src未被指定。

现在我们来看看具体这么用,看下面这个例子就一目了然了。

  • dim =0
import torch

x = torch.tensor([[0.9413, 0.9476, 0.1104, 0.9898, 0.6443],
            [0.6913, 0.8924, 0.7530, 0.8874, 0.0557]])

result = torch.zeros(3, 5)
indices = torch.tensor([[0, 1, 2, 0, 0], 
                        [2, 0, 0, 1, 2]])
result.scatter_(0, indices, x)

输出为

tensor([[0.9413, 0.8924, 0.7530, 0.9898, 0.6443],
        [0.0000, 0.9476, 0.0000, 0.8874, 0.0000],
        [0.6913, 0.0000, 0.1104, 0.0000, 0.0557]])

dim = 0的情形:

比如上例中,dim=0,所以根据这个规则来self[index[i][j]][j] = src[i][j]来确定替换规则。
index中的值决定了src中的值在result中的放置位置。
dim=0时,则将列固定起来,先看第0列:
对于第0行,首先找到x的第0列第0行的值为0.9413,然后在用index[0][0]的值来找将要在result中放置的位置。
在这个例子中,index[0][0]=0, 所以0.9413将放置在result[0][0]这个位置。
对于result中的各项,他们的寻址过程如下:

x[0][1] = 0.9476 -> indices[0][1]=1 -> result[ index = 1 ][1] = 0.9476

x[1][3] = 0.8874 -> indices[1][3]=1 -> result[ index = 1 ][3] = 0.8874

依此类推。

以下为dim = 1的情形:

x[0][0] = 0.9413 -> indices[0][0]=0 -> result[0][index = 0] = 0.9413

x[0][3] = 0.9898 -> indices[0][3]=0 -> result[0][index = 0] = 0.9898 ## 将上一步的值覆盖了

x[0][4] = 0.6443 -> indices[0][4]=0 -> result[0][index = 0] = 0.6443 ## 再次将上一步的值覆盖了

因此result[0][0]的值为0.6443.

dim = 1

x = torch.tensor([[0.9413, 0.9476, 0.1104, 0.9898, 0.6443],
                        [0.6913, 0.8924, 0.7530, 0.8874, 0.0557]])
result = torch.zeros(3, 5)
indices = torch.tensor([[0, 1, 2, 0, 0], 
                        [2, 0, 0, 1, 2]])
result.scatter_(1, indices, x)

输出为

tensor([[0.6443, 0.9476, 0.1104, 0.0000, 0.0000],
        [0.7530, 0.8874, 0.0557, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])

用于产生one hot编码的向量
当没有src值时,则所有用于填充的值均为value值。

需要注意的时候,这个时候index.shape[dim]必须与result.shape[dim]相等,否则会报错。

result = torch.zeros(3, 5)
indices = torch.tensor([[0, 1, 2, 0, 0], 
                        [2, 0, 3, 1, 2],
                        [2, 1, 3, 1, 4]])
result.scatter_(1, indices, value=1)      

输出为

tensor([[1., 1., 1., 0., 0.],
        [1., 1., 1., 1., 0.],
        [0., 1., 1., 1., 1.]])

例如 indices = [1,2,3,4,5],将他转换为one-hot的形式.

indices = torch.tensor(list(range(5))).view(5,1)
result = torch.zeros(5, 5)
result.scatter_(1, indices, 1)        

输出为

tensor([[1., 0., 0., 0., 0.],
        [0., 1., 0., 0., 0.],
        [0., 0., 1., 0., 0.],
        [0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 1.]])

十、torch.Tensor.view()的用法

根据简单有效原则,先上一张图片:
在这里插入图片描述

图中先给出一个名字为C的Tensor,人为赋值为[ [ [ 1, 2, 3], [ 3, 4, 5 ] ], [ [ 6, 7, 8 ], [7, 8 , 9] ], [ [ 12, 34, 51 ], [ 78, 79, 80 ] ] ],在这一组简单的数据中,batchsize=3即3个数据单元 [ [ 1, 2, 3], [ 3, 4, 5 ] ]、[ [ 6, 7, 8 ], [7, 8 , 9] ]、[ [ 12, 34, 51 ], [ 78, 79, 80 ] ],对于每个数据单元,例如 [ [ 1, 2, 3], [ 3, 4, 5 ] ]又可以看到是由[ 1, 2, 3]和[ 3, 4, 5 ] 组成,其他两个同理,因此可以得到数据单元的长度length=3, height=2,如果数据总体按照(batchsize, length, height)的格式来描述,那么C可以描述为(3, 3, 2),对应于实际,我想C应该就是用来表达总体的数据集。

D = C.view(3,-1)D = C.view(batchsize, -1)时(备注:-1为自动计算剩余维数),可以看到效果,首先由三维Tensor C变成了二维的Tensor B,然后本来在C中是两行数据[[1, 2, 3], [3, 4, 5]]经过view(batchsize, -1)以后合并为一行[1, 2, 3, 3, 4, 5],这样对应实际例如图片的输入,1张图片是由多个行向量排列到一块的。即[ [ , ,.., ], [ , ,..., ],...[ , ,..., ] ]这样的结构,所以实际输入到网络中有时候需要将原本基本向量即带有行和列的向量平整为行向量。

E = D.view(1,1,-1)时候,可以看到生成的是一个3维Tensor,和view()中参数个数保持一致,第二个例子没有太多实际的意义,只是为了进一步测试view的用法。

十一、Pytorch tensor.sum()

Pytorch 中 tensor.sum(axis) 会按照指定的维度进行求和,但是对于 N * C * H * W 这种类型的输入,如果通过 sum 获得 N * C * 1 * 1 的输出该怎么做?

a = torch.ones(size=(1, 3, 4, 4))  # 全1矩阵
b = a.sum(axis=[2, 3], keepdim=False)
print(b.shape)    # (1, 3)
print(b)          # [[16, 16, 16]] 
 
a = torch.ones(size=(1, 3, 4, 4))  # 全1矩阵
b = a.mean(axis=[2, 3], keepdim=True)
print(b.shape)    # (1, 3, 1, 1)
print(b)          # [[ [[1]], [[1]], [[1]] ]]

注意: axis参数可以同时接收多个维度,并可同时在这些维度上进行指定操作。

十二、torch.sum(input, dim, out=None)

 torch.sum(input, dim, out=None)

参数说明

  • input:输入的tensor矩阵。
  • dim:求和的方向。若input为2维tensor矩阵,dim=0,对列求和;dim=1,对行求和。注意:输入的形状可为其他维度(3维或4维),可根据dim设定对相应的维度求和。
  • out: 输出,一般不指定。

例子

这里给了一个四维的tensor,代表的是输入到网络中的一张图片(batchSize,channel,height,width)。dim=1,对通道求和。

Code

import torch

def function():
    data1 = torch.rand([1, 3, 3, 3])
    print("data1_shape: ", data1.shape)
    print("data1: ", data1)

    data2 = torch.sum(data1, dim=1)
    print("data2_shape: ", data2.shape)
    print("data2: ", data2)

if __name__ == '__main__':
    function()

结果显示

对第二个通道求和的,因此第二个通道变为了1,默认被隐藏,不被显示。理论上的维度为[1, 1, 3, 3],隐藏后维度为[1, 3, 3]。
在这里插入图片描述

十三、Python的图像库(Opencv、PIL、matplotlib、skimage)的使用(读取、存储、变换、滤波)

Opencv
OpenCV的全称是:Open Source Computer Vision Library。OpenCV是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。

读取操作
注意: cv2读取的图片通道使按B、G、R排列的,而非RGB顺序。因此工程中opencv库与其他库混用时要注意,可以用img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)转化为RGB通道。

基本读取操作

import cv2 
img = cv2.imread('image.jpg') #可读取tif格式图像
if img == None:               #判断图像是否存在
	print ('This file may not be available') 
cv2.imshow('the window name',img)
cv2.waitKey()#在imshow之后如果没有waitKey语句则不会正常显示图像。
CV2.imwrite('new_image.jpg',img)

img numpy数组矩阵;img.shape 获取图像的(高,宽,通道数);img.size图像的像素点数;img.dtype图像的数据类型(uint8)。

灰度图读取

gray = cv2.imread('image.jpg',cv2.IMREAD_GRAYSCALE) #cv2.IMREAD_UNCHANGED 包含alpha通道(透明度)
#使用颜色转换函数cv2.cvtColor
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

通道操作

b,g,r = cv2.split(img) #通道分离
merge_img = cv2.merge((b,g,r))

ROI操作

#数组的切片操作
roi = img[100:300,30:350,:]

图像处理
缩放与旋转

#缩放
img_resize = cv2.resize(img,(h,w)) #w,宽;h,高
#旋转 opencv中目前没有现成函数直接用来图像旋转,是用仿射变换函数warpAffine来实现的。
#cv2.getRotationMatrix2D() 参数1:旋转中心点;参数2:旋转角度;参数3:缩放大小。输出:旋转矩阵
Matrix = cv2.getRotationMatrix2D((cols/2,rows/2),90,1)
img_rotate = cv2.warpAffine(img,MMatrix,(cols,rows))
#翻转cv2.flip(img,filpcode)
img_filp=cv2.flip(img,0) #0:-垂直翻转
img_filp=cv2.flip(img,1) #1:-水平翻转
img_filp=cv2.flip(img,-1) #负值:-垂直+水平翻转

十四、图像滤波

对图像进行滤波,可以有两种效果:一种是平滑滤波,用来抑制噪声;另一种是微分算子,可用来检测边缘和特征提取。

#中值滤波
img_medianBlur=cv2.medianBlur(img,5)
#均值滤波
img_Blur=cv2.blur(img,(5,5))
#高斯滤波
img_GaussianBlur=cv2.GaussianBlur(img,(7,7),0) 

PIL
PIL(Python Imaging Library),即Pillow,相比opencv更为轻巧。Image模块是在Python PIL图像处理中常见的模块,对图像进行基础操作的功能基本都包含于此模块内。如open、save、show等功能。

读取操作
PIL读取图片获得的不是矩阵,而是Image格式,可以利用numpy进行转化。

基本读取操作

from PIL import Image
import numpy as np
#读取图像
img = Image.open('image.jpg') #可以读取tif格式图片
img.show()              #显示图像
img_arr = np.array(img) #3维矩阵
#存储图像
new_img = Image.fromarray(img_arr)
new_img.save('newimage.jpg') #如果不转矩阵可直接用'.save'保存

img.format 图像格式;img.mode 图像类型,L为灰度图,RGB为真彩色,RGBA含有透明度通道。
注意 img.size返回的是图像的宽高(w,h),不包含通道;而opencv与matplotlib中返回的是图像的像素总数。

灰度图读取

gary = Image.open('image.jpg').convert('L')

通道操作

#通道的分离合并
r,g,b = img.split()
img = Image.merge('RGB',(r,g,b))
#复制图像
img_copy = img.copy()

ROI获取

roi = img.crop((200,300,400,500))# (左、上、右、下)即左上角和右下角像素点的坐标'x'与'y'

图像处理
缩放与旋转

#缩放
img_resize = img.resize((h,w))
#旋转
img_r90 = img.rotate(90) #旋转90度
img_transpose_tb = img.transpose(Image.FLIP_TOP_BOTTOM)#上下翻转
img_transpose_lr = img.transpose(Image.FLIP_LEFT_RIGHT)#左右翻转

图像增强
PILImageEnhance类专门用于图像增强处理,可以增强(减弱)图像的亮度、对比度、色度、以及锐度。

from PIL import Image
from PIL import ImageEnhance

#原始图像
img = Image.open('image.jpg')
#亮度增强
img_bright = ImageEnhance.Brightness(imag)
brightness = 3
image_brighted = img_bright.enhance(brightness)
image_brighted.show()
#色度增强
img_color = ImageEnhance.Color(img)
color = 2
image_colored = img_color.enhance(color)
image_colored.show()
#对比度增强
img_contrast = ImageEnhance.Contrast(img)
contrast = 3
image_contrasted = img_contrast.enhance(contrast)
image_contrasted.show()
#锐度增强
img_sharp = ImageEnhance.Sharpness(img)
sharpness = 2
image_sharped = img_sharp.enhance(sharpness)
image_sharped.show()

图像滤波
由PIL中的ImageFilter类实现。

from PIL import ImageFilter
#模糊滤波
img_blur = img.filter(ImageFilter.BLUR)
#轮廓滤波
img_contour = img.filter(ImageFilter.CONTOUR)
#细节滤波
img_detail = img.filter(ImageFilter.DETAIL)
#边界增强滤波
img_edge_enhance = img.filter(ImageFilter.EDGE_ENHANCE)
#锐化滤波
img_sharp = img.filter(ImageFilter.SHARPEN)
#高斯模糊滤波
img_gauss = img.filter(ImageFilter.GaussianBlur(radius=2))  # radius指定平滑半径,也就是模糊的程度。

matplotlib
Matplotlib是一个Python 2D绘图库,它可以在不同的平台上以各种硬拷贝格式和交互环境生成发布质量数据。Matplotlib可以用于Python脚本、Python和IPython shell、Jupyter notebook、web应用服务器和四个图形用户界面工具包。对于简单的绘图,pyplot模块提供了一个类似于matlab的接口,特别是与IPython结合使用时。对于power用户,您可以通过面向对象的界面或通过MATLAB用户熟悉的一组函数来完全控制线样式、字体属性、轴属性等.

图像读取
基本读取操作

import matplotlib.pyplot as plt
import numpy as np 

img  = plt.imread('image.jpg') #可以读取tif文件
plt.imshow(img) 
#plt.axis('off')  #关闭坐标轴上的数字
plt.show()
plt.savefig('new_img.jpg')

灰度图

img_r = img[:,:,0] #取单通道-r通道
plt.imshow(img_r)
plt.show()

输出图像示例:
在这里插入图片描述
在这里插入图片描述

输出并不是灰度图,得到灰度图需要添加cmap = 'Greys_r' 参数

plt.imshow(img_r,cmap='Greys_r')
plt.show()

灰度图:
在这里插入图片描述

skimage
scikit-image是一组用于图像处理和计算机视觉的算法。“skimage”的主要包只提供了一些用于转换图像数据类型的实用程序;大多数功能程序存在其子包中。读取功能包含在io模块中。

读取操作
基本读取操作

可以读取tif格式图片,保存之前要将数据转换成uint8,否则会报错。

from skimage import io

img = io.imread('image.jpg')  
io.imshow(img)
io.show() 
io.imsave('new_img.jpg',img)

灰度图读取

img_grey = io.imread('image.jpg',as_grey=True)
print(img_grey.dtype)  #float64    原因加入as_grey参数后,imread将灰度图的矩阵值归一化。
#可以用skimage中的color模块得到灰度图
img_grey = color.rgb2grey(img)  #彩色转灰度
skimage.clolor.grey2rgb(img_grey)#灰度转彩色

图像处理
图像缩放与旋转

from skimage import transform  
#resize
img_resize = transform.resize(img,(h,w)) #(h,w)新图像尺寸
#rescale #按比例缩放 
img_r1 = transform.rescale(img,0.1) #将图像高宽缩小为原图片大小的0.1倍
img_r2 = transform.rescale(img,[0.5,0.2])#高缩小到0.5倍,宽缩小到0.2倍
#旋转rotate
img_90 = transform.rotate(img,90) #旋转90度不改变大小
img_30 = transform.rotate(img,30,resize=True)#旋转30度,同时改变大小

图像滤波
skimage库中通过filters模块进行滤波操作。

  • sobel算子,sobel算子可以用来检测边缘
from skimage import filters
edges = filters.sobel(img)
  • roberts算子,用于检测边缘
edges = filters.roberts(img)
  • canny算子,用于提取边缘特征,在feature模块内,通过修改sigma的值来调整效果,sigma越小线条越细。
from skimage import feature
edges1 = feature.canny(img) #sigma=1
edges2 = feature.canny(img,sigma=3)#sigma=3
  • gabor滤波,用于边缘检测和纹理特征提取,通过调整frequency值来调整滤波效果,返回一对边缘结果,一个是用真实滤波核的滤波结果,另一个是想象滤波核的滤波结果。
filt_real,filt_fake = fileters.gabor_filter(img,frequency=0.4)
  • gaussian滤波,多维滤波器,平滑滤波,可以消除高斯噪声,通过调节sigma的值来调整滤波效果,sigma越大,图像越模糊。
edges = filters.gaussian(img,sigma=0.4)
  • median滤波,中值滤波,平湖滤波,消除噪声。需要用skimage.morphology模块来设置滤波器的形状,滤波器越大,图像越模糊。
from skimage.morphology import disk
edges = filters.median(img,disk(5))

图像批处理操作

  • 从不同文件夹里读取不同格式的图片:
import skimage.io as io
from skimage import data_dir
path='c:/pic/*.jpg:d:/pic/*.png'
img_coll = io.ImageCollection(path)

path是两个路径合在一起的字符串,中间由:隔开,可以再添加其他路径。

  • 批量重命名
import os 
os.rename(oldname,newname)#oldname,newname为文件名或者路径,需包含文件后缀。

十五、python 三种常用图像库opencv、matplotlib、PIL图片读取方式对比总结

一.opencv: cv2

'cv2'
import cv2
import os
# im1 = cv2.imread(r'C:\Users\admin\Desktop\Celeba\celeba/000001.jpg')#对
# im1 = cv2.imread(r'C:\Users\admin\Desktop\Celeba\celeba')#报错,要明确到图片名
im1 = cv2.imread(os.path.join(r'C:\Users\admin\Desktop\Celeba\celeba','000001.jpg'))#等同第3行
 
im2 = cv2.resize(im1,(200,200)) #默认双线性插值
cv2.imshow('abcd',im2)
cv2.waitKey(0) #无限期等待输入,按任意键结束

1.① opencv读进来的图片已经是一个numpy矩阵了!!!彩色图片维度是(高度,宽度,通道数)。数据类型是uint8
②opencv读进来的图片的通道排列是BGR,而不是主流的RGB

2.①img1 = cv2.imread(path) 读入图片
②cv2.imshow(winname, img1)显示图片:winname: 窗口名; image: 要显示的图片
③cv2.imwrite(path, img1)保存图片
④cv2.waitKey(0) 无限期等待输入,按任意键结束
⑤cv2.destroyAllWindows() 销毁所有创建的窗口
⑥cv2.resize( ),参数输入是 宽×高×通道

二**.matplotlib : from matplotlib import pyplot as plt**

'matplotlib'
import cv2
import os
from matplotlib import pyplot as plt
 
im1 = cv2.imread(os.path.join(r'C:\Users\admin\Desktop\Celeba\celeba','000001.jpg'))
# im1 = plt.imread(os.path.join(r'C:\Users\admin\Desktop\Celeba\celeba','000001.jpg'))#同上
"""createPlot = subplot(222)表示画布分成(2*2=4)个小区域,
并将图createPlot绘制在画布中的第二个子区域,也就是右上角位置。"""
#cv2的BGR格式
plt.subplot(121)
plt.imshow(im1)
plt.title('BGR')
#调为正常的BGR
plt.subplot(122)
im1_ = im1[:,:,::-1] #BGR -> RGB
plt.imshow(im1_)
plt.title('RGB')
'plt.imshow(img)是在图像上增加一个img,不负责显示。plt.show()才能显示'
plt.show()
#获取属性
print(im1.shape)
print(im1.size)
print(im1.dtype)

1.①调用matplotlib进行图片展示时,需要注意调整图片的通道顺序。因为cv2读取图片的通道顺序是BGR,而matplotlib是RGB,故调用plt.imshow(img)前,必须将img的通道调整为RGB。
②plt.imshow(img)是在图像上增加一个img,不负责显示。plt.show()才能实现显示。
注:plt.imshow(img)显示的图是带坐标的,不想要显示坐标,需跟着一行代码 plt.axis(‘off’)

2.①img1 = plt.imread(path) 读入图片
②plt.imshow(img1)后,还需plt.show()来实现显示。
③plt.savefig(path)保存图片:在其前必须写plt.imshow(img1),否则保存图全是空白的。
④plt.subplot(122),表示画布分成(1*2=2)个小区域, 并将图绘制在画布中的第二个子区域,也就是右上角位置。

三. PIL: from PIL import Image

'PIL Image'
from PIL import Image
import numpy as np
import os
 
im1 = Image.open(os.path.join(r'C:\Users\admin\Desktop\Celeba\celeba','000001.jpg'))
im1.show()
 
#除了cv2,其他库读进来的图片都不是矩阵,我们将图片转矩阵
arr = np.array(im1)
print(arr.shape)
print(arr.dtype)
#矩阵再转为图像
im2 = Image.fromarray(arr)
# im2.save('01.png')

1 除了cv2,其他库读进来的图片都不是矩阵。所以我们经常在图片读进来后,先转为矩阵下的操作,之后再转回图片。
2.① img1 = Image.open(path) 读入图片
②PIL下,读入图片后都是对图片做直接操作。如:img1.show() 直接显示图片,img1.resize((w,h)),img1.save(path)等。

十六、tensorflow实战(一) 打印一个tensor值

在tensorflow中,打印一个tensor值必须在在一个会话Session中进行,并且可以使用Session.run()或Tensor.eval()进行打印x的值:

  • 使用 print(sess.run(x))
  • 使用print(x.eval())

Session.run和Tensor.eval的区别和联系

例子

import tensorflow as tf

x = tf.ones(shape=[2, 3], dtype=tf.int32,name='x')
y= tf.zeros(shape=[2, 3], dtype=tf.float32,name='y')

with tf.Session() as sess:
    print(sess.run([x,y]))   #一次能打印两个
    print(x.eval())
    print(y.eval()) #一次只能打印一个

十七、Pytorch:输出整个tensor的方法

Pytorch:输出整个tensor的方法

torch.set_printoptions(profile="full")
print(x) # prints the whole tensor
torch.set_printoptions(profile="default") # reset
print(x) # prints the truncated tensor

将这个代码放在import torch之后就可以了,full代表输出所有,deflaut是默认输出部分

十八、torch.cat() 函数用法

torch.cat是将两个张量(tensor)拼接在一起,cat是concatnate的意思,即拼接,联系在一起。

  • 使用torch.cat((A,B),dim)时,除拼接维数dim数值可不同外其余维数数值需相同,方能对齐。
C = torch.cat( (A,B),0 )  #按维数0拼接(竖着拼)
C = torch.cat( (A,B),1 )  #按维数1拼接(横着拼)
>>> import torch
>>> A=torch.ones(2,3) #2x3的张量(矩阵)                                     
>>> A
tensor([[ 1.,  1.,  1.],
        [ 1.,  1.,  1.]])
>>> B=2*torch.ones(4,3)#4x3的张量(矩阵)                                    
>>> B
tensor([[ 2.,  2.,  2.],
        [ 2.,  2.,  2.],
        [ 2.,  2.,  2.],
        [ 2.,  2.,  2.]])
>>> C=torch.cat((A,B),0)#按维数0(行)拼接
>>> C
tensor([[ 1.,  1.,  1.],
         [ 1.,  1.,  1.],
         [ 2.,  2.,  2.],
         [ 2.,  2.,  2.],
         [ 2.,  2.,  2.],
         [ 2.,  2.,  2.]])
>>> C.size()
torch.Size([6, 3])
>>> D=2*torch.ones(2,4) #2x4的张量(矩阵)
>>> C=torch.cat((A,D),1)#按维数1(列)拼接
>>> C
tensor([[ 1.,  1.,  1.,  2.,  2.,  2.,  2.],
        [ 1.,  1.,  1.,  2.,  2.,  2.,  2.]])
>>> C.size()
torch.Size([2, 7])

十九、torch.zeros和torch.ones

torch.zeros:用来将tensor中元素值全置为0

a=torch.zeros(2,2)
''
tensor([[0., 0.],
        [0., 0.]])
''
b=torch.zeros(3)
''
tensor([0., 0., 0.])
''

torch.ones:用来将tensor中元素值全置为1

c=torch.ones(2,2)
''
tensor([[1., 1.],
        [1., 1.]])
''

二十、torch.ones(),torch.add(),torch.zeros(),torch.squeeze()

Tensor与numpy相互转换

import torch
x=torch.Tensor(2,3)#生成一个2*3的Tensor张量
 
将Tensor转换为numpy数组
y=x.numpy()
 
将numpy数组转换为Tensor
import numpy as np
z=torch.from_numpy(x)

torch.ones()

torch.ones(*sizes, out=None) → Tensor

返回一个全为1 的张量,形状由可变参数sizes定义。

参数:

  • sizes (int…) – 整数序列,定义了输出形状
  • out (Tensor, optional) – 结果张量

例子:

>>> torch.ones(2, 3)
 
 1  1  1
 1  1  1
[torch.FloatTensor of size 2x3]
 
>>> torch.ones(5)
 
 1
 1
 1
 1
 1
[torch.FloatTensor of size 5]

torch.add()

torch.add(input, value, out=None)
对输入张量input逐元素加上标量值value,并返回结果到一个新的张量out,即 out=tensor+value。
 
torch.add(input, value=1, other, out=None)
other 张量的每个元素乘以一个标量值value,并加到iput 张量上。返回结果到输出张量out。即,out=input+(other∗value)
 
两个张量 input and other的尺寸不需要匹配,但元素总数必须一样。
 
注意 :当两个张量形状不匹配时,输入张量的形状会作为输出张量的尺寸。

torch.zeros()

torch.zeros(*sizes, out=None) → Tensor

返回一个全为标量 0 的张量,形状由可变参数sizes 定义。

参数:

  • sizes (int…) – 整数序列,定义了输出形状
  • out (Tensor, optional) – 结果张量

例子:

>>> torch.zeros(2, 3)
 
 0  0  0
 0  0  0
[torch.FloatTensor of size 2x3]生成2*3的张量矩阵
 
>>> torch.zeros(5)
 
 0
 0
 0
 0
 0
[torch.FloatTensor of size 5]
 
x=torch.zeros(2,3,4)
print(x)
 
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],
 
        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])生成23*4的矩阵
 
x=torch.zeros(2,3,4,5)
print(x)
 
tensor([[[[0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.]],
 
         [[0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.]],
 
         [[0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.]]],
 
 
        [[[0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.]],
 
         [[0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.]],
 
         [[0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0.]]]])
从后向前看,最后两位为数组的维度4*5,前一位3表示有34*5的矩阵,在前一位2表示,这样34*5的矩阵有2个,以此类推

torch.squeeze()

torch.squeeze(input, dim=None, out=None)
将输入张量形状中的1 去除并返回。 如果输入是形如(1×B×1×C×1×D),那么输出形状就为: (A×B×C×D)
当给定dim时,那么挤压操作只在给定维度上。例如,输入形状为: (1×B), squeeze(input, 0) 
将会保持张量不变,只有用 squeeze(input, 1),形状会变成 (A×B)。
 
注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。
 
参数:
 
input (Tensor) – 输入张量
dim (int, optional) – 如果给定,则input只会在给定维度挤压
out (Tensor, optional) – 输出张量
例子:
 
>>> x = torch.zeros(2,1,2,1,2)
>>> x.size()
(2L, 1L, 2L, 1L, 2L)
>>> y = torch.squeeze(x)
>>> y.size()
(2L, 2L, 2L)
>>> y = torch.squeeze(x, 0)
>>> y.size()
(2L, 1L, 2L, 1L, 2L)
>>> y = torch.squeeze(x, 1)
>>> y.size()
(2L, 2L, 1L, 2L)

二十、torch.zeros() and torch.ones()

import torch

if __name__ == '__main__':

    a = torch.zeros(3,2)
    print(a)
    b = torch.ones(12)
    print(b)
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

二十一、PyTorch 使用GPU训练

使用 GPU 训练只需要在原来的代码中修改几处就可以了。

我们有两种方式实现代码在 GPU 上进行训练

方法一 .cuda()
我们可以通过对网络模型,数据,损失函数这三种变量调用 .cuda() 来在GPU上进行训练

在这里插入图片描述

# 将网络模型在gpu上训练
model = Model()
model = model.cuda()

# 损失函数在gpu上训练
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.cuda()

# 数据在gpu上训练
for data in dataloader:                        
	imgs, targets = data
	imgs = imgs.cuda()
	targets = targets.cuda()

但是如果电脑没有 GPU 就会报错,更好的写法是先判断 cuda 是否可用:

#将网络模型在gpu上训练**
model = Model()
if torch.cuda.is_available():
	model = model.cuda()

#损失函数在gpu上训练**
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():	
	loss_fn = loss_fn.cuda()

#数据在gpu上训练**
for data in dataloader:                        
	imgs, targets = data
    if torch.cuda.is_available():
        imgs = imgs.cuda()
        targets = targets.cuda()

代码案例:

#以 CIFAR10 数据集为例,展示一下完整的模型训练套路,完成对数据集的分类问题**

import torch
import torchvision

from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import time

#准备数据集**
train_data = torchvision.datasets.CIFAR10(root="dataset", train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10(root="dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)

#获得数据集的长度 len(), 即length**
train_data_size = len(train_data)
test_data_size = len(test_data)

#格式化字符串, format() 中的数据会替换 {}**
print("训练数据集及的长度为: {}".format(train_data_size))
print("测试数据集及的长度为: {}".format(test_data_size))

#利用DataLoader 来加载数据**
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

#创建网络模型**
class Model(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )


    def forward(self, input):
        input = self.model(input)
        return input

model = Model()
if torch.cuda.is_available():
    model = model.cuda()                        # 在 GPU 上进行训练

#创建损失函数**
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
    loss_fn = loss_fn.cuda()                    # 在 GPU 上进行训练

#优化器**
learning_rate = 1e-2        # 1e-2 = 1 * (10)^(-2) = 1 / 100 = 0.01
optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate)

#设置训练网络的一些参数**
total_train_step = 0                        # 记录训练的次数
total_test_step = 0                         # 记录测试的次数
epoch = 10                                  # 训练的轮数

#添加tensorboard**
writer = SummaryWriter("logs_train")
start_time = time.time()                    # 开始训练的时间
for i in range(epoch):
    print("------第 {} 轮训练开始------".format(i+1))

    # 训练步骤开始
    for data in train_dataloader:
        imgs, targets = data
        if torch.cuda.is_available():
            imgs = imgs.cuda()
        targets = targets.cuda()            # 在gpu上训练
        outputs = model(imgs)               # 将训练的数据放入
        loss = loss_fn(outputs, targets)    # 得到损失值

        optimizer.zero_grad()               # 优化过程中首先要使用优化器进行梯度清零
        loss.backward()                     # 调用得到的损失,利用反向传播,得到每一个参数节点的梯度
        optimizer.step()                    # 对参数进行优化
        total_train_step += 1               # 上面就是进行了一次训练,训练次数 +1

        # 只有训练步骤是100 倍数的时候才打印数据,可以减少一些没有用的数据,方便我们找到其他数据
        if total_train_step % 100 == 0:
            end_time = time.time()          # 训练结束时间
            print("训练时间: {}".format(end_time - start_time))
            print("训练次数: {}, Loss: {}".format(total_train_step, loss))
            writer.add_scalar("train_loss", loss.item(), total_train_step)


    # 如何知道模型有没有训练好,即有咩有达到自己想要的需求
    # 我们可以在每次训练完一轮后,进行一次测试,在测试数据集上跑一遍,以测试数据集上的损失或正确率评估我们的模型有没有训练好

    # 顾名思义,下面的代码没有梯度,即我们不会利用进行调优
    total_test_loss = 0
    total_accuracy = 0                                      # 准确率
    with torch.no_grad():
        for data in test_dataloader:                        # 测试数据集中取数据
            imgs, targets = data
            if torch.cuda.is_available():
                imgs = imgs.cuda()                          # 在 GPU 上进行训练
                targets = targets.cuda()
            outputs = model(imgs)
            loss = loss_fn(outputs, targets)                # 这里的 loss 只是一部分数据(data) 在网络模型上的损失
            total_test_loss = total_test_loss + loss        # 整个测试集的loss
            accuracy = (outputs.argmax(1) == targets).sum() # 分类正确个数
            total_accuracy += accuracy                      # 相加

    print("整体测试集上的loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy / test_data_size))
    writer.add_scalar("test_loss", total_test_loss)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)
    total_test_loss += 1                                    # 测试完了之后要 +1

    torch.save(model, "model_{}.pth".format(i))
    print("模型已保存")

writer.close()

方法二 .to(device)
指定 训练的设备

device = torch.device("cpu")	# 使用cpu训练
device = torch.device("cuda")	# 使用gpu训练 
device = torch.device("cuda:0")	# 当电脑中有多张显卡时,使用第一张显卡
device = torch.device("cuda:1")	# 当电脑中有多张显卡时,使用第二张显卡

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

使用 GPU 训练

model = model.to(device)

loss_fn = loss_fn.to(device)

for data in train_dataloader:
    imgs, targets = data
    imgs = imgs.to(device)
    targets = targets.to(device)

**代码示例:

#以 CIFAR10 数据集为例,展示一下完整的模型训练套路,完成对数据集的分类问题**

import torch
import torchvision

from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import time

#定义训练的设备**
device = torch.device("cuda")

#准备数据集**
train_data = torchvision.datasets.CIFAR10(root="dataset", train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10(root="dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)

#获得数据集的长度 len(), 即length**
train_data_size = len(train_data)
test_data_size = len(test_data)

#格式化字符串, format() 中的数据会替换 {}**
print("训练数据集及的长度为: {}".format(train_data_size))
print("测试数据集及的长度为: {}".format(test_data_size))

#利用DataLoader 来加载数据**
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

#创建网络模型**
class Model(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )


    def forward(self, input):
        input = self.model(input)
        return input

model = Model()
model = model.to(device)                    # 在 GPU 上进行训练

#创建损失函数**
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)                # 在 GPU 上进行训练

#优化器**
learning_rate = 1e-2        # 1e-2 = 1 * (10)^(-2) = 1 / 100 = 0.01
optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate)

#设置训练网络的一些参数**
total_train_step = 0                        # 记录训练的次数
total_test_step = 0                         # 记录测试的次数
epoch = 10                                  # 训练的轮数

#`添加tensorboard**
writer = SummaryWriter("logs_train")
start_time = time.time()                    # 开始训练的时间
for i in range(epoch):
    print("------第 {} 轮训练开始------".format(i+1))

    # 训练步骤开始
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        outputs = model(imgs)               # 将训练的数据放入
        loss = loss_fn(outputs, targets)    # 得到损失值

        optimizer.zero_grad()               # 优化过程中首先要使用优化器进行梯度清零
        loss.backward()                     # 调用得到的损失,利用反向传播,得到每一个参数节点的梯度
        optimizer.step()                    # 对参数进行优化
        total_train_step += 1               # 上面就是进行了一次训练,训练次数 +1

        # 只有训练步骤是100 倍数的时候才打印数据,可以减少一些没有用的数据,方便我们找到其他数据
        if total_train_step % 100 == 0:
            end_time = time.time()          # 训练结束时间
            print("训练时间: {}".format(end_time - start_time))
            print("训练次数: {}, Loss: {}".format(total_train_step, loss))
            writer.add_scalar("train_loss", loss.item(), total_train_step)


    # 如何知道模型有没有训练好,即有咩有达到自己想要的需求
    # 我们可以在每次训练完一轮后,进行一次测试,在测试数据集上跑一遍,以测试数据集上的损失或正确率评估我们的模型有没有训练好

    # 顾名思义,下面的代码没有梯度,即我们不会利用进行调优
    total_test_loss = 0
    total_accuracy = 0                                      # 准确率
    with torch.no_grad():
        for data in test_dataloader:                        # 测试数据集中取数据
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = model(imgs)
            loss = loss_fn(outputs, targets)                # 这里的 loss 只是一部分数据(data) 在网络模型上的损失
            total_test_loss = total_test_loss + loss        # 整个测试集的loss
            accuracy = (outputs.argmax(1) == targets).sum() # 分类正确个数
            total_accuracy += accuracy                      # 相加

    print("整体测试集上的loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy / test_data_size))
    writer.add_scalar("test_loss", total_test_loss)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)
    total_test_loss += 1                                    # 测试完了之后要 +1

    torch.save(model, "model_{}.pth".format(i))
    print("模型已保存")

writer.close()

在这里插入图片描述

【注】对于网络模型和损失函数,直接调用 .cuda() 或者 .to() 即可。但是数据和标注需要返回变量

为了方便记忆,最好都返回变量

使用Google colab进行训练

二十二、Python执行时间的计算方法小结

  • 首先说一下我遇到的坑,生产上遇到的问题,我调度Python脚本执行并监控这个进程,python脚本运行时间远远大于python脚本中自己统计的程序执行时间。
  • 监控python脚本执行的时间是36个小时,而python脚本中统计自己执行的时间是4个小时左右。
  • 问题暴漏之后首先想到的是Linux出了问题,查找各种日志未发现有何异常。
  • 然后是想到python中用到的py2neo的写数据异步,阻塞进程执行。
  • 最后,终于找到问题的所在:python脚本使用统计时间的方式是time.clock(),而这种方式统计的是CPU的执行时间,不是程序的执行时间。
  • 接下来,就几种python的统计时间方式对比一下:

方法1:

import datetime
starttime = datetime.datetime.now()
#long running
#do something other
endtime = datetime.datetime.now()
print (endtime - starttime).seconds

datetime.datetime.now() 获取的是当前日期,在程序执行结束之后,这个方式获得的时间值为程序执行的时间。

方法2:

start = time.time()
#long running
#do something other
end = time.time()
print end-start

time.time() 获取自纪元以来的当前时间(以秒为单位)。如果系统时钟提供它们,则可能存在秒的分数。所以这个地方返回的是一个浮点型类型。这里获取的也是程序的执行时间。

方法3:

start = time.clock()
#long running
#do something other
end = time.clock()
print end-start

time.clock()返回程序开始或第一次被调用clock()以来的CPU时间。 这具有与系统记录一样多的精度。返回的也是一个浮点类型。 这里获得的是CPU的执行时间。

注:程序执行时间=cpu时间 + io时间 + 休眠或者等待时间

二十三、查看GPU占用率的方法以及解决神经网络训练过程中GPU占用率低的问题

一、查看GPU占用率的方法

  1. 使用终端命令nvidia-smi -l 3查看GPU使用情况。
    在这里插入图片描述

其中命令末尾的3表示3秒刷新一次,时间可自行修改。
在这里插入图片描述

  1. 使用任务管理器查看。
    在这里插入图片描述

GPU下方的选项需选择cuda才可以查看具体的GPU占用率。

二、解决神经网络训练过程中GPU占用率低的问题

GPU占用率过低可以根据自己显存的大小来调节batchsize和num_workers参数
在这里插入图片描述
在这里插入图片描述
博主这里通过调大workers以及batchsize的大小很好地提高了GPU的占用率,但是CPU的占用率依然是拉满,这是因为在训练神经网络的过程中在CPU主要负责对数据进行预处理以及记录训练日志,GPU主要负责进行前向传播与反向传播,CPU没有执行完步骤GPU无法开始执行任务,因此CPU总是占用率拉满而GPU无法获得很好的利用,可以通过将记录训练日志的代码注释达到释放CPU占用率从而提升训练速度的效果。

二十四、tensor.clone() 和 tensor.detach()

1 tensor.clone()

返回tensor的拷贝,返回的新tensor和原来的tensor具有同样的大小和数据类型

  • 原tensor的requires_grad=True
  • clone()返回的tensor是中间节点,梯度会流向原tensor,即返回的tensor的梯度会叠加在原tensor上
>>> import torch
>>> a = torch.tensor(1.0, requires_grad=True)
>>> b = a.clone()
>>> id(a), id(b)  # a和b不是同一个对象
(140191154302240, 140191145593424)
>>> a.data_ptr(), b.data_ptr()  # 也不指向同一块内存地址
(94724518544960, 94724519185792)
>>> a.requires_grad, b.requires_grad  # 但b的requires_grad属性和a的一样,同样是True
(True, True)
>>> c = a * 2
>>> c.backward()
>>> a.grad
tensor(2.)
>>> d = b * 3
>>> d.backward()
>>> b.grad  # b的梯度值为None,因为是中间节点,梯度值不会被保存
>>> a.grad  # b的梯度叠加在a上
tensor(5.)

原tensor的requires_grad=False

>>> import torch
>>> a = torch.tensor(1.0)
>>> b = a.clone()
>>> id(a), id(b)  # a和b不是同一个对象
(140191169099168, 140191154762208)
>>> a.data_ptr(), b.data_ptr()  # 也不指向同一块内存地址
(94724519502912, 94724519533952)
>>> a.requires_grad, b.requires_grad  # 但b的requires_grad属性和a的一样,同样是False
(False, False)
>>> b.requires_grad_()
>>> c = b * 2
>>> c.backward()
>>> b.grad
tensor(2.)
>>> a.grad  # None

2 tensor.detach()

从计算图中脱离出来。

返回一个新的tensor,新的tensor和原来的tensor共享数据内存,但不涉及梯度计算,即requires_grad=False。修改其中一个tensor的值,另一个也会改变,因为是共享同一块内存,但如果对其中一个tensor执行某些内置操作,则会报错,例如resize_、resize_as_、set_、transpose_。

>>> import torch
>>> a = torch.rand((3, 4), requires_grad=True)
>>> b = a.detach()
>>> id(a), id(b)  # a和b不是同一个对象了
(140191157657504, 140191161442944)
>>> a.data_ptr(), b.data_ptr()  # 但指向同一块内存地址
(94724518609856, 94724518609856)
>>> a.requires_grad, b.requires_grad  # b的requires_grad为False
(True, False)
>>> b[0][0] = 1
>>> a[0][0]  # 修改b的值,a的值也会改变
tensor(1., grad_fn=<SelectBackward>)
>>> b.resize_((4, 3))  # 报错

RuntimeError: set_sizes_contiguous is not allowed on a Tensor created from .data or .detach().
3. tensor.clone().detach() 还是 tensor.detach().clone()

两者的结果是一样的,即返回的tensor和原tensor在梯度上或者数据上没有任何关系,一般用前者。

二十五、torch.stack()函数

torch.stack()函数:
torch.stack(sequence, dim=0)
1.函数功能:

  • 沿一个新维度对输入张量序列进行连接,序列中所有张量应为相同形状;stack
    函数返回的结果会新增一个维度,而stack()函数指定的dim参数,就是新增维度的(下标)位置。

2.参数列表:

  • sequence:参与创建新张量的几个张量;
  • dim:新增维度的(下标)位置,当dim = -1时默认最后一个维度;
  • 返回值:输出张量。
# 输入张量信息:
# a=[i][j]
# b=[i][j]
 
c = stack((a,b), dim=0)
 
# 输出张量信息:
# c[0][i][j] = a[i][j]
# c[1][i][j] = b[i][j]

3.应用举例:

1)

import torch
a = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = torch.tensor([[11, 22, 33], [44, 55, 66], [77, 88, 99]])
c = torch.stack([a, b], 0)
print(a)
print(b)
print(c)
 
# 输出信息:
tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])
tensor([[11, 22, 33],
        [44, 55, 66],
        [77, 88, 99]])
tensor([[[ 1,  2,  3],
         [ 4,  5,  6],
         [ 7,  8,  9]],
 
        [[11, 22, 33],
         [44, 55, 66],
         [77, 88, 99]]])

2)

import torch
a = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = torch.tensor([[11, 22, 33], [44, 55, 66], [77, 88, 99]])
c = torch.stack([a, b], 1)
print(a)
print(b)
print(c)
 
# 输出信息:
tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])
tensor([[11, 22, 33],
        [44, 55, 66],
        [77, 88, 99]])
tensor([[[ 1,  2,  3],
         [11, 22, 33]],
 
        [[ 4,  5,  6],
         [44, 55, 66]],
 
        [[ 7,  8,  9],
         [77, 88, 99]]])

3)

import torch
a = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = torch.tensor([[11, 22, 33], [44, 55, 66], [77, 88, 99]])
c = torch.stack([a, b], 2)
print(a)
print(b)
print(c)
 
# 输出信息:
tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])
tensor([[11, 22, 33],
        [44, 55, 66],
        [77, 88, 99]])
tensor([[[ 1, 11],
         [ 2, 22],
         [ 3, 33]],
 
        [[ 4, 44],
         [ 5, 55],
         [ 6, 66]],
 
        [[ 7, 77],
         [ 8, 88],
         [ 9, 99]]])

4)

import torch
a = torch.tensor([[[1, 2, 3], [4, 5, 6], [7, 8, 9]],[[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
b = torch.tensor([[[11, 22, 33], [44, 55, 66], [77, 88, 99]], [[11, 22, 33], [44, 55, 66], [77, 88, 99]]])
c = torch.stack([a, b], 3)
print(a)
print(b)
print(c)
 
# 输出信息:
tensor([[[1, 2, 3],
         [4, 5, 6],
         [7, 8, 9]],
 
        [[1, 2, 3],
         [4, 5, 6],
         [7, 8, 9]]])
tensor([[[11, 22, 33],
         [44, 55, 66],
         [77, 88, 99]],
 
        [[11, 22, 33],
         [44, 55, 66],
         [77, 88, 99]]])
tensor([[[[ 1, 11],
          [ 2, 22],
          [ 3, 33]],
         [[ 4, 44],
          [ 5, 55],
          [ 6, 66]],
         [[ 7, 77],
          [ 8, 88],
          [ 9, 99]]],
        [[[ 1, 11],
          [ 2, 22],
          [ 3, 33]],
         [[ 4, 44],
          [ 5, 55],
          [ 6, 66]],
         [[ 7, 77],
          [ 8, 88],
          [ 9, 99]]]])

二十六、数据并行加载在多个gpu上

一般数据读取会涉及两个函数:

  • 1.get_dataset(data_path,cfg)。其中的__getitem__(self,idx)函数,这个函数用于读取给定路径下的数据并处理成所需的格式,返回一个数据的data,data一般为字典格式。作用:规范数据,path–>tensor(data)。
  • 2.dataloader(get_dataset),可直接调用pytorch中的接口。通过
for i ,data_batch in enumerate(dataloader)

调用前文提到的__getitem__,返回batch_size个data。之后按照pytirch中dataload函数,将多个data进一步处理,返回data_batch。

##先初始化model的网络结构,再将model定义为数据并行的状态
model = DataParallel(model.cuda(),device_ids=[0,1,2]##训练数据前向传播
model(data_batch)

之后在使用model(data_batch)时,数据便会加载至model所在的gpu上。

pytorch中的DataLoader

def collate(data_batch, samples_per_gpu=1, num_gpus=1):
    ...
    return data
 
 
##partial 冻结collate函数中的参数
data_loader = DataLoader(
        dataset,
        batch_size=batch_size,
        sampler=sampler,
        num_workers=num_workers,
        collate_fn= partial(collate, samples_per_gpu = imgs_per_gpu,num_gpus=num_gpus),
        pin_memory=False,
        **kwargs)

shuffle是否打乱数据,True为打乱的意思,False反之。

sampler可以是自己定义的一个函数,用于将数据切块,比如数据索引为[0,1,2,3,4,5,6,7,8,9],batch_size为2,则切块后为[[0,1],[2,3],[4,5],…],在sampler函数中也可先将索引打乱再切块。若sampler不为None,则shuffle必须为False。

collate_fn可以是自己定义的函数,用于将读取的多个data进一步处理,返回值便是data_batch。

torch.nn.parallel.DataParallel函数
在model(data_batch)运行forward(x)前,会先在DataParallel的scatter函数中将data_batch加载至gpu上。
在这里插入图片描述

scatter调用scatter_map函数,data_batch中的数据以torch.tensor为最小单位,于是遍历每个tensor。

  • 若gpu数量为1,则整个tensor加载至指定gpu上。
  • 若gpu数量多个,则会将tensor按第一维度平分至多个gpu上。一般第一维是batch_size,便可适当的将数据平分至各个gpu上。

加载上gpu时出现的问题:
2D图像检测时,图片大小相同,所以把多张图像torch.stack([tensors],dim=0),可以运行出ncw*h大小的tensor。但在3D点云检测时,每帧图像所对应的点云数量不相同,大小不同,所以torch.stack无法运行,即无法将多帧点云合并,不合并会导致多gpu加载时将一帧点云分成多份加载至各个gpu上。

解决方案
mmcv中的DataContainer,将多帧点云形成的多个tensor存在与list中,再mmcv.parallel.DataContainer(list),之后以list为单元,将len(list)平分至各个gpu上。

另外使用mmcv.parallel.MMDataParallel代替pytorch中的DataParallel。

二十七、Pytorch collate_fn用法

二十八、Pytorch基础——torch.randperm

torch.randperm(n):将0~n-1(包括0和n-1)随机打乱后获得的数字序列,函数名是random permutation缩写

【sample】

 torch.randperm(10)
===> tensor([2, 3, 6, 7, 8, 9, 1, 5, 0, 4])

二十九、详解Tensor用法

Tensor的操作
如果本文对你有帮助,欢迎点赞、订阅以及star我的项目。
你的支持是我创作的最大动力!

张量的数据属性与 NumPy 数组类似,如下所示:

在这里插入图片描述

张量的操作主要包括张量的结构操作和张量的数学运算操作。

  • Tensor的结构操作包括:创建张量,查看属性,修改形状,指定设备,数据转换, 索引切片,广播机制,元素操作,归并操作;
  • Tensor的数学运算包括:标量运算,向量运算,矩阵操作,比较操作。

创建张量

Pytorch中创建张量的方法有很多,如下图所示:

在这里插入图片描述

在深度学习过程中最多使用5 55个维度的张量:标量(0维张量),向量(1维度张量),矩阵(2维张量),3维张量,4维张量,5维张量

创建标量**(0维张量)**
仔细观察下述代码,看看有什么区别:

x = torch.tensor(2)
print(x, x.shape, x.type())
y = torch.Tensor(2)
print(y, y.shape, y.type())
tensor(2) torch.Size([]) 
torch.LongTensortensor([0., 0.]) torch.Size([2]) 
torch.FloatTensor

注意到了torch.tensor与torch.Tensor的区别没?一字之差,结果差别却很大。

  • torch.Tensor(2) 使用全局默认 dtype(FloatTensor),返回一个size为2 22的向量,初值为 0 00;
  • torch.tensor(2) 返回常量 2 22,数据类型从数据推断而来,其中的2 22表示的是数据值。

创建向量(1维度张量)
向量只不过是一个元素序列的数组。例如,表示一个地区一段时间的气温。

x = torch.FloatTensor([23.5, 24.6, 25.9, 26.1])
print(x, x.shape, x.type())
tensor([23.5000, 24.6000, 25.9000, 26.1000]) torch.Size([4]) 
torch.FloatTensor

创建矩阵(2维向量)
从上面可知,创建矩阵的方式有很多,我们选择 from_numpy 的方式将 numpy 数组转换成 torch 张量。下面以波士顿房价的数据集为例子,它包含在机器学习包scikit-learn中。该数据集包含了 506 个样本,其中每个样本有 13 个特征。

from sklearn.datasets import load_bostonboston = load_boston() # 下载数据集
boston_tensor=torch.from_numpy(boston.data)
print(boston_tensor[:2])
print(boston_tensor.shape)
print(boston_tensor.type())
tensor([[6.3200e-03, 1.8000e+01, 2.3100e+00, 0.0000e+00, 5.3800e-01, 6.5750e+00,     6.5200e+01, 4.0900e+00, 1.0000e+00, 2.9600e+02, 1.5300e+01, 3.9690e+02,     4.9800e+00],    [2.7310e-02, 0.0000e+00, 7.0700e+00, 0.0000e+00, 4.6900e-01, 6.4210e+00,     7.8900e+01, 4.9671e+00, 2.0000e+00, 2.4200e+02, 1.7800e+01, 3.9690e+02,     9.1400e+00]], dtype=torch.float64)
torch.Size([506, 13])
torch.DoubleTensor

最常见的三维张量就是图片,例如[ 224 , 224 , 3 ] [224, 224, 3][224,224,3],下面我们演示如何加载图片数据。

from PIL import Image
panda = np.array(Image.open("../images/panda.jpg").resize((224,224)))
panda_tensor=torch.from_numpy(panda)
print(panda_tensor.size())
print(panda_tensor.dtype)
plt.imshow(panda_tensor)
torch.Size([224, 224, 3])torch.uint8

在这里插入图片描述

创建4维张量
4维张量最常见的例子就是批图像。例如,加载一批 [ 64 , 224 , 224 , 3 ] [64, 224, 224, 3][64,224,224,3] 的图片,其中 64 6464 表示批尺寸,[ 224 , 224 , 3 ] [224, 224, 3][224,224,3] 表示图片的尺寸。

from glob import glob
data_path = "./data/cats/"
imgs = glob(data_path+'*.jpg')
imgs_np = np.array([np.array(Image.open(img).resize((224,224))) for img in imgs])
imgs_np = imgs_np.reshape(-1, 224, 224, 3)
imgs_tensor=torch.from_numpy(imgs_np)
print(imgs_tensor.shape)
print(imgs_tensor.dtype)
torch.Size([397, 224, 224, 3])
torch.uint8

上面代码中一共读取了 397 397397 张图片。

创建5维张量
使用5维度张量的例子是视频数据。视频数据可以划分为片段,一个片段又包含很多张图片。例如,[ 32 , 30 , 224 , 224 , 3 ] [32, 30, 224, 224, 3][32,30,224,224,3] 表示有 32 3232 个视频片段,每个视频片段包含 30 3030 张图片,每张图片的尺寸为 [ 224 , 224 , 3 ] [224, 224, 3][224,224,3]。下面,我们模拟产生这样一个尺寸的5维数据(注意:只是模拟产生5 55维数据,并不是真的视频数据)。

video_tensor=torch.randn(32,30,224,224,3)
print(video_tensor.shape)
print(video_tensor.dtype)
torch.Size([32, 30, 224, 224, 3])
torch.float32

查看属性
张量有很多属性,下面我们看看常用的属性有哪些?

  • tensor.shape,tensor.size(): 返回张量的形状;
  • tensor.ndim:查看张量的维度;
  • tensor.dtype,tensor.type():查看张量的数据类型;
  • tensor.is_cuda:查看张量是否在GPU上;
  • tensor.grad:查看张量的梯度;
  • tensor.requires_grad:查看张量是否可微。
tensor = torch.randn(2,3)
print("形状: ", tensor.shape, tensor.size())
print("维度: ", tensor.ndim)
print("类型: ", tensor.dtype, tensor.type())
print("cuda: ", tensor.is_cuda)
print("梯度: ", tensor.grad)
形状:  torch.Size([2, 3]) torch.Size([2, 3])
维度:  2
类型:  torch.float32 torch.FloatTensor
cuda:  False    
梯度:  None

其中,torch.FloatTensor 就是32 3232位的浮点数。

修改张量的形状
在处理数据和构建网络的时候,时常需要修改 Tensor 的形状。涉及到修改形状的常见函数如下:

  • tensor.numel():计算Tensor的元素个数;
  • tensor.view(*shape):修改Tensor的形状。view()返回的Tensor与源Tensor共享内容。使用view必须要求源Tensor是连续的,否则会执行失败。view(-1)实现展平;
  • tensor.resize(*shape):功能类似与 view,resize不要求Tensor内存连续;
  • tensor.reshape(*shape):修改Tensor的形状,Reshape返回新的Tensor;
  • tensor.unsqueeze(pos):在指定位置添加一个维度;
  • tensor.squeeze():消除维度为1 11的维。
import torch
x = torch.randn(2,3)
print("元素个数: {}".format(x.numel()))
# view调整尺寸
print("\ntensor.view(3,2): \n{}".format(x.view(3,2)))
print("tensor.view(-1): {}".format(x.view(-1)))
# resize调整尺寸
print("\ntensor.resize(3,2): \n{}".format(x.resize(3,2)))
# reshape调整尺寸
print("\ntensor.reshape(3,2): \n{}".format(x.reshape(3,2)))
print("tensor.reshape(-1): {}".format(x.reshape(-1)))
#添加一个维度
x123 = x.unsqueeze(0)
x213 = x.unsqueeze(1)
x231 = x.unsqueeze(2)
print("\ntensor.unsqueeze(0): {}".format(x123.shape))
print("tensor.unsqueeze(1): {}".format(x213.shape))
print("tensor.unsqueeze(2): {}".format(x231.shape))
# 去掉维度为1的维
print("\ntensor.squeeze(): {}".format(x123.squeeze().shape))
print("tensor.squeeze(): {}".format(x213.squeeze().shape))
print("tensor.squeeze(): {}".format(x231.squeeze().shape))
元素个数: 6
tensor.view(3,2): 
tensor([[ 0.1274, -1.5990],        [-0.8852, -1.3436],        [-0.7716,  1.5765]])
tensor.view(-1): tensor([ 0.1274, -1.5990, -0.8852, -1.3436, -0.7716,  1.5765])
tensor.resize(3,2): 
tensor([[ 0.1274, -1.5990],        [-0.8852, -1.3436],        [-0.7716,  1.5765]])
tensor.reshape(3,2): 
tensor([[ 0.1274, -1.5990],        [-0.8852, -1.3436],        [-0.7716,  1.5765]])
tensor.reshape(-1): 
tensor([ 0.1274, -1.5990, -0.8852, -1.3436, -0.7716,  1.5765])
tensor.unsqueeze(0): torch.Size([1, 2, 3])
tensor.unsqueeze(1): torch.Size([2, 1, 3])
tensor.unsqueeze(2): torch.Size([2, 3, 1])
tensor.squeeze(): torch.Size([2, 3])
tensor.squeeze(): torch.Size([2, 3])
tensor.squeeze(): torch.Size([2, 3])

在上述函数中,有三个函数都可以调整Tensor的尺寸:view,reshape,resize。他们之间有什么区别嘛?

  • reshape可以由torch.reshape(),tensor.reshape()调用,而view只能通过tensor.view()调用。
  • view()方法只能改变连续的张量,否则必须先调用.contiguous()方法使内存连续;.reshape()方法不受此限制。方法.transpose(), .permute()会使的Tensor在内存中不连续。
  • view() 返回的Tensor与源Tensor共享内存;.reshape()返回的Tensor与源Tensor不共享内存;
  • resize() 与 .reshape()效果类似。
  • 如果只想重塑Tensor,建议使用.reshape;如果关注内存希望两个Tensor共享内存,建议使用.view()。
    指定设备
    PyTorch 为CPU 和 GPU 提供了不同的张量实现。每个张量都可以转化到 GPU 中,以便大规模计算。创建Tensor时,默认指定的设备是CPU。

创建Tensor时,我们可以通过torch.tensor([…],dtype=,device=‘cpu/cuda’) 指定Tensor所属的设置是CPU还是GPU;也可以通过 tensor.to(device=cpu/cuda)或者tensor.cuda(),tensor.cpu() 把张量转化到指定的设备上。

# 创建CPU上的张量
tensor_cpu = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=torch.float64, device='cpu')
print(f"tensor_cpu = \n{tensor_cpu}")
# 创建GPU上的张量
tensor_gpu = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=torch.float64, device='cuda')
print(f"tensor_gpu = \n{tensor_gpu}")
tensor_cpu = tensor([[1., 2.],        [3., 4.],        [5., 6.]], dtype=torch.float64)
tensor_gpu = tensor([[1., 2.],        [3., 4.],        [5.,6.]],device='cuda:0',dtype=torch.float64)
# cpu -> gpu
tensor_gpu_cpu = tensor_gpu.to(device='cpu')print(f"tensor_gpu_cpu = {tensor_gpu_cpu}")
# gpu -> cpu
tensor_cpu_gpu = tensor_cpu.to(device='cuda')print(f"tensor_cpu_gpu = {tensor_cpu_gpu}")
tensor_gpu_cpu = tensor([[ 5., 10.],        [15., 20.],        [25., 30.]], dtype=torch.float64)
tensor_cpu_gpu = tensor([[ 5., 10.],        [15., 20.],        [25., 30.]], device='cuda:0', dtype=torch.float64)

数据转换
有时候,我们需要把Tensor转换成普通的数据,我们可以使用下列方式进行操作:

  • .numpy():把Tensor转换成numpy数据;
  • .item():如果Tensor为单元素,则返回Python标量;
  • .detach():返回一个与当前计算图分离且无梯度的新张量;
# 把tensor转化为numpy array
f = torch.tensor([1.0, 2.0, 3.0, 4.0], dtype=torch.float64)
f_numpy = f.numpy()
print(type(f_numpy), f_numpy) # <class 'numpy.ndarray'> [1. 2. 3. 4.]
# 把单元素Tensor转化为python标量
f_item = f[0].item()
print(type(f_item), f_item) # <class 'float'> 1.0
# 获取分离计算图的新张量
a = torch.tensor([1.0, 2.0], requires_grad=True)
b = torch.tensor([3.0, 4.0], requires_grad=True)
c = a + bd = c.detach()
print("c.grad: {}; d.grad: {}".format(c.requires_grad, d.requires_grad))
# c is grad: True; d is grad: False

索引切片
Tensor的索引切片操作与Numpy类似,也是Pytorch中经常使用的操作方式。需要注意的是,一般情况下索引结果与源数据共享内存。从Tensor中获取元素除了可以通过索引,也可以使用专有的函数。

常见的操作函数如下:

★提取元素的操作:

  • torch.index_select(input, dim, index):在指定维度上选择一些行或列;
  • torch.nonzero(input):获取非0元素的下标;
  • torch.masked_select(input, mask):使用二元值(真值表)进行选择元素;
  • torch.gather(input, dim, index):在指定的维度上选择数据,输出的形状与index一致;
  • torch.take(input, index):将输入看成一维数组,输出与index同形状。

★会对元素进行修改的操作:

  • torch.scatter_(input, dim, index, src):为gather的反操作,根据指定索引填充数据;
  • torch.where(condition, x, y):根据条件进行选择填充,这个操作函数用的非常多;
  • torch.masked_fill:使用二元值(真值表)进行填充元素;
  • torch.index_fill:使用下标进行填充。
    代码从两部分进行演示,先展示提取元素的操作:
# 索引操作
>>> x = torch.randn(2, 3)
tensor([[ 0.3607, -0.2859, -0.3938],        [ 0.2429, -1.3833, -2.3134]])
# index_select
>>> x = torch.randn(2, 3)
>>> torch.index_select(x, dim=1, index=torch.tensor([0,2])) tensor([[-0.5883,  0.4322],        [ 0.4612, -0.2675]])
# masked_select 
>>> x = torch.randn(2, 3)
>>> mask = x>0 # 产生真值Tensor
>>> select2 = torch.masked_select(x, mask) # 选择真值为True的元素
>>> select2
tensor([0.5605, 0.5895])
# nonzeros
>>> torch.nonzeros(mask)tensor([[0, 1],        [1, 0]])
# gather
>>> #out[i][j] = input[index[i][j]][j]  # if dim == 0
>>> #out[i][j] = input[i][index[i][j]]  # if dim == 1
>>> index = torch.LongTensor([[0, 1, 1]])
>>> gather1 = torch.gather(x, dim=0, index=index)
>>> print("gather1: {}".format(gather1))
gather1: tensor([[ 0.3607, -1.3833, -2.3134]])
    
>>> index = torch.LongTensor([[0,1,1],[1,1,1]])
>>> a = torch.gather(x, dim=1, index=index)
>>> print("gather2: {}".format(a))
gather2: tensor([[ 0.3607, -0.2859, -0.2859],        [-1.3833, -1.3833, -1.3833]])
# take
>>> src = torch.tensor([[4, 3, 5], [6, 7, 8]])
>>> out = torch.take(src, index=torch.tensor([0, 3, 5]))
>>> print("out: {}".format(out))
out: tensor([4, 6, 8])

会对元素进行修改的操作:

# scatter_
>>> z = torch.zeros(2,3)
>>> b = z.scatter_(dim=1, index, a)
>>> print("b: {}".format(b))b: tensor([[ 0.3607, -0.2859,  0.0000],        [ 0.0000, -1.3833,  0.0000]])
# where
>>> x = torch.rand(3, 2)
>>> y = torch.ones(3, 2)>>> result = torch.where(x>0.5, x, y)
>>> print("result: {}".format(result))
result: tensor([[1.0000, 1.0000],        [1.0000, 0.8238],        [0.5557, 0.9770]])
# masked_fill
>>> x = torch.rand(3, 2)
>>> torch.masked_fill(x, x < 0.5, -1.0)
tensor([[-1.0000, -1.0000],        [ 0.8935, -1.0000],        [-1.0000, -1.0000]])
# index_fill
>>> x = torch.rand(3, 2)
>>> torch.index_fill(x , dim = 0, index = torch.tensor([0,1]), value = 100)
tensor([[100.0000, 100.0000],        [100.0000, 100.0000],        [  0.9798,   0.5548]])

在上述函数中,torch.gather() 和 torch.scatter_() 让人很难理解。借用官网的一段代码进行详细说明一下:

# gather
out[i][j][k] = input[index[i][j][k]][j][k]  # if dim == 0
out[i][j][k] = input[i][index[i][j][k]][k]  # if dim == 1
out[i][j][k] = input[i][j][index[i][j][k]]  # if dim == 2

>>> t = torch.tensor([[1, 2], [3, 4]])
>>> tor.gather(t, dim=1, index=torch.tensor([[0, 0], [1, 0]]))
tensor([[ 1,  1],        [ 4,  3]])

我们可以想象在index上进行填数,因为输出和index是一样的形状。如果dim=1,意为在index每个元素值作为j,该元素所在的行作为i,在t中进行取值填充;如果dim=0,意为在index 每个元素值作为i,该元素所在的列作为j,在t中进行取值填充。

# scatter_
self[index[i][j][k]][j][k] = src[i][j][k]  # if dim == 0
self[i][index[i][j][k]][k] = src[i][j][k]  # if dim == 1
self[i][j][index[i][j][k]] = src[i][j][k]  # if dim == 2
>>> src = torch.arange(1, 11).reshape((2, 5))
>>> src
tensor([[ 1,  2,  3,  4,  5],        [ 6,  7,  8,  9, 10]])
>>> index = torch.tensor([[0, 1, 2, 0]])
>>> torch.zeros(3, 5, dtype=src.dtype).scatter_(0, index, src)
tensor([[1, 0, 0, 4, 0],        [0, 2, 0, 0, 0],        [0, 0, 3, 0, 0]])

scatter_ 的作用与 gather 刚好相反,取值方式与 gather 也类似。

广播机制

广播机制是向量运算的重要技巧。下面演示Tensor如何执行广播操作。torch.broadcast_tensors 可以将多个张量根据广播规则转换成相同的维度。

>>> A = np.arange(0, 40, 10).reshape(4, 1)
>>> B = np.arange(0, 3)#把ndarray转换为Tensor
>>> A1 = torch.from_numpy(A)  #形状为4x1
>>> B1 = torch.from_numpy(B)  #形状为3
#Tensor自动实现广播
>>> C = A1 + B1 # 形状为4+3
tensor([[ 0,  1,  2],        [10, 11, 12],        [20, 21, 22],        [30, 31, 32]], dtype=torch.int32)
>>> A_broad, B_broad = torch.broadcast_tensors(A1, B1)
>>> C = A_broad + B_broad
tensor([[ 0,  1,  2],        [10, 11, 12],        [20, 21, 22],        [30, 31, 32]], dtype=torch.int32)

归并分割

归并,意为对Tensor进行合并,这类操作的输出形状大于输入形状,是升维操作;分割,意为对Tensor进行切割细分,这类操作的输出形状小于输入形状,是降维操作。常见的操作函数如下:

  • torch.cat(tensor, dim=0):在指定维度连接多个Tensor,不会增加维度;
  • torch.stack(tensor, dim=0):在指定维度堆叠多个Tensor,会增加维度;
  • torch.split(tensor, split, dim=0):将一个张量分割为多个张量,不会减少维度,是torch.cat()的反向操作。
# cat操作
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497]])
>>> torch.cat((x, x, x), 0)
tensor([[ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497],        [ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497],        [ 0.6580, -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497]])
>>> torch.cat((x, x, x), 1)
tensor([[ 0.6580, -1.0969, -0.4614,  0.6580, -1.0969, -0.4614,  0.6580,         -1.0969, -0.4614],        [-0.1034, -0.5790,  0.1497, -0.1034, -0.5790,  0.1497, -0.1034,         -0.5790,  0.1497]])
# stack
>>> x = torch.randn(2, 3)
>>> torch.stack((x, x, x), dim = 0).size()
torch.Size([3, 2, 3])
>>> torch.stack((x, x, x), dim = 1).size()
torch.Size([2, 3, 3])
>>> torch.stack((x, x, x), dim = 2).size()
torch.Size([2, 3, 3])
# split
>>> a = torch.arange(10).reshape(5,2)
>>> atensor([[0, 1],        [2, 3],        [4, 5],        [6, 7],        [8, 9]])
>>> torch.split(a, 2)(tensor([[0, 1],         [2, 3]]), tensor([[4, 5],         [6, 7]]), tensor([[8, 9]]))
>>> torch.split(a, [1,4])(tensor([[0, 1]]), 
                          tensor([[2, 3],         [4, 5],         [6, 7],         [8, 9]]))

元素操作
Tensor中也有很多元素操作的函数,也经常使用,例如:

  • abs/add:绝对值和加法;
  • ceil/floor:向上取整和向下取整;
  • clamp(t, min, max):将元素限定在指定区域内;
  • round(t):保留整数部分,四舍五入;
  • trunc(t):保留整数部分,向0归整;
  • sigmoid/tanh/softmax:激活函数。
>>> t = torch.randn(1, 3)
>>> torch.clamp(t, 0, 1)
tensor([[0., 0., 1.]])

比较操作
比较操作一般是进行逐个元素比较,常用的函数如下:

  • eq:比较两个Tensor是否相等,支持广播操作;
  • equal:比较两个Tensor是否具有相同的值和shape;
  • max/min:返回最值;
  • topk:返回指定维度上最高的K个值。
>>> x=torch.linspace(0,10,6).view(2,3)
>>> torch.max(x) 
tensor(10.)
>>> torch.max(x,dim=0)
torch.return_types.max(values=tensor([ 6.,  8., 10.]),
                       indices=tensor([1, 1, 1]))
>>> torch.topk(x, k=1, dim=0)
torch.return_types.topk(
    values=tensor([[ 6.,  8., 10.]]),
    indices=tensor([[1, 1, 1]]))

标量运算
Tensor的数学运算符分为:标量运算符,向量运算符,矩阵运算符,加减乘除,乘方,三角函数,指数,对数,逻辑比较运算等标量运算符。标量运算符的特点是逐元素运算。

>>> a = torch.tensor([[1.0,2],[-3,4.0]])
>>> b = torch.tensor([[5.0,6],[7.0,8.0]])
>>> a+b # 加法
>>> a-b # 减法
>>> a*b # 乘法
>>> a/b # 除法
>>> torch.remainder(x, 2) # 取余数
>>> a**2 # 乘方
>>> a**(0.5) # 开方
>>> torch.sqrt(a) # 开方
>>> a%3 # 求模
>>> torch.fmod(a, 2)
>>> a//3 # 地板除法
>>> a >= 2 # torch.ge(a, 2)
>>> (a >= 2)&(a <= 3) # 逻辑运算
>>> (a >= 2)|(a <= 3) # 逻辑运算

向量运算
向量运算符只在一个特定轴上运算,将一个向量映射到一个标量或者另外一个向量。

三十、统计值

a=torch.arange(1,10).float()
print(torch.sum(a))
print(torch.mean(a))
print(torch.max(a))
print(torch.min(a))
print(torch.prod(a))#累乘
print(torch.std(a))#标准差
print(torch.var(a))#方差
print(torch.median(a))#中位数

矩阵操作
深度学习中存在大量的矩阵运算,常见的算法有两种:逐个元素相乘和点积相乘。Pytorch中常用的矩阵函数如下:

  • dot(t1, t2):计算Tensor(1D)的内积或者点积;
  • mm(mat1,mat2)/bmm(batch1, batch2):计算矩阵乘法/含batch的3D矩阵的乘法
  • mv(t1, v1):计算矩阵与向量的乘法;
  • t:转置;
  • svd(t):计算t的SVD分解。
printf("hello world!");

三十一、Python可视化mhd格式和raw格式的医学图像并保存的方法

mhd格式的文件里面包含的是raw图像的一些头信息,比如图片大小,拍摄日期等等,那么如何可视化图像呢?

import cv2
import SimpleITK as sitk
import matplotlib.pyplot as plt
import numpy as np
image =sitk.ReadImage(path)
image = sitk.GetArrayFromImage(image)
#image = np.squeeze(image[slice, ...]) # if the image is 3d, the slice is integer
plt.imshow(image,cmap='gray')
plt.axis('off')
plt.show()
cv2.imwrite('1.png',image)

这里path是mhd文件的路径,并且在该路径下需要有相应的raw文件
这里展示一下我的mdk文件和保存的png文件
在这里插入图片描述

在这里插入图片描述
在这里如果cv2和ITK模块没有的话可以用pip安装

pip install opencv-python 
pip install SimpleITK

三十二、DICOM文件格式转MHD,RAW文件格式

介绍:
医学图像通常是通过dicom文件格式进行存储的,但为了便于读取和使用也常常将其转化为:每个病人一个mhd文件和一个同名的raw文件的格式,mhd即meta header data,数据头部信息,raw存储了像素信息,如下:
在这里插入图片描述
其中mhd文件存储了dicom的头部信息,由于头部信息很多生成mhd的方法有所不同,所以这里展示了通过ITK(ITK是美国国家卫生院下属的国立医学图书馆开发的一款医学图像处理软件包,是一个开源的、跨平台的影像分析扩展软件工具。)产生mhd文件的文件内容,有以下头部信息:

在这里插入图片描述

实现
下面是实现的代码,里面有详细的注释:

import cv2
import os
import pydicom
import numpy
import SimpleITK
 
# 路径和列表声明
PathDicom = "./DicomResource/"  # 与python文件同一个目录下的文件夹,存储dicom文件
SaveRawDicom = "./SaveRaw/"     # 与python文件同一个目录下的文件夹,用来存储mhd文件和raw文件
lstFilesDCM = []
 
# 将PathDicom文件夹下的dicom文件地址读取到lstFilesDCM中
for dirName, subdirList, fileList in os.walk(PathDicom):
	for filename in fileList:
		if ".dcm" in filename.lower():  # 判断文件是否为dicom文件
			print(filename)
			lstFilesDCM.append(os.path.join(dirName, filename))  # 加入到列表中
 
# 第一步:将第一张图片作为参考图片,并认为所有图片具有相同维度
RefDs = pydicom.read_file(lstFilesDCM[0])  # 读取第一张dicom图片
 
# 第二步:得到dicom图片所组成3D图片的维度
ConstPixelDims = (int(RefDs.Rows), int(RefDs.Columns), len(lstFilesDCM)) # ConstPixelDims是一个元组
 
# 第三步:得到x方向和y方向的Spacing并得到z方向的层厚
ConstPixelSpacing = (float(RefDs.PixelSpacing[0]), float(RefDs.PixelSpacing[1]), float(RefDs.SliceThickness))
 
# 第四步:得到图像的原点
Origin = RefDs.ImagePositionPatient
 
# 根据维度创建一个numpy的三维数组,并将元素类型设为:pixel_array.dtype
ArrayDicom = numpy.zeros(ConstPixelDims, dtype=RefDs.pixel_array.dtype)  # array is a numpy array
 
# 第五步:遍历所有的dicom文件,读取图像数据,存放在numpy数组中
i = 0
for filenameDCM in lstFilesDCM:
	ds = pydicom.read_file(filenameDCM)
	ArrayDicom[:, :, lstFilesDCM.index(filenameDCM)] = ds.pixel_array
	cv2.imwrite("out_" + str(i) + ".png", ArrayDicom[:, :, lstFilesDCM.index(filenameDCM)])
	i += 1
 
# 第六步:对numpy数组进行转置,即把坐标轴(x,y,z)变换为(z,y,x),这样是dicom存储文件的格式,即第一个维度为z轴便于图片堆叠
ArrayDicom = numpy.transpose(ArrayDicom, (2, 0, 1))
 
# 第七步:将现在的numpy数组通过SimpleITK转化为mhd和raw文件
sitk_img = SimpleITK.GetImageFromArray(ArrayDicom, isVector=False)
sitk_img.SetSpacing(ConstPixelSpacing)
sitk_img.SetOrigin(Origin)
SimpleITK.WriteImage(sitk_img, os.path.join(SaveRawDicom, "sample" + ".mhd"))

结果:
将dicom文件进行转化,产生mhd和raw文件
在这里插入图片描述

三十三、在keras中model.fit_generator()和model.fit()的区别说明

首先Keras中的fit()函数传入的x_train和y_train是被完整的加载进内存的,当然用起来很方便,但是如果我们数据量很大,那么是不可能将所有数据载入内存的,必将导致内存泄漏,这时候我们可以用fit_generator函数来进行训练。

keras中文文档

fit

fit(x=None, y=None, batch_size=None, epochs=1, verbose=1, callbacks=None, validation_split=0.0, validation_data=None, shuffle=True, class_weight=None, sample_weight=None, initial_epoch=0, steps_per_epoch=None, validation_steps=None)

以给定数量的轮次(数据集上的迭代)训练模型。

参数

  • x: 训练数据的 Numpy 数组(如果模型只有一个输入), 或者是 Numpy 数组的列表(如果模型有多个输入)。
    如果模型中的输入层被命名,你也可以传递一个字典,将输入层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例如 TensorFlow
    数据张量)数据,x 可以是 None(默认)。

  • y: 目标(标签)数据的 Numpy 数组(如果模型只有一个输出), 或者是 Numpy 数组的列表(如果模型有多个输出)。
    如果模型中的输出层被命名,你也可以传递一个字典,将输出层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例如 TensorFlow
    数据张量)数据,y 可以是 None(默认)。

  • batch_size: 整数或 None。每次梯度更新的样本数。如果未指定,默认为 32。

  • epochs: 整数。训练模型迭代轮次。一个轮次是在整个 x 和 y 上的一轮迭代。 请注意,与 initial_epoch
    一起,epochs 被理解为 「最终轮次」。模型并不是训练了 epochs 轮,而是到第 epochs 轮停止训练。

  • verbose: 0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行。

  • callbacks: 一系列的 keras.callbacks.Callback 实例。一系列可以在训练时使用的回调函数。 详见
    callbacks。

  • validation_split: 0 和 1 之间的浮点数。用作验证集的训练数据的比例。 模型将分出一部分不会被训练的验证数据,并将在每一轮结束时评估这些验证数据的误差和任何其他模型指标。 验证数据是混洗之前 x 和y 数据的最后一部分样本中。

  • validation_data: 元组 (x_val,y_val) 或元组 (x_val,y_val,val_sample_weights), 用来评估损失,以及在每轮结束时的任何模型度量指标。 模型将不会在这个数据上进行训练。这个参数会覆盖 validation_split。

  • shuffle: 布尔值(是否在每轮迭代之前混洗数据)或者 字符串 (batch)。 batch 是处理 HDF5 数据限制的特殊选项,它对一个 batch 内部的数据进行混洗。 当 steps_per_epoch 非 None 时,这个参数无效。

  • class_weight: 可选的字典,用来映射类索引(整数)到权重(浮点)值,用于加权损失函数(仅在训练期间)。 这可能有助于告诉模型 「更多关注」来自代表性不足的类的样本。

  • sample_weight: 训练样本的可选 Numpy 权重数组,用于对损失函数进行加权(仅在训练期间)。 您可以传递与输入样本长度相同的平坦(1D)Numpy 数组(权重和样本之间的 1:1 映射), 或者在时序数据的情况下,可以传递尺寸为 (samples, sequence_length) 的 2D 数组,以对每个样本的每个时间步施加不同的权重。 在这种情况下,你应该确保在 compile() 中指定 sample_weight_mode=“temporal”。

  • initial_epoch: 整数。开始训练的轮次(有助于恢复之前的训练)。

  • steps_per_epoch: 整数或 None。 在声明一个轮次完成并开始下一个轮次之前的总步数(样品批次)。 使用 TensorFlow 数据张量等输入张量进行训练时,默认值 None 等于数据集中样本的数量除以 batch 的大小,如果无法确定,则为 1。

  • validation_steps: 只有在指定了 steps_per_epoch 时才有用。停止前要验证的总步数(批次样本)。

返回

  • 一个 History 对象。其 History.history 属性是连续 epoch 训练损失和评估值,以及验证集损失和评估值的记录(如果适用)。

异常

RuntimeError: 如果模型从未编译。

ValueError: 在提供的输入数据与模型期望的不匹配的情况下。

fit_generator

fit_generator(generator, steps_per_epoch=None, epochs=1, verbose=1, callbacks=None, validation_data=None, validation_steps=None, class_weight=None, max_queue_size=10, workers=1, use_multiprocessing=False, shuffle=True, initial_epoch=0)

使用 Python 生成器(或 Sequence 实例)逐批生成的数据,按批次训练模型。

生成器与模型并行运行,以提高效率。 例如,这可以让你在 CPU 上对图像进行实时数据增强,以在 GPU 上训练模型。

keras.utils.Sequence 的使用可以保证数据的顺序, 以及当 use_multiprocessing=True 时 ,保证每个输入在每个 epoch 只使用一次。

参数

  • generator: 一个生成器,或者一个 Sequence (keras.utils.Sequence) 对象的实例, 以在使用多进程时避免数据的重复。 生成器的输出应该为以下之一:

    • 一个 (inputs, targets) 元组

    • 一个 (inputs, targets, sample_weights) 元组。

这个元组(生成器的单个输出)组成了单个的 batch。 因此,这个元组中的所有数组长度必须相同(与这一个 batch 的大小相等)。 不同的 batch 可能大小不同。 例如,一个 epoch 的最后一个 batch 往往比其他 batch 要小, 如果数据集的尺寸不能被 batch size 整除。 生成器将无限地在数据集上循环。当运行到第 steps_per_epoch 时,记一个 epoch 结束。

  • steps_per_epoch: 在声明一个 epoch 完成并开始下一个 epoch 之前从 generator 产生的总步数(批次样本)。 它通常应该等于你的数据集的样本数量除以批量大小。 对于 Sequence,它是可选的:如果未指定,将使用len(generator) 作为步数。

  • epochs: 整数。训练模型的迭代总轮数。一个 epoch 是对所提供的整个数据的一轮迭代,如 steps_per_epoch 所定义。注意,与 initial_epoch 一起使用,epoch 应被理解为「最后一轮」。模型没有经历由 epochs 给出的多次迭代的训练,而仅仅是直到达到索引 epoch 的轮次。

  • verbose: 0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行。

  • callbacks: keras.callbacks.Callback 实例的列表。在训练时调用的一系列回调函数。

  • validation_data: 它可以是以下之一:

验证数据的生成器或 Sequence 实例

一个 (inputs, targets) 元组

一个 (inputs, targets, sample_weights) 元组。

在每个 epoch 结束时评估损失和任何模型指标。该模型不会对此数据进行训练。

  • validation_steps: 仅当 validation_data 是一个生成器时才可用。 在停止前 generator 生成的总步数(样本批数)。 对于 Sequence,它是可选的:如果未指定,将使用 len(generator) 作为步数。

  • class_weight: 可选的将类索引(整数)映射到权重(浮点)值的字典,用于加权损失函数(仅在训练期间)。 这可以用来告诉模型「更多地关注」来自代表性不足的类的样本。

  • max_queue_size: 整数。生成器队列的最大尺寸。 如未指定,max_queue_size 将默认为 10。

  • workers: 整数。使用的最大进程数量,如果使用基于进程的多线程。 如未指定,workers 将默认为 1。如果为 0,将在主线程上执行生成器。

  • use_multiprocessing: 布尔值。如果 True,则使用基于进程的多线程。 如未指定, use_multiprocessing 将默认为 False。 请注意,由于此实现依赖于多进程,所以不应将不可传递的参数传递给生成器,因为它们不能被轻易地传递给子进程。

  • shuffle: 是否在每轮迭代之前打乱 batch 的顺序。 只能与 Sequence (keras.utils.Sequence) 实例同用。

  • initial_epoch: 开始训练的轮次(有助于恢复之前的训练)。

返回

一个 History 对象。其 History.history 属性是连续 epoch 训练损失和评估值,以及验证集损失和评估值的记录(如果适用)。

异常

ValueError: 如果生成器生成的数据格式不正确。

def generate_arrays_from_file(path):
 while True:
 with open(path) as f:
 for line in f:
 # 从文件中的每一行生成输入数据和标签的 numpy 数组,
 x1, x2, y = process_line(line)
 yield ({'input_1': x1, 'input_2': x2}, {'output': y})
 f.close()
  
model.fit_generator(generate_arrays_from_file('/my_file.txt'),
 steps_per_epoch=10000, epochs=10)

总结:

在使用fit函数的时候,需要有batch_size,但是在使用fit_generator时需要有steps_per_epoch

以上这篇在keras中model.fit_generator()和model.fit()的区别说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

三十四、Python的namedtuple使用详解

namedtuple是继承自tuple的子类。namedtuple创建一个和tuple类似的对象,而且对象拥有可访问的属性。

from collections import namedtuple
 
# 定义一个namedtuple类型User,并包含name,sex和age属性。
User = namedtuple('User', ['name', 'sex', 'age'])
 
# 创建一个User对象
user = User(name='kongxx', sex='male', age=21)
 
# 也可以通过一个list来创建一个User对象,这里注意需要使用"_make"方法
user = User._make(['kongxx', 'male', 21])
 
print user
# User(name='user1', sex='male', age=21)
 
# 获取用户的属性
print user.name
print user.sex
print user.age
 
# 修改对象属性,注意要使用"_replace"方法
user = user._replace(age=22)
print user
# User(name='user1', sex='male', age=21)
 
# 将User对象转换成字典,注意要使用"_asdict"
print user._asdict()
# OrderedDict([('name', 'kongxx'), ('sex', 'male'), ('age', 22)])

三十五、namedtuple的用法

ython的Collections模块提供了不少好用的数据容器类型,其中一个精品当属namedtuple。
namedtuple能够用来创建类似于元祖的数据类型,除了能够用索引来访问数据,能够迭代,更能够方便的通过属性名来访问数据。
在python中,传统的tuple类似于数组,只能通过下标来访问各个元素,我们还需要注释每个下标代表什么数据。通过使用namedtuple,每个元素有了自己的名字,类似于C语言中的struct,这样数据的意义就可以一目了然了。当然,声明namedtuple是非常简单方便的

三十六、python—之sitk中的Getspacing,origin,direction

 
itk_img = sitk.ReadImage(img_file) 
img_array = sitk.GetArrayFromImage(itk_img) # indexes are z,y,x (notice the ordering)
center = np.array([node_x,node_y,node_z])   # nodule center
origin = np.array(itk_img.GetOrigin())      # x,y,z  Origin in world coordinates (mm)
spacing = np.array(itk_img.GetSpacing())    # spacing of voxels in world coor. (mm)
v_center =np.rint((center-origin)/spacing)  # nodule center in voxel space (still x,y,z ordering)

其中 origin是,原始图像中心点在相机坐标系的位置

spacing是两个像素之间的间隔

direction是方向:这个没太搞懂

在这里插入图片描述
左边图是世界坐标(不懂自行百度),右边是图像坐标,

三十七、matplotlib绘图优化-使用np.histogram绘制直方图(柱状图)

简介
这里会介绍绘制直方图的一些方式。其实绘制直方图可以使用plt.hist来进行绘制,参考连接 : Python数据处理之Matplotlib学习–直方图绘制。但是直方图的绘制有个问题(主要是我对直方图的操作不是很熟练),所以画一些好看的图比较麻烦。

于是这里我们会使用numpy.histogram来先完成直方图的统计,在使用柱状图来进行绘制。

numpy.histogram的介绍
这里我们简单介绍一下numpy.histogram的使用,看一下他的输入和输出。下面是一个简单的例子。

# 生成一些随机数
rng = np.random.RandomState(10)
data1 = rng.normal(size=1000)
data2 = rng.normal(size=1000)
# 查看np.histogram的输出
binRange = np.arange(-1,1,0.1)
print(binRange)
np.histogram(data1, bins=binRange)

关于np.histogram的输入,data1表示需要统计的数据, binRange是按照这里的区间进行统计。在这里binRange生成如下,表示从-1到1, 每隔0.1取一个。np.histogram就会统计各个区间的个数,如data1在[-1, -0.9)之间样本的个数。

[-1.00000000e+00 -9.00000000e-01 -8.00000000e-01 -7.00000000e-01
 -6.00000000e-01 -5.00000000e-01 -4.00000000e-01 -3.00000000e-01
 -2.00000000e-01 -1.00000000e-01 -2.22044605e-16  1.00000000e-01
  2.00000000e-01  3.00000000e-01  4.00000000e-01  5.00000000e-01
  6.00000000e-01  7.00000000e-01  8.00000000e-01  9.00000000e-01]

我们简单看一下np.histogram的输出。他的输出有两个部分:

  • 第一个部分表示每个区间的统计个数;
  • 第二个部分表示每个区间的边界;
    在这里插入图片描述
    在这里插入图片描述

matplotlib绘图优化-使用np.histogram绘制直方图(柱状图)
显示百分比
有的时候, 我们可以显示占比而不是具体的个数, 我们可以使用density=True这个参数来进行设置. 需要注意的是, 在使用density=True的时候, 最后计算出来的结果不一定是1, 而是要和区间的大小进行加权之后是1. 我们看下面的一个例子.

rng=np.random.RandomState(10)
data1=rng.normal(size=1000)
bin_range=[-1,0,2] # 设置区间, 分别是[-1,0]和[0,2]
# 计算每一个区间所占的百分比
hist,_ = np.histogram(data1, bin_range, density=True)
hist
"""
array([0.42822678, 0.28588661])
"""

可以看到这里计算出的百分比的和不是1, 这个百分比需要和区间大小进行考虑. 我们分别使用区间大小乘以每一个范围所占的比分比, 结果就是1了.

np.sum(hist*np.array([1,2]))
"""
1.0
"""

np.histogram结合plt.bar绘制直方图
这一部分我们简单介绍一下使用bar来达到hist一样的效果。

绘制多个直方图
首先介绍绘制多个直方图在同一张图中。我们可以看一下下面的例子。就是先用np.histogram统计出每一个区间内data的分布,接着使用plt.bar进行绘制即可。

注意直方图的位置需要通过第一个参数x-w(这个算出的是柱中间的坐标,左右各0.5*w)和width来进行调节。

hist1,_ = np.histogram(data1, bins=binRange)
hist2,_ = np.histogram(data2, bins=binRange)
# 绘制图像
fig, ax1 = plt.subplots()
fig.set_size_inches(10, 6)
plt.set_cmap('RdBu')
x = np.arange(len(binRange)-1)*3
w=0.3
# 绘制多个bar在同一个图中, 这里需要控制width
plt.bar(x-w, hist1, width = 2*w, align='center')
plt.bar(x+w, hist2, width = 2*w, align='center')
# 设置坐标轴的标签
ax1.yaxis.set_tick_params(labelsize=15) # 设置y轴的字体的大小
ax1.set_xticks(x) # 设置xticks出现的位置
# 设置坐标轴名称
ax1.set_ylabel("Count", fontsize='xx-large')
# 设置标题
ax1.set_title('The Distribution of Normal1 Data and Normal2 Data', fontsize='x-large')
# 设置图例
plt.legend(('Normal1','Nomral2'),fontsize = 'x-large', loc='upper right')
plt.show()

最终的绘制的效果如下图所示:
在这里插入图片描述

matplotlib绘图优化-使用np.histogram绘制直方图(柱状图)
绘制堆叠直方图
对于有两类的数据,我们除了上面这样两个绘制在一起,我们还可以绘制为堆叠的样式,下面简单看一下例子。

这一部分的使用基本是和上面是一样的,唯一的不同就是第二个需要加上bottom=hist1,也就是在hist1上面绘制hist2的内容。

hist1,_ = np.histogram(data1, bins=binRange)
hist2,_ = np.histogram(data2, bins=binRange)
# 绘制图像
fig, ax1 = plt.subplots()
fig.set_size_inches(10, 6)
plt.set_cmap('RdBu')
x = np.arange(len(binRange)-1)*3
w=1
# 绘制多个bar在同一个图中, 这里需要控制width
plt.bar(x, hist1, width = 2*w, align='center')
plt.bar(x, hist2, width = 2*w, align='center', bottom=hist1)
# 设置坐标轴的标签
ax1.yaxis.set_tick_params(labelsize=15) # 设置y轴的字体的大小
ax1.set_xticks(x) # 设置xticks出现的位置
# 设置坐标轴名称
ax1.set_ylabel("Count", fontsize='xx-large')
# 设置标题
ax1.set_title('The Distribution of Normal1 Data and Normal2 Data', fontsize='x-large')
# 设置图例
plt.legend(('Normal1','Nomral2'),fontsize = 'x-large', loc='upper right')
plt.show()

最终绘制的结果如下图所示:
在这里插入图片描述

matplotlib绘图优化-使用np.histogram绘制直方图(柱状图)

三十八、copy.copy()函数用法

b = copy.copy(a)

copy.copy这个函数结果会因为是可变或者不可变导致结果不同

只能拷贝一层。根据类型有关。如果是列表(可变类型),深拷贝。如果是元组(不可变)浅拷贝

如果里面还有嵌套的对象,浅拷贝

import copy
a = [1,2,3,4]
#相当于深拷贝
b = copy.copy(a)
print(id(a))
print(id(b))
a.append(5)
print(a)
print(b)
运行结果:
18904264
18489224
[1, 2, 3, 4, 5]
[1, 2, 3, 4]
import copy
a = (1,2,3,4)
#相当于浅拷贝
b = copy.copy(a)
print(id(a))
print(id(b))
运行结果:
18713160
18713160
import copy
a = [11,22,33]
b = [44,55,66]
c = [a,b]
d = copy.copy(c)
print(id(c))
print(id(d))
print(c)
print(d)
a.append(120)
#c[0].append(120)
print(c)
print(d)
运行结果:
18772104
7561416
[[11, 22, 33], [44, 55, 66]]
[[11, 22, 33], [44, 55, 66]]
[[11, 22, 33, 120], [44, 55, 66]]
[[11, 22, 33, 120], [44, 55, 66]]
import copy
a = [11,22,33]
b = [44,55,66]
c = (a,b)
d = copy.copy(c)
print(id(c))
print(id(d))
print(c)
print(d)
a.append(120)
#c[0].append(120)
print(c)
print(d)
运行结果:
10951368
10951368
([11, 22, 33], [44, 55, 66])
([11, 22, 33], [44, 55, 66])
([11, 22, 33, 120], [44, 55, 66])
([11, 22, 33, 120], [44, 55, 66])

三十九、如何使用 PyCharm 调试程序

1.准备代码:

# coding=utf-8
 
 
 
 
class TestDebug:
    def __init__(self):
        self.a = 1
        self.b = 2
 
 
    def test01(self):
        print('test01开始了!')
        self.test02()
        print('test01结束了!')
 
 
    def test02(self):
        print(self.a)
        print(self.b)
        c = 3
        print(c)
 
 
 
 
if __name__ == '__main__':
    obj_test_debug = TestDebug()
    obj_test_debug.test01()

2.使用 PyCharm 打开需要调试的程序:

在这里插入图片描述

3.在需要调试代码位置设置断点,鼠标左键单击行数后面位置即可,再次单击取消设置断点:
在这里插入图片描述

4.右键菜单点击 debug 或者点击右上角 debug 按钮或者 shift+F9 进行调试:

在这里插入图片描述

5.开始调试后,程序会在设置断点位置停止运行,程序执行当前行会标记为蓝色,下方的状态变量查看窗口会显示变量当前值:

在这里插入图片描述

6.调试快捷键:

  • F9 继续运行程序
  • F8 执行下一条语句
  • F7 进入当前语句的函数内

7.进入控制台后,点击下面的 python 图标可以开启交互调试模式,在交互式调试模式下,可在 Console 分页输入 Python 语句,且语句的执行环境与当前调用堆栈的断点执行环境相同。如图所示,输入 d = self.a + self.b 后返回 debugger 界面可以看到 d = 3:
在这里插入图片描述
在这里插入图片描述

四十、医学影像学dicom,mhd及raw文件读取与可视化

医学影像学
医学影像是指为了医疗或医学研究,对人体或人体某部份,以非侵入方式取得内部组织影像的技术与处理过程。

目前的主要处理方式有:X光成像(X-ray),电脑断层扫描(CT),核磁共振成像(MRI), 超声成像(ultrasound),正子扫描(PET),脑电图(EEG),脑磁图(MEG),眼球追踪(eye-tracking),穿颅磁波刺激(TMS)等现代成像技术,用来检查人体无法用非手术手段检查的部位的过程。

医学影像学的崛起使得对于病人的诊断有了更加直观的证据。除此之外,对于医学领域的相关研究,也有着巨大的影响。

dicom文件相关
1.1 什么是dicom图像

DICOM(Digital Imaging and Communications in Medicine)即医学数字成像和通信,是医学图像和相关信息的国际标准。其是目前应用最为广泛的医学影像格式,常见的CT,核磁共振,心血管成像等大多采用的是docim格式的存储。

1.2 dicom图像中有什么
doicm主要存储两方面信息:

(1)关于患者的PHI(protected health imformation)信息
简单来说,就是患者的相关信息(这些信息是收到保护的)。例如,姓名,性别,年龄,继往病历等

(2)直观的图像信息
一部分信息是扫描过后患者图像的某一层(切片,稍后会讲到),医生可以通过专门的dicom阅读器去打开,从而察看患者病情。另一部分是相关的设备信息,例如生产的dicom图像是X光机扫描出的X光图像的某一层,那么dicom就会存储关于此X光机的相关设备信息。

更具体地划分,这些信息可以被分为以下四类:

(1)Patient
例如患者的姓名,性别,体重,ID等等,反映的主要是患者个人的相关信息。

(2)Study
也就是关于患者接受某项检查所记录的信息,例如检查号,检查日期,检查时患者的年龄,检查的部位等等。

(3)Series
存储关于检查的简单信息,例如图像的层厚,层间距,图像方位等等。

(4)Image
存储关于检查结果(例如X光影像的某一层)的详细信息,例如影像拍摄日期,图像的分辨率,像素间距等等。

注意
dicom文件 ≠ 患者影像

一方面,在实际检查过程中,例如CT检查,是利用X光线等放射性光线与探测器一同围绕人体做多层的断面扫描。也就是说,我们得到的结果是一层一层的断面图像,将这些断面图像在z轴叠加起来,才是一个完整的医学影像。而每一个dicom存储其中一层图像(但又不仅仅是存储图像),换句话说,一位患者有多个dicom文件。
在这里插入图片描述

如上图,一个完整的医学影像是由许多个断面图像在z轴上堆叠而成的三维图像。而一个dicom文件只存储其中的一切图像及相关信息。

另一方面,dicom文件不仅仅存储的是患者影响的切片,还存储着相关的信息(在上面已经提到)。

综上,dicom是一个存储患者所拍摄医学影像的某一层断面图和相关信息的文件。

1.3 dicom结构及组成
dicom文件内部的组成结构如下图:
在这里插入图片描述

可以看出,一个完整的dicom文件由两部分组成:文件头和数据集。

1.3.1 文件头
Dicom文件头包含的是关于数据集的相关信息,主要有以下几部分:

⋅ ·⋅ 文件导言

⋅ ·⋅ Dicom前缀,也是计算机读取过程中判断一个文件是否是dicom文件的依据。

⋅ ·⋅ 文件信息元素

1.3.2 数据集
数据集即存储相关数据的地方,数据集的基本单位是数据元。数据元由四个部分组成:

(1) Tag
也即标签部分,用于识别数据的具体类型。包括组号(Group)和元素号(Element)两部分。在这里,Group对应的编码是指所存储信息的大类,也就是指明信息是patient,study,series,image四大类中哪一个大类。在确定了Group以后iu,再去检索Element从而确定具体的信息。例如(0008,0050)指的就是病患的检查号信息。

更具体的Tag表见附录。

(2) VR(值表示)
存储Tag指向信息的数据类型。

(3)VL(数据长度)
保存Tag指向信息的数据长度。

(4)值域
保存Tag指向信息的具体值。

一般来说,在处理时,dicom显得有些麻烦。所以会把dicom文件进行转换,从而处理转换后的文件。这里采用的转换方法是将dicom文件转换为raw文件和mhd文件。

raw文件相关
Raw文件,意为“未处理的文件”,其保存的是纯像素信息。常常是一个病人的所有dicom文件中的图像提取出来放在一个raw文件里。也就是说,一个病人对应一个raw文件,其中存储的是该病人的图像信息。(可以理解为将该病人不同的dicom切片图像都叠到一起,形成了一个三维图像)。

mhd文件相关
上面已经提到,dicom文件除了包含切片图像外,还包含其他的一些信息。那么在文件格式转换后,图像信息被raw文件提取,非图像信息则存储在mhd头文件中。简单来说,mhd头文件是存储关于一个病人的所有dicom文件中的非图像信息。

由上述关系可以知道:raw文件与mhd文件是一一对应的,且它们的数量小于等于(实际中一定是小于)dicom文件数量。

相应代码
2.1 dicom可视化

import matplotlib.patches as patches
import matplotlib.pyplot as plt
import pydicom
from pydicom.data import get_testdata_files
import os
import xlrd
import xlwt
print(__doc__)
path = r'C:\Users\86152.000\Desktop\python\M'
files = os.listdir(path)
for filename in files:
    dataset = pydicom.dcmread(filename)
# Normal mode:
print()
print("Filename.........:", filename)
print("Storage type.....:", dataset.SOPClassUID)
print()
pat_name = dataset.PatientName
display_name = pat_name.family_name + ", " + pat_name.given_name
print("Patient's name...:", display_name)
print("Patient id.......:", dataset.PatientID)
print("Modality.........:", dataset.Modality)
print("Study Date.......:", dataset.StudyDate)
if 'PixelData' in dataset:
    rows = int(dataset.Rows)
    cols = int(dataset.Columns)
    print("Image size.......: {rows:d} x {cols:d}, {size:d} bytes".format(
        rows=rows, cols=cols, size=len(dataset.PixelData)))
    if 'PixelSpacing' in dataset:
        print("Pixel spacing....:", dataset.PixelSpacing)
# use .get() if not sure the item exists, and want a default value if missing
print("Slice location...:", dataset.get('SliceLocation', "(missing)"))
def readexcel():
            workbook=xlrd.open_workbook(r'C:\Users\86152.000\Desktop\python\annotated_coordinates.xlsx')
            print(workbook.sheet_names())
            sheet1=workbook.sheet_by_name('Sheet1')
            nrows=sheet1.nrows
            ncols=sheet1.ncols
            print(nrows,ncols)
            r=2
            c=4
            while True:
                cellA=sheet1.cell(r,c).value
                cellB=sheet1.cell(r,c).value
                print(cellA,cellB)
                rect=patches.Rectangle((20,30),10,20,linewidth=1,edgecolor='red',facecolor='none')
                ax=plt.subplot(1,1,1)
                ax.add_patch(rect)
                plt.imshow(dataset.pixel_array, cmap=plt.cm.bone)
                plt.show()
                r=r+1
                if r==nrows:
                    c=c+1
                    r=2
                    if c==ncols-2:
                       print('完成')
                       break
readexcel()

输出如下:
在这里插入图片描述
在这里插入图片描述

这里的红框可以去掉,在代码里注释即可。(本想标注dicom数据的,结果导师说应该是raw数据)

2.2 dicom读取并转换为raw及mhd文件

import cv2
import os
import pydicom
import numpy
import SimpleITK

# 路径和列表声明
# 与python文件同一个目录下的文件夹,存储dicom文件,该文件路径最好不要含有中文
PathDicom = r"/home/Wangling/TuoBaoqiang/python/Test1"
# 与python文件同一个目录下的文件夹,用来存储mhd文件和raw文件,该文件路径最好不要含有中文
SaveRawDicom = r"/home/Wangling/TuoBaoqiang/python/Data_Img/test1"

lstFilesDCM= []
lstFilesDCM_0 = []
temp = []
listdicom = []

# dicom文件夹下的第一层目录 ——> group一维列表
root_1 = os.listdir(PathDicom)
group = []
for i in range(0,len(root_1)):
    group.append(PathDicom + '/' + root_1[i])   #group列表

# dicom文件夹下的第二层目录 ———> 000x一维列表
root_2 = []
for j in range(0,len(group)):
    root_2.append(os.listdir(group[j]))   # group下dicom文件夹,root_2为二维列表,例[['0001', '0002'], ['0003', '0104']]
nameformhd = sum(root_2,[])  # 将root_2降为一维列表,以备程序末尾mhd文件的命名

# 每个dicom文件的完整目录 ———> /group/000x
for m in range(0,len(group)):
    for n in range(0,len(root_2[0])):     # 假设每个group中dicom文件夹的数量是相等的,即10,如果有改变,要修改!
        eachdicom = group[m] + '/' + root_2[m][n]
        lstFilesDCM_0.append(eachdicom)
# print('The dicom filelist -> %s'%lstFilesDCM_0) # 打印dicom文件列表

# 将完整列表lstFilesDCM_0下的dicom文件地址读取到listdicom中
for k in range(0,len(lstFilesDCM_0)):
    for dirName, subdirList, fileList in os.walk(lstFilesDCM_0[k]):
        for filename in fileList:
            if "img" in filename.lower():  # 判断文件是否为dicom文件
                temp.append(os.path.join(dirName, filename))
        listdicom.append(temp)   # 加入到列表中
        temp = []
# print('listdicom=%s'%listdicom)   #打印dicom全部文件载入的列表
# print('The number of listdicom is %d'%len(listdicom))    #打印预备处理dicom文件的个数

for filenames in listdicom:
    # print('the current processing list is %s'%filenames)   #打印当前正在处理的dicom列表
    lstFilesDCM = filenames
# 第一步:将最后一张图片作为参考图片,并认为所有图片具有相同维度
    RefDs = pydicom.read_file(lstFilesDCM[-1],force = True)  # 读取最后一张dicom图片
    RefDs_0 = pydicom.read_file(lstFilesDCM[0],force = True)    # 读取第一张dicom图片,第三步备用

# 第二步:得到dicom图片所组成3D图片的维度
    ConstPixelDims = (int(RefDs.Rows), int(RefDs.Columns), len(lstFilesDCM))  # ConstPixelDims是一个元组

# 第三步:得到x方向和y方向的Spacing并得到z方向的层厚
    RefDs.SliceThickness = abs(RefDs_0.ImagePositionPatient[2]-RefDs.ImagePositionPatient[2])/(len(lstFilesDCM)-1)
     #  打印slicethickness的值,判断数据是否正确
    print(float(RefDs_0.SliceThickness),RefDs.SliceThickness,RefDs_0.ImagePositionPatient[2],RefDs.ImagePositionPatient[2],len(lstFilesDCM))

    print(RefDs_0.ImagePositionPatient)
    print(RefDs.ImagePositionPatient)

    ConstPixelSpacing = (float(RefDs.PixelSpacing[0]), float(RefDs.PixelSpacing[1]), float(RefDs.SliceThickness))

# 第四步:得到图像的原点
    Origin = RefDs.ImagePositionPatient

# 根据维度创建一个numpy的三维数组,并将元素类型设为:pixel_array.dtype
    ArrayDicom = numpy.zeros(ConstPixelDims, dtype=RefDs.pixel_array.dtype)  # array is a numpy array

# 第五步:遍历所有的dicom文件,读取图像数据,存放在numpy数组中
    i = 0
    for filenameDCM in lstFilesDCM:
        ds = pydicom.read_file(filenameDCM)
        ArrayDicom[:, :, lstFilesDCM.index(filenameDCM)] = ds.pixel_array
        cv2.imwrite("out_" + str(i) + ".png", ArrayDicom[:, :, lstFilesDCM.index(filenameDCM)])
        i += 1

# 第六步:对numpy数组进行转置,即把坐标轴(x,y,z)变换为(z,y,x),这样是dicom存储文件的格式,即第一个维度为z轴便于图片堆叠
    ArrayDicom = numpy.transpose(ArrayDicom, (2, 0, 1))

# 第七步:将现在的numpy数组通过SimpleITK转化为mhd和raw文件
    sitk_img = SimpleITK.GetImageFromArray(ArrayDicom, isVector=False)
    sitk_img.SetSpacing(ConstPixelSpacing)
    sitk_img.SetOrigin(Origin)
    SimpleITK.WriteImage(sitk_img,os.path.join(SaveRawDicom,nameformhd[listdicom.index(filenames)]+ ".mhd"))

输出如下:
在这里插入图片描述

2.3 raw文件可视化

import os
import SimpleITK as sitk
import matplotlib.pyplot as plt
 
if __name__=="__main__":
    #识别中文路径名
    path=r"C:\Users\86152.000\Desktop\python\SaveRaw\0002.mhd"
    pwd = os.getcwd()
    os.chdir(os.path.dirname(path))
 
    #读取切片图像
    image = sitk.ReadImage(os.path.basename(path))
    image = sitk.GetArrayFromImage(image)
 
    #显示图像
    for i in range(1,image.shape[0],1):
        plt.figure()
        plt.imshow(image[i,:,:], cmap='gray')
        plt.pause(1)
        plt.close()
        print(i)

输出如下:
在这里插入图片描述

2.4 遍历指定文件

import os
import SimpleITK as sitk
import matplotlib.pyplot as plt

def show_files(path, all_files):
    # 首先遍历当前目录所有文件及文件夹
    file_list = os.listdir(path)
    # 准备循环判断每个元素是否是文件夹还是文件,是文件的话,把名称传入list,是文件夹的话,递归
    for file in file_list:
        # 利用os.path.join()方法取得路径全名,并存入cur_path变量,否则每次只能遍历一层目录
        cur_path = os.path.join(path, file)
        # 判断是否是文件夹
        if os.path.isdir(cur_path):
            show_files(cur_path, all_files)
        else:
            # 判断是否满足后缀
            if suffix in cur_path:
                all_files.append(cur_path)
    return all_files


# 对指定的文件进行的操作
import os


def show_files(path, all_files):
    # 首先遍历当前目录所有文件及文件夹
    file_list = os.listdir(path)
    # 准备循环判断每个元素是否是文件夹还是文件,是文件的话,把名称传入list,是文件夹的话,递归
    for file in file_list:
        # 利用os.path.join()方法取得路径全名,并存入cur_path变量,否则每次只能遍历一层目录
        cur_path = os.path.join(path, file)
        # 判断是否是文件夹
        if os.path.isdir(cur_path):
            show_files(cur_path, all_files)
        else:
            # 判断是否满足后缀
            if suffix in cur_path:
                all_files.append(cur_path)
    return all_files


# 对指定的文件进行的操作
def handle_file(file_path):
    open(file_path)
    print(file_path)
  

def find_file():
    # 传入空的list接收文件名
    all_files = show_files(read_dir_path, [])
    # 循环打印show_files函数返回的文件名列表
    for file in all_files:
        handle_file(file)
        print(file)


if __name__ == "__main__":
    # 要读取的文件夹
    read_dir_path = r'C:\Users\86152.000\Desktop'
    # 要匹配的文件类型
    suffix = '.mhd'

    find_file()

输出如下:
在这里插入图片描述

附录(常见Tag说明)
Patient Tag

Group(组号)	Element(元素号)	Tag Description	中文解释	VR
0010	0010	Patient’s Name	患者姓名	PN
0010	0020	Patient ID	患者ID	LO
0010	0030	Patient’s Birth Date	患者出生日期	DA
0010	0032	Patient’s Birth Time	患者出生时间	TM
0010	0040	Patient’s Sex	患者性别	CS
0010	1030	Patient’s Weight	患者体重	DS
0010	21C0	Pregnancy Status	怀孕状态	US
Study Tag
Group	Elemen	Tag Description	中文解释	VR
0008	0050	Accession Number:A RIS generated number that identifies the order for the Study.	检查号:RIS的生成序号,用以标识做检查的次序.	SH
0020	0010	Study ID	检查ID.	SH
0020	000D	Study Instance UID: Unique identifier for the Study.	检查实例号:唯一标记不同检查的号码.	UI
0008	0020	Study Date: Date the Study started.	检查日期:检查开始的日期.	DA
0008	0030	Study Time:Time the Study started.	检查时间:检查开始的时间.	TM
0008	0061	Modalities in Study	一个检查中含有的不同检查类型.	CS
0008	0015	Body Part Examined	检查的部位.	CS
0008	1030	Study Description	检查的描述	. LO
0010	1010	Patient’s Age	做检查时刻的患者年龄,而不是此刻患者的真实年龄.	AS

Series Tag

Group	Element	Tag Description	中文解释	VR
0020	0011	Series Number:A number that identifies this Series.	序列号:识别不同检查的号.	IS
0020	000E	Series Instance UID:Unique identifier for the Series.	序列实例号:唯一标记不同序列的号码.	UI
0008	0060	Modality 检查模态(MRI/CT/CR/DR)	CS	
0008	103E	Series Description	检查描述和说明	LO
0008	0021	Series Date	检查日期	DA
0008	0031	Series Time	检查时间	TM
0020	0032	Image Position (Patient):The x, y and z coordinates of the upper left hand corner of the image, in mm.	图像位置:图像的左上角在空间坐标系中的x,y,z坐标,单位是毫米.如果在检查中,则指该序列中第一张影像左上角的坐标.	DS
0020	0037	Image Orientation (Patient):The direction cosines of the first row and the first column with respect to the patient.	图像方位	DS
0018	0050	Slice Thickness:Nominal slice thickness, in mm.	层厚.	DS
0018	0088	Spacing Between Slices	层与层之间的间距,单位为mm	DS
0020	1041	Slice Location:Relative position of exposure expressed in mm.	实际的相对位置,单位为mm.	DS
008	0023	MR Acquisition	收购者	CS
0018	0015	Body Part Examined	身体部位	. CS

Image Tag

Group	Element	Tag Description	中文解释	VR
0008	0008	Image Type:Image identification characteristics.	图像类型	CS
0008	0018	SOP Instance UID	SOP实例	UID.
0008	0023	Content Date:The date the image pixel data creation started.	影像拍摄的日期.	DA
0008	0033	Content Time	影像拍摄的时间.	TM
0020	0013	Image/Instance Number:A number that identifies this image.	图像码:辨识图像的号码.	IS
0028	0002	Samples Per Pixel:Number of samples (planes) in this image.	图像上的采样率.	US
0028	0004	Photometric Interpretation:Specifies the intended interpretation of the pixel data.	光度计的解释,对于CT图像,用两个枚举值MONOCHROME1,MONOCHROME2.用来判断图像是否是彩色的,MONOCHROME1/2是灰度图, RGB则是真彩色图,还有其他.	CS
0028	0010	Rows: Number of rows in the image.	图像的总行数,行分辨率.	US
0028	0011	Columns: Number of columns in the image.	图像的总列数,列分辨率.	US
0028	0030	Pixel Spacing:Physical distance in the patient between the center of each pixel.	像素间距.像素中心之间的物理间距.	DS
0028	0100	Bits Allocated:Number of bits allocated for each pixel sample. Each sample shall have the same number of bits allocated.	分配的位数:存储每一个像素值时分配的位数,每一个样本应该拥有相同的这个值.	US
0028	0101	Bits Stored:Number of bits stored for each pixel sample. Each sample shall have the same number of bits stored	存储的位数:有1216列举值.存储每一个像素用的位数.每一个样本应该有相同值.	US

四十一、Python format 用法详解

**一、填充字符串

  1. 位置**
print("hello {0}, this is {1}.".format("world", "python"))  # 根据位置下标进行填充
print("hello {}, this is {}.".format("world", "python"))  # 根据顺序自动填充
print("hello {0}, this is {1}. {1} is a new language.".format("world", "python"))  # 同一参数可以填充多次

输出:

hello world, this is python.
hello world, this is python.
hello world, this is python. python is a new language.
  1. key
obj = "world"
name = "python"
print("hello {obj}, this is {name}.".format(obj = obj, name = name))

输出:

hello world, this is python.
  1. 列表
list = ["world", "python"]
print("hello {names[0]}, this is {names[1]}.".format(names = list))

输出:

hello world, this is python.
4. 字典

dict = {"obj":"world", "name":"python"}
print("hello {names[obj]}, this is {names[name]}.".format(names = dict))

输出:

hello world, this is python.

注意:

访问字典的 key,不用引号。

  1. 类属性
class Names():
    obj = "world"
    name = "python"

print("hello {names.obj}, this is {names.name}.".format(names = Names))

输出:

hello world, this is python.
  1. 魔法参数
args = [",", "inx"]
kwargs = {"obj": "world", "name": "python"}
print("hello {obj}{} this is {name}.".format(*args, **kwargs))

输出:

hello world, this is python.

注意:

这里的 format(*args, **kwargs) 等价于 format(“,”, “inx”, obj = “world”, name = “python”)。

二、数字格式化
在这里插入图片描述

三、其他用法

  1. 转义
print("{{hello}} {{{0}}}".format("world"))

输出:

{hello} {world}
  1. format 作为函数变量
name = "python"
hello = "hello, welcome to {} world!".format
print(hello(name))

输出:

hello, welcome to python world!
  1. 格式化 datatime
from datetime import datetime
now = datetime.now()
print("{:%Y-%m-%d %X}".format(now))

输出:

2020-12-15 19:46:24
  1. {}内嵌{}
print("hello {0:>{1}} ".format("world", 10))

输出:

hello      world

四、参考
python format 用法详解

Python format 格式化函数

四十二、Couldnotloaddynamiclibrary‘cublas64_10.dll‘

四十三、model.fit_generator()详细解读

from keras import models
model = models.Sequential()

首先,利用keras,搭建顺序模型,具体搭建步骤省略。完成搭建后,我们需要将数据送入模型进行训练,送入数据的方式有很多种,models.fit_generator()是其中一种方式。具体说,model.fit_generator()是利用生成器,分批次向模型送入数据的方式,可以有效节省单次内存的消耗。具体函数形式如下:

fit_generator(self, generator, steps_per_epoch, epochs=1, verbose=1, \
callbacks=None, validation_data=None, validation_steps=None,\
 class_weight=None, max_q_size=10, workers=1, pickle_safe=False, initial_epoch=0)

参数解释:

  • generator:一般是一个生成器函数;

  • steps_per_epochs:是指在每个epoch中生成器执行生成数据的次数,若设定 steps_per_epochs=100,这情况如下图所示;
    在这里插入图片描述

  • epochs:指训练过程中需要迭代的次数;

  • verbose:默认值为1,是指在训练过程中日志的显示模式,取 1 时表示“进度条模式”,取2时表示“每轮一行”,取0时表示“安静模式”;

  • validation_data, validation_steps指验证集的情况,使用方式和generator,
    steps_per_epoch相同;

models.fit_generator()会返回一个history对象,history.history 属性记录训练过程中,连续 epoch 训练损失和评估值,以及验证集损失和评估值,可以通过以下方式调取这些值!

acc = history.history["acc"]
val_acc = history.history["val_acc"]
loss = history.history["loss"]
val_loss = history.history["val_loss"]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

【网络星空】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值