以前に cifar10 を使用しました。モデルのサイズが大きいため、より複雑なデータ セットを理解できるため、ここでは cifar100 と呼ばれるより複雑なデータ セットを使用します。名前が示すとおり、これは 100 カテゴリの画像データ セットです。データの機密性が高まり、より複雑になります。
データセットを定義する
import torchvision
import torch
#定义数据集
class Dataset(torch.utils.data.Dataset):
def __init__(self, train):
#在线加载数据集
#更多数据集:https://pytorch.org/vision/stable/datasets.html
self.data = torchvision.datasets.CIFAR100(root='data',
train=train,
download=True)
#更多数据增强:https://pytorch.org/vision/stable/transforms.html
self.compose = torchvision.transforms.Compose([
#原本是32*32的,缩放到300*300,这是为了适应预训练模型的习惯,便于它抽取图像特征
torchvision.transforms.Resize(300),
#随机左右翻转,这是一种图像增强,很显然,左右翻转不影响图像的分类结果
torchvision.transforms.RandomHorizontalFlip(p=0.5),
#图像转矩阵数据,值域是0-1之间
torchvision.transforms.ToTensor(),
#让图像的3个通道的数据分别服从3个正态分布,这3分数据是从一个大的数据集上统计得出的
#投影也是为了适应预训练模型的习惯
torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
def __len__(self):
return len(self.data)
def __getitem__(self, i):
#取数据
x, y = self.data[i]
#应用compose,图像转数据
x = self.compose(x)
return x, y
dataset = Dataset(train=True)
x, y = dataset[0]
print(len(dataset), x.shape, y)
ここでは、オンラインでデータセットをロードできる torchvision のデータセットのロード方法を使用するため、上記のコメントの接続を通じてより多くのデータセットを取得できます。root='data' は、ダウンロードされたデータ セットをローカル ディスクに保存するパス、つまりデータ キャッシュの場所を指します。次のパラメータ トレインの値は、データのトレーニングを指すブール値です。ダウンロードされるセット この部分はまだテスト部分です。トレーニング セットの両方の部分が必要であるため、ここでは変数が使用されます。
compose 変数は、torchvision によって提供されるもう 1 つの関数であり、画像のデータ拡張です。特定の方法は、コメント内のリンクからも見つけることができます。以下に、一般的に使用されるものを示します。最初の 1 つはサイズ変更です。画像のズーム。元は 32x32 でしたが、ここでは 300x300 に均一にスケーリングされています。これは、事前トレーニング モデルの習慣に適応し、事前トレーニング モデルによる画像特徴の抽出を容易にするためです。トレーニング モデルは、トレーニングに 300x300 の画像を使用します。2 番目のアプリケーションの画像強調はランダムな左右反転であり、反転確率は 0.5 に設定されていますが、cifar100 データ セットの場合、左右反転は画像の分類結果に影響を与えません。さらに、このデータ強調はデータセットをより豊富にするためです。ツール クラス ToTensor を使用してイメージを行列に変換します。値の範囲は 0 ~ 1 です。最後に、データを正規化します。つまり、画像データの 3 つのチャネルを 3 つの正規分布に従わせ、3 つの正規分布の平均と標準偏差をその上に書き込みます。
getitem 関数は毎回データのバッチを取得し、この画像に合成を適用して画像を強化し、画像をデータに変換します。
ローダーを定義する
#每次从loader获取一批数据时回回调,可以在这里做一些数据整理的工作
#这里写的只是个例子,事实上这个回调函数什么也没干..
def collate_fn(data):
#取数据
x = [i[0] for i in data]
y = [i[1] for i in data]
#比如可以手动转换数据格式
x = torch.stack(x)
y = torch.LongTensor(y)
return x, y
#数据加载器
loader = torch.utils.data.DataLoader(dataset=dataset,
batch_size=8,
shuffle=True,
drop_last=True,
collate_fn=collate_fn)
x, y = next(iter(loader))
print(len(loader), x.shape, y)
ローダーのコードについては何も言うことはありません。唯一言及すべきことは、collate_fn です。この関数は、ローダーからデータのバッチがフェッチされるたびにコールバックされるため、この関数内でデータの照合を行うことができます。
(6250, torch.Size([8, 3, 300, 300]), tensor([50, 54, 98, 51, 77, 96, 72, 81]))
明らかに、x は 8 つのイメージ、y は 8 つの整数、値は 0 ~ 100 です。
転移学習
一般的なモデルの最初の部分では、データを読み込み、次にレイヤーごとに特徴を抽出し、最後にデータをベクトルに抽出し、それを完全に接続されたニューラル ネットワークに入れて分類し、次に訓練されたニューラル ネットワークに入れます。ネットワーク モデルに関しては、層の多くは実際に再利用できます。たとえば、ここでのモデルは回帰の結果ですが、回帰したくない場合はどうすればよいでしょうか。それは非常に単純です。最後の層を切り取り、次に 3 つの新しい層を再接続し、これら 3 つの層の間で分類または回帰を行うかどうかを決定します。つまり、前のレイヤーをトレーニングしないか、これらのレイヤーは基本的にトレーニング済みであるため、再トレーニングしたとしても難易度は低くなります。
これは移行学習です。その中心的なアイデアは、以前にトレーニングされたモデルと、その一部の層 (特に浅い層) のパラメータを再利用することです。これらの層は画像データの特徴抽出を担当するためです。私の新しいモデルでは、次のことができます。データ特徴抽出の作業も行っているため、再利用されます。
モデルを定義する
前に述べたように、事前トレーニングされたモデルが必要であり、この作業を完了するには torchvision を使用します。その中で、多くの事前トレーニングされたモデルが提供されます。リンクにはその他のオプションもあります。 torchvision 、モデルを再組み立てします。ここでは特徴部分のみが必要で、後でそれを完全に接続された出力層に接続します。その後、分類するか返すかを決定できます。
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
#加载预训练模型
#更多模型:https://pytorch.org/vision/stable/models.html#table-of-all-available-classification-weights
pretrained = torchvision.models.efficientnet_v2_s(
weights=torchvision.models.EfficientNet_V2_S_Weights.IMAGENET1K_V1)
#重新组装模型,只要特征抽取部分
pretrained = torch.nn.Sequential(
pretrained.features,
pretrained.avgpool,
torch.nn.Flatten(start_dim=1),
)
#锁定参数,不训练
for param in pretrained.parameters():
param.requires_grad_(False)
pretrained.eval()
self.pretrained = pretrained
#线性输出层,这部分是要重新训练的
self.fc = torch.nn.Sequential(
torch.nn.Linear(1280, 256),
torch.nn.ReLU(),
torch.nn.Linear(256, 256),
torch.nn.ReLU(),
torch.nn.Linear(256, 100),
)
def forward(self, x):
#调用预训练模型抽取参数,因为预训练模型是不训练的,所以这里不需要计算梯度
with torch.no_grad():
#[8, 3, 300, 300] -> [8, 1280]
x = self.pretrained(x)
#计算线性输出
#[8, 1280] -> [8, 100]
return self.fc(x)
model = Model()
x = torch.randn(8, 3, 300, 300)
print(model.pretrained(x).shape, model(x).shape)
モデルトレーニング
#训练
def train():
#注意这里的参数列表,只包括要训练的参数即可
optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3)
loss_fun = torch.nn.CrossEntropyLoss()
model.fc.train()
#定义计算设备,优先使用gpu
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model.to(device)
print('device=', device)
for i, (x, y) in enumerate(loader):
#如果使用gpu,数据要搬运到显存里
x = x.to(device)
y = y.to(device)
out = model(x)
loss = loss_fun(out, y)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if i % 500 == 0:
acc = (out.argmax(dim=1) == y).sum().item() / len(y)
print(i, loss.item(), acc)
#保存模型,只保存训练的部分即可
torch.save(model.fc.to('cpu'), 'model/8.model')
デバイスの定義は通常、GPU がある場合は GPU を使用して計算します。
テスト
@torch.no_grad()
def test():
#加载保存的模型
model.fc = torch.load('model/8.model')
model.fc.eval()
#加载测试数据集,共10000条数据
loader_test = torch.utils.data.DataLoader(dataset=Dataset(train=False),
batch_size=8,
shuffle=True,
drop_last=True)
correct = 0
total = 0
for i in range(100):
x, y = next(iter(loader_test))
#这里因为数据量不大,使用cpu计算就可以了
out = model(x).argmax(dim=1)
correct += (out == y).sum().item()
total += len(y)
print(correct / total)
テスト データ セットはここにロードされます。ここでのトレーニングは False であり、最終的な正解率は 70% と比較的高いことに注意してください。