- 正常情况下,torch读取数据的时候是Batch Size小批量读取。
- 首先找到所有数据集的路径保持到一个变量中,之后需要读取哪个数据的时候,就根据这个变量中的路径索引去读取。因为硬件的限制,从硬盘中读取数据到显存中所花的时间要远远大于从内存中读取数据到显存中。因此,如果程序直接是从硬盘上读取数据到显存中,就会非常耗时。
- 我们可以把对应的图像直接全部都读取到内存中,然后将其转换为CPU类型的Tensor Dataloader,之后迭代的时候,从这个Tensor中迭代,那么网络在读取数据的时候,就是直接从内存中进行读取了,那么提高的速度就不是一点半点儿了,MNIST训练一个epoch的时间可以由原来的50s减少到5s,速度可以说是提高了10倍。
核心代码
## train_set = torchvision.datasets.MNIST(DOWNLOAD_PATH, train=True, download=True,
transform=transform_mnist) #train_set其实只是读取的一个路径,但是并没有读取数据主体
train_data = train_set.data.float().unsqueeze(1) / 255.0 #读取所有的数据集路径,然后将对应的数据集读取到内存中
train_label = train_set.targets
train_dataset = TensorDataset(train_data,train_label) #将原本保存在内存中的变量转化为Tensor
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True)
完整代码
代码
### import time
import torch
import torch.nn.functional as F
import torchvision
from einops import rearrange
from torch import nn
from torch import optim
from torch.utils.data import TensorDataset, DataLoader
#残差模块,放在每个前馈网络和注意力之后
class Residual(nn.Module):
def __init__(self, fn):
super().__init__()
self.fn = fn
def forward(self, x, **kwargs):
return self.fn(x, **kwargs) + x
#layernorm归一化,放在多头注意力层和激活函数层。用绝对位置编码的BERT,layernorm用来自身通道归一化
class PreNorm(nn.Module):
def __init__(self, dim, fn):
super().__init__()
self.norm = nn.LayerNorm(dim)
self.fn = fn
def forward(self, x, **kwargs):
return self.fn(self.norm(x), **kwargs)
#放置多头注意力后,因为在于多头注意力使用的矩阵乘法为线性变换,后面跟上由全连接网络构成的FeedForward增加非线性结构
class FeedForward(nn.Module):
def __init__(self, dim, hidden_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, dim)
)
def forward(self, x):
return self.net(x)
#多头注意力层,多个自注意力连起来。使用qkv计算
class Attention(nn.Module):
def __init__(self, dim, heads=8):
super().__init__()
self.heads = heads
self.scale = dim ** -0.5
self.to_qkv = nn.Linear(dim, dim * 3, bias=False)
self.to_out = nn.Linear(dim, dim)
def forward(self, x, mask = None):
b, n, _, h = *x.shape, self.heads
qkv = self.to_qkv(x)
q, k, v = rearrange(qkv, 'b n (qkv h d) -> qkv b h n d', qkv=3, h=h)
dots = torch.einsum('bhid,bhjd->bhij', q, k) * self.scale
if mask is not None:
mask = F.pad(mask.flatten(1), (1, 0), value = True)
assert mask.shape[-1] == dots.shape[-1], 'mask has incorrect dimensions'
mask = mask[:, None, :] * mask[:, :, None]
dots.masked_fill_(~mask, float('-inf'))
del mask
attn = dots.softmax(dim=-1)
out = torch.einsum('bhij,bhjd->bhid', attn, v)
out = rearrange(out, 'b h n d -> b n (h d)')
out = self.to_out(out)
return out
class Transformer(nn.Module):
def __init__(self, dim, depth, heads, mlp_dim):
super().__init__()
self.layers = nn.ModuleList([])
for _ in range(depth):
self.layers.append(nn.ModuleList([
Residual(PreNorm(dim, Attention(dim, heads = heads))),
Residual(PreNorm(dim, FeedForward(dim, mlp_dim)))
]))
def forward(self, x, mask=None):
for attn, ff in self.layers:
x = attn(x, mask=mask)
x = ff(x)
return x
#将图像切割成一个个图像块,组成序列化的数据输入Transformer执行图像分类任务。
class ViT(nn.Module):
def __init__(self, *, image_size, patch_size, num_classes, dim, depth, heads, mlp_dim, channels=3):
super().__init__()
assert image_size % patch_size == 0, 'image dimensions must be divisible by the patch size'
num_patches = (image_size // patch_size) ** 2
patch_dim = channels * patch_size ** 2
self.patch_size = patch_size
self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
self.patch_to_embedding = nn.Linear(patch_dim, dim)
self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
self.transformer = Transformer(dim, depth, heads, mlp_dim)
self.to_cls_token = nn.Identity()
self.mlp_head = nn.Sequential(
nn.Linear(dim, mlp_dim),
nn.GELU(),
nn.Linear(mlp_dim, num_classes)
)
def forward(self, img, mask=None):
p = self.patch_size
x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1 = p, p2 = p)
x = self.patch_to_embedding(x)
cls_tokens = self.cls_token.expand(img.shape[0], -1, -1)
x = torch.cat((cls_tokens, x), dim=1)
x += self.pos_embedding
x = self.transformer(x, mask)
x = self.to_cls_token(x[:, 0])
return self.mlp_head(x)
def train_epoch(model, optimizer, data_loader, loss_history):
total_samples = len(data_loader.dataset)
model = model.cuda()
model.train()
for i, (data, target) in enumerate(data_loader):
# print("data.shape:", data.shape)
data=data.cuda()
target=target.cuda()
optimizer.zero_grad()
output = F.log_softmax(model(data), dim=1)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if i % 100 == 0:
print('[' + '{:5}'.format(i * len(data)) + '/' + '{:5}'.format(total_samples) +
' (' + '{:3.0f}'.format(100 * i / len(data_loader)) + '%)] Loss: ' +
'{:6.4f}'.format(loss.item()))
loss_history.append(loss.item())
def evaluate(model, data_loader, loss_history):
model.eval()
total_samples = len(data_loader.dataset)
correct_samples = 0
total_loss = 0
model = model.cuda()
with torch.no_grad():
for data, target in data_loader:
data = data.cuda()
target = target.cuda()
output = F.log_softmax(model(data), dim=1)
loss = F.nll_loss(output, target, reduction='sum')
_, pred = torch.max(output, dim=1)
total_loss += loss.item()
correct_samples += pred.eq(target).sum()
avg_loss = total_loss / total_samples
loss_history.append(avg_loss)
print('\nAverage test loss: ' + '{:.4f}'.format(avg_loss) +
' Accuracy:' + '{:5}'.format(correct_samples) + '/' +
'{:5}'.format(total_samples) + ' (' +
'{:4.2f}'.format(100.0 * correct_samples / total_samples) + '%)\n')
# 设置随机种子
torch.manual_seed(42)
# 定义MNIST数据集的下载路径
DOWNLOAD_PATH = './data/mnist'
# 定义每个batch中包含的样本数量
batch_size = 5000
# 定义数据集预处理操作
transform_mnist = torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.1307,), (0.3081,))
])
# 设置数据加载器的线程数
num_workers = 0
# 读入训练集和测试集数据
train_set = torchvision.datasets.MNIST(DOWNLOAD_PATH, train=True, download=True, transform=transform_mnist)
test_set = torchvision.datasets.MNIST(DOWNLOAD_PATH, train=False, download=True, transform=transform_mnist)
# 将数据集转换为张量
train_data = train_set.data.float().unsqueeze(1) / 255.0
train_labels = train_set.targets
test_data = test_set.data.float().unsqueeze(1) / 255.0
test_labels = test_set.targets
# 将数据集包装成Dataset对象
train_dataset = TensorDataset(train_data, train_labels)
test_dataset = TensorDataset(test_data, test_labels)
# 创建训练集和测试集的DataLoader
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)
N_EPOCHS = 100
'''
patch大小为 7x7(对于 28x28 图像,这意味着每个图像 4 x 4 = 16 个patch)、10 个可能的目标类别(0 到 9)和 1 个颜色通道(因为图像是灰度)。
在网络参数方面,使用了 64 个单元的维度,6 个 Transformer 块的深度,8 个 Transformer 头,MLP 使用 128 维度。'''
model = ViT(image_size=28, patch_size=7, num_classes=10, channels=1,
dim=64, depth=6, heads=8, mlp_dim=128)
optimizer = optim.Adam(model.parameters(), lr=0.003)
train_loss_history, test_loss_history = [], []
for epoch in range(1, N_EPOCHS + 1):
start_time = time.time()
print('Epoch:', epoch)
train_epoch(model, optimizer, train_loader, train_loss_history)
evaluate(model, test_loader, test_loss_history)
print('Execution time:', '{:5.2f}'.format(time.time() - start_time), 'seconds')
速度对比
- RTX2060 使用transformer 训练一个epoch的时间
从硬盘中读取数据
Batch_Size time
300 62s
500 59s
1000 57s
3000 48s
5000 47s
10000 50s
将数据读入到内存中再运行
100 51s
500 12s 1500M 37%
1000 7.18s 1700M 30%
5000 5.84s 3400M 67%
10000 4.65s 5400M 54%文章来源:https://www.toymoban.com/news/detail-448626.html
这速度差的真的不是一点半点的文章来源地址https://www.toymoban.com/news/detail-448626.html
从硬盘中读取数据
Batch_Size time
300 62s
500 59s
1000 57s
3000 48s
5000 47s
10000 50s
将数据读入到内存中再运行
100 51s
500 12s 1500M 37%
1000 7.18s 1700M 30%
5000 5.84s 3400M 67%
10000 4.65s 5400M 54%
这速度差的真的不是一点半点的
到了这里,关于PyTorch读取数据集全部进内存,使网络的训练速度提高10倍以上!!!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!