私の連絡先情報
郵便メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
ここでは、CIFAR データセットを分類するために VGG-16 ネットワークを実装します。
序文
《大規模画像認識のための超深層畳み込みネットワーク》
ICLR2015 国際会議
VGGGオックスフォード出身です五通常のグ幾何学的ググループのグループによって提案されました(VGG の名前の由来がわかるはずです)。このネットワークは、ILSVRC 2014 での関連作業です。主な作業は、ネットワークの深さを増やすと、ネットワークの最終パフォーマンスにある程度の影響を与える可能性があることを証明することです。 VGG には VGG16 と VGG19 の 2 つの構造があります。両者に本質的な違いはありませんが、ネットワークの深さが異なります。
VGG原理
AlexNet と比較した VGG16 の改善点は次のとおりです。複数の連続した 3x3 コンボリューション カーネルを使用して、AlexNet (11x11、7x7、5x5) のより大きなコンボリューション カーネルを置き換えます。 。特定の受容野 (出力に対する入力画像の局所的なサイズ) については、複数の非線形層によりネットワークの深さが増し、より複雑な学習モードが保証されるため、スタックされた小さな畳み込みカーネルを使用する方が、大きな畳み込みカーネルを使用するよりも優れています。コストは比較的小さい (パラメータが少ない)。
簡単に言うと、VGG では、7x7 コンボリューション カーネルを置き換えるために 3 つの 3x3 コンボリューション カーネルが使用され、5*5 コンボリューション カーネルを置き換えるために 2 つの 3x3 コンボリューション カーネルが使用されます。これの主な目的は、同じ条件下で同じことを保証することです。受容野の深さが増し、ネットワークの深さが増し、ニューラルネットワークの効果がある程度向上します。
たとえば、ストライド 1 の 3 つの 3x3 コンボリューション カーネルのレイヤーごとの重ね合わせは、サイズ 7 の受容野とみなすことができます (実際には、3 つの 3x3 の連続コンボリューションが 7x7 コンボリューションと同等であることを意味します)。合計パラメータは 3x(9xC^2) です。7x7 畳み込みカーネルが直接使用される場合、パラメータの合計数は 49xC^2 です。ここで、C は入力チャネルと出力チャネルの数を指します。明らかに、27xC2 49xC未満2、つまりパラメータが削減され、3x3 コンボリューション カーネルは画像のプロパティをより適切に維持するのに役立ちます。
5*5 コンボリューション カーネルの代わりに 2 つの 3x3 コンボリューション カーネルを使用できる理由を以下に説明します。
5x5 畳み込みは、5x5 領域内でスライドする小さな完全接続ネットワークと見なされます。最初に 3x3 畳み込みフィルターを使用して畳み込み、次に、この完全接続層を使用して 3x3 畳み込み出力を接続することもできます。 3x3 畳み込み層として見られます。このようにして、1 つの 5x5 コンボリューションの代わりに 2 つの 3x3 コンボリューションをカスケード (重ね合わせる) ことができます。
詳細を次の図に示します。
VGG ネットワーク構造
VGG ネットワークの構造は次のとおりです (VGG16 と VGG19 の両方が存在します)。
GGネットワーク構造
上の図の列 D に示すように、VGG16 には 16 の隠れ層 (13 の畳み込み層と 3 つの全結合層) が含まれています。
上図の列 E に示すように、VGG19 には 19 の隠れ層 (16 の畳み込み層と 3 つの全結合層) が含まれています。
VGG ネットワークの構造は非常に一貫しており、最初から最後まで 3x3 コンボリューションと 2x2 最大プーリングを使用します。
VGGの利点
VGGNet の構造は非常に単純で、ネットワーク全体で同じコンボリューション カーネル サイズ (3x3) と最大プーリング サイズ (2x2) が使用されます。
いくつかの小さなフィルター (3x3) 畳み込み層の組み合わせは、1 つの大きなフィルター (5x5 または 7x7) 畳み込み層よりも優れています。
ネットワーク構造を継続的に深くすることでパフォーマンスが向上することが確認されました。
VGGのデメリット
VGG はより多くのコンピューティング リソースを消費し、より多くのパラメーター (これは 3x3 畳み込みのポットではありません) を使用するため、メモリ使用量も増加します (140M)。
CIFAR (Canadian Institute For Advanced Research) データセットは、コンピューター ビジョンの分野で広く使用されている小さな画像データセットで、主に機械学習とコンピューター ビジョン アルゴリズムのトレーニング、特に画像認識や分類などのタスクに使用されます。 CIFAR データセットは、CIFAR-10 と CIFAR-100 の 2 つの主要な部分で構成されます。
CIFAR-10 は、60,000 枚の 32x32 カラー画像を含むデータセットであり、10 のカテゴリに分割されており、各カテゴリには 6,000 枚の画像が含まれています。 10 カテゴリは、飛行機、車、鳥、猫、鹿、犬、カエル、馬、ボート、トラックです。データセットでは、トレーニングに 50,000 枚の画像が使用され、テストに 10,000 枚の画像が使用されます。 CIFAR-10 データセットは、適度なサイズと豊富なクラス情報により、コンピューター ビジョン分野の研究および教育において非常に人気のあるデータセットの 1 つとなっています。
CIFAR データ セットは、画像分類、オブジェクト認識、畳み込みニューラル ネットワーク (CNN) のトレーニングとテストなどのタスクに一般的に使用されます。適度なサイズと豊富なカテゴリ情報により、画像認識アルゴリズムを検討する初心者や研究者にとって理想的です。さらに、コンピューター ビジョンや機械学習の多くのコンテストでも、出場者のアルゴリズムのパフォーマンスを評価するベンチマークとして CIFAR データセットが使用されています。
データセットを準備するには、すでにダウンロードしていますが、うまくいかない場合は、公式Webサイトからダウンロードするか、直接お渡しします。
データセットが必要な場合は、電子メールでお問い合わせください: [email protected]
私のデータセットはもともと torchvision でダウンロードされたデータを通じて生成されましたが、今はそれをやりたくありません。データセットの定義と DataLoader の読み込みを段階的に実装し、このプロセスを理解したいと考えています。データセットの処理プロセスを学ぶことで、ディープラーニングについてさらに深く学ぶことができます。
データセットのスタイルは次のとおりです。
データセットのラベル カテゴリは、.meta
ファイルは保存されているので、解析する必要があります .meta
ファイルを使用してすべてのタグデータを読み取ります。解析コードは次のとおりです。
# 首先了解所有的标签,TODO 可以详细了解一下这个解包的过程
import pickle
def unpickle(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
meta_data = unpickle('./dataset_method_1/cifar-10-batches-py/batches.meta')
label_names = meta_data[b'label_names']
# 将字节标签转换为字符串
label_names = [label.decode('utf-8') for label in label_names]
print(label_names)
分析の結果は次のとおりです。
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
データセットはダウンロードされているので、ファイルの内容を読み取る必要があります。ファイルはバイナリファイルであるため、読み取るにはバイナリ読み取りモードを使用する必要があります。
読み取りコードは次のとおりです。
# 载入单个批次的数据
import numpy as np
def load_data_batch(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
X = dict[b'data']
Y = dict[b'labels']
X = X.reshape(10000, 3, 32, 32).transpose(0, 2, 3, 1) # reshape and transpose to (10000, 32, 32, 3)
Y = np.array(Y)
return X, Y
# 加载第一个数据批次
data_batch_1 = './dataset_method_1/cifar-10-batches-py/data_batch_1'
X1, Y1 = load_data_batch(data_batch_1)
print(f'数据形状: {X1.shape}, 标签形状: {Y1.shape}')
結果:
数据形状: (10000, 32, 32, 3), 标签形状: (10000,)
上記のテストの後、データをロードする方法がわかりました。次に、すべてのデータをロードしましょう。
トレーニング セットをロードします。
# 整合所有批次的数据
def load_all_data_batches(batch_files):
X_list, Y_list = [], []
for file in batch_files:
X, Y = load_data_batch(file)
X_list.append(X)
Y_list.append(Y)
X_all = np.concatenate(X_list)
Y_all = np.concatenate(Y_list)
return X_all, Y_all
batch_files = [
'./dataset_method_1/cifar-10-batches-py/data_batch_1',
'./dataset_method_1/cifar-10-batches-py/data_batch_2',
'./dataset_method_1/cifar-10-batches-py/data_batch_3',
'./dataset_method_1/cifar-10-batches-py/data_batch_4',
'./dataset_method_1/cifar-10-batches-py/data_batch_5'
]
X_train, Y_train = load_all_data_batches(batch_files)
print(f'训练数据形状: {X_train.shape}, 训练标签形状: {Y_train.shape}')
Y_train = Y_train.astype(np.int64)
出力:
训练数据形状: (50000, 32, 32, 3), 训练标签形状: (50000,)
テストセットをロードします:
test_batch = './dataset_method_1/cifar-10-batches-py/test_batch'
X_test, Y_test = load_data_batch(test_batch)
Y_test = Y_test.astype(np.int64)
print(f'测试数据形状: {X_test.shape}, 测试标签形状: {Y_test.shape}')
出力:
测试数据形状: (10000, 32, 32, 3), 测试标签形状: (10000,)
Dataset クラスのサブクラスを定義すると、その後のバッチ トレーニング用のデータローダーのロードが容易になります。
Dataset のサブクラスが実装する必要があるメソッドは 3 つあります。
__init__()
クラスコンストラクター__len__()
データセットの長さを返します__getitem__()
データセットからデータの一部を取得するここでの私の実装は次のとおりです。
from torch.utils.data import DataLoader, Dataset
# 定义 Pytorch 的数据集
class CIFARDataset(Dataset):
def __init__(self, images, labels, transform=None):
self.images = images
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.images)
def __getitem__(self, idx):
image = self.images[idx]
label = self.labels[idx]
if self.transform:
image = self.transform(image)
return image, label
transform_train = transforms.Compose(
[transforms.Pad(4),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
transforms.RandomHorizontalFlip(),
transforms.RandomGrayscale(),
transforms.RandomCrop(32, padding=4),
])
# 把数据集变成 Image 的数组,不然好像不能进行数据的增强
# 改变训练数据
from PIL import Image
def get_PIL_Images(origin_data):
datas = []
for i in range(len(origin_data)):
data = Image.fromarray(origin_data[i])
datas.append(data)
return datas
train_data = get_PIL_Images(X_train)
train_loader = DataLoader(CIFARDataset(train_data, Y_train, transform_train), batch_size=24, shuffle=True)
# 测试集的预处理
transform_test = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]
)
test_loader = DataLoader(CIFARDataset(X_test, Y_test, transform_test), batch_size=24, shuffle=False)
上記の VGG16 ネットワークに基づいて Pytorch フレームワークを実装します。
主に分けられるのは:
実装は次のとおりです。
class VGG16(nn.Module):
def __init__(self):
super(VGG16, self).__init__()
# 卷积层,这里进行卷积
self.convolusion = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=3, padding=1), # 设置为padding=1 卷积完后,数据大小不会变
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.Conv2d(96, 96, kernel_size=3, padding=1),
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.Conv2d(96, 96, kernel_size=3, padding=1),
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(96, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.AvgPool2d(kernel_size=1, stride=1)
)
# 全连接层
self.dense = nn.Sequential(
nn.Linear(512, 4096), # 32*32 的图像大小经过 5 次最大化池化后就只有 1*1 了,所以就是 512 个通道的数据输入全连接层
nn.ReLU(inplace=True),
nn.Dropout(0.4),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.4),
)
# 输出层
self.classifier = nn.Linear(4096, 10)
def forward(self, x):
out = self.convolusion(x)
out = out.view(out.size(0), -1)
out = self.dense(out)
out = self.classifier(out)
return out
トレーニングとテストの場合は、モデルをインスタンス化し、最適化関数、損失関数、損失率を定義して、トレーニングとテストを実行するだけです。
コードは以下のように表示されます。
ハイパーパラメータの定義:
# 定义模型进行训练
model = VGG16()
# model.load_state_dict(torch.load('./my-VGG16.pth'))
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=5e-3)
loss_func = nn.CrossEntropyLoss()
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.4, last_epoch=-1)
テスト機能:
def test():
model.eval()
correct = 0 # 预测正确的图片数
total = 0 # 总共的图片数
with torch.no_grad():
for data in test_loader:
images, labels = data
images = images.to(device)
outputs = model(images).to(device)
outputs = outputs.cpu()
outputarr = outputs.numpy()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
accuracy = 100 * correct / total
accuracy_rate.append(accuracy)
print(f'准确率为:{accuracy}%'.format(accuracy))
トレーニング エポック:
# 定义训练步骤
total_times = 40
total = 0
accuracy_rate = []
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
for epoch in range(total_times):
model.train()
model.to(device)
running_loss = 0.0
total_correct = 0
total_trainset = 0
print("epoch: ",epoch)
for i, (data,labels) in enumerate(train_loader):
data = data.to(device)
outputs = model(data).to(device)
labels = labels.to(device)
loss = loss_func(outputs,labels).to(device)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
_,pred = outputs.max(1)
correct = (pred == labels).sum().item()
total_correct += correct
total_trainset += data.shape[0]
if i % 100 == 0 and i > 0:
print(f"正在进行第{i}次训练, running_loss={running_loss}".format(i, running_loss))
running_loss = 0.0
test()
scheduler.step()
トレーニングされたモデルを保存します。
torch.save(model.state_dict(), './my-VGG16.pth')
accuracy_rate = np.array(accuracy_rate)
times = np.linspace(1, total_times, total_times)
plt.xlabel('times')
plt.ylabel('accuracy rate')
plt.plot(times, accuracy_rate)
plt.show()
print(accuracy_rate)
model_my_vgg = VGG16()
model_my_vgg.load_state_dict(torch.load('./my-VGG16-best.pth',map_location='cpu'))
from torchvision import transforms
from PIL import Image
# 定义图像预处理步骤
preprocess = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
# transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]),
])
def load_image(image_path):
image = Image.open(image_path)
image = preprocess(image)
image = image.unsqueeze(0) # 添加批次维度
return image
image_data = load_image('./plane2.jpg')
print(image_data.shape)
output = model_my_vgg(image_data)
verify_data = X1[9]
verify_label = Y1[9]
output_verify = model_my_vgg(transform_test(verify_data).unsqueeze(0))
print(output)
print(output_verify)
出力:
torch.Size([1, 3, 32, 32])
tensor([[ 1.5990, -0.5269, 0.7254, 0.3432, -0.5036, -0.3267, -0.5302, -0.9417,
0.4186, -0.1213]], grad_fn=<AddmmBackward0>)
tensor([[-0.6541, -2.0759, 0.6308, 1.9791, 0.8525, 1.2313, 0.1856, 0.3243,
-1.3374, -1.0211]], grad_fn=<AddmmBackward0>)
print(label_names[torch.argmax(output,dim=1,keepdim=False)])
print(label_names[verify_label])
print("pred:",label_names[torch.argmax(output_verify,dim=1,keepdim=False)])
airplane
cat
pred: cat
馬を確認する
検証犬