You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Tune-PyTorch.md 8.7 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. # Tune - PyTorch
  2. This example uses flaml to tune a pytorch model on CIFAR10.
  3. ## Prepare for tuning
  4. ### Requirements
  5. ```bash
  6. pip install torchvision "flaml[blendsearch,ray]"
  7. ```
  8. Before we are ready for tuning, we first need to define the neural network that we would like to tune.
  9. ### Network Specification
  10. ```python
  11. import torch
  12. import torch.nn as nn
  13. import torch.nn.functional as F
  14. import torch.optim as optim
  15. from torch.utils.data import random_split
  16. import torchvision
  17. import torchvision.transforms as transforms
  18. class Net(nn.Module):
  19. def __init__(self, l1=120, l2=84):
  20. super(Net, self).__init__()
  21. self.conv1 = nn.Conv2d(3, 6, 5)
  22. self.pool = nn.MaxPool2d(2, 2)
  23. self.conv2 = nn.Conv2d(6, 16, 5)
  24. self.fc1 = nn.Linear(16 * 5 * 5, l1)
  25. self.fc2 = nn.Linear(l1, l2)
  26. self.fc3 = nn.Linear(l2, 10)
  27. def forward(self, x):
  28. x = self.pool(F.relu(self.conv1(x)))
  29. x = self.pool(F.relu(self.conv2(x)))
  30. x = x.view(-1, 16 * 5 * 5)
  31. x = F.relu(self.fc1(x))
  32. x = F.relu(self.fc2(x))
  33. x = self.fc3(x)
  34. return x
  35. ```
  36. ### Data
  37. ```python
  38. def load_data(data_dir="data"):
  39. transform = transforms.Compose([
  40. transforms.ToTensor(),
  41. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  42. ])
  43. trainset = torchvision.datasets.CIFAR10(
  44. root=data_dir, train=True, download=True, transform=transform)
  45. testset = torchvision.datasets.CIFAR10(
  46. root=data_dir, train=False, download=True, transform=transform)
  47. return trainset, testset
  48. ```
  49. ### Training
  50. ```python
  51. from ray import tune
  52. def train_cifar(config, checkpoint_dir=None, data_dir=None):
  53. if "l1" not in config:
  54. logger.warning(config)
  55. net = Net(2**config["l1"], 2**config["l2"])
  56. device = "cpu"
  57. if torch.cuda.is_available():
  58. device = "cuda:0"
  59. if torch.cuda.device_count() > 1:
  60. net = nn.DataParallel(net)
  61. net.to(device)
  62. criterion = nn.CrossEntropyLoss()
  63. optimizer = optim.SGD(net.parameters(), lr=config["lr"], momentum=0.9)
  64. # The `checkpoint_dir` parameter gets passed by Ray Tune when a checkpoint
  65. # should be restored.
  66. if checkpoint_dir:
  67. checkpoint = os.path.join(checkpoint_dir, "checkpoint")
  68. model_state, optimizer_state = torch.load(checkpoint)
  69. net.load_state_dict(model_state)
  70. optimizer.load_state_dict(optimizer_state)
  71. trainset, testset = load_data(data_dir)
  72. test_abs = int(len(trainset) * 0.8)
  73. train_subset, val_subset = random_split(
  74. trainset, [test_abs, len(trainset) - test_abs])
  75. trainloader = torch.utils.data.DataLoader(
  76. train_subset,
  77. batch_size=int(2**config["batch_size"]),
  78. shuffle=True,
  79. num_workers=4)
  80. valloader = torch.utils.data.DataLoader(
  81. val_subset,
  82. batch_size=int(2**config["batch_size"]),
  83. shuffle=True,
  84. num_workers=4)
  85. for epoch in range(int(round(config["num_epochs"]))): # loop over the dataset multiple times
  86. running_loss = 0.0
  87. epoch_steps = 0
  88. for i, data in enumerate(trainloader, 0):
  89. # get the inputs; data is a list of [inputs, labels]
  90. inputs, labels = data
  91. inputs, labels = inputs.to(device), labels.to(device)
  92. # zero the parameter gradients
  93. optimizer.zero_grad()
  94. # forward + backward + optimize
  95. outputs = net(inputs)
  96. loss = criterion(outputs, labels)
  97. loss.backward()
  98. optimizer.step()
  99. # print statistics
  100. running_loss += loss.item()
  101. epoch_steps += 1
  102. if i % 2000 == 1999: # print every 2000 mini-batches
  103. print("[%d, %5d] loss: %.3f" % (epoch + 1, i + 1,
  104. running_loss / epoch_steps))
  105. running_loss = 0.0
  106. # Validation loss
  107. val_loss = 0.0
  108. val_steps = 0
  109. total = 0
  110. correct = 0
  111. for i, data in enumerate(valloader, 0):
  112. with torch.no_grad():
  113. inputs, labels = data
  114. inputs, labels = inputs.to(device), labels.to(device)
  115. outputs = net(inputs)
  116. _, predicted = torch.max(outputs.data, 1)
  117. total += labels.size(0)
  118. correct += (predicted == labels).sum().item()
  119. loss = criterion(outputs, labels)
  120. val_loss += loss.cpu().numpy()
  121. val_steps += 1
  122. # Here we save a checkpoint. It is automatically registered with
  123. # Ray Tune and will potentially be passed as the `checkpoint_dir`
  124. # parameter in future iterations.
  125. with tune.checkpoint_dir(step=epoch) as checkpoint_dir:
  126. path = os.path.join(checkpoint_dir, "checkpoint")
  127. torch.save(
  128. (net.state_dict(), optimizer.state_dict()), path)
  129. tune.report(loss=(val_loss / val_steps), accuracy=correct / total)
  130. print("Finished Training")
  131. ```
  132. ### Test Accuracy
  133. ```python
  134. def _test_accuracy(net, device="cpu"):
  135. trainset, testset = load_data()
  136. testloader = torch.utils.data.DataLoader(
  137. testset, batch_size=4, shuffle=False, num_workers=2)
  138. correct = 0
  139. total = 0
  140. with torch.no_grad():
  141. for data in testloader:
  142. images, labels = data
  143. images, labels = images.to(device), labels.to(device)
  144. outputs = net(images)
  145. _, predicted = torch.max(outputs.data, 1)
  146. total += labels.size(0)
  147. correct += (predicted == labels).sum().item()
  148. return correct / total
  149. ```
  150. ## Hyperparameter Optimization
  151. ```python
  152. import numpy as np
  153. import flaml
  154. import os
  155. data_dir = os.path.abspath("data")
  156. load_data(data_dir) # Download data for all trials before starting the run
  157. ```
  158. ### Search space
  159. ```python
  160. max_num_epoch = 100
  161. config = {
  162. "l1": tune.randint(2, 9), # log transformed with base 2
  163. "l2": tune.randint(2, 9), # log transformed with base 2
  164. "lr": tune.loguniform(1e-4, 1e-1),
  165. "num_epochs": tune.loguniform(1, max_num_epoch),
  166. "batch_size": tune.randint(1, 5) # log transformed with base 2
  167. }
  168. ```
  169. ### Budget and resource constraints
  170. ```python
  171. time_budget_s = 600 # time budget in seconds
  172. gpus_per_trial = 0.5 # number of gpus for each trial; 0.5 means two training jobs can share one gpu
  173. num_samples = 500 # maximal number of trials
  174. np.random.seed(7654321)
  175. ```
  176. ### Launch the tuning
  177. ```python
  178. import time
  179. start_time = time.time()
  180. result = flaml.tune.run(
  181. tune.with_parameters(train_cifar, data_dir=data_dir),
  182. config=config,
  183. metric="loss",
  184. mode="min",
  185. low_cost_partial_config={"num_epochs": 1},
  186. max_resource=max_num_epoch,
  187. min_resource=1,
  188. scheduler="asha", # Use asha scheduler to perform early stopping based on intermediate results reported
  189. resources_per_trial={"cpu": 1, "gpu": gpus_per_trial},
  190. local_dir='logs/',
  191. num_samples=num_samples,
  192. time_budget_s=time_budget_s,
  193. use_ray=True)
  194. ```
  195. ### Check the result
  196. ```python
  197. print(f"#trials={len(result.trials)}")
  198. print(f"time={time.time()-start_time}")
  199. best_trial = result.get_best_trial("loss", "min", "all")
  200. print("Best trial config: {}".format(best_trial.config))
  201. print("Best trial final validation loss: {}".format(
  202. best_trial.metric_analysis["loss"]["min"]))
  203. print("Best trial final validation accuracy: {}".format(
  204. best_trial.metric_analysis["accuracy"]["max"]))
  205. best_trained_model = Net(2**best_trial.config["l1"],
  206. 2**best_trial.config["l2"])
  207. device = "cpu"
  208. if torch.cuda.is_available():
  209. device = "cuda:0"
  210. if gpus_per_trial > 1:
  211. best_trained_model = nn.DataParallel(best_trained_model)
  212. best_trained_model.to(device)
  213. checkpoint_path = os.path.join(best_trial.checkpoint.value, "checkpoint")
  214. model_state, optimizer_state = torch.load(checkpoint_path)
  215. best_trained_model.load_state_dict(model_state)
  216. test_acc = _test_accuracy(best_trained_model, device)
  217. print("Best trial test set accuracy: {}".format(test_acc))
  218. ```
  219. ### Sample of output
  220. ```
  221. #trials=44
  222. time=1193.913584947586
  223. Best trial config: {'l1': 8, 'l2': 8, 'lr': 0.0008818671030627281, 'num_epochs': 55.9513429004283, 'batch_size': 3}
  224. Best trial final validation loss: 1.0694482081472874
  225. Best trial final validation accuracy: 0.6389
  226. Files already downloaded and verified
  227. Files already downloaded and verified
  228. Best trial test set accuracy: 0.6294
  229. ```
  230. [Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/tune_pytorch.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/tune_pytorch.ipynb)