PyTorch框架学习二十——模型微调(Finetune)

PyTorch框架学习二十——模型微调(Finetune)

  • 一、Transfer Learning:迁移学习
  • 二、Model Finetune:模型的迁移学习
  • 三、看个例子:用ResNet18预训练模型训练一个图片二分类任务

因为模型微调的内容没有实际使用过,但是后面是肯定会要了解的,所以这里算是一个引子,简单从概念上介绍一下迁移学习与模型微调,后面有时间或需要用到时再去详细了解。

一、Transfer Learning:迁移学习

是机器学习(ML)的一项分支,主要研究源域的知识如何应用到目标域。将源域所学习到的知识应用到目标任务当中,用于提升在目标任务里模型的性能

所以迁移学习的主要目的就是借助其他的知识提升模型性能。

详细了解可以参考这篇综述:《A Survey on Transfer Learning》

二、Model Finetune:模型的迁移学习

训练一个Model,就是去更新它的权值,这里的权值可以称为知识,从AlexNet的卷积核可视化中,我们可以看到大多数卷积核为边缘等信息,这些信息就是AlexNet在ImageNet上学习到的知识,所以可以把权值理解为神经网络在特定任务中学习到的知识,而这些知识可以迁移,将其迁移到新任务中,这样就完成了一个Transfer Learning,这就是模型微调,这就是为什么称Model Finetune为Transfer Learning,它其实是将权值认为是知识,把这些知识应用到新任务中去。

为什么要 Model Finetune?

一般来说需要模型微调的任务都有如下特点:在新任务中数据量较小,不足以训练一个较大的Model。可以用Model Finetune的方式辅助我们在新任务中训练一个较好的模型,让训练过程更快。

模型微调的步骤

一般来说,一个神经网络模型可以分为Features ExtractorClassifer两部分,前者用于提取特征,后者用于合理分类,通常我们习惯对Features Extractor的结构和参数进行保留,而仅修改Classifer来适应新任务。这是因为新任务的数据量太小,预训练参数已经具有共性,不再需要改变,如果再用这些小数据训练,可能反而过拟合。

所以步骤如下:

  1. 获取预训练模型参数
  2. 加载参数至模型(load_state_dict)
  3. 修改输出层以适应新任务

模型微调训练方法

因为需要保留Features Extractor的结构和参数,提出了两种训练方法:

  1. 固定预训练的参数:requires_grad = False 或者 lr = 0,即不更新参数;
  2. Features Extractor部分设置很小的学习率,这里用到参数组(params_group)的概念,分组设置优化器的参数。

三、看个例子:用ResNet18预训练模型训练一个图片二分类任务

涉及到的data:https://pan.baidu.com/s/115grxHrq6kMZBg6oC2fatg
提取码:yld7

