2021年5月2日星期日

I try to build a incremental learning system for a 16 class plant image data based on ICARL but the training loss keep constant around 0.6931

I am a beginner and want to build a incremental learning system based on ICARL for plant identification, i used the NORSE package to build my own resnet implementation. The custom imagefolder dataloader feed data based on the range of class number given and my dataset is 16 class flower image with 2688 train image and 768 test image for each class. I had try for a couple of days tuning the learning rate, batch size and epoch number but the training loss didnt change much...

The main coding:

import os  #os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"     os.environ["CUDA_VISIBLE_DEVICES"]="0"    #import all module  import torch  import torch.nn as nn  from torch.utils.data import DataLoader, TensorDataset   from torchvision.datasets import ImageFolder, DatasetFolder   from torch.utils.data import SubsetRandomSampler  from torch.utils.data import SequentialSampler  from torch.utils.data import BatchSampler  from torch.nn.parameter import Parameter  import torchvision  import numpy as np  from SpykeTorch import snn  from SpykeTorch import functional as sf  from SpykeTorch import visualization as vis  from SpykeTorch import utils  from torchvision import transforms  from tqdm import tqdm  import struct  import glob  import random  from PIL import Image  from torch import Tensor    import torchvision.datasets as dsets  import torchvision.models as models  from torch.autograd import Variable  import torch.optim as optim  import torch.nn.functional as F  import matplotlib.pyplot as plt  import matplotlib.gridspec as gridspec  from torch import multiprocessing  from data_loader import FIORA16, iCIFAR10  from model import iCaRLNet  from tqdm import tqdm  from tqdm.notebook import trange  from tqdm.contrib import tenumerate  import time  from memory_profiler import profile    use_cuda = True  #torch.autograd.set_detect_anomaly(True)      random.seed(0)  torch.manual_seed(0)  torch.cuda.manual_seed(0)  np.random.seed(0)    def show_images(images):      N = images.shape[0]      fig = plt.figure(figsize=(1, N))      gs = gridspec.GridSpec(1, N)      gs.update(wspace=0.05, hspace=0.05)        for i, img in tenumerate(images):          ax = plt.subplot(gs[i])          plt.axis('off')          ax.set_xticklabels([])          ax.set_yticklabels([])          ax.set_aspect('equal')          plt.imshow(img)      plt.show()    transform = transforms.Compose([              transforms.Resize(32),              transforms.RandomCrop(32, padding=4),              transforms.RandomHorizontalFlip(),              #transforms.Grayscale(),              transforms.ToTensor(),              transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))  ])    transform_test = transforms.Compose([          #transforms.Grayscale(),          transforms.Resize(32),          transforms.ToTensor(),          transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))  ])      def run():      multiprocessing.freeze_support()      print('loop')          # Hyper Parameters  total_classes = 16  num_classes = 2    # Initialize CNN  if __name__ == '__main__':      run()      K = 2000 # total number of exemplars      icarl = iCaRLNet(2048,1)      icarl.cuda()              for s in trange(0, total_classes, num_classes):          t1_curr1 = perf_counter()          print("training for class:",s,"~",s+num_classes-1)          print("Load Datasets")          train_set = FIORA16(root='data/train',                               class_num=range(s,s+num_classes),                               transform=transform_test)                    train_loader = torch.utils.data.DataLoader(train_set, batch_size=100,                                                     shuffle=True, num_workers=2)                            test_set = FIORA16(root='data/test',                              class_num=range(s+num_classes),                              transform=transform_test)                    test_loader = torch.utils.data.DataLoader(test_set, batch_size=100,                                                    shuffle=True, num_workers=2)                    print("Update representation via BackProp")          icarl.update_representation(train_set)          m = K // icarl.n_classes                    print("Reduce exemplar sets for known classes")          icarl.reduce_exemplar_sets(m)                    print("Construct exemplar sets for new classes")          for y in trange(icarl.n_known, icarl.n_classes):              print ("Constructing exemplar set for class-%d..." %(y)),              images = train_set.get_image_class(y)              icarl.construct_exemplar_set(images, m, transform_test)              print ("Done")                    for y, P_y in tenumerate(icarl.exemplar_sets):              print ("Exemplar set for class-%d:" % (y), len(P_y))              #for i in range(10):                  #show_images(P_y[i])            icarl.n_known = icarl.n_classes          print ("iCaRL known classes: %d" % icarl.n_known)                    print("Classify images by neares-means-of-exemplars")          print("train data test")          total = 0.0          correct = 0.0          for indices, images, labels in tqdm(train_loader):              images = Variable(images).cuda()              preds = icarl.classify(images, transform_test)              total += labels.size(0)              correct += (preds.data.cpu() == labels).sum()            print('Train Accuracy: %d %%' % (100 * correct / total))                    print("test data test")          total = 0.0          correct = 0.0          for indices, images, labels in tqdm(test_loader):              images = Variable(images).cuda()              preds = icarl.classify(images, transform_test)              total += labels.size(0)              correct += (preds.data.cpu() == labels).sum()            print('Test Accuracy: %d %%' % (100 * correct / total))                    print("################################################################################")                        #per batch timer          t1_curr2 = perf_counter()              print("Elapsed time:", t1_curr2, t1_curr1)            print("Elapsed time during the whole program in seconds:",t1_curr2-t1_curr1)            print("Elapsed time during the whole program in minutes:",(t1_curr2-t1_curr1)/60)                    print("################################################################################")  

