<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用pytorch讀取資料集一般有三種情況
讀取官方給的資料集,例如Imagenet,CIFAR10,MNIST等
這些庫呼叫torchvision.datasets.XXXX()即可,例如想要讀取MNIST資料集
import torch import torch.nn as nn import torch.utils.data as Data import torchvision train_data = torchvision.datasets.MNIST( root='./mnist/', train=True, # this is training data transform=torchvision.transforms.ToTensor(), # Converts a PIL.Image or numpy.ndarray to # torch.FloatTensor of shape (C x H x W) and normalize in the range [0.0, 1.0] download=True, )
這樣就會自動從網上下載MNIST資料集,並且以儲存好的資料格式來讀取
然後直接定義DataLoader的一個物件,就可以進行訓練了
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True) for epoch in range(EPOCH): for step, (b_x, b_y) in enumerate(train_loader): # gives batch data, normalize x when iterate train_loader XXXX XXXX
這種就比較常用了,針對影象的分類問題
適用情況是,對於圖片的多分類問題,圖片按照指定的格式來存放:
按照上面的格式來存放圖片,根路徑下面儲存了許多資料夾,每個資料夾中存放了某一類的圖片,並且資料夾名就是類的對映,例如這樣,根目錄就是learn_pytorch,下面的每個資料夾代表一個類,類的名字隨便命名,在訓練過程中會自動被對映成0,1,2,3…
儲存成這樣的格式之後,就可以直接利用pytorch定義好的派生類ImageFolder來讀取了,ImageFolder其實就是Dataset的派生類,專門被定義來讀取特定格式的圖片的,它也是 torchvision庫幫我們方便使用的,比如這樣
然後就可以作為DataLoader的資料集輸入用了
from torchvision.datasets import ImageFolder data_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5, 0.5, 0.5]) ]) dataset = ImageFolder("/home/xxx/learn_pytorch/",transform = data_transform) train_loader = Data.DataLoader(dataset=dataset, batch_size=BATCH_SIZE, shuffle=True)
它的建構函式要求輸入兩個引數,一個根目錄,一個對資料的操作,因為圖片被自動讀取成PILimage資料格式,因此Totensor()必不可少,而且可以用transforms.Compose把許多操作合成一個引數輸入,就能實現資料增強,非常方便。上面的例子是先轉成tensor,然後歸一化,沒做資料增強的各種操作。如果要資料增強,可以再加一些裁剪、反轉之類的,都可以。比如下面的
transforms.RandomSizedCrop transforms.RandomHorizontalFlip()
還有一個問題是,如何知道資料夾名被對映成了什麼標籤,這個可以直接檢視定義的物件的class_to_idx屬性
這個ImageFolder產生的dataset物件,第一維就是第幾張圖片,第二維元素0是圖片矩陣 元素1是label
接下來就是建立模型+訓練了
訓練的過程和第一種一樣
這種情況是最通用的,適用於不是分類問題,或者標籤不是簡單的檔名的對映
思路就是自己定義一個Dataset的派生類,並且對資料的處理、資料增強之類的都需要自己定義,這些定義的時候利用__call_()就可以了
實現過程是:
首先
定義一個Dataset的派生類,這個派生類目標是過載兩個魔法方法 __ len __ (),__ getitem__()
__ len __ ()
函數是在呼叫 len(物件)的時候會被呼叫並返回,過載的目的是,在呼叫的時候返回資料集的大小__getitem __()
函數可讓物件程式設計可迭代的,定義了它之後就可以使得對像被for語句迭代,過載它的目的是能夠使得它每次都迭代返回資料集的一個樣本現在定義一個派生類
class FaceLandmarksDataset(Dataset): """Face Landmarks dataset.""" def __init__(self, csv_file, root_dir, transform=None): """ Args: csv_file (string): Path to the csv file with annotations. root_dir (string): Directory with all the images. transform (callable, optional): Optional transform to be applied on a sample. """ self.landmarks_frame = pd.read_csv(csv_file) self.root_dir = root_dir self.transform = transform def __len__(self): return len(self.landmarks_frame) def __getitem__(self, idx): img_name = os.path.join(self.root_dir, self.landmarks_frame.iloc[idx, 0]) image = io.imread(img_name) landmarks = self.landmarks_frame.iloc[idx, 1:].as_matrix() landmarks = landmarks.astype('float').reshape(-1, 2) sample = {'image': image, 'landmarks': landmarks} if self.transform: sample = self.transform(sample) return sample
建構函式就是定義了一些屬性,例如讀取出儲存整個資料集的表格,然後len就是返回了資料集的數目,getitem則是定義了迭代返回一個資料集樣本,返回值可以是包含訓練樣本和標籤的list,也可以是字典,根據這個不同後面的用法也回不太一樣(無非就是索引是數位還是key的區別)
除此之外,Dataset一般還會要求輸入對資料集的操作,要是不想資料增強,就加個ToTensor就可以(因為要轉換成tensor才能訓練),要是想資料增強就自己加一些新的類(沒錯,ToTensor、各種資料增強的函數其實都是一個類,然後定義的一個物件),接著用transforms.Compose把他們連在一起就可以了。上面的transform寫的是None,就是不進行資料處理,直接輸出
然後範例化這個類,就可以作為DataLoader的引數輸入了
face_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv', root_dir='faces/')
這時候分析一下這個物件,定義它的引數就是init建構函式需要的,然後對他進行迭代的時候會自動呼叫getitem 例如下面的操作結果是
for i in range(len(face_dataset)): sample = face_dataset[i] print(sample['image']) print(i,sample['image'].shape, sample['landmarks'].shape)
可以看到每次迭代的時候都會輸入一個字典
接下來定義一下DataLoader,就可以去迭代輸入了,當然這裡還不行,因為需要將資料集轉換成tensor才能輸入到模型進行訓練
那麼接下來就是考慮剛才那個DataSet類裡的transform怎麼改,最初給的是None,不做處理,因此出來的還是ImageArray,至少要實現ToTensor才行。
實現ToTensor這個類就主要用到了 __call __()魔法函數
__ call__()函數比較特殊,可以讓物件本身變成可呼叫的,可以後面加括號並輸入引數,然後就會自動呼叫call這個魔法函數
Totensor類的實現如下,注意numpy和tensor陣列區別在 一個通道數在後,一個通道數在前,因此還需要交換不同維度的位置
class ToTensor(object): """Convert ndarrays in sample to Tensors.""" def __call__(self, sample): image, landmarks = sample['image'], sample['landmarks'] # swap color axis because # numpy image: H x W x C # torch image: C X H X W image = image.transpose((2, 0, 1)) return {'image': torch.from_numpy(image), 'landmarks': torch.from_numpy(landmarks)}
使用的時候先定義一個物件,然後 物件(引數)就會自動呼叫call函數了
再看幾個資料增強的類的實現,它們所有的相似點都是,call函數的引數都是sample,也就是輸入的資料集
class Rescale(object): """Rescale the image in a sample to a given size. Args: output_size (tuple or int): Desired output size. If tuple, output is matched to output_size. If int, smaller of image edges is matched to output_size keeping aspect ratio the same. """ def __init__(self, output_size): assert isinstance(output_size, (int, tuple)) self.output_size = output_size def __call__(self, sample): image, landmarks = sample['image'], sample['landmarks'] h, w = image.shape[:2] if isinstance(self.output_size, int): if h > w: new_h, new_w = self.output_size * h / w, self.output_size else: new_h, new_w = self.output_size, self.output_size * w / h else: new_h, new_w = self.output_size new_h, new_w = int(new_h), int(new_w) img = transform.resize(image, (new_h, new_w)) # h and w are swapped for landmarks because for images, # x and y axes are axis 1 and 0 respectively landmarks = landmarks * [new_w / w, new_h / h] return {'image': img, 'landmarks': landmarks} class RandomCrop(object): """Crop randomly the image in a sample. Args: output_size (tuple or int): Desired output size. If int, square crop is made. """ def __init__(self, output_size): assert isinstance(output_size, (int, tuple)) if isinstance(output_size, int): self.output_size = (output_size, output_size) else: assert len(output_size) == 2 self.output_size = output_size def __call__(self, sample): image, landmarks = sample['image'], sample['landmarks'] h, w = image.shape[:2] new_h, new_w = self.output_size top = np.random.randint(0, h - new_h) left = np.random.randint(0, w - new_w) image = image[top: top + new_h, left: left + new_w] landmarks = landmarks - [left, top] return {'image': image, 'landmarks': landmarks}
這兩個就很清晰了,首先是建構函式要求在定義物件的時候輸入引數,接著再用call實現直接呼叫物件。
用的時候就可以
transformed_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv', root_dir='faces/', transform=transforms.Compose([ Rescale(256), RandomCrop(224), ToTensor() ])) for i in range(len(transformed_dataset)): sample = transformed_dataset[i] print(i, sample['image'].size(), sample['landmarks'].size()) if i == 3: break
分析一下,首先定義過載DataSet類的物件,transform引數寫成上面定義的三個操作類的組合,回頭去看這個類的定義
self.transform = transform
上面就定義了一個三個類聯合起來的物件
if self.transform: sample = self.transform(sample)
然後直接呼叫該物件,呼叫了三個類的call函數,就返回了處理後的資料集了
最後終於可以迭代訓練了
dataloader = DataLoader(transformed_dataset, batch_size=4, shuffle=True, num_workers=4)
定義一個DataLoader的物件,剩下的用法就和第二種的一樣,兩重回圈進行訓練了,這個DataLoader也有點技巧,就是每次對它迭代的時候,返回的還是DataSet類物件返回值的形式,但是裡面的內容又在前面加了一個維度,大小就是batch_size,也就是說,DataLoader物件呼叫的時候每次從迭代器裡取出來batch_size個樣本,並把它們堆疊起來(這個堆疊是在列表/字典內堆疊的),每次迭代出來的內容還都是一個字典/陣列
這是我隨便搭的一個簡單模型,測試一下
import os import torch import torch.nn as nn import torch.utils.data as Data import torchvision import matplotlib.pyplot as plt from torchvision import transforms from torchvision.datasets import ImageFolder import matplotlib.pyplot as plt %matplotlib inline #定義幾個引數 EPOCH = 20 BATCH_SIZE = 4 LR = 0.001 #讀取資料 data_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5, 0.5, 0.5]) ]) dataset = ImageFolder("/home/xxx/learn_pytorch/",transform = data_transform) print(dataset[0][0].size()) print(dataset.class_to_idx) #定義 train_loader = Data.DataLoader(dataset=dataset, batch_size=BATCH_SIZE, shuffle=True) #定義模型類,是 nn.Module的繼承類,思路是先把每個層都定義出來,每個都是模型類的屬性,然後再定義一個成員函數forward()作為前向傳播過程,就可以把每個層連起來了,通過這個就搭好了整個模型 class CNN(nn.Module): def __init__(self): super(CNN,self).__init__() self.conv1 = nn.Sequential( nn.Conv2d(3,16,5,1,2), nn.ReLU(), nn.MaxPool2d(kernel_size=2), ) self.conv2 = nn.Sequential( nn.Conv2d(16, 32, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(2), ) self.conv3 = nn.Sequential( nn.Conv2d(32, 64, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(2), ) self.conv4 = nn.Sequential( nn.Conv2d(64, 128, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(2), ) self.out1 = nn.Sequential( nn.Linear(128*16*30, 1000), nn.ReLU(), ) self.out2 = nn.Sequential( nn.Linear(1000, 100), nn.ReLU(), ) self.out3 = nn.Sequential( nn.Linear(100, 4), ) def forward(self, x): x = self.conv1(x) x = self.conv2(x) x = self.conv3(x) x = self.conv4(x) x = x.view(x.size(0), -1) # flatten the output of conv2 to (batch_size, 32 * 7 * 7) x = self.out1(x) x = self.out2(x) output = self.out3(x) return output, x # return x for visualization #如果使用GPU訓練要把模型和tensor放到GPU上,通過.cuda來實現 cnn = CNN().cuda() print(cnn) #定義優化器物件、損失函數 optimizer = torch.optim.Adam(cnn.parameters(), lr=LR) # optimize all cnn parameters loss_func = nn.CrossEntropyLoss() # the target label is not one-hotted #二重回圈開始訓練,外層迴圈是迭代次數,第二重回圈就是每次對batch_size的資料讀取並訓練 for epoch in range(EPOCH): accy_count = 0 for step,(b_x,b_y) in enumerate(train_loader): output = cnn(b_x.cuda())[0] loss = loss_func(output,b_y.cuda()) #carcute loss optimizer.zero_grad() #clear gradient loss.backward() #sovel gradient optimizer.step() #gradient sovel output_index = torch.max(output,1)[1].cpu().data.numpy() accy_count += float((output_index==b_y.data.numpy()).astype(int).sum()) accuracy = accy_count/(BATCH_SIZE * train_loader.__len__()) print("Epoch:",epoch," accuracy is: ",accuracy)
使用GPU訓練的時候,要把模型、tensor都放在GPU上,就是後面加個.cuda(),例如定義模型物件的時候,cnn.cuda()
還有輸入進模型、計算loss的時候,b_x.cuda() b_y.cuda()
tensor a 轉numpy a.data.numpy()
如果是在GPU上,要先a.cpu().data.numpy()
nn.CrossEntropyLoss()這個損失函數是個大坑,它是softmax + 歸一化,所以使用這個損失函數的時候模型最後就不要再加softmax了,不然會發現自己的損失就那幾個值,也降不下去
輸入模型的 input影象,格式為(batch_size,Nc,H,W)的四維矩陣
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45