# -*- coding: utf-8 -*-
import os
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as pltimport sys
hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__)+os.path.sep+".."+os.path.sep+"..")
sys.path.append(hello_pytorch_DIR)from tools.my_dataset import AntsDataset
from tools.common_tools import set_seed
import torchvision.models as models
import torchvision
BASEDIR = os.path.dirname(os.path.abspath(__file__))
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("use device :{}".format(device))set_seed(1)  # 设置随机种子
label_name = {"ants": 0, "bees": 1}# 参数设置
MAX_EPOCH = 25
BATCH_SIZE = 16
LR = 0.001
log_interval = 10
val_interval = 1
classes = 2
start_epoch = -1
lr_decay_step = 7# ============================ step 1/5 数据 ============================
data_dir = os.path.abspath(os.path.join(BASEDIR, "..", "..", "data", "hymenoptera_data"))
if not os.path.exists(data_dir):raise Exception("\n{} 不存在,请下载 07-02-数据-模型finetune.zip  放到\n{} 下,并解压即可".format(data_dir, os.path.dirname(data_dir)))train_dir = os.path.join(data_dir, "train")
valid_dir = os.path.join(data_dir, "val")norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]train_transform = transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize(norm_mean, norm_std),
])valid_transform = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize(norm_mean, norm_std),
])# 构建MyDataset实例
train_data = AntsDataset(data_dir=train_dir, transform=train_transform)
valid_data = AntsDataset(data_dir=valid_dir, transform=valid_transform)# 构建DataLoder
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)# ============================ step 2/5 模型 ============================# 1/3 构建模型
resnet18_ft = models.resnet18()# 2/3 加载参数
# flag = 0
flag = 1
if flag:path_pretrained_model = os.path.join(BASEDIR, "..", "..", "data", "finetune_resnet18-5c106cde.pth")if not os.path.exists(path_pretrained_model):raise Exception("\n{} 不存在,请下载 07-02-数据-模型finetune.zip\n放到 {}下,并解压即可".format(path_pretrained_model, os.path.dirname(path_pretrained_model)))state_dict_load = torch.load(path_pretrained_model)resnet18_ft.load_state_dict(state_dict_load)# 法1 : 冻结卷积层
flag_m1 = 0
# flag_m1 = 1
if flag_m1:for param in resnet18_ft.parameters():param.requires_grad = Falseprint("conv1.weights[0, 0, ...]:\n {}".format(resnet18_ft.conv1.weight[0, 0, ...]))# 3/3 替换fc层
num_ftrs = resnet18_ft.fc.in_features
resnet18_ft.fc = nn.Linear(num_ftrs, classes)resnet18_ft.to(device)
# ============================ step 3/5 损失函数 ============================
criterion = nn.CrossEntropyLoss()                                                   # 选择损失函数# ============================ step 4/5 优化器 ============================
# 法2 : conv 小学习率
# flag = 0
flag = 1
if flag:fc_params_id = list(map(id, resnet18_ft.fc.parameters()))     # 返回的是parameters的 内存地址base_params = filter(lambda p: id(p) not in fc_params_id, resnet18_ft.parameters())optimizer = optim.SGD([{'params': base_params, 'lr': LR*0},   # 0{'params': resnet18_ft.fc.parameters(), 'lr': LR}], momentum=0.9)else:optimizer = optim.SGD(resnet18_ft.parameters(), lr=LR, momentum=0.9)               # 选择优化器scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=lr_decay_step, gamma=0.1)     # 设置学习率下降策略# ============================ step 5/5 训练 ============================
train_curve = list()
valid_curve = list()for epoch in range(start_epoch + 1, MAX_EPOCH):loss_mean = 0.correct = 0.total = 0.resnet18_ft.train()for i, data in enumerate(train_loader):# forwardinputs, labels = datainputs, labels = inputs.to(device), labels.to(device)outputs = resnet18_ft(inputs)# backwardoptimizer.zero_grad()loss = criterion(outputs, labels)loss.backward()# update weightsoptimizer.step()# 统计分类情况_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).squeeze().cpu().sum().numpy()# 打印训练信息loss_mean += loss.item()train_curve.append(loss.item())if (i+1) % log_interval == 0:loss_mean = loss_mean / log_intervalprint("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))loss_mean = 0.# if flag_m1:print("epoch:{} conv1.weights[0, 0, ...] :\n {}".format(epoch, resnet18_ft.conv1.weight[0, 0, ...]))scheduler.step()  # 更新学习率# validate the modelif (epoch+1) % val_interval == 0:correct_val = 0.total_val = 0.loss_val = 0.resnet18_ft.eval()with torch.no_grad():for j, data in enumerate(valid_loader):inputs, labels = datainputs, labels = inputs.to(device), labels.to(device)outputs = resnet18_ft(inputs)loss = criterion(outputs, labels)_, predicted = torch.max(outputs.data, 1)total_val += labels.size(0)correct_val += (predicted == labels).squeeze().cpu().sum().numpy()loss_val += loss.item()loss_val_mean = loss_val/len(valid_loader)valid_curve.append(loss_val_mean)print("Valid:\t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val_mean, correct_val / total_val))resnet18_ft.train()train_x = range(len(train_curve))
train_y = train_curvetrain_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curveplt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')plt.legend(loc='upper right')
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()