The model used:

import torch  import torch.nn as nn  import torch.nn.functional as F  import torch.optim as optim  from torch.autograd import Variable  import numpy as np  from PIL import Image  from torch import multiprocessing  from resnet import resnet18  from My_resnet import Norse  from tqdm import tqdm  from tqdm.notebook import trange  from tqdm.notebook import trange  from tqdm.contrib import tenumerate  from norse.torch.module.leaky_integrator import LILinearCell  import os  import random  from memory_profiler import profile    random.seed(0)  torch.manual_seed(0)  torch.cuda.manual_seed(0)  np.random.seed(0)  #.requires_grad_()    #check device  import torch  device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")  # Assuming that we are on a CUDA machine, this should print a CUDA device:  print(device)    # Hyper Parameters  num_epochs = 50  batch_size = 32  learning_rate = 0.002    class iCaRLNet(nn.Module):      def __init__(self, feature_size, n_classes):          # Network architecture          super(iCaRLNet, self).__init__()          self.feature_extractor = Norse()          #self.feature_extractor = resnet18()          self.feature_extractor.fc =\              LILinearCell(self.feature_extractor.snn.fc.input_size, feature_size)          #nn.Linear(self.feature_extractor.fc.in_features, feature_size)          self.bn = nn.BatchNorm1d(feature_size, momentum=0.01)          self.ReLU = nn.ReLU()          self.fc = nn.Linear(feature_size, n_classes, bias=False)            self.n_classes = n_classes          self.n_known = 0            # List containing exemplar_sets          # Each exemplar_set is a np.array of N images          # with shape (N, C, H, W)          self.exemplar_sets = []            # Learning method          self.cls_loss = nn.CrossEntropyLoss()          self.dist_loss = nn.BCELoss()          self.optimizer = optim.Adam(self.parameters(), lr=learning_rate,                                      weight_decay=0.00001)          #self.optimizer = optim.SGD(self.parameters(), lr=learning_rate,                                     #weight_decay=0.00001)            # Means of exemplars          self.compute_means = True          self.exemplar_means = []        def forward(self, x):          #print('shape a1 ' + str(x.shape))          x = self.feature_extractor(x)          x = self.bn(x)          x = self.ReLU(x)          x = self.fc(x)          return x        def increment_classes(self, n):          """Add n classes in the final fc layer"""          in_features = self.fc.in_features          out_features = self.fc.out_features          weight = self.fc.weight.data            self.fc = nn.Linear(in_features, out_features+n, bias=False)          self.fc.weight.data[:out_features] = weight          self.n_classes += n        def classify(self, x, transform):          #print("Classify images by neares-means-of-exemplars")          """          Args:              x: input image batch          Returns:              preds: Tensor of size (batch_size,)          """          batch_size = x.size(0)          self.eval()          if self.compute_means:              print ("Computing mean of exemplars..."),              exemplar_means = []              for P_y in tqdm(self.exemplar_sets):                  features = []                  # Extract feature for each exemplar in P_y                  with torch.no_grad():                      for ex in (P_y):                          #ex = ex.detach()                          ex = ex.cuda()                          #ex = ex.requires_grad_(False).cuda()                          feature = self.feature_extractor(ex.unsqueeze(0))                          feature = feature.squeeze()                          feature.data = feature.data / feature.data.norm() # Normalize                          features.append(feature)                      features = torch.stack(features)                      mu_y = features.mean(0).squeeze()                      mu_y.data = mu_y.data / mu_y.data.norm() # Normalize                      exemplar_means.append(mu_y)              self.exemplar_means = exemplar_means              self.compute_means = False              print ("Done")            exemplar_means = self.exemplar_means          means = torch.stack(exemplar_means) # (n_classes, feature_size)          means = torch.stack([means] * batch_size) # (batch_size, n_classes, feature_size)          means = means.transpose(1, 2) # (batch_size, feature_size, n_classes)                    with torch.no_grad():              feature = self.feature_extractor(x) # (batch_size, feature_size)              for i in range(feature.size(0)): # Normalize                  feature.data[i] = feature.data[i] / feature.data[i].norm()              feature = feature.unsqueeze(2) # (batch_size, feature_size, 1)              feature = feature.expand_as(means) # (batch_size, feature_size, n_classes)                dists = (feature - means).pow(2).sum(1).squeeze() #(batch_size, n_classes)              _, preds = dists.min(1)            return preds                def construct_exemplar_set(self, images, m, transform):          """Construct an exemplar set for image set            Args:              images: np.array containing images of a class          """          print("Compute and cache features for each example")          features = []          self.eval()                    for img in tqdm(images):              x = img.detach().cuda()              #x = img.requires_grad_(False).cuda()              #x = transform(Image.fromarray(img))#.cuda()              feature = self.feature_extractor(x.unsqueeze(0)).data.cpu().numpy()              feature = feature / np.linalg.norm(feature) # Normalize              features.append(feature[0])            features = np.array(features)          class_mean = np.mean(features, axis=0)          class_mean = class_mean / np.linalg.norm(class_mean) # Normalize            exemplar_set = []          exemplar_features = [] # list of Variables of shape (feature_size,)          print(m)                    for k in trange(m):              S = np.sum(exemplar_features, axis=0)              phi = features              mu = class_mean              mu_p = 1.0/(k+1) * (phi + S)              mu_p = mu_p / np.linalg.norm(mu_p)              i = np.argmin(np.sqrt(np.sum((mu - mu_p) ** 2, axis=1)))                exemplar_set.append(images[i])              exemplar_features.append(features[i])              """              print "Selected example", i              print "|exemplar_mean - class_mean|:",              print np.linalg.norm((np.mean(exemplar_features, axis=0) - class_mean))              #features = np.delete(features, i, axis=0)              """                            #self.exemplar_sets.append(np.array(exemplar_set))          self.exemplar_sets.append(exemplar_set)                          def reduce_exemplar_sets(self, m):          for y, P_y in tenumerate(self.exemplar_sets):              self.exemplar_sets[y] = P_y[:m]          def combine_dataset_with_exemplars(self, dataset):          for y, P_y in tenumerate(self.exemplar_sets):              exemplar_images = P_y              exemplar_labels = [y] * len(P_y)              dataset.append(exemplar_images, exemplar_labels)        def update_representation(self, dataset):            self.compute_means = True            print("Increment number of weights in final fc layer")          classes = list(set(dataset.targets))          new_classes = [cls for cls in classes if cls > self.n_classes - 1]          self.increment_classes(len(new_classes))          self.cuda()          print ("%d new classes" % (len(new_classes)))            print("Form combined training set")          self.combine_dataset_with_exemplars(dataset)            loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,                                                 shuffle=True, num_workers=2, drop_last=True)            print("Store network outputs with pre-update parameters")          q = torch.zeros(len(dataset), self.n_classes).cuda()                    for indices, images, labels in tqdm(loader):              images = Variable(images).cuda()              #images = images.requires_grad_().cuda()              indices = indices.cuda()              labels = labels              g = torch.sigmoid(self.forward(images))              q[indices] = g.data                    q = Variable(q).cuda()          #q = q.requires_grad_().cuda()            print("Run network training")          optimizer = self.optimizer          #scaler = torch.cuda.amp.GradScaler()          self.train()            for epoch in trange(num_epochs):              for i, (indices, images, labels) in enumerate(loader):                  images = Variable(images).cuda()                  labels = Variable(labels).cuda()                  #images = images.requires_grad_().cuda()                  #labels = labels.requires_grad_().cuda()                  indices = indices.cuda()                                    #with torch.cuda.amp.autocast():                  optimizer.zero_grad()                  g = self.forward(images)                                    # Classification loss for new classes                  loss = self.cls_loss(g, labels)                  #loss = loss / len(range(self.n_known, self.n_classes))                    # Distilation loss for old classes                  if self.n_known > 0:                      g = torch.sigmoid(g)                      q_i = q[indices]                      dist_loss = sum(self.dist_loss(g[:,y], q_i[:,y])\                              for y in range(self.n_known))                      dist_loss = dist_loss / self.n_known                      loss += dist_loss                    #loss.backward()                  #optimizer.step()                  #scaler.scale(loss).backward()                  #scaler.step(optimizer)                  #scaler.update()                  #if (i+1) % 2 == 0:                      #scaler.step(optimizer)                      #scaler.update()                      #optimizer.zero_grad()                        if (i+1) % 10 == 0:                      print ('Epoch [%d/%d], Iter [%d/%d] Loss: %.4f'                              %(epoch+1, num_epochs, i+1, len(dataset)//batch_size, loss.data))  

