Update
[beaver.git] / beaver.py
index 074e137..7800527 100755 (executable)
--- a/beaver.py
+++ b/beaver.py
@@ -233,7 +233,7 @@ def oneshot_trace_loss(mazes, output, policies, height, width):
     return (output - targets).abs().sum() / masks.sum()
 
 
-def oneshot(gpt, task):
+def oneshot(gpt, learning_rate_scheduler, task):
     t = gpt.training
     gpt.eval()
 
@@ -261,8 +261,12 @@ def oneshot(gpt, task):
         nn.Linear(args.dim_model, dim_out),
     ).to(device)
 
+    learning_rate_scheduler.reset()
+
     for n_epoch in range(args.nb_epochs):
-        learning_rate = learning_rate_schedule[n_epoch]
+        learning_rate = learning_rate_scheduler.get_learning_rate()
+        log_string(f"learning_rate {n_epoch} {learning_rate}")
+
         optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
 
         acc_train_loss, nb_train_samples = 0, 0
@@ -280,6 +284,8 @@ def oneshot(gpt, task):
             loss.backward()
             optimizer.step()
 
+        learning_rate_scheduler.update(n_epoch + 1, acc_train_loss)
+
         acc_test_loss, nb_test_samples = 0, 0
         for mazes, policies in task.policy_batches(split="test"):
             output_gpt = eval_mygpt(
@@ -337,6 +343,75 @@ def oneshot(gpt, task):
 ######################################################################
 
 
+class LearningRateScheduler:
+    def get_learning_rate(self):
+        pass
+
+    def update(self, nb_finished_epochs, loss):
+        pass
+
+    def reset(self):
+        pass
+
+    def get_state(self):
+        return vars(self)
+
+    def set_state(self, state):
+        print(f"{state=}")
+        for k, v in state.items():
+            setattr(self, k, v)
+
+
+class StepWiseScheduler(LearningRateScheduler):
+    def __init__(self, schedule):
+        self.nb_finished_epochs = 0
+        self.schedule = schedule
+
+    def get_learning_rate(self):
+        return self.schedule[self.nb_finished_epochs]
+
+    def update(self, nb_finished_epochs, loss):
+        self.nb_finished_epochs = nb_finished_epochs
+
+    def reset(self):
+        self.nb_finished_epochs = 0
+
+    def get_state(self):
+        return {"nb_finished_epochs": self.nb_finished_epochs}
+
+
+class AutoScheduler(LearningRateScheduler):
+    def __init__(self, learning_rate_init, growth=1.0, degrowth=0.2):
+        self.learning_rate_init = learning_rate_init
+        self.learning_rate = learning_rate_init
+        self.growth = growth
+        self.degrowth = degrowth
+        self.pred_loss = None
+
+    def get_learning_rate(self):
+        return self.learning_rate
+
+    def update(self, nb_finished_epochs, loss):
+        if self.pred_loss is not None:
+            if loss >= self.pred_loss:
+                self.learning_rate *= self.degrowth
+            else:
+                self.learning_rate *= self.growth
+        self.pred_loss = loss
+
+    def reset(self):
+        self.learning_rate = self.learning_rate_init
+
+    def get_state(self):
+        return {
+            "learning_rate_init": self.learning_rate_init,
+            "pred_loss": self.pred_loss,
+        }
+
+
+######################################################################
+
+
 class Task:
     def batches(self, split="train", nb_to_use=-1, desc=None):
         pass
@@ -551,6 +626,36 @@ log_string(f"nb_parameters {nb_parameters} ({int(nb_parameters/1e6)}M)")
 
 ######################################################################
 
+if args.learning_rate_schedule == "auto":
+    learning_rate_scheduler = AutoScheduler(args.learning_rate)
+
+elif args.learning_rate_schedule == "cos":
+    schedule = {}
+    for n_epoch in range(args.nb_epochs):
+        u = n_epoch / args.nb_epochs * math.pi
+        schedule[n_epoch] = args.learning_rate * 0.5 * (1 + math.cos(u))
+    learning_rate_scheduler = StepWiseScheduler(schedule)
+    log_string(f"learning_rate_schedule {schedule}")
+
+else:
+    u = {
+        int(k): float(v)
+        for k, v in [
+            tuple(x.split(":")) for x in args.learning_rate_schedule.split(",")
+        ]
+    }
+
+    schedule = {}
+    learning_rate = args.learning_rate
+    for n_epoch in range(args.nb_epochs):
+        if n_epoch in u:
+            learning_rate = u[n_epoch]
+        schedule[n_epoch] = learning_rate
+    learning_rate_scheduler = StepWiseScheduler(schedule)
+    log_string(f"learning_rate_schedule {schedule}")
+
+######################################################################
+
 nb_epochs_finished = 0
 
 if args.no_checkpoint:
@@ -562,6 +667,7 @@ else:
         checkpoint = torch.load(checkpoint_name)
         nb_epochs_finished = checkpoint["nb_epochs_finished"]
         model.load_state_dict(checkpoint["model_state"])
+        learning_rate_scheduler.set_state(checkpoint["learning_rate_scheduler_state"])
         torch.set_rng_state(checkpoint["rng_state"])
         if torch.cuda.is_available():
             torch.cuda.set_rng_state(checkpoint["cuda_rng_state"])
@@ -571,9 +677,9 @@ else:
     except FileNotFoundError:
         log_string("starting from scratch.")
 
-    except:
-        log_string("error when loading the checkpoint.")
-        exit(1)
+    except:
+    # log_string("error when loading the checkpoint.")
+    # exit(1)
 
 ######################################################################
 
@@ -586,30 +692,6 @@ train_set_perplexity = math.exp(entropy)
 
 ##############################
 
-if args.learning_rate_schedule == "cos":
-    learning_rate_schedule = {}
-    for n_epoch in range(args.nb_epochs):
-        u = n_epoch / args.nb_epochs * math.pi
-        learning_rate_schedule[n_epoch] = args.learning_rate * 0.5 * (1 + math.cos(u))
-else:
-    u = {
-        int(k): float(v)
-        for k, v in [
-            tuple(x.split(":")) for x in args.learning_rate_schedule.split(",")
-        ]
-    }
-
-    learning_rate_schedule = {}
-    learning_rate = args.learning_rate
-    for n_epoch in range(args.nb_epochs):
-        if n_epoch in u:
-            learning_rate = u[n_epoch]
-        learning_rate_schedule[n_epoch] = learning_rate
-
-log_string(f"learning_rate_schedule {learning_rate_schedule}")
-
-##############################
-
 if nb_epochs_finished >= args.nb_epochs:
     n_epoch = nb_epochs_finished
     train_perplexity = compute_perplexity(
@@ -627,10 +709,11 @@ if nb_epochs_finished >= args.nb_epochs:
 
 ##############################
 
-for n_epoch in range(nb_epochs_finished, args.nb_epochs):
-    learning_rate = learning_rate_schedule[n_epoch]
+learning_rate_scheduler.reset()
 
-    log_string(f"learning_rate {learning_rate}")
+for n_epoch in range(nb_epochs_finished, args.nb_epochs):
+    learning_rate = learning_rate_scheduler.get_learning_rate()
+    log_string(f"learning_rate {n_epoch} {learning_rate}")
 
     if args.optim == "sgd":
         optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
@@ -660,6 +743,8 @@ for n_epoch in range(nb_epochs_finished, args.nb_epochs):
         loss.backward()
         optimizer.step()
 
+    learning_rate_scheduler.update(n_epoch + 1, acc_train_loss)
+
     train_perplexity = math.exp(min(100, acc_train_loss / nb_train_samples))
     test_perplexity = compute_perplexity(
         model, task, prompt_len=task.height * task.width, split="test"
@@ -674,6 +759,7 @@ for n_epoch in range(nb_epochs_finished, args.nb_epochs):
     checkpoint = {
         "nb_epochs_finished": n_epoch + 1,
         "model_state": model.state_dict(),
+        "learning_rate_scheduler_state": learning_rate_scheduler.get_state(),
         "rng_state": torch.get_rng_state(),
     }
 
@@ -687,6 +773,6 @@ for n_epoch in range(nb_epochs_finished, args.nb_epochs):
 ######################################################################
 
 if args.oneshot:
-    oneshot(model, task)
+    oneshot(model, learning_rate_scheduler, task)
 
 ######################################################################