输出结果为:

use device :cpu
Training:Epoch[000/025] Iteration[010/016] Loss: 0.6572 Acc:60.62%
epoch:0 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)
Valid:	 Epoch[000/025] Iteration[010/010] Loss: 0.4565 Acc:84.97%
Training:Epoch[001/025] Iteration[010/016] Loss: 0.4074 Acc:85.00%
epoch:1 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)
Valid:	 Epoch[001/025] Iteration[010/010] Loss: 0.2846 Acc:93.46%
Training:Epoch[002/025] Iteration[010/016] Loss: 0.3542 Acc:83.12%
epoch:2 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)
Valid:	 Epoch[002/025] Iteration[010/010] Loss: 0.2904 Acc:89.54%
Training:Epoch[003/025] Iteration[010/016] Loss: 0.2266 Acc:93.12%
epoch:3 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)
Valid:	 Epoch[003/025] Iteration[010/010] Loss: 0.2252 Acc:94.12%
Training:Epoch[004/025] Iteration[010/016] Loss: 0.2805 Acc:87.50%
epoch:4 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)
Valid:	 Epoch[004/025] Iteration[010/010] Loss: 0.1953 Acc:95.42%
Training:Epoch[005/025] Iteration[010/016] Loss: 0.2423 Acc:91.88%
epoch:5 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)
Valid:	 Epoch[005/025] Iteration[010/010] Loss: 0.2399 Acc:92.16%
Training:Epoch[006/025] Iteration[010/016] Loss: 0.2455 Acc:90.00%
epoch:6 conv1.weights[0, 0, ...] :tensor([[-0.0104, -0.0061, -0.0018,  0.0748,  0.0566,  0.0171, -0.0127],[ 0.0111,  0.0095, -0.1099, -0.2805, -0.2712, -0.1291,  0.0037],[-0.0069,  0.0591,  0.2955,  0.5872,  0.5197,  0.2563,  0.0636],[ 0.0305, -0.0670, -0.2984, -0.4387, -0.2709, -0.0006,  0.0576],[-0.0275,  0.0160,  0.0726, -0.0541, -0.3328, -0.4206, -0.2578],[ 0.0306,  0.0410,  0.0628,  0.2390,  0.4138,  0.3936,  0.1661],[-0.0137, -0.0037, -0.0241, -0.0659, -0.1507, -0.0822, -0.0058]],grad_fn=<SelectBackward>)

可以看出,模型的训练从一开始就有了较高的准确率,比较快速地进入了较好训练状态,相比于不借助其他知识的普通训练,速度上要快很多。

而且这里是用分组参数的方法将特征提取部分的学习率设置为0,这样就不改变特征提取部分的参数了,而将全连接层的学习率正常设置,从上面的结果也能看出特征提取部分的权值一直没有改变(改变的是全连接层的权值,所以准确率才会提升)。

ps:这次笔记涉及的迁移学习的知识还只是基础,以后若有需要还要更加深入。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/491652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sql中 in , not in , exists , not exists效率分析

in和exists执行时&#xff0c;in是先执行子查询中的查询&#xff0c;然后再执行主查询。而exists查询它是先执行主查询&#xff0c;即外层表的查询&#xff0c;然后再执行子查询。 exists 和 in 在执行时效率单从执行时间来说差不多&#xff0c;exists要稍微优于in。在使用时一…

美国服务机器人技术路线图

来源&#xff1a;美国国家科学基金会服务机器人正在以高速的增长速度加速步入我们的日常生活。正是基于广阔的市场前景&#xff0c;美国国家科学基金会颁布了《美国机器人技术路线图》&#xff0c;其中服务机器人是其中的重点一章。服务机器人的主要应用领域服务机器人是一类用…