The neural network:

import torch  import torch.nn as nn  import torch.nn.functional as F  from torch import multiprocessing  from norse.torch import LIFParameters, LIFState  from norse.torch.module.lif import LIFCell, LIFRecurrentCell  from norse.torch.module.leaky_integrator import LILinearCell  from norse.torch.functional.lif import LIFFeedForwardState  from norse.torch.functional.leaky_integrator import LIState  from tqdm.notebook import trange    import importlib  from norse.torch.module import encode  encode = importlib.reload(encode)    from torch.autograd import Variable  from typing import NamedTuple    def decode(x):      x, _ = torch.max(x, 0)      log_p_y = nn.functional.log_softmax(x, dim=1)      return log_p_y      #return x    def decode_last(x):      x = x[-1]      log_p_y = nn.functional.log_softmax(x, dim=1)      return log_p_y    def conv3x3(in_planes, out_planes, stride=1):      return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)    class ConvNet(nn.Module):      def __init__(          self,  num_channels=64, feature_size=32, method="super", alpha=100, num_classes=16      ):          super(ConvNet, self).__init__()            #self.features = int(((feature_size - 4) / 2 - 4) / 2)#+1            self.conv1 = torch.nn.Conv2d(num_channels, 128, 2, 2, 0)          self.conv2 = torch.nn.Conv2d(128, 256, 9, 2, 2)          self.conv3 = torch.nn.Conv2d(256, 512, 9, 2, 2)          #self.fc1 = torch.nn.Linear(self.features * self.features * 50, 512)          self.lif0 = LIFCell(p=LIFParameters(method=method, alpha=alpha))          self.lif1 = LIFCell(p=LIFParameters(method=method, alpha=alpha))          self.lif2 = LIFCell(p=LIFParameters(method=method, alpha=alpha))          self.lif3 = LIFCell(p=LIFParameters(method=method, alpha=alpha))          #self.avgpool = nn.AvgPool2d(4)          self.fc = LILinearCell(512, 2048)#feature_size to be tuned          self.bn1 = nn.BatchNorm2d(128)          self.bn2 = nn.BatchNorm2d(256)          self.bn3 = nn.BatchNorm2d(512)                def forward(self, x):          seq_length = x.shape[0]          batch_size = x.shape[1]                    # specify the initial states          s0 = s1 = s2 = s3 = so = None            voltages = torch.zeros(              seq_length, batch_size, 2048, device=x.device, dtype=x.dtype          )            for ts in range(seq_length):              z = self.conv1(x[ts, :])              #print('shape z1 ' + str(z.shape))              z = self.bn1(z)              #print('shape z2 ' + str(z.shape))              z, s0 = self.lif0(F.relu(z), s0)              #print('shape z3 ' + str(z.shape))              z = self.conv2(z)              #print('shape z4 ' + str(z.shape))              z = self.bn2(z)              #print('shape z5 ' + str(z.shape))              z, s1 = self.lif1(F.relu(z), s1)              #print('shape z6 ' + str(z.shape))              z = self.conv3(z)              #print('shape z7 ' + str(z.shape))              z = self.bn3(z)              #print('shape z8 ' + str(z.shape))              z, s2 = self.lif1(F.relu(z), s2)              #print('shape z9 ' + str(z.shape))              #z = self.avgpool(z)              #print('shape z10 ' + str(z.shape))              z = z.view(z.size(0), -1)               #print('shape z11 ' + str(z.shape))              z, s3 = self.lif2(F.relu(z), s3)              #print('shape z12 ' + str(z.shape))              v, so = self.fc(F.relu(z), so)              #print('shape z13 ' + str(v.shape))              voltages[ts, :, :] = v          return voltages            class Norse(nn.Module):      def __init__(self):          super(Norse, self).__init__()          self.in_planes = 64          T = 15          self.pre_layers = nn.Sequential(conv3x3(3,64),                                          nn.BatchNorm2d(64),                                          nn.ReLU(True),)          #self.encoder = encode.SpikeLatencyLIFEncoder(T)          self.encoder = encode.ConstantCurrentLIFEncoder(T)          self.snn = ConvNet(alpha=80)          self.decoder = decode        def forward(self, x):          #print('shape x1 ' + str(x.shape))          x = self.pre_layers(x)          #print('shape x2 ' + str(x.shape))          x = self.encoder(x)          #print('shape x3 ' + str(x.shape))          x = self.snn(x)          #print('shape x4 ' + str(x.shape))          x = self.decoder(x)          #print('shape x5 ' + str(x.shape))          return x      

