🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
在前面的章节中,我们了解了利用卷积神经网络( CNN ) 和预训练模型来执行图像分类。本章将进一步巩固我们对 CNN 的理解,以及在实际应用中利用它们时要考虑的各种实际方面。我们将从了解 CNN 使用类激活图( CAM )预测它们所做的类的原因开始。在此之后,我们将了解可以用来提高模型准确性的各种数据增强。最后,我们将了解模型在现实世界中可能出错的各种情况,并强调在这种情况下应该注意的方面以避免陷阱。
本章将涵盖以下主题:
- 生成 CAM
- 了解批量标准化和数据增强的影响
- 模型实施过程中需要注意的实际问题
生成 CAM
想象一个场景,您已经建立了一个能够做出良好预测的模型。但是,您正在向其展示模型的利益相关者想要了解模型预测为何如此的原因。在这种情况下,CAM 会派上用场。一个示例 CAM 如下所示,其中左侧是输入图像,右侧突出显示了用于提出类预测的像素:
让我们了解如何在训练模型后生成 CAM。特征图是卷积操作之后的中间激活。通常,这些激活图的形状是n-channels x height x width. 如果我们取所有这些激活的平均值,它们会显示图像中所有类的热点。但是,如果我们对仅对特定类别重要的位置感兴趣(例如,),我们只需要找出其中负责该类别的那些特征图。对于生成这些特征图的卷积层,我们可以计算其相对于类的梯度。请注意,只有那些负责预测的通道 catn-channels cat cat 会有很高的梯度。这意味着我们可以使用梯度信息来给每一个赋予权重,n-channels并获得一个专门针对 的激活图 cat。
现在我们了解了如何生成 CAM 的高级策略,让我们一步一步付诸实践:
- 决定要为哪个类计算 CAM,以及要为神经网络中的哪个卷积层计算 CAM。
- 计算任何卷积层产生的激活值——假设随机卷积层的特征形状为 512 x 7 x 7。
- 获取从该层产生的关于感兴趣类的梯度值。输出梯度形状为 256 x 512 x 3 x 3(这是卷积张量的形状——即in-channels x out-channels x kernel-size x kernel-size)。
- 计算每个输出通道内梯度的平均值。输出形状为 512。
- 计算加权激活图——它是 512 个梯度均值乘以 512 个激活通道。输出形状为 512 x 7 x 7。
- 计算加权激活图的平均值(跨 512 个通道)以获取形状为 7 x 7 的输出。
- 调整(放大)加权激活图输出的大小,以获取与输入大小相同的图像。这样做是为了我们有一个类似于原始图像的激活图。
- 将加权激活图叠加到输入图像上。
下图来自论文Grad-CAM: Gradient-weighted Class Activation Mapping ( https://arxiv.org/abs/1610.02391 ) 形象地描述了前面的步骤:
整个过程的关键在于步骤5。我们考虑该步骤的两个方面:
- 如果某个像素很重要,那么 CNN 将在这些像素处有很大的激活。
- 如果某个卷积通道对于所需类别很重要,则该通道的梯度将非常大。
在将这两者相乘时,我们确实最终得到了所有像素的重要性图。
上述策略在代码中实现,以了解 CNN 模型预测图像指示疟疾事件可能性的原因,如下所示:
1.下载数据集并导入相关包:
-
import os
-
if
not os.path.exists(
'cell_images'):
-
!pip install -U -q torch_snippets
-
!wget -q ftp:
/
/lhcftp.nlm.nih.gov
/Open-Access-Datasets
/
-
Malaria
/cell_images.zip
-
!unzip -qq cell_images.zip
-
!rm cell_images.zip
-
from torch_snippets import
*
2.指定对应于输出类的索引:
id2int = {'Parasitized': 0, 'Uninfected': 1}
3.执行要在图像之上完成的转换:
-
from torchvision import transforms
as T
-
-
trn_tfms
= T.Compose([
-
T.ToPILImage(),
-
T.Resize(
128),
-
T.CenterCrop(
128),
-
T.ColorJitter(brightness
=(
0.95,1.05),
-
contrast
=(
0.95,1.05),
-
saturation
=(
0.95,1.05),
-
hue
=
0.05),
-
T.RandomAffine(
5, translate
=(
0.01,0.1)),
-
T.ToTensor(),
-
T.Normalize(mean
=[
0.5,
0.5,
0.5],
-
std
=[
0.5,
0.5,
0.5]),
-
])
在前面的代码中,我们在输入图像之上有一个转换管道——这是一个调整图像大小的管道(128在这种情况下,它确保一个维度的最小尺寸为 ),然后从中央。此外,我们正在执行随机颜色抖动和仿射变换。接下来,我们使用.ToTensor方法对图像进行缩放,使其具有介于0和之间的值1,最后,我们对图像进行归一化。正如第 4 章介绍卷积神经网络中所讨论的,我们也可以使用该imgaug库。
- 指定要对验证图像进行的转换:
-
val_tfms
= T.Compose([
-
T.ToPILImage(),
-
T.Resize(
128),
-
T.CenterCrop(
128),
-
T.ToTensor(),
-
T.Normalize(mean
=[
0.5,
0.5,
0.5],
-
std
=[
0.5,
0.5,
0.5]),
-
])
4.定义数据集类 - MalariaImages:
-
class
MalariaImages(
Dataset):
-
-
def
__init__(
self, files, transform=None):
-
self.files = files
-
self.transform = transform
-
logger.info(
len(self))
-
-
def
__len__(
self):
-
return
len(self.files)
-
-
def
__getitem__(
self, ix):
-
fpath = self.files[ix]
-
clss = fname(parent(fpath))
-
img = read(fpath,
1)
-
return img, clss
-
-
def
choose(
self):
-
return self[randint(
len(self))]
-
-
def
collate_fn(
self, batch):
-
_imgs, classes =
list(
zip(*batch))
-
if self.transform:
-
imgs = [self.transform(img)[
None] \
-
for img
in _imgs]
-
classes = [torch.tensor([id2int[clss]]) \
-
for
class
in classes]
-
imgs, classes = [torch.cat(i).to(device) \
-
for i
in [imgs, classes]]
-
return imgs, classes, _imgs
5.获取训练和验证数据集和数据加载器:
-
device
=
'cuda'
if torch.cuda.
is_available()
else
'cpu'
-
all_files
= Glob(
'cell_images/*/*.png')
-
np.
random.seed(
10)
-
np.
random.shuffle(
all_files)
-
-
from sklearn.model_selection import train_
test_split
-
trn_files, val_files
= train_
test_split(
all_files, \
-
random_state
=
1)
-
-
trn_ds
= MalariaImages(trn_files, transform
=trn_tfms)
-
val_ds
= MalariaImages(val_files, transform
=val_tfms)
-
trn_dl
= DataLoader(trn_ds,
32, shuffle
=
True,
-
collate_fn
=trn_ds.collate_fn)
-
val_dl
= DataLoader(val_ds,
32, shuffle
=
False,
-
collate_fn
=val_ds.collate_fn)
6.定义模型 - MalariaClassifier:
-
def convBlock(ni,
no):
-
return nn.
Sequential(
-
nn.Dropout(
0.2),
-
nn.Conv
2d(ni,
no, kernel_
size
=
3, padding
=
1),
-
nn.ReLU(inplace
=
True),
-
nn.BatchNorm
2d(
no),
-
nn.MaxPool
2d(
2),
-
)
-
-
class MalariaClassifier(nn.Module):
-
def __init__(
self):
-
super().__init__()
-
self.model
= nn.
Sequential(
-
convBlock(
3,
64),
-
convBlock(
64,
64),
-
convBlock(
64,
128),
-
convBlock(
128,
256),
-
convBlock(
256,
512),
-
convBlock(
512,
64),
-
nn.Flatten(),
-
nn.Linear(
256,
256),
-
nn.Dropout(
0.2),
-
nn.ReLU(inplace
=
True),
-
nn.Linear(
256, len(id
2int))
-
)
-
self.loss_fn
= nn.CrossEntropyLoss()
-
-
def forward(
self, x):
-
return
self.model(x)
-
-
def
compute_metrics(
self, preds, targets):
-
loss
=
self.loss_fn(preds, targets)
-
acc
=(torch.max(preds,
1)[
1]
=
=targets).float().mean()
-
return loss, acc
7.定义对一批数据进行训练和验证的函数:
-
def
train_batch(model, data, optimizer, criterion):
-
model.
train()
-
ims, labels, _ = data
-
_preds =
model(ims)
-
optimizer.
zero_grad()
-
loss, acc =
criterion(_preds, labels)
-
loss.
backward()
-
optimizer.
step()
-
return loss.
item(), acc.
item()
-
-
@torch.
no_grad()
-
def
validate_batch(model, data, criterion):
-
model.
eval()
-
ims, labels, _ = data
-
_preds =
model(ims)
-
loss, acc =
criterion(_preds, labels)
-
return loss.
item(), acc.
item()
8.在越来越多的时期训练模型:
-
model
= MalariaClassifier().
to(device)
-
criterion
= model.
compute_metrics
-
optimizer
= optim.Adam(model.parameters(), lr
=
1e-
3)
-
n_epochs
=
2
-
-
log
=
Report(n_epochs)
-
for ex
in range(n_epochs):
-
N
= len(trn_dl)
-
for bx,
data
in enumerate(trn_dl):
-
loss, acc
= train_batch(model,
data, optimizer, \
-
criterion)
-
log.
record(ex
+(bx
+
1)
/N,trn_loss
=loss,trn_acc
=acc, \
-
end
=
'\r')
-
-
N
= len(val_dl)
-
for bx,
data
in enumerate(val_dl):
-
loss, acc
=
validate_batch(model,
data, criterion)
-
log.
record(ex
+(bx
+
1)
/N,val_loss
=loss,val_acc
=acc, \
-
end
=
'\r')
-
-
log.
report_avgs(ex
+
1)
9.取模型中第五层的卷积层convBlock:
-
im
2fmap
= nn.
Sequential(
*(list(model.model[:
5].children())
+ \
-
list(model.model[
5][:
2].children())))
在前面的代码行中,我们正在获取模型的第四层以及其中的前两层convBlock——恰好是该Conv2D层。
10.定义im2gradCAM获取输入图像并获取与图像激活对应的热图的函数:
-
def im
2gradCAM(x):
-
model.eval()
-
logits
= model(x)
-
heatmaps
= []
-
activations
= im
2fmap(x)
-
print(activations.shape)
-
pred
= logits.max(-
1)[-
1]
-
# 得到模型的prediction
-
model.
zero_grad()
-
# 相对于
-
# 模型最自信的 logit计算梯度
-
logits[
0,pred].backward(retain_graph
=
True)
-
# 获取所需特征图位置的梯度
-
# 并获取每个特征图的平均梯度
-
pooled_grads
= model.model[-
7][
1]\.
-
weight.grad.
data.mean((
0,2,3))
-
# 将每个激活图乘以
-
# 范围内 i 的相应梯度平均值
-
for i
in range(activations.shape[
1]):
-
activations[:,i,:,:]
*
= pooled_grads[i]
-
# 取所有加权激活图的平均值
-
# (已由每个 fmap 的 avg.grad 加权)
-
heatmap
=torch.mean(activations, dim
=
1)[
0].cpu().detach()
-
return heatmap,
'Uninfected'
if pred.item() \
-
else
'Parasitized'
11.定义upsampleHeatmap函数以将热图上采样为与图像形状对应的形状:
-
SZ
=
128
-
def upsampleHeatmap(map, img):
-
m,M
= map.min(), map.m
ax()
-
map
=
255
* ((map-m)
/ (M-m))
-
map
= np.uint
8(map)
-
map
= cv
2.resize(map, (SZ,SZ))
-
map
= cv
2.applyColorMap(
255-map, cv
2.COLORMAP_JET)
-
map
= np.uint
8(map)
-
map
= np.uint
8(map
*
0.7
+ img
*
0.3)
-
return map
在前面的代码行中,我们对图像进行了反规范化,并将热图覆盖在图像之上。
12.在一组图像上运行上述函数:
-
N
=
20
-
_val_dl
= DataLoader(val_ds, batch_
size
=N, shuffle
=
True, \
-
collate_fn
=val_ds.collate_fn)
-
x,y,z
=
next(iter(_val_dl))
-
-
for i
in range(N):
-
image
= resize(z [i], SZ)
-
heatmap, pred
= im
2gradCAM(x[i:i
+
1])
-
if(pred
=
=
'Uninfected'):
-
continue
-
heatmap
= upsampleHeatmap(heatmap, image)
-
subplots([image, heatmap], nc
=
2、figsize
=(
5,3),\
-
suptitle
=pred)
上述代码的输出如下:
从这里,我们可以看到预测是这样的,因为内容以红色突出显示(具有最高的 CAM 值)。
现在我们已经了解了如何使用经过训练的模型为图像生成类激活热图,我们可以解释是什么让某个分类如此。
在下一节中,让我们了解有助于构建模型的数据增强的其他技巧。
了解数据增强和批量标准化的影响
提高模型准确性的一种巧妙方法是利用数据增强。我们已经在第 4 章介绍 卷积神经网络中看到了这一点,我们在其中使用数据增强来提高翻译图像分类的准确性。在现实世界中,您会遇到具有不同属性的图像——例如,一些图像可能更亮,一些可能在边缘附近包含感兴趣的对象,而一些图像可能比其他图像更不稳定。在本节中,我们将了解数据增强的使用如何帮助提高模型的准确性。此外,我们将了解数据增强实际上如何成为我们模型的伪正则化器。
为了了解数据增强和批量标准化的影响,我们将通过一个识别交通标志的数据集。我们将评估三种情况:
- 没有批量标准化/数据增强
- 只有批量标准化,但没有数据增强
- 批量标准化和数据增强
请注意,鉴于数据集和处理在三个场景中保持不变,并且只有数据增强和模型(添加批量标准化层)不同,我们将仅提供第一个场景的以下代码,而其他两个GitHub 上的 notebook 中提供了这些场景。
编码道路标志检测
让我们在没有数据增强和批量标准化的情况下为道路标志检测编写代码,如下所示:
1.下载数据集并导入相关包:
-
import os
-
if
not os.path.exists(
'GTSRB'):
-
!pip install -U -q torch_snippets
-
!wget -qq https:
/
/sid.erda.dk
/public
/archives
/
-
daaeac
0d
7ce
1152aea
9b
61d
9f
1e
19370
/
-
GTSRB_
Final_Training_Images.zip
-
!wget -qq https:
/
/sid.erda.dk
/public
/archives
/
-
daaeac
0d
7ce
1152aea
9b
61d
9f
1e
19370
/
-
GTSRB_
Final_
Test_Images.zip
-
!unzip -qq GTSRB_
Final_Training_Images.zip
-
!unzip -qq GTSRB_
Final_
Test_Images.zip
-
!wget https:
/
/raw.githubusercontent.com
/georgesung
/
-
traffic_
sign_classification_german
/master
/signnames.csv
-
!rm GTSRB_
Final_Training_Images.zip
-
GTSRB_
Final_
Test_Images.zip
-
-
from torch_snippets import
*
2.将类 ID 分配给可能的输出类:
-
classIds
= pd.
read_csv(
'signnames.csv')
-
classIds.
set_
index(
'ClassId', inplace
=
True)
-
classIds
= classIds.
to_dict()[
'SignName']
-
classIds
= {f
'{k:05d}':v
for k,v
in classIds.items()}
-
id
2int
= {v:ix
for ix,(k,v)
in enumerate(classIds.items())}
3.在图像之上定义转换管道,无需任何增强:
-
from torchvision import transforms
as T
-
trn_tfms
= T.Compose([
-
T.ToPILImage(),
-
T.Resize(
32),
-
T.CenterCrop(
32),
-
# T.ColorJitter(brightness
=(
0.8,1.2),
-
# contrast
=(
0.8,1.2),
-
# saturation
=(
0.8,1.2),
-
# hue
=
0.25),
-
# T.RandomAffine(
5, translate
=(
0.01,0.1)),
-
T.ToTensor(),
-
T.Normalize(mean
=[
0.485,
0.456,
0.406],
-
std
=[
0.229,
0.224,
0.225]),
-
])
-
-
val_tfms
= T.Compose([
-
T.ToPILImage(),
-
T.Resize(
32),
-
T.CenterCrop(
32),
-
T.ToTensor(),
-
T.Normalize(mean
=[
0.485,
0.456,
0.406],
-
std
=[
0.229,
0.224,
0.225]),
-
])
在前面的代码中,我们指定将每个图像转换为 PIL 图像,然后从中心调整和裁剪图像。此外,我们正在缩放图像以使像素值介于该方法之间0并1使用该.ToTensor方法。最后,我们正在对输入图像进行归一化,以便可以利用预训练的模型。
4.定义数据集类 - GTSRB:
-
class
GTSRB(
Dataset):
-
-
def
__init__(
self, files, transform=None):
-
self.files = files
-
self.transform = transform
-
logger.info(
len(self))
-
-
def
__len__(
self):
-
return
len(self.files)
-
-
def
__getitem__(
self, ix):
-
fpath = self.files[ix]
-
clss = fname(parent(fpath))
-
img = read(fpath,
1)
-
return img, classIds[clss]
-
-
def
choose(
self):
-
return self[randint(
len(self))]
-
def collate_fn(
self, batch):
-
imgs, classes
= list(zip(
*batch))
-
if
self.transform:
-
imgs
=[
self.transform(img)[None] \
-
for img
in imgs]
-
classes
= [torch.tensor([id
2int[clss]]) \
-
for clss
in classes]
-
imgs, classes
= [torch.cat(i).
to(device) \
-
for i
in [imgs, classes]]
-
return imgs, classes
5.创建训练和验证数据集和数据加载器:
-
device
=
'cuda'
if torch.cuda.
is_available()
else
'cpu'
-
all_files
= Glob(
'GTSRB/Final_Training/Images/*/*.ppm')
-
np.
random.seed(
10)
-
np.
random.shuffle(
all_files)
-
-
from sklearn.model_selection import train_
test_split
-
trn_files, val_files
= train_
test_split(
all_files, \
-
random_state
=
1)
-
-
trn_ds
= GTSRB(trn_files, transform
=trn_tfms)
-
val_ds
= GTSRB(val_files, transform
=val_tfms)
-
trn_dl
= DataLoader(trn_ds,
32, shuffle
=
True, \
-
collate_fn
=trn_ds.collate_fn)
-
val_dl
= DataLoader(val_ds,
32, shuffle
=
False, \
-
collate_fn
=val_ds.collate_fn)
6.定义模型 - SignClassifier:
-
import torchvision.models
as models
-
-
def convBlock(ni,
no):
-
return nn.
Sequential(
-
nn.Dropout(
0.2),
-
nn.Conv
2d(ni,
no, kernel_
size
=
3, padding
=
1),
-
nn.ReLU(inplace
=
True),
-
#nn.BatchNorm
2d(
no),
-
nn.MaxPool
2d(
2),
-
)
-
-
class SignClassifier(nn.Module):
-
def __init__(
self):
-
super().__init__()
-
self.model
= nn.
Sequential(
-
convBlock(
3,
64),
-
convBlock(
64,
64),
-
convBlock(
64,
128),
-
convBlock(
128,
64),
-
nn.Flatten(),
-
nn.Linear(
256,
256),
-
nn.Dropout(
0.2),
-
nn.ReLU(inplace
=
True),
-
nn.Linear(
256, len(id
2int))
-
)
-
self.loss_fn
= nn.CrossEntropyLoss()
-
-
def forward(
self, x):
-
return
self.model(x)
-
-
def
compute_metrics(
self, preds, targets):
-
ce_loss
=
self.loss_fn(preds, targets)
-
acc
=(torch.max(preds,
1)[
1]
=
=targets).float().mean()
-
return ce_loss, acc
7.分别定义对一批数据进行训练和验证的函数:
-
def
train_batch(model, data, optimizer, criterion):
-
model.
train()
-
ims, labels = data
-
_preds =
model(ims)
-
optimizer.
zero_grad()
-
loss, acc =
criterion(_preds, labels)
-
loss.
backward()
-
optimizer.
step()
-
return loss.
item(), acc.
item()
-
-
@torch.
no_grad()
-
def
validate_batch(model, data, criterion):
-
model.
eval()
-
ims, labels = data
-
_preds =
model(ims)
-
loss, acc =
criterion(_preds, labels)
-
return loss.
item(), acc.
item()
8.定义模型并在越来越多的时期训练它:
-
model
= SignClassifier().
to(device)
-
criterion
= model.
compute_metrics
-
optimizer
= optim.Adam(model.parameters(), lr
=
1e-
3)
-
n_epochs
=
50
-
-
log
=
Report(n_epochs)
-
for ex
in range(n_epochs):
-
N
= len(trn_dl)
-
for bx,
data
in enumerate(trn_dl):
-
loss, acc
= train_batch(model,
data, optimizer, \
-
criterion)
-
log.
record(ex
+(bx
+
1)
/N,trn_loss
=loss, trn_acc
=acc, \
-
end
=
'\r')
-
-
N
= len(val_dl)
-
for bx,
data
in enumerate(val_dl):
-
loss, acc
=
validate_batch(model,
data, criterion)
-
log.
record(ex
+(bx
+
1)
/N, val_loss
=loss, val_acc
=acc, \
-
end
=
'\r')
-
-
log.
report_avgs(ex
+
1)
-
if ex
=
=
10: optimizer
= optim.Adam(model.parameters(), \
-
lr
=
1e-
4)
粗体代码行是您将在三个场景中更改的代码行。三种场景在训练和验证准确率方面的结果如下:
Augment | Batch-Norm | 训练精度 | 验证准确性 |
No | No | 95.9 | 94.5 |
No | Yes | 99.3 | 97.7 |
Yes | Yes | 97.7 | 97.6 |
请注意,在前面的三个场景中,我们看到以下内容:
- 当没有批量标准化时,该模型没有那么高的准确性。
- 当我们只进行批量归一化但没有数据增强时,模型的准确性大大提高,但模型在训练数据上过度拟合。
- 具有批量归一化和数据增强的模型具有很高的准确性和最小的过度拟合(因为训练和验证损失值非常相似)。
考虑到批量标准化和数据增强的重要性,在下一节中,我们将了解在训练/实现我们的图像分类模型时需要注意的一些关键方面。
模型实施过程中需要注意的实际问题
到目前为止,我们已经看到了构建图像分类模型的各种方法。在本节中,我们将了解构建模型时需要注意的一些实际注意事项。我们将在本章中讨论的内容如下:
- 处理不平衡的数据
- 执行分类时图像内对象的大小
- 训练和验证图像之间的区别
- 网络中卷积层和池化层的数量
- 在 GPU 上训练的图像大小
- 利用 OpenCV 实用程序
处理不平衡的数据
想象一个场景,你试图预测一个在我们的数据集中很少出现的对象——假设在总图像的 1% 中。例如,这可以是预测 X 射线图像是否提示罕见肺部感染的任务。
我们如何衡量经过训练以预测罕见肺部感染的模型的准确性?如果我们简单地为所有图像预测一类没有感染,分类的准确率是 99%,但仍然没有用。描述稀有对象类别出现次数和模型正确预测稀有对象类别的次数的混淆矩阵在这种情况下会派上用场。因此,在这种情况下要查看的正确指标集是与混淆矩阵相关的指标。
一个典型的混淆矩阵如下所示:
在前面的混淆矩阵中,0代表没有感染,1代表感染。通常,我们会填写矩阵以了解我们的模型有多准确。
接下来是确保模型得到训练的问题。通常,损失函数(二元或分类交叉熵)负责确保在错误分类量很高时损失值很高。但是,除了损失函数之外,我们还可以为稀有类图像分配更高的权重,从而确保我们向模型明确提及我们想要正确分类稀有类图像。
除了分配类权重之外,我们已经看到图像增强和/或迁移学习有助于提高模型的准确性。此外,在增强图像时,我们可以对稀有类图像进行过度采样,以增加它们在总体中的混合。
图像中对象的大小
想象一个场景,大图像中存在的小斑块决定了图像的类别——例如,肺部感染识别,其中某些微小结节的存在表明疾病发生。在这种情况下,图像分类很可能导致结果不准确,因为对象只占整个图像的一小部分。对象检测在这种情况下会派上用场(我们将在下一章中研究)。
解决这些问题的高级直觉是首先将输入图像划分为更小的网格单元(假设是 10 x 10 网格),然后确定网格单元是否包含感兴趣的对象。
处理训练数据和验证数据之间的差异
想象一个场景,您已经建立了一个模型来预测眼睛的图像是否表明该人可能患有糖尿病视网膜病变。为了构建模型,您已经收集了数据,对其进行了整理、裁剪、标准化,然后最终构建了一个在验证图像上具有非常高准确度的模型。然而,假设,当模型在真实环境中使用时(比如医生/护士),模型不能很好地预测。让我们了解一些可能的原因:
- 在医生办公室拍摄的图像是否与用于训练模型的图像相似?
- 如果您在已完成所有预处理的精选数据集上构建模型,而在医生端拍摄的图像未经过精选,则训练时使用的图像和真实世界的图像可能会有很大不同。
- 如果与用于收集用于培训的图像的设备相比,用于在医生办公室捕获图像的设备具有不同的捕获图像分辨率,则图像可能会有所不同。
- 如果在两个地方捕获图像的照明条件不同,则图像可能会有所不同。
- 主题(图像)是否足以代表整个人群?
- 如果图像是在男性群体的图像上进行训练但在女性群体上进行测试的,或者如果训练图像和现实世界的图像通常对应于不同的人口统计数据,则图像具有代表性。
- 训练和验证是否有条不紊地进行?
- 想象一个场景,有 10,000 张图像,前 5,000 张图像属于一个类,最后 5,000 个图像属于另一个类。在构建模型时,如果我们不将数据集随机化,而是将数据集拆分为使用连续索引(没有随机索引)的训练和验证,我们可能会在训练时看到一个类的更高表示,而在验证期间看到另一类的更高表示。
一般来说,在最终用户使用系统之前,我们需要确保训练、验证和真实世界的图像都具有相似的数据分布。
flatten 层的节点数
考虑一个场景,您正在处理尺寸为 300 x 300 的图像。从技术上讲,我们可以执行五个以上的卷积池化操作,以获得具有尽可能多特征的最终层。此外,在这种情况下,我们可以在 CNN 中拥有任意数量的通道。但实际上,一般来说,我们会设计一个网络,使其在展平层中有 500-5,000 个节点。
正如我们在第 4 章,卷积神经网络介绍中看到的,如果我们在 flatten 层中有更多的节点,那么当 flatten 层在连接到最后的密集层之前,我们将有非常多的参数。分类层。
一般来说,最好有一个预训练的模型来获得展平层,以便适当地激活相关的过滤器。此外,在利用预训练模型时,请确保冻结预训练模型的参数。
通常,在不太复杂的分类练习中,CNN 中可训练参数的数量可以在 100 万到 1000 万之间。
图片尺寸
假设我们正在处理尺寸非常高的图像——例如,2,000 x 1,000 的形状。在处理如此大的图像时,我们需要考虑以下可能性:
- 可以将图像调整为较小的尺寸吗?如果调整大小,对象的图像可能不会丢失信息;但是,如果将文本文档的图像调整为较小的尺寸,则可能会丢失大量信息。
- 我们可以有一个较小的批次大小,以便批次适合 GPU 内存吗?通常,如果我们正在处理大图像,很有可能对于给定的批量大小,GPU 内存不足以对批量图像执行计算。
- 图像的某些部分是否包含大部分信息,因此可以裁剪图像的其余部分吗?
利用 OpenCV 实用程序
OpenCV 是一个开源包,它包含大量模块,可帮助从图像中获取信息(更多关于 OpenCV 实用程序的信息,请参见第 18 章,使用 OpenCV 实用程序进行图像分析)。它是计算机视觉深度学习革命之前使用的最著名的库之一。传统上,它建立在多个手工设计的特性之上,在编写本书时,OpenCV 有一些集成深度学习模型输出的包。
想象一个场景,您必须将模型移至生产环境;在这种情况下,通常更倾向于降低复杂性——有时甚至以准确性为代价。如果任何 OpenCV 模块解决了您已经尝试解决的问题,通常应该优先于构建模型(除非从头开始构建模型比利用现成的模块可以显着提高准确性)。
概括
在本章中,我们了解了构建 CNN 模型时需要考虑的多个实际方面——批量标准化、数据增强、使用 CAM 解释结果,以及在将模型投入生产时需要注意的一些场景.
在下一章中,我们将切换齿轮并学习对象检测的基础知识——我们不仅将识别与图像中的对象对应的类,还会在对象的位置周围绘制一个边界框。
问题
- CAM是如何获得的?
- 训练模型时,批量标准化和数据增强有何帮助?
- CNN 模型过拟合的常见原因是什么?
- CNN 模型在数据科学家端使用训练和验证数据但在现实世界中不使用的各种场景是什么?
- 我们利用 OpenCV 包的各种场景是什么?