使用matplotlib画图时不能同时打开太多张图

使用matplotlib画图时有时会收到来自matplotlib的runtime warming的警告&#xff0c;原因可能是同时打开太多张图&#xff0c;最常见的情况是在一个循环中画图&#xff0c;每次循环都新建一个图&#xff0c;但是未关闭新建的图&#xff0c;当循环次数多了之后内存就吃不消了。 …

OpenCV与图像处理学习一——图像基础知识、读入、显示、保存图像、灰度转化、通道分离与合并

OpenCV与图像处理学习一——图像基础知识、读入、显示、保存图像、灰度转化、通道分离与合并一、图像基础知识1.1 数字图像的概念1.2 数字图像的应用1.3 OpenCV介绍二、图像属性2.1 图像格式2.2 图像尺寸2.2.1 读入图像2.2.2 显示图像2.2.3 保存图像2.3 图像分辨率和通道数2.3.…

【Docker】Docker学习笔记:安装部署

Docker 是实现轻量级的操作系统虚拟化解决方案。 Docker目前已经支持非常多的Linux平台&#xff0c;Ubuntu、Red Hat企业版Linux、Debian、CentOs、Fedora、Oracle Linux等。如果使用虚拟环境&#xff0c;甚至可以在OS X 和 windows中运行。 安装的条件 Docker目前只能在64位CP…

定了!5G商用牌照近期发放​​​​,透露两大信息(附:2019年5G行业关键材料及市场研究报告)...

来源&#xff1a;世界科技创新论坛据工信微报和新华社消息&#xff1a;日前&#xff0c;全球5G正在进入商用部署的关键期。坚持自主创新与开放合作相结合&#xff0c;我国5G产业已建立竞争优势。5G标准是全球产业界共同参与制定的统一国际标准&#xff0c;我国声明的标准必要专…

python保存和加载数组

通过np.save("filename.npy",a)保存数组&#xff0c;数组可以为高维。利用这种方法&#xff0c;保存文件的后缀名字一定会被置为.npy&#xff0c;这种格式最好只用numpy.load("filename")来读取。 举个例子&#xff1a; import numpy as npa np.array([…

OpenCV与图像处理学习二——图像直方图与色彩空间

OpenCV与图像处理学习二——图像直方图与色彩空间2.4 图像直方图&#xff08;Image Histogram&#xff09;2.4.1 直方图的绘制2.4.2 三通道直方图绘制2.5 颜色空间2.5.1 RGB颜色空间2.5.2 HSV颜色空间&#xff08;Hue、Saturation、Value&#xff09;2.5.3 HSI2.5.4 CMYK&#…

JavaScript基础5——关于ECMAscript的函数

ECMAScript的函数概述(一般定义到<head>标签之间)(1)定义函数&#xff0c;JavaScript一般有三种定义函数方法&#xff1a; *第一种是使用function语句定义函数&#xff08;静态方法&#xff09; 1 function 函数名(var1,var2,...,varX) 2 { 3 4 代码&…

中国科学家首次观察到量子世界的宇称时间对称

来源&#xff1a;中国新闻网 中国科学家调控量子跳双人舞 首次观察到量子世界的宇称时间对称中新社合肥6月3日电 (吴兰 范琼)记者3日从中国科学技术大学获悉&#xff0c;该校杜江峰院士的研究团队通过调控量子跳出双人舞&#xff0c;在国际上首次观察到量子世界的宇称时间对称。…

tf.data详解

转自https://www.cnblogs.com/hellcat/p/8569651.html Dataset有两个重要的类&#xff1a;Dataset和Iterator。 Dataset可以看作是相同类型“元素”的有序列表。在实际使用时&#xff0c;单个“元素”可以是向量&#xff0c;也可以是字符串、图片&#xff0c;甚至是tuple或者…

nodejs设置x-xss-protection解决xss问题

