Python学习(二)——配套《PyTorch深度学习实战》
1. class类的定义
在 Python 中,class
是一种用户自定义的数据类型,它允许你根据需要创建自己的对象。类提供了一种方式来封装数据和与数据相关的操作。以下是类的一些基本概念:
- 定义类:使用关键字
class
来定义一个类,后跟类名和一对圆括号,其中可以包含一个父类的名称(用于继承)。如果类没有继承父类,圆括号可以省略。
class MyClass:
pass
- 属性:类可以包含属性,这些属性是与类相关联的变量。属性可以在类的内部定义,也可以通过方法设置。
class MyClass:
def __init__(self, value):
self.my_attribute = value
- 方法:类可以包含方法,这些方法是定义在类内部的函数,用于访问和操作对象的属性。
class MyClass:
def __init__(self, value):
self.my_attribute = value
def my_method(self):
return self.my_attribute
- 构造函数:
__init__
方法是一个特殊的方法,它在创建新对象时自动调用,用于初始化对象的状态。
class MyClass:
def __init__(self, value):
self.my_attribute = value
- 析构函数:
__del__
方法是另一个特殊的方法,它在对象被销毁时调用。
class MyClass:
def __del__(self):
print("Object is being destroyed")
- 继承:类可以继承其他类,这意味着它们可以获取父类的所有属性和方法。
class ParentClass:
pass
class ChildClass(ParentClass):
pass
- 多态:在继承的背景下,多态允许子类重写父类的方法,以提供特定于子类的行为。
class ParentClass:
def my_method(self):
print("Parent method")
class ChildClass(ParentClass):
def my_method(self):
print("Child method")
parent = ParentClass()
child = ChildClass()
parent.my_method() # 输出: Parent method
child.my_method() # 输出: Child method
-
封装:类提供了一种封装数据和操作数据的方法的方式,这有助于保护数据不被外部直接访问和修改。
-
实例化:创建类的一个对象称为实例化。这通常通过调用类并传递必要的参数来完成。
obj = MyClass(10)
类是 Python 中实现面向对象编程(OOP)的核心概念,它们允许你创建复杂的数据结构和行为模式。
更形象化的解释如下
这段代码定义了一个名为 MyClass
的 Python 类,其中包含了一个特殊的方法 __init__
,这是一个构造函数,用于初始化类的新实例。让我详细解释一下每一部分:
-
class MyClass:
:- 这是 Python 中定义类的方式。
class
是关键字,后面跟着类名MyClass
。类名通常采用大驼峰命名法(首字母大写,每个单词的首字母大写,不使用下划线)。
- 这是 Python 中定义类的方式。
-
def __init__(self, value):
:- 这是定义类的一个方法。
__init__
是一个特殊的方法,被称为类的构造函数或初始化方法。当创建类的新实例(对象)时,这个方法会被自动调用。 def
是定义函数的关键字。__init__
是方法的名称,它遵循 Python 的命名约定,用于类的初始化。(self, value)
是方法的参数列表。self
是指向类实例的引用,它允许方法访问类的属性和其他方法。value
是一个额外的参数,用于传递给构造函数的初始值。
- 这是定义类的一个方法。
-
self.my_attribute = value
:- 这行代码在类的实例中创建了一个属性
my_attribute
,并将其值设置为传递给构造函数的value
参数。 self
关键字用于引用类的当前实例,这样你就可以在类的内部访问和修改实例的状态。my_attribute
是属性的名称,它可以是任何有效的 Python 标识符。value
是传递给构造函数的参数,它被用来初始化my_attribute
。
- 这行代码在类的实例中创建了一个属性
当你创建 MyClass
的一个新实例时,你可以这样做:
my_object = MyClass(42)
这里,42
是传递给构造函数的参数,它将被用来初始化 my_object
的 my_attribute
属性。之后,你可以通过 my_object.my_attribute
来访问这个属性的值。
在 Python 的类中,self
是一个约定俗成的名称,用于引用类的当前实例。它不是一个关键字,而是一个按照惯例使用的名称,你可以选择其他名称,但使用 self
是为了保持代码的可读性和一致性。在定义任何一个新的类时,通常都会包含 self
作为第一个参数,尽管这不是强制性的,但它是强烈推荐的。
self
的作用可以形象地解释如下:
-
身份标识:想象一下,你正在参加一个大型的聚会,每个人都是某个“类”的实例。在聚会中,你需要一种方式来区分自己和他人。
self
就像是你的名字,它帮助你在聚会中识别自己。 -
属性和方法的访问:在聚会中,你可能会谈论自己的属性(比如你的眼睛颜色、身高等)和展示你的方法(比如跳舞、讲故事等)。在类中,
self
允许你访问和修改实例的属性和其他方法,就像你通过自己的名字来谈论自己的属性和展示自己的技能一样。 -
参数传递:当你在类的方法中使用
self
时,你实际上是在告诉 Python:“嘿,我需要这个实例的引用,这样我就可以访问它的属性和方法了。”这就像是在聚会中,当你说“我”时,你不需要指明具体是谁,因为上下文已经清楚地表明了你在谈论自己。
下面是一个简单的例子,展示了 self
的使用:
class Person:
def __init__(self, name, age):
self.name = name # 使用 self 来设置实例的属性
self.age = age
def introduce(self):
print(f"Hello, my name is {self.name} and I am {self.age} years old.") # 使用 self 来访问属性
# 创建 Person 类的一个实例
person = Person("Alice", 30)
# 调用实例的方法
person.introduce()
在这个例子中,self
用于在 Person
类的构造函数和 introduce
方法中引用当前实例。当你创建 Person
类的实例并调用 introduce
方法时,self
确保了方法能够正确地访问和使用实例的属性。
那是不是新定义一个class的时候一定要有self?
在 Python 中,并不是每个新定义的类都必须包含 __init__
方法,这取决于你是否需要在创建类实例时初始化属性。__init__
方法是一个特殊的方法,它在创建新实例时自动调用,用于设置对象的初始状态。
如果你的类不需要初始化任何属性,那么你可以省略 __init__
方法。但是,如果你的类需要在创建对象时设置一些属性,那么 __init__
方法就非常有用。在 __init__
方法中使用 self
来引用实例本身,允许你为实例设置属性。
以下是一个简单的例子,展示了如何使用 __init__
方法:
class Person:
def __init__(self, name, age):
self.name = name # 使用 self 来设置实例的属性
self.age = age
# 创建 Person 类的实例
person = Person("Alice", 30)
print(person.name) # 输出: Alice
print(person.age) # 输出: 30
在这个例子中,__init__
方法用于初始化 Person
类的两个属性:name
和 age
。当你创建一个新的 Person
实例时,你需要提供这两个属性的值。
如果你的类不需要初始化任何属性,你可以这样定义:
class Person:
pass
# 创建 Person 类的实例
person = Person()
# 你仍然可以为实例添加属性
person.name = "Alice"
person.age = 30
print(person.name) # 输出: Alice
print(person.age) # 输出: 30
在这个例子中,我们没有定义 __init__
方法,这意味着 Person
类的实例在创建时没有任何属性。但是,你仍然可以在创建实例后为它们添加属性。
总的来说,是否需要在类中声明 __init__
方法以及使用 self
来设置属性,取决于你的类是否需要在创建时初始化属性。如果需要,那么 __init__
方法和 self
是非常有用的工具。如果不需要,那么可以省略它们。
针对这个,自己写了一个案例,如下:
class shiyan:
def __init__(self,width,length,depth,vol):
self.width = width
self.length = length
self.depth = depth
self.vol = vol
def intr(self):
vol = self.width * self.length * self.depth
可以理解为在这个类的定义中,self存在的意义是表示“我自己的(这个类的)”,我理解的是这样可以避免在许多类的定义时有误解,可能两个都要用到例如a这个变量,所以用self.width就表示为“这个类的width”
当然上述代码并没有完成我希望他完成的功能:保存长宽高,能调用方法输出体积。自己已经觉得有问题,不能既定义、输入了体积,又能再次计算出体积
修改之后,如下的写法是正确的:
class shiyan:
def __init__(self,width,length,depth):
self.width = width
self.length = length
self.depth = depth
#self.vol = vol
def intr(self):
return self.width * self.length * self.depth
#return (vol)
print(abb.intr())
这样也是可以的:
class shiyan:
def __init__(self,width,length,depth):
self.width = width
self.length = length
self.depth = depth
#self.vol = vol
def intr(self):
vol = self.width * self.length * self.depth
return (vol)
print(abb.intr())
2. dir()是什么函数
在 Python 中,dir()
函数是一个内置函数,它用于列出给定对象的属性和方法。当你对一个模块、类或对象调用 dir()
函数时,它会返回一个包含该对象所有属性和方法名称的列表。
例如,models
可能是一个模块、类或对象的名称。当你执行 dir(models)
时,Python 会返回 models
中定义的所有属性和方法的名称列表。这包括内置的属性和方法(如 __init__
、__doc__
等)以及任何用户定义的属性和方法。
这里有几个使用场景:
- 探索模块:如果你导入了一个模块,但不确定它提供了哪些功能,你可以使用
dir()
来查看模块中的所有属性和方法。
import numpy
print(dir(numpy))
- 探索类:如果你定义了一个类,或者导入了一个类的实例,你可以使用
dir()
来查看类中定义的所有属性和方法。
class MyClass:
def my_method(self):
pass
obj = MyClass()
print(dir(obj))
- 探索对象:对于任何对象,
dir()
都可以帮助你了解该对象有哪些属性和方法。
my_list = [1, 2, 3]
print(dir(my_list))
dir()
函数是 Python 中一个非常有用的工具,可以帮助你快速了解一个对象的公共接口。然而,它返回的列表中可能包含一些你不应该直接访问的内部属性(以单下划线 _
开头)或特殊方法(以双下划线 __
开头),这些通常是为 Python 解释器或框架内部使用的。在实际使用中,你应该关注那些不以下划线开头的属性和方法。
用在上面我自己定义的shiyan类,会返回如下结果:
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'intr']
名称以双下划线(__
)开头和结尾的属性和方法(也称为 dunder 方法或特殊方法)是 Python 为类自动提供的,它们是 Python 对象模型的一部分。这些特殊方法为对象提供了一些内置的功能和行为。以下是一些常见的特殊方法及其用途:
__init__(self, ...)
:类的构造函数,当创建新实例时自动调用。__del__(self)
:类的析构函数,当对象被销毁时自动调用。__str__(self)
:返回对象的字符串表示,用于可读性较好的字符串转换。__repr__(self)
:返回对象的官方字符串表示,通常用于调试。__len__(self)
:返回容器类型(如列表、字典)的长度。__getitem__(self, key)
:允许使用下标访问对象的元素,如obj[key]
。__setitem__(self, key, value)
:允许设置对象的元素,如obj[key] = value
。__iter__(self)
:返回迭代器对象,使得对象可以用于迭代,如for x in obj
。__next__(self)
:迭代器的下一个元素方法,用于迭代。__call__(self)
:允许一个实例像函数那样被调用,如obj()
。__eq__(self, other)
:定义等于(==)运算符的行为。__ne__(self, other)
:定义不等于(!=)运算符的行为。__lt__(self, other)
、__le__(self, other)
、__gt__(self, other)
、__ge__(self, other)
:定义比较运算符的行为。
这些特殊方法使得 Python 的类更加灵活和强大。通过定义这些特殊方法,你可以控制类的实例在特定操作下的行为。例如,如果你定义了 __str__
和 __repr__
方法,你可以控制当你打印对象或使用 repr()
函数时的输出。
此外,还有一些特殊属性,如 __dict__
,它是一个字典,包含了对象的所有属性,以及 __class__
,它指向对象的类。
这些特殊方法和属性是 Python 的内置特性,不需要你显式调用,它们在适当的上下文中自动工作。
3. resnet = models.resnet101(pretrained=True)的作用
在 PyTorch 深度学习框架中,这行代码用于加载一个预训练的 ResNet101 模型。让我们分解这行代码的含义:
models:这是 PyTorch 提供的一个模块,其中包含了多种预定义的神经网络架构,包括但不限于 ResNet、VGG、Inception 等。
resnet101:这是 ResNet 系列中的一个特定模型,"101" 表示该模型包含 101 层。ResNet(Residual Network)是由微软研究院提出的一种深度残差学习框架,它通过引入残差学习解决了深度神经网络训练中的退化问题。
pretrained=True:这个参数告诉 PyTorch 你希望加载一个已经在大型数据集(通常是 ImageNet)上预训练过的模型。预训练模型的权重已经通过在大量数据上的训练得到了优化,这可以大大加快你在特定任务上的训练速度,并提高模型的性能。
resnet:这是你给加载的模型对象指定的变量名。通过这个变量,你可以访问模型的层、进行预测或者继续训练模型。
这行代码执行后,resnet 将成为一个 ResNet101 模型的实例,其权重已经通过在 ImageNet 数据集上的预训练进行了初始化。这意味着你可以使用这个模型进行特征提取或者微调(fine-tuning),以适应你的特定任务,如图像分类、目标检测或语义分割等。
4. 如下代码的作用:
from torchvision import transforms
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)])
这段代码是在使用 PyTorch 的 torchvision
库来定义一个图像预处理流水线。在深度学习中,特别是在计算机视觉任务中,预处理是准备图像数据以供模型训练和推理的重要步骤。以下是代码的详细解释:
-
导入
transforms
模块:from torchvision import transforms
这行代码从
torchvision
库中导入transforms
模块,该模块提供了一系列的图像转换操作。 -
创建预处理流水线:
preprocess = transforms.Compose([ # 预处理操作列表 ])
这里使用
transforms.Compose
来创建一个预处理流水线。Compose
接受一个包含多个预处理操作的列表,按顺序依次对图像应用这些操作。 -
调整图像大小:
transforms.Resize(256),
使用
transforms.Resize
将图像大小调整为 256x256 像素。这是常见的预处理步骤,因为大多数模型需要固定大小的输入。 -
中心裁剪图像:
transforms.CenterCrop(224),
使用
transforms.CenterCrop
从图像中心裁剪出 224x224 像素的区域。这是许多预训练模型的标准输入尺寸。 -
转换为张量:
transforms.ToTensor(),
使用
transforms.ToTensor
将 PIL 图像或 NumPyndarray
转换为 PyTorch 张量,并将图像的像素值从 [0, 255] 归一化到 [0.0, 1.0]。 -
标准化:
transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] )
使用
transforms.Normalize
对图像进行标准化处理。这里使用的均值(mean)和标准差(std)是 ImageNet 数据集上预训练模型通常使用的值。标准化有助于模型更快地收敛。
这个预处理流水线可以用于图像分类任务,确保输入图像与预训练模型的期望输入格式一致。以下是如何将这个预处理流水线应用于图像的示例:
from PIL import Image
# 打开图像文件
image = Image.open("path_to_your_image.jpg")
# 应用预处理流水线
image_processed = preprocess(image)
# 现在 image_processed 是一个 PyTorch 张量,可以用于模型推理
在实际应用中,可以将这个预处理流水线与数据加载器结合使用,以便在训练和验证过程中自动应用这些预处理步骤。
5.如下代码的功能:
import torch
batch_t = torch.unsqueeze(img_t, 0)
这段代码涉及到 PyTorch 库,它用于处理张量(tensors),这是 PyTorch 中的基本数据结构,类似于 NumPy 数组但可以在 GPU 上运行以加速计算。让我们分解这行代码的含义:
导入 PyTorch 库:
python
import torch
这行代码导入了 PyTorch 库,使得你可以使用它的功能。
添加批量维度:
python
batch_t = torch.unsqueeze(img_t, 0)
img_t 是一个已经预处理的图像张量,通常是一个四维张量,其形状为 [C, H, W],其中 C 是通道数,H 是高度,W 是宽度。
torch.unsqueeze 函数用于在张量的指定位置添加一个维度。在这个例子中,unsqueeze(img_t, 0) 在 img_t 的第 0 个位置(即最前面)添加了一个维度。
这个操作将 img_t 从 [C, H, W] 形状变为 [1, C, H, W],其中最前面的 1 表示批量大小(batch size)。这个批量大小为 1 的张量现在可以被用于批量处理,即使它只包含一个图像。
6. resnet.eval()的功能
在 PyTorch 中,resnet.eval() 是一个非常重要的调用,用于将模型设置为评估模式(evaluation mode)。这通常在进行模型推理(即在测试或验证数据集上进行预测)时使用。以下是 eval() 方法的主要作用和重要性:
禁用 Dropout 和 Batch Normalization:在训练过程中,为了正则化模型并防止过拟合,可能会使用 Dropout 和 Batch Normalization 等技术。Dropout 会在训练时随机关闭一些神经元,而 Batch Normalization 会根据批次中的样本调整激活值。在评估模式下,eval() 会禁用这些层的行为,确保它们在推理时表现得像在训练时一样,但不会随机关闭神经元或根据批次调整激活值。
保持 Batch Normalization 统计:当模型处于评估模式时,Batch Normalization 层会使用在训练过程中计算得到的均值和方差,而不是当前批次的统计数据。这有助于模型在推理时保持一致的输出。
确保模型行为一致:在评估模式下,模型的所有层都会按照它们在训练时的配置来运行,但不会进行梯度更新。这确保了模型在推理时的行为与训练时一致。
准备模型进行推理:在进行模型推理之前,调用 eval() 是一个好习惯,它提醒你模型即将用于推理,而不是训练。
自然会联想到模型有几种模式
在 PyTorch 中,与评估模式(evaluation mode)相对应的是训练模式(training mode)。这两种模式通过调用模型实例上的 .eval()
和 .train()
方法来设置。以下是这两种模式的主要区别:
-
训练模式(Training Mode):
- 这是模型的默认模式,当你创建或加载一个模型时,它通常处于训练模式。
- 在训练模式下,模型中的所有层都按照它们在训练时的配置运行,包括随机关闭神经元的 Dropout 层和根据批次数据调整参数的 Batch Normalization 层。
- 训练模式用于模型的学习过程,即通过反向传播和梯度下降来更新模型的权重。
-
评估模式(Evaluation Mode):
- 通过调用
.eval()
方法将模型设置为评估模式。 - 在评估模式下,模型中的某些层(如 Dropout 和 Batch Normalization)会改变它们的行为,以确保在推理时输出的一致性。
- 评估模式用于模型的推理过程,即在测试或验证数据集上进行预测,或者在生产环境中使用模型进行推断。
- 通过调用
除了这两种模式,还有一些其他的概念与模型的训练和评估有关:
-
混合精度训练(Mixed Precision Training): 这是一种技术,它使用单精度浮点数(float32)和半精度浮点数(float16)来训练模型,以提高训练速度并减少内存使用。
-
梯度累积(Gradient Accumulation): 这是一种在训练过程中使用的技术,允许模型在多个小批量上进行前向和反向传播,然后累积梯度,最后执行一次权重更新。这在显存有限时特别有用。
-
模型保存和加载(Model Saving and Loading): 在训练过程中,你可能需要保存模型的状态,以便在以后恢复训练或进行评估。PyTorch 提供了
torch.save()
和torch.load()
函数来保存和加载模型的状态。 -
模型的微调(Fine-tuning): 这是指在预训练模型的基础上,针对特定任务进行额外训练的过程。在微调过程中,你可能会将模型设置为训练模式,并调整学习率或其他超参数。
在实际应用中,正确地在训练模式和评估模式之间切换是非常重要的,因为它直接影响模型的行为和性能。