皆さんこんにちは、私は Wei Xue AI です。今日は、誰もが特徴抽出のプロセス全体を理解できるように、畳み込みニューラル ネットワークにおける画像特徴抽出に対するコンピューター ビジョン 12 視覚化研究の応用を紹介します。
畳み込みニューラル ネットワークにおける画像特徴抽出のプロセス全体を理解するには、それを人間の脳による視覚情報の処理と比較することができます。私たちが物体を見るときと同じように、脳は輪郭、色、質感など、さまざまなニューロンを通じてさまざまな特性の情報を処理します。
1. 画像特徴抽出の概要
CNN では、入力画像がレイヤーごとに処理され、各レイヤーで異なる特徴情報が抽出されます。これらのレイヤーは、画像内のエッジ、コーナー、線などの特定のパターンや形状を認識するさまざまな「フィルター」と考えることができます。レイヤーの数が徐々に増加するにつれて、CNN は画像内のテクスチャ、形状、構造など、より複雑な特徴を抽出できるようになります。
入力画像が猫の写真であると仮定します。CNN の第 1 層は猫の体の端と角を検出し、第 2 層は猫の耳の形状と顔の輪郭を抽出し、第 3 層は猫の毛、目の質感、形。この特徴抽出プロセスは視覚化できるため、CNN が画像情報をどのように学習して処理するかをより深く理解できるようになります。視覚化を通じて、CNN によって抽出された画像の特徴をさまざまなレベルで確認できます。低レベルの特徴にはエッジやテクスチャが含まれ、高レベルの特徴には目、鼻、口などのより抽象的で意味論的な情報が含まれることがわかります。
2. CNNの特徴抽出原理
畳み込みニューラル ネットワークは、畳み込み演算とプーリング演算を通じて画像から特徴を抽出します。原則は次のとおりです。
入力画像ⅡI は複数の畳み込み層とプーリング層によって処理され、最終的な特徴マップFFF._ _ 畳み込み層では、学習可能なフィルターのセットを使用しますWWW は入力画像に対して畳み込み演算を実行し、バイアスbbb、つまり:
C = W * I + b C = W * I + bC=W∗私+b
その中で、∗ *∗ は畳み込み演算を意味します。このようにして、各フィルターは入力画像上をスライドし、畳み込み演算を計算して、対応する特徴マップを取得します。
特徴マップでは、活性化関数 (ReLU など) を介して非線形活性化が実行され、活性化特徴マップが取得されます。
A = ReLU ( C ) A = \text{ {ReLU}}(C)あ=ReLU (C)
次に、活性化特徴マップはプーリング層によってダウンサンプリングされ、特徴マップの空間次元が削減されます。一般的に使用されるプーリング操作は最大プーリングです。これは、出力機能として各プーリング ウィンドウの最大値を選択します。これにより、計算量を削減しながら、最も顕著な特徴が維持されます。
複数の畳み込み操作とプーリング操作を行った後、さまざまなサイズの一連の特徴マップが取得されます。これらの特徴マップには、低レベルのエッジやテクスチャから高レベルのセマンティック情報に至るまで、入力画像のさまざまなレベルの特徴が含まれています。
これらの特徴マップは、分類、検出、またはその他のタスクのために完全に接続されたレイヤーに渡すことができます。全結合層は、特徴マップをベクトルに平坦化し、重み行列と乗算し、バイアスを追加し、最後にソフトマックス関数などの活性化関数を通じて最終出力を取得します。
CNN ネットワークは、畳み込みとプーリング操作を通じて画像の特徴を自動的に学習するため、画像データをより深く理解して分析し、それをさまざまなコンピューター ビジョン タスクに適用できるようになります。
3. CNN抽出特徴量可視化処理
ここで、コードを通じて画像特徴抽出のプロセスを実装します。
import matplotlib.pyplot as plt
import torch
from PIL import Image
import numpy as np
import sys
sys.path.append("..")
from torchvision import transforms
# 对于给定的一个网络层的输出x,x为numpy格式的array,维度为[0, channels, width, height]
def draw_features(width, height, channels,x,savename):
fig = plt.figure(figsize=(32,32))
fig.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95, wspace=0.05, hspace=0.05)
for i in range(channels):
plt.subplot(height,width, i + 1)
plt.axis('off')
img = x[0, i, :, :]
pmin = np.min(img)
pmax = np.max(img)
img = (img - pmin) / (pmax - pmin + 0.000001)
plt.imshow(img, cmap='gray')
# print("{}/{}".format(i, channels))
fig.savefig(savename, dpi=300)
fig.clf()
plt.close()
# 读取模型
def load_checkpoint(filepath):
checkpoint = torch.load(filepath)
model = checkpoint['model'] # 提取网络结构
model.load_state_dict(checkpoint['net_state_dict']) # 加载网络权重参数
for parameter in model.parameters():
parameter.requires_grad = False
model.eval()
return model
savepath = './'
def predict(model):
# 读入模型
model = load_checkpoint(model)
print(model)
##将模型放置在gpu上运行
if torch.cuda.is_available():
model.cuda()
img = Image.open(img_path).convert('RGB')
INPUT_SIZE =(224,224) # 根据需要调整图像的大小
# 创建图像转换函数
transform = transforms.Compose([
transforms.Resize(INPUT_SIZE),
transforms.ToTensor(),
])
# 对图像进行转换
img = transform(img).unsqueeze(0)
if torch.cuda.is_available():
img = img.cuda()
# 查看每一层处理的图片信息
with torch.no_grad():
x = model.conv1(img)
x = model.bn1(x)
draw_features(5,5,15, x.cpu().numpy(), "{}/f1_conv1.png".format(savepath))
x = model.relu(x)
draw_features(5,5,15, x.cpu().numpy(), "{}/f1_conv2.png".format(savepath))
x = model.layer1(x)
draw_features(5,5,15, x.cpu().numpy(), "{}/f1_conv3.png".format(savepath))
x = model.layer2(x)
draw_features(5,5,15, x.cpu().numpy(), "{}/f1_conv4.png".format(savepath))
x = model.layer3(x)
draw_features(5,5,15, x.cpu().numpy(), "{}/f1_conv5.png".format(savepath))
if __name__ == "__main__":
trained_model = 'resnet_model.pkl'
img_path = 'cat.png'
predict(trained_model)
モデル ResNet モデルを構築します: メインの視覚化関数と同じレベルにディレクトリを作成します: models->ClassNetwork->ResNet.py
import math
import torch
import torch.nn as nn
def conv3x3(in_planes, out_planes, stride=1):
"3x3 convolution with padding"
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * Bottleneck.expansion, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * Bottleneck.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
residual = self.downsample(x)
#attention block
out += residual
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, dataset='cifar10', depth=18, num_classes=10, bottleneck=False):
super(ResNet, self).__init__()
self.dataset = dataset
if self.dataset.startswith('cifar'):
self.inplanes = 16
# print(bottleneck)
if bottleneck == True:
n = int((depth - 2) / 9)
block = Bottleneck
else:
n = int((depth - 2) / 6)
block = BasicBlock
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.layer1 = self._make_layer(block, 16, n)
self.layer2 = self._make_layer(block, 32, n, stride=2)
self.layer3 = self._make_layer(block, 64, n, stride=2)
self.avgpool = nn.AvgPool2d(8)
self.fc = nn.Linear(64 * block.expansion, num_classes)
elif dataset == 'imagenet':
blocks = {
18: BasicBlock, 34: BasicBlock, 50: Bottleneck, 101: Bottleneck, 152: Bottleneck, 200: Bottleneck}
layers = {
18: [2, 2, 2, 2], 34: [3, 4, 6, 3], 50: [3, 4, 6, 3], 101: [3, 4, 23, 3], 152: [3, 8, 36, 3],
200: [3, 24, 36, 3]}
assert layers[depth], 'invalid detph for ResNet (depth should be one of 18, 34, 50, 101, 152, and 200)'
self.inplanes = 64
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(blocks[depth], 64, layers[depth][0])
self.layer2 = self._make_layer(blocks[depth], 128, layers[depth][1], stride=2)
self.layer3 = self._make_layer(blocks[depth], 256, layers[depth][2], stride=2)
self.layer4 = self._make_layer(blocks[depth], 512, layers[depth][3], stride=2)
self.avgpool = nn.AvgPool2d(7)
self.fc = nn.Linear(512 * blocks[depth].expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
def _make_layer(self, block, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes))
return nn.Sequential(*layers)
def forward(self, x):
if self.dataset == 'cifar10' or self.dataset == 'cifar100':
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
elif self.dataset == 'imagenet':
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
4. 可視化処理の動作
ここでは猫の画像を入力します。
プログラムを実行すると、ResNet のネットワーク構造が次のようになっていることがわかります。
ResNet(
(conv1): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(1): BasicBlock(
(conv1): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(16, 32, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(32, 64, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(avgpool): AvgPool2d(kernel_size=8, stride=8, padding=0)
(fc): Linear(in_features=64, out_features=100, bias=True)
)
次に、各層の画像処理プロセスが生成されます:
conv1 第 1 層の処理:
Relu レイヤーの処理:
Layer1 レイヤー処理:
レイヤー2レイヤー処理:
レイヤー3レイヤー処理:
V. まとめ
CNN における画像特徴抽出は、人間の視覚システムの動作原理をシミュレートし、さまざまなレベルの入力データ (画像など) の特徴表現を層ごとに抽出して、入力データの深層学習と分析を実現します。CNN は、畳み込み層、プーリング層、および全結合層で構成される多層構造を通じて、低レベルの特徴 (エッジ、テクスチャなど) から高レベルの意味概念 (たとえば、オブジェクトの形状、色、テクスチャなど)。この階層的な特徴表現により、CNN は画像分類、ターゲット検出、顔認識、画像生成などのさまざまなコンピューター ビジョン タスクにおいて優れたパフォーマンスを発揮します。従来の手動による特徴抽出方法と比較して、CNN は手動で特徴を設計する必要がなく、最適な特徴表現を自動的に学習できるため、手動介入のコストが大幅に削減され、汎化能力と堅牢性が向上します。