dataset.py 10.0 KB

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