Backpropagate gradients through the computation graph starting from this variable. :param engine: The backward engine to use for gradient computation. If not provided, the global engine will be used. :type engine: EngineLM, optional :raises Exception: If no backwar
(self, engine: EngineLM = None)
| 149 | return "\n".join([g.value for g in self.gradients]) |
| 150 | |
| 151 | def backward(self, engine: EngineLM = None): |
| 152 | """ |
| 153 | Backpropagate gradients through the computation graph starting from this variable. |
| 154 | |
| 155 | :param engine: The backward engine to use for gradient computation. If not provided, the global engine will be used. |
| 156 | :type engine: EngineLM, optional |
| 157 | |
| 158 | :raises Exception: If no backward engine is provided and no global engine is set. |
| 159 | :raises Exception: If both an engine is provided and the global engine is set. |
| 160 | """ |
| 161 | if ((engine is None) and (SingletonBackwardEngine().get_engine() is None)): |
| 162 | raise Exception("No backward engine provided. Either provide an engine as the argument to this call, or use `textgrad.set_backward_engine(engine)` to set the backward engine.") |
| 163 | elif ((engine is not None) and (SingletonBackwardEngine().get_engine() is not None)): |
| 164 | raise Exception("Both an engine is provided and the global engine is set. Be careful when doing this.") |
| 165 | |
| 166 | backward_engine = engine if engine else SingletonBackwardEngine().get_engine() |
| 167 | """Taken from https://github.com/karpathy/micrograd/blob/master/micrograd/engine.py""" |
| 168 | # topological order all the predecessors in the graph |
| 169 | topo = [] |
| 170 | visited = set() |
| 171 | |
| 172 | def build_topo(v): |
| 173 | if v not in visited: |
| 174 | visited.add(v) |
| 175 | for predecessor in v.predecessors: |
| 176 | build_topo(predecessor) |
| 177 | topo.append(v) |
| 178 | |
| 179 | build_topo(self) |
| 180 | |
| 181 | # go one variable at a time and apply the chain rule to get its gradient |
| 182 | # TODO: we should somehow ensure that we do not have cases such as the predecessors of a variable requiring a gradient, but the variable itself not requiring a gradient |
| 183 | |
| 184 | self.gradients = set() |
| 185 | for v in reversed(topo): |
| 186 | if v.requires_grad: |
| 187 | v.gradients = _check_and_reduce_gradients(v, backward_engine) |
| 188 | if v.get_grad_fn() is not None: |
| 189 | v.grad_fn(backward_engine=backward_engine) |
| 190 | |
| 191 | def generate_graph(self, print_gradients: bool=False): |
| 192 | """ |
nothing calls this directly
no test coverage detected