在Node.js中设置X-XSS-Protection可以通过使用helmet库来完成。 首先&#xff0c;确保已经安装了helmet库。如果没有安装&#xff0c;可以运行以下命令进行安装&#xff1a; npm install helmet --save 然后&#xff0c;在你的Node.js应用程序中引入并配置helmet库&#xff…

OpenCV与图像处理学习三——线段、矩形、圆、椭圆、多边形的绘制以及文字的添加

OpenCV与图像处理学习三——线段、矩形、圆、椭圆、多边形的绘制以及文字的添加一、OpenCV中的绘图函数1.1 线段绘制1.2 矩形绘制1.3 圆绘制1.4 椭圆的绘制1.5 多边形绘制1.6 添加文字上两次笔记主要知识点回顾&#xff1a; 数字图像基本概念图像的读取、显示与保存图像直方图…

第二百一十九天 how can I 坚持

今天好冷&#xff0c;白天在家待了一天&#xff0c;晚上&#xff0c;老贾生日&#xff0c;生日快乐&#xff0c;去海底捞吃了个火锅&#xff0c;没感觉呢。 今天还发现了个好游戏&#xff0c;纪念碑谷&#xff0c;挺新颖&#xff0c;就是难度有点大了。 好累。睡觉&#xff0c;…

AI英雄 | 论人工智能与自由意志,请看尤瓦尔与李飞飞的这场“激辩”

来源&#xff1a;Towards Data Science尤瓦尔赫拉利和李飞飞在斯坦福大学展开了一场别开生面的对话&#xff0c;他们所提出的问题已经远远超出了我们可以解答的范围。《连线》杂志主编尼古拉斯•汤普森在座无虚席的纪念礼堂主持了这场90分钟的谈话。赫拉利&#xff08;Harari&a…

OpenCV与图像处理学习四——图像几何变换:平移、缩放、旋转、仿射变换与透视变换

OpenCV与图像处理学习四——图像几何变换&#xff1a;平移、缩放、旋转、仿射变换与透视变换二、图像的几何变换2.1 图像平移2.2 图像缩放&#xff08;上采样与下采样&#xff09;2.3 图像旋转2.4 仿射变换2.5 透视变化2.6 几何变化小结续上次的笔记&#xff1a;OpenCV与图像处…

python打乱顺序的洗牌函数

numpy.random.shuffle&#xff08;x&#xff09; x&#xff1a;序列或者数组 对于多维数组&#xff0c;只对第一维进行洗牌&#xff0c;子数组的顺序改变了&#xff0c;但是它们的内容保持不变。 >>> arr np.arange(10) >>> np.random.shuffle(arr) >…

课后作业和动手动脑

一&#xff0c;运行TestInherits.java 通过super调用基类构造方法&#xff0c;必是子类构造方法中的第一个语句。 二.为什么子类的构造方法在运行之前&#xff0c;必须调用父类的构造方法&#xff1f;能不能反过来&#xff1f;为什么不能反过来&#xff1f; 构造函数的主要作用…

OpenCV与图像处理学习五——图像滤波与增强:线性、非线性滤波、直方图均衡化与Gamma变换

OpenCV与图像处理学习五——图像滤波与增强&#xff1a;线性、非线性滤波、直方图均衡化与Gamma变换三、图像滤波与增强3.1 线性滤波3.1.1 方框滤波3.1.2 均值滤波3.1.3 高斯滤波3.1.4 一般卷积滤波3.2 非线性滤波3.2.1 中值滤波3.2.2 双边滤波3.3 图像直方图均衡化3.3.1 单通道…

张钹院士:人工智能技术已进入第三代

来源&#xff1a;经济观察报近日&#xff0c;中科院院士、清华大学人工智能研究院院长张钹教授接受记者采访时认为&#xff0c;目前基于深度学习的人工智能在技术上已经触及天花板。从长远来看&#xff0c;必须得走人类智能这条路&#xff0c;最终要发展人机协同&#xff0c;人…