The dataloader:

from torchvision.datasets import VisionDataset  import os  import os.path  from typing import Any, Callable, cast, Dict, List, Optional, Tuple  from torchvision.datasets import CIFAR10  from torch import multiprocessing  from PIL import Image  from tqdm import tqdm  from tqdm.notebook import trange  from tqdm.contrib import tenumerate  from torchvision.datasets import ImageFolder  from torchvision.datasets import DatasetFolder  from torch.utils.data import SubsetRandomSampler  from typing import Any, Callable, cast, Dict, List, Optional, Tuple    import numpy as np  import torch          def has_file_allowed_extension(filename: str, extensions: Tuple[str, ...]) -> bool:      return filename.lower().endswith(extensions)      def is_image_file(filename: str) -> bool:      return has_file_allowed_extension(filename, IMG_EXTENSIONS)      def find_classes(directory: str) -> Tuple[List[str], Dict[str, int]]:      classes = sorted(entry.name for entry in os.scandir(directory) if entry.is_dir())      if not classes:          raise FileNotFoundError(f"Couldn't find any class folder in {directory}.")        class_to_idx = {cls_name: i for i, cls_name in enumerate(classes)}      return classes, class_to_idx      def make_dataset(      directory: str,      class_num: list,      class_to_idx: Optional[Dict[str, int]] = None,      extensions: Optional[Tuple[str, ...]] = None,      is_valid_file: Optional[Callable[[str], bool]] = None,  ) -> List[Tuple[str, int]]:        directory = os.path.expanduser(directory)        if class_to_idx is None:          _, class_to_idx = find_classes(directory)      elif not class_to_idx:          raise ValueError("'class_to_index' must have at least one entry to collect any samples.")        both_none = extensions is None and is_valid_file is None      both_something = extensions is not None and is_valid_file is not None      if both_none or both_something:          raise ValueError("Both extensions and is_valid_file cannot be None or not None at the same time")        if extensions is not None:            def is_valid_file(x: str) -> bool:              return has_file_allowed_extension(x, cast(Tuple[str, ...], extensions))        is_valid_file = cast(Callable[[str], bool], is_valid_file)        instances = []      available_classes = set()      for target_class in sorted(class_to_idx.keys()):          class_index = class_to_idx[target_class]          target_dir = os.path.join(directory, target_class)          if not os.path.isdir(target_dir):              continue          for root, _, fnames in sorted(os.walk(target_dir, followlinks=True)):              for fname in sorted(fnames):                  path = os.path.join(root, fname)                  if is_valid_file(path):                      item = path, class_index                      if class_index in class_num:                          instances.append(item)                        if target_class not in available_classes:                          available_classes.add(target_class)        empty_classes = set(class_to_idx.keys()) - available_classes      if empty_classes:          msg = f"Found no valid file for the classes {', '.join(sorted(empty_classes))}. "          if extensions is not None:              msg += f"Supported extensions are: {', '.join(extensions)}"          raise FileNotFoundError(msg)        return instances    def pil_loader(path: str) -> Image.Image:      # open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)      with open(path, 'rb') as f:          img = Image.open(f)          return img.convert('RGB')      # TODO: specify the return type  def accimage_loader(path: str) -> Any:      import accimage      try:          return accimage.Image(path)      except IOError:          # Potentially a decoding problem, fall back to PIL.Image          return pil_loader(path)      def default_loader(path: str) -> Any:      from torchvision import get_image_backend      if get_image_backend() == 'accimage':          return accimage_loader(path)      else:          return pil_loader(path)    class FIORA16(VisionDataset):      def __init__(              self,              root: str,              class_num: list,              loader: Callable[[str], Any] = default_loader,              extensions: Optional[Tuple[str, ...]]=('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', '.tiff', '.webp'),              transform: Optional[Callable] = None,              target_transform: Optional[Callable] = None,              is_valid_file: Optional[Callable[[str], bool]] = None,      ) -> None:          super(FIORA16, self).__init__(root, transform=transform,                                        target_transform=target_transform,                                        )          classes, class_to_idx = self.find_classes(self.root)          self.class_num=list(class_num)          samples = self.make_dataset(self.root, self.class_num, class_to_idx, extensions, is_valid_file)            self.loader = loader          self.extensions = extensions            self.classes = classes          self.class_to_idx = class_to_idx          self.samples = samples          self.data =[]          for i in trange(len(self.samples)):              sample = self.loader(self.samples[i][0])              if self.transform is not None:                  sample = self.transform(sample)              self.data.append(sample)          self.targets = [s[1] for s in samples]        @staticmethod      def make_dataset(          directory: str,          class_num: list,          class_to_idx: Dict[str, int],          extensions: Optional[Tuple[str, ...]] = None,          is_valid_file: Optional[Callable[[str], bool]] = None,      ) -> List[Tuple[str, int]]:          return make_dataset(directory, class_num, class_to_idx, extensions=extensions, is_valid_file=is_valid_file)        def find_classes(self, dir: str) -> Tuple[List[str], Dict[str, int]]:          """Same as :func:`find_classes`.          This method can be overridden to only consider          a subset of classes, or to adapt to a different dataset directory structure.          """          return find_classes(dir)            def __getitem__(self, index: int) -> Tuple[Any, Any]:          img, target = self.data[index], self.targets[index]          #path, _ = self.samples[index]          #sample = self.loader(path)          #if self.transform is not None:              #sample = self.transform(sample)          if self.target_transform is not None:              target = self.target_transform(target)            return index, img, target        def __len__(self) -> int:          return len(self.targets)        IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', '.tiff', '.webp')        def get_image_class(self, label):          datac = []          for i in trange(len(self.targets)):              if self.targets[i]==label:                  datac.append(self.data[i])          return datac        def append(self, images, labels):          self.data.extend(images)          self.targets = self.targets + labels              def pil_loader(path: str) -> Image.Image:          # open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)          with open(path, 'rb') as f:              img = Image.open(f)              return img.convert('RGB')          # TODO: specify the return type      def accimage_loader(path: str) -> Any:          import accimage          try:              return accimage.Image(path)          except IOError:              # Potentially a decoding problem, fall back to PIL.Image              return pil_loader(path)          def default_loader(path: str) -> Any:          from torchvision import get_image_backend          if get_image_backend() == 'accimage':              return accimage_loader(path)          else:              return pil_loader(path)    
https://stackoverflow.com/questions/67357855/i-try-to-build-a-incremental-learning-system-for-a-16-class-plant-image-data-bas May 02, 2021 at 11:04PM

没有评论:

发表评论