dataset.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import os
  2. import numpy as np
  3. import PIL
  4. import torch
  5. from torch.utils.data import Dataset, DataLoader, Sampler
  6. from torchvision import transforms
  7. from collections import defaultdict
  8. from random import shuffle, choices
  9. import random
  10. import tqdm
  11. from modules import devices, shared, images
  12. import re
  13. from ldm.modules.distributions.distributions import DiagonalGaussianDistribution
  14. re_numbers_at_start = re.compile(r"^[-\d]+\s*")
  15. class DatasetEntry:
  16. def __init__(self, filename=None, filename_text=None, latent_dist=None, latent_sample=None, cond=None, cond_text=None, pixel_values=None, weight=None):
  17. self.filename = filename
  18. self.filename_text = filename_text
  19. self.weight = weight
  20. self.latent_dist = latent_dist
  21. self.latent_sample = latent_sample
  22. self.cond = cond
  23. self.cond_text = cond_text
  24. self.pixel_values = pixel_values
  25. class PersonalizedBase(Dataset):
  26. def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_token="*", model=None, cond_model=None, device=None, template_file=None, include_cond=False, batch_size=1, gradient_step=1, shuffle_tags=False, tag_drop_out=0, latent_sampling_method='once', varsize=False, use_weight=False):
  27. re_word = re.compile(shared.opts.dataset_filename_word_regex) if shared.opts.dataset_filename_word_regex else None
  28. self.placeholder_token = placeholder_token
  29. self.flip = transforms.RandomHorizontalFlip(p=flip_p)
  30. self.dataset = []
  31. with open(template_file, "r") as file:
  32. lines = [x.strip() for x in file.readlines()]
  33. self.lines = lines
  34. assert data_root, 'dataset directory not specified'
  35. assert os.path.isdir(data_root), "Dataset directory doesn't exist"
  36. assert os.listdir(data_root), "Dataset directory is empty"
  37. self.image_paths = [os.path.join(data_root, file_path) for file_path in os.listdir(data_root)]
  38. self.shuffle_tags = shuffle_tags
  39. self.tag_drop_out = tag_drop_out
  40. groups = defaultdict(list)
  41. print("Preparing dataset...")
  42. for path in tqdm.tqdm(self.image_paths):
  43. alpha_channel = None
  44. if shared.state.interrupted:
  45. raise Exception("interrupted")
  46. try:
  47. image = images.read(path)
  48. #Currently does not work for single color transparency
  49. #We would need to read image.info['transparency'] for that
  50. if use_weight and 'A' in image.getbands():
  51. alpha_channel = image.getchannel('A')
  52. image = image.convert('RGB')
  53. if not varsize:
  54. image = image.resize((width, height), PIL.Image.BICUBIC)
  55. except Exception:
  56. continue
  57. text_filename = f"{os.path.splitext(path)[0]}.txt"
  58. filename = os.path.basename(path)
  59. if os.path.exists(text_filename):
  60. with open(text_filename, "r", encoding="utf8") as file:
  61. filename_text = file.read()
  62. else:
  63. filename_text = os.path.splitext(filename)[0]
  64. filename_text = re.sub(re_numbers_at_start, '', filename_text)
  65. if re_word:
  66. tokens = re_word.findall(filename_text)
  67. filename_text = (shared.opts.dataset_filename_join_string or "").join(tokens)
  68. npimage = np.array(image).astype(np.uint8)
  69. npimage = (npimage / 127.5 - 1.0).astype(np.float32)
  70. torchdata = torch.from_numpy(npimage).permute(2, 0, 1).to(device=device, dtype=torch.float32)
  71. latent_sample = None
  72. with devices.autocast():
  73. latent_dist = model.encode_first_stage(torchdata.unsqueeze(dim=0))
  74. #Perform latent sampling, even for random sampling.
  75. #We need the sample dimensions for the weights
  76. if latent_sampling_method == "deterministic":
  77. if isinstance(latent_dist, DiagonalGaussianDistribution):
  78. # Works only for DiagonalGaussianDistribution
  79. latent_dist.std = 0
  80. else:
  81. latent_sampling_method = "once"
  82. latent_sample = model.get_first_stage_encoding(latent_dist).squeeze().to(devices.cpu)
  83. if use_weight and alpha_channel is not None:
  84. channels, *latent_size = latent_sample.shape
  85. weight_img = alpha_channel.resize(latent_size)
  86. npweight = np.array(weight_img).astype(np.float32)
  87. #Repeat for every channel in the latent sample
  88. weight = torch.tensor([npweight] * channels).reshape([channels] + latent_size)
  89. #Normalize the weight to a minimum of 0 and a mean of 1, that way the loss will be comparable to default.
  90. weight -= weight.min()
  91. weight /= weight.mean()
  92. elif use_weight:
  93. #If an image does not have a alpha channel, add a ones weight map anyway so we can stack it later
  94. weight = torch.ones(latent_sample.shape)
  95. else:
  96. weight = None
  97. if latent_sampling_method == "random":
  98. entry = DatasetEntry(filename=path, filename_text=filename_text, latent_dist=latent_dist, weight=weight)
  99. else:
  100. entry = DatasetEntry(filename=path, filename_text=filename_text, latent_sample=latent_sample, weight=weight)
  101. if not (self.tag_drop_out != 0 or self.shuffle_tags):
  102. entry.cond_text = self.create_text(filename_text)
  103. if include_cond and not (self.tag_drop_out != 0 or self.shuffle_tags):
  104. with devices.autocast():
  105. entry.cond = cond_model([entry.cond_text]).to(devices.cpu).squeeze(0)
  106. groups[image.size].append(len(self.dataset))
  107. self.dataset.append(entry)
  108. del torchdata
  109. del latent_dist
  110. del latent_sample
  111. del weight
  112. self.length = len(self.dataset)
  113. self.groups = list(groups.values())
  114. assert self.length > 0, "No images have been found in the dataset."
  115. self.batch_size = min(batch_size, self.length)
  116. self.gradient_step = min(gradient_step, self.length // self.batch_size)
  117. self.latent_sampling_method = latent_sampling_method
  118. if len(groups) > 1:
  119. print("Buckets:")
  120. for (w, h), ids in sorted(groups.items(), key=lambda x: x[0]):
  121. print(f" {w}x{h}: {len(ids)}")
  122. print()
  123. def create_text(self, filename_text):
  124. text = random.choice(self.lines)
  125. tags = filename_text.split(',')
  126. if self.tag_drop_out != 0:
  127. tags = [t for t in tags if random.random() > self.tag_drop_out]
  128. if self.shuffle_tags:
  129. random.shuffle(tags)
  130. text = text.replace("[filewords]", ','.join(tags))
  131. text = text.replace("[name]", self.placeholder_token)
  132. return text
  133. def __len__(self):
  134. return self.length
  135. def __getitem__(self, i):
  136. entry = self.dataset[i]
  137. if self.tag_drop_out != 0 or self.shuffle_tags:
  138. entry.cond_text = self.create_text(entry.filename_text)
  139. if self.latent_sampling_method == "random":
  140. entry.latent_sample = shared.sd_model.get_first_stage_encoding(entry.latent_dist).to(devices.cpu)
  141. return entry
  142. class GroupedBatchSampler(Sampler):
  143. def __init__(self, data_source: PersonalizedBase, batch_size: int):
  144. super().__init__(data_source)
  145. n = len(data_source)
  146. self.groups = data_source.groups
  147. self.len = n_batch = n // batch_size
  148. expected = [len(g) / n * n_batch * batch_size for g in data_source.groups]
  149. self.base = [int(e) // batch_size for e in expected]
  150. self.n_rand_batches = nrb = n_batch - sum(self.base)
  151. self.probs = [e%batch_size/nrb/batch_size if nrb>0 else 0 for e in expected]
  152. self.batch_size = batch_size
  153. def __len__(self):
  154. return self.len
  155. def __iter__(self):
  156. b = self.batch_size
  157. for g in self.groups:
  158. shuffle(g)
  159. batches = []
  160. for g in self.groups:
  161. batches.extend(g[i*b:(i+1)*b] for i in range(len(g) // b))
  162. for _ in range(self.n_rand_batches):
  163. rand_group = choices(self.groups, self.probs)[0]
  164. batches.append(choices(rand_group, k=b))
  165. shuffle(batches)
  166. yield from batches
  167. class PersonalizedDataLoader(DataLoader):
  168. def __init__(self, dataset, latent_sampling_method="once", batch_size=1, pin_memory=False):
  169. super(PersonalizedDataLoader, self).__init__(dataset, batch_sampler=GroupedBatchSampler(dataset, batch_size), pin_memory=pin_memory)
  170. if latent_sampling_method == "random":
  171. self.collate_fn = collate_wrapper_random
  172. else:
  173. self.collate_fn = collate_wrapper
  174. class BatchLoader:
  175. def __init__(self, data):
  176. self.cond_text = [entry.cond_text for entry in data]
  177. self.cond = [entry.cond for entry in data]
  178. self.latent_sample = torch.stack([entry.latent_sample for entry in data]).squeeze(1)
  179. if all(entry.weight is not None for entry in data):
  180. self.weight = torch.stack([entry.weight for entry in data]).squeeze(1)
  181. else:
  182. self.weight = None
  183. #self.emb_index = [entry.emb_index for entry in data]
  184. #print(self.latent_sample.device)
  185. def pin_memory(self):
  186. self.latent_sample = self.latent_sample.pin_memory()
  187. return self
  188. def collate_wrapper(batch):
  189. return BatchLoader(batch)
  190. class BatchLoaderRandom(BatchLoader):
  191. def __init__(self, data):
  192. super().__init__(data)
  193. def pin_memory(self):
  194. return self
  195. def collate_wrapper_random(batch):
  196. return BatchLoaderRandom(batch)