From 8e4268c805c23484a325e08aedf95431c40eaa77 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Mon, 9 Jun 2025 14:34:54 -0700 Subject: [PATCH 1/3] Add work-in-progress for visualizing gradients tutorial (issue #3186) --- .../visualizing_gradients_tutorial.py | 426 ++++++++++++++++++ index.rst | 8 + 2 files changed, 434 insertions(+) create mode 100644 advanced_source/visualizing_gradients_tutorial.py diff --git a/advanced_source/visualizing_gradients_tutorial.py b/advanced_source/visualizing_gradients_tutorial.py new file mode 100644 index 00000000000..6e9e81bfd98 --- /dev/null +++ b/advanced_source/visualizing_gradients_tutorial.py @@ -0,0 +1,426 @@ +""" +Visualizing Gradients +===================== + +**Author**: `Justin Silver `_ + +By performance and efficiency reasons, PyTorch does not save the +intermediate gradients when running back-propagation. To visualize the +gradients of these internal layer tensor, we have to explicitly tell +PyTorch to retain those values with the ``retain_grad`` parameter. + +By the end of this tutorial, you will be able to: + +- Visualize gradients after backward propagation in a neural network +- Differentiate between *leaf* and *non-leaf* tensors +- Know when to use\ ``retain_grad`` vs. ``require_grad`` + +""" + + +###################################################################### +# Introduction +# ------------ +# +# When training neural networks with PyTorch, it is easy to disregard the +# internal mechanisms of the PyTorch library. For example, to run +# back-propagation the API requires a single call to ``loss.backward()``. +# This tutorial will dive into how exactly those gradients are calculated +# and stored in two different kinds of PyTorch tensors: *leaf*, and +# *non-leaf*. It will also cover how we can extract and visualize +# gradients at any neuron in the computational graph. Some important +# barriers to efficient neural network training are vanishing/exploding +# gradients, which lead to slow training progress and/or broken +# optimization pipelines. Thus, it is important to understand how +# information flows from one end of the network, through the computational +# graph, and finally to the parameters we want to optimize. +# + + +###################################################################### +# Setup +# ----- +# +# First, make sure PyTorch is installed and then import the necessary +# libraries +# + +import torch +import torch.nn as nn +import torch.nn.functional as F + + +###################################################################### +# Next, we will instantiate an extremely simple network so that we can +# focus on the gradients. This will be an affine layer followed by a ReLU +# activation. Note that the ``requires_grad=True`` is necessary for the +# parameters (``W`` and ``b``) so that PyTorch tracks operations involving +# those tensors. We’ll discuss more about this attribute shortly. +# + +# tensor setup +x = torch.ones(1, 3) # input with shape: (1, 3) +W = torch.ones(3, 2, requires_grad=True) # weights with shape: (3, 2) +b = torch.ones(1, 2, requires_grad=True) # bias with shape: (1, 2) +y = torch.ones(1, 2) # output with shape: (1, 2) + +# forward pass +z = (x @ W) + b # pre-activation with shape: (1, 2) +y_pred = F.relu(z) # activation with shape: (1, 2) +loss = F.mse_loss(y_pred, y) # scalar loss + + +###################################################################### +# Before we perform back-propagation on this network, we need to know the +# difference between *leaf* and *non-leaf* nodes. This is important +# because the distinction affects how gradients are calculated and stored. +# + + +###################################################################### +# Leaf vs. non-leaf tensors +# ------------------------- +# +# The backbone for PyTorch Autograd is a dynamic computational graph which +# keeps a record of input tensor data, all subsequent operations on those +# tensors, and finally the resulting new tensors. It is a directed acyclic +# graph (DAG) which can be used to compute gradients along every node all +# the way from the roots (output tensors) to the leaves (input tensors) +# using the chain rule from calculus. +# +# In the context of a generic DAG then, a *leaf* is simply a node which is +# at the input (beginning) of the graph, and *non-leaf* nodes are +# everything else. +# +# To start the generation of the computational graph which can be used for +# gradient calculation, we need to pass in the ``requires_grad=True`` +# parameter to the tensor constructors. That is because by default, +# PyTorch is not tracking gradients on any created tensors. To verify +# this, try removing the parameter above and then run back-propagation: +# +# :: +# +# >>> loss.backward() +# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn +# +# This runtime error is telling us that the tensor is not tracking +# gradients and has no associated gradient function. Thus, it cannot +# back-propagate to the leaf tensors and calculate the gradients for each +# node. +# +# From the above discussion, we can see that ``x``, ``W``, ``b``, and +# ``y`` are leaf tensors, whereas ``z``, ``y_pred``, and ``loss`` are +# non-leaf tensors. We can verify this with the class attribute +# ``is_leaf()``: +# + +# prints all True because new tensors are leafs by convention +print(f"{x.is_leaf=}") +print(f"{W.is_leaf=}") +print(f"{b.is_leaf=}") +print(f"{y.is_leaf=}") + +# prints all False because tensors are the result of an operation +# with at least one tensor having requires_grad=True +print(f"{z.is_leaf=}") +print(f"{y_pred.is_leaf=}") +print(f"{loss.is_leaf=}") + + +###################################################################### +# The distinction between leaf and non-leaf is important, because that +# attribute determines whether the tensor’s gradient will be stored in the +# ``grad`` property after the backward pass, and thus be usable for +# gradient descent optimization. We’ll cover this some more in the +# following section. +# +# Also note that by convention, when the user creates a new tensor, +# PyTorch automatically makes it a leaf node. This is the case even though +# is no computational graph associated with the tensor. For example: +# + +a = torch.tensor([1.0, 5.0, 2.0]) +a.is_leaf + + +###################################################################### +# Now that we understand what makes a tensor a leaf vs. non-leaf, the +# second piece of the puzzle is knowing when PyTorch calculates and stores +# gradients for the tensors in its computational graph. +# + + +###################################################################### +# ``requires_grad`` +# ================= +# +# To tell PyTorch to explicitly start tracking gradients, when we create +# the tensor, we can pass in the parameter ``requires_grad=True`` to the +# class constructor (by default it is ``False``). This tells PyTorch to +# treat the tensor as a leaf tensor, and all the subsequent operations +# will generate results which also need to require the gradient for +# back-propagation to work. This is because the backward pass uses the +# chain rule from calculus, where intermediate gradients ‘flow’ backward +# through the network. +# +# We already did this for the parameters we want to optimize, so we’re +# good. If you need to change the property though, you can call +# ``requires_grad_()`` on the tensor to change it (notice the ``_`` +# suffix). +# +# Similar to the analysis above, we can sanity-check which nodes in our +# network have to calculate the gradient for back-propagation to work. +# + +# prints all False because tensors are leaf nodes +print(f"{x.requires_grad=}") +print(f"{y.requires_grad=}") + +# prints all True because requires_grad=True in constructor +print(f"{W.requires_grad=}") +print(f"{b.requires_grad=}") + +# prints all True because tensors are non-leaf nodes +print(f"{z.requires_grad=}") +print(f"{y_pred.requires_grad=}") +print(f"{loss.requires_grad=}") + + +###################################################################### +# A useful heuristic to remember is that whenever a tensor is a non-leaf, +# it **has** to have ``requires_grad=True``, otherwise back-propagation +# would fail. If the tensor is a leaf, then it will only have +# ``requires_grad=True`` if it was specifically set by the user. Another +# way to phrase this is that if at least one of the inputs to the tensor +# requires the gradient, then it will require the gradient as well. +# +# There are two exceptions to the above guideline: +# +# 1. Using ``nn.Module`` and ``nn.Parameter`` +# 2. `Locally disabling gradient computation with context +# managers `__ +# +# For the first case, if you subclass the ``nn.Module`` base class, then +# by default all of the parameters of that module will have +# ``requires_grad`` automatically set to ``True``. e.g.: +# + +class Model(nn.Module): + def __init__(self) -> None: + super().__init__() + self.conv1 = nn.Conv2d(1, 20, 5) + self.conv2 = nn.Conv2d(20, 20, 5) + + def forward(self, x): + x = F.relu(self.conv1(x)) + return F.relu(self.conv2(x)) + +m = Model() + +for name, param in m.named_parameters(): + print(name, param.requires_grad) + + +###################################################################### +# For the second case, if you wrap one of the gradient context managers +# around a tensor, then computations behave as if none of the inputs +# require grad. +# + +z = (x @ W) + b # same as before + +with torch.no_grad(): # could also use torch.inference_mode() + z2 = (x @ W) + b + +print(f"{z.requires_grad=}") +print(f"{z2.requires_grad=}") + + +###################################################################### +# In summary, ``requires_grad`` tells autograd which tensors need to have +# their gradients calculated for back-propagation to work. This is +# different from which gradients have to be stored inside the tensor, +# which is the topic of the next section. +# + + +###################################################################### +# Back-propagation +# ---------------- +# +# To actually perform optimization (e.g. SGD, Adam, etc.), we need to run +# the backward pass so that we can extract the gradients. +# + +loss.backward() + + +###################################################################### +# This single function call populated the ``grad`` property of all leaf +# tensors which had their ``requires_grad=True``. The ``grad`` is the +# gradient of the loss with respect to the tensor we are probing. +# + +print(f"{W.grad=}") +print(f"{b.grad=}") + + +###################################################################### +# You might be wondering about the other tensors in our network. Let’s +# check the remaining leaf nodes: +# + +print(f"{x.grad=}") +print(f"{y.grad=}") + + +###################################################################### +# Interestingly, these gradients haven’t been populated into the ``grad`` +# property and they default to ``None``. This is expected behavior though +# because we did not explicitly tell PyTorch to calculate gradient with +# the ``requires_grad`` parameter. +# +# Let’s now look at an intermediate non-leaf node: +# + +print(f"{z.grad=}") + + +###################################################################### +# We also get ``None`` for the gradient, but now PyTorch warns us that a +# non-leaf node’s ``grad`` attribute is being accessed. It might come as a +# surprise that we can’t access the gradient for intermediate tensors in +# the computational graph, since they **have** to calculate the gradient +# for back-propagation to work. PyTorch errs on the side of performance +# and assumes that you don’t need to access intermediate gradients if +# you’re trying to optimize leaf tensors. To change this behavior, we can +# use the ``retain_grad()`` function. +# + + +###################################################################### +# ``retain_grad`` +# --------------- +# +# When we call ``retain_grad()`` on a tensor, this signals to the autograd +# engine that we want to have that tensor’s ``grad`` populated after +# calling ``backward()``. +# +# We can verify that PyTorch is not storing gradients for non-leaf tensors +# by accessing the ``retains_grad`` flag: +# + +# Prints all False because we didn't tell PyTorch to store gradients with `retain_grad()` +print(f"{z.retains_grad=}") +print(f"{y_pred.retains_grad=}") +print(f"{loss.retains_grad=}") + + +###################################################################### +# We can also check the other leaf tensors, but note that by convention, +# this attribute will print ``False`` for any leaf node, even if that +# tensor was set to require its gradient. This is true even if you call +# ``retain_grad()`` on a leaf node that has ``requires_grad=True``, which +# results in a no-op. +# + +# Prints all False because these are leaf tensors +print(f"{x.retains_grad=}") +print(f"{y.retains_grad=}") +print(f"{b.retains_grad=}") +print(f"{W.retains_grad=}") + +W.retain_grad() +print(f"{W.retains_grad=}") # still False + + +###################################################################### +# If we try calling ``retain_grad()`` on a node that has +# ``require_grad=False``, PyTorch actually throws an error. +# +# :: +# +# >>> x.retain_grad() +# RuntimeError: can't retain_grad on Tensor that has requires_grad=False +# + + +###################################################################### +# In summary, using ``retain_grad()`` and ``retains_grad`` only make sense +# for non-leaf nodes, since the ``grad`` attribute has to be populated for +# leaf tensors that have ``requires_grad=True``. By default, these +# non-leaf nodes do not retain (store) their gradient after +# back-propagation. +# +# We can change that by rerunning the forward pass, telling PyTorch to +# store the gradients, and then performing back-propagation. +# + +# forward pass +z = (x @ W) + b +y_pred = F.relu(z) +loss = F.mse_loss(y_pred, y) + +# tell PyTorch to store the gradients after backward() +z.retain_grad() +y_pred.retain_grad() +loss.retain_grad() + +# have to zero out gradients otherwise they would accumulate +W.grad = None +b.grad = None + +# back-propagation +loss.backward() + +# print gradients for all tensors that have requires_grad=True +print(f"{W.grad=}") +print(f"{b.grad=}") +print(f"{z.grad=}") +print(f"{y_pred.grad=}") +print(f"{loss.grad=}") + + +###################################################################### +# Note we get the same result for ``W.grad`` as before. Also note that +# because the loss is scalar, the gradient of the loss with respect to +# itself is simply ``1.0``. +# + + +###################################################################### +# (work-in-progress) Real-world example - visualizing gradient flow +# ----------------------------------------------------------------- +# +# We used a toy example above, but let’s now apply the concepts we learned +# to the visualization of intermediate gradients in a more powerful neural +# network: ResNet. +# + + +###################################################################### +# (work-in-progress) Conclusion +# ----------------------------- +# +# This table can be used as a cheat-sheet which summarizes the above +# discussions. The following scenarios are the only ones that are valid +# for PyTorch tensors. +# +# ============ ================== ================ =================================== ============================= +# ``is_leaf`` ``requires_grad`` ``retains_grad`` ``require_grad()`` ``retain_grad()`` +# ============ ================== ================ =================================== ============================= +# True False False sets ``require_grad`` to True/False no-op +# True True False sets ``require_grad`` to True/False no-op +# False True False no-op sets ``retains_grad`` to True +# False True True no-op no-op +# ============ ================== ================ =================================== ============================= + + +###################################################################### +# References +# ---------- +# +# https://docs.pytorch.org/tutorials/beginner/basics/autogradqs_tutorial +# +# https://docs.pytorch.org/docs/stable/notes/autograd.html#setting-requires-grad +# \ No newline at end of file diff --git a/index.rst b/index.rst index 54bd8b37466..615928ecf25 100644 --- a/index.rst +++ b/index.rst @@ -100,6 +100,13 @@ Welcome to PyTorch Tutorials :link: intermediate/pinmem_nonblock.html :tags: Getting-Started +.. customcarditem:: + :header: Visualizing Gradients in PyTorch + :card_description: Learn how gradients are calculated with Autograd for leaf and non-leaf tensors. + :image: _static/img/thumbnails/cropped/generic-pytorch-logo.png + :link: advanced/visualizing_gradients_tutorial.html + :tags: Getting-Started + .. Image/Video .. customcarditem:: @@ -947,6 +954,7 @@ Additional Resources intermediate/nlp_from_scratch_index intermediate/tensorboard_tutorial intermediate/pinmem_nonblock + advanced/visualizing_gradients_tutorial .. toctree:: :maxdepth: 1 From 0bb828540b8fd0f7b82b36ae619ed36bc750ed37 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Thu, 12 Jun 2025 16:52:29 -0700 Subject: [PATCH 2/3] Shorten first section, add suggestions, and start ResNet example (WIP) Still a work in progress, but I significantly reduced the first section and added some helpful images for the computational graph. I also added links for most terms. The WIP section with ResNet I still have to debug. I'm not sure my method for retaining the intermediate gradients is valid. See discussion on pull request. --- .../comp-graph-1.png | Bin 0 -> 42318 bytes .../comp-graph-2.png | Bin 0 -> 46658 bytes .../visualizing_gradients_tutorial.py | 638 +++++++++++------- 3 files changed, 385 insertions(+), 253 deletions(-) create mode 100644 _static/img/visualizing_gradients_tutorial/comp-graph-1.png create mode 100644 _static/img/visualizing_gradients_tutorial/comp-graph-2.png diff --git a/_static/img/visualizing_gradients_tutorial/comp-graph-1.png b/_static/img/visualizing_gradients_tutorial/comp-graph-1.png new file mode 100644 index 0000000000000000000000000000000000000000..407f4aadd41c3c2ec8bde6055237b528dad751d3 GIT binary patch literal 42318 zcmbrm1yon<_AR^#5fu;+=>`>%5G7Pn8UqCtNlEFFZcw@frBp;fT0pu%8c9(k1*E$} ze&n|{c+UUad)|A;_l=KtyhDY}{_Xw5T64`g*W-U*UWV`_^+^Ol2<2oY6%ho-6+y77 z@$uk0gLL2D!ykA$w`C-e1N6TS6)CR}gb|UGy!p`K-Ta`l`a|1&(d8a0N-jd;PyX{x zau`wy!t$h1uCvBzj!%DFd0b?6TU|NSv#pvd93n2$FkgdTpc>3Poo*UQ=``Co>GJ&%s2 zyj>@*$14|ZInjL9+4rU-e05F4XsrM2>iV4$=T`IJp71dW3X0oxk{^3YtfwrumbL$0 zW^?;;*|Z6d#gLGLgM)JVb>>)+t=~B0uIMc!rAPUW{vt^fB*k#_GB~=OGKW7L|DwEp z_$`v_mjemM#eIV5g{go3{JFYz>=gNj4?*G(!!Yq-*Nx+goXqULA_j@Ko0_*Cw$ z-WJ;58oOpYOXlM0y0SBTK>zgV(_*^?YQ+Z+M&?_KJP(J2Bk++A;YG5OtgNhB4M9|F zoSahP;@ANJ0n4L-+_}Gx?wIkL&z0Y9D%PHvi;GBXZRTCA523{U`!8b?j|Nz#KB0ri z>CYnPLF}tnp2^AQ1*IhpUmzq%DlYiZqeo2FuTy1LY*Mqz)x=yiSl%447!c$*{9UX2 z9NVVzKObw>mpiU4f0H<^y*b~9cnA!;oEdgMsEB1bdIA~rox~+1Ryt)Em_$VADY&21 zl`U1CLQ=D{8`o!k*0&GOkZ9zjNCiT3xxCQHPJ18GjBPvMHeU=WYHZ~0^B2#)4WC9+RyczL823-KkQ zt1&AY)VC9p^qC(lqu{RE&8?_qL?5v9T(rh|!MpF*UA8P8935lmh{Ko++TZ2a;~&0H z5dqP-_PKCE782=Hxs{gE5Mt1JMP^(TGn&tMw-G{tlqV^H)xJh5)(601eVg5 zl$4N%zCC=o)T^IV*VBF)e@sbHvp#$N(xpqDfq|!9czWJd9qQ{-;UedwCPcJ$HWya& zuUk(lUr|3Ccji~hnbT?|R_9pdUc7z#R)~{@1t&f}-qPN_@mst^j`JV$SN&Pp*}kfo zKH1sazXk_+^jj(4iMXnoynpcmTT)7@y0P)N>&|BVT8Ah%g~6rAS#@1$4-_6hzQ7QX z=hHp#`**b`J`s_Rc>K#ADtdY%BsD85GR1Xk#5Z>F($On(DRFMAGE9^`8^fb_T~~k? zjnB;`6||cdb8!*MdQ#CpQ#dv@wt|_88|%%{W#i&%_^e%f*>P2`tGl~-O=xF6&o3lI z8fKe_?uM6yFA>wli*lFvA3uJa`QUyOr<(rq>$Y0o(>R7)>>HS#s(tUEAOaYp#Q1nD zBO{}0PU~!TOGCjkKT>pmraxLOr>CbU78grWjOluOG(Z}r{7wc63M6j(7z18DzUun= zV>fT!EVdlMUMwC9kuP}g z_*Y9?+jc))YMcvnYGr1 ze~l0SrEP2+93FV%JMXTMR99Ck=jjtRx3mZj%HO<+#Y;_?d?$vP!PWZNvzumSob9oK z7doOMBD}*`WW3I2)m@VQ~ zg=ft8)Kp?}GG1qA=kn?*_BSEM0H*F66?rW0-@i}&^yzgR#o4p*Sy}QX^t#)(4!73F z(6)fe`FT_L$M4mNmT!ijb%?=(FJ><90vs;b`4+Md;$x;i@f z{TI2pDd5v}Rz~Ya* zp8C~JTx4YO&+Z_qtK&T|t_4qS%gcAG+KtqB*9;7(5l~(h(|h{#M8vIgh~L>uN=<8& zKYA9=pF1a|tkrWkle6TkOa$uF`d_j%$}XF9b39a1YEDrkRrwLgsYXmkXSPgz_i(~< zSN@gB^wioni4a5j-%=FxZA^zhyrL1swyoUzHz*#zx5Rq*?SG8N`kQ>NQyp=~5ic(< zv3M;)xo-Ql$-B68}1qt4+tgU$xV^=tBTqy}olnHJ2PRwcy72QuQ3Wo5{bnaaJIOX9q zH(jBvoPLn;6ZS!oJ!R2rKRo>H@$bDMj6dznwh_4V}=3JaAi?$C7>?FIGi zA3k-LBgEM6KYnz6`{t3K&r2)p^l(3nfq@~kL-c^QF@#P6qNfibjnCIt7ZM8#$&t^N zBjOO`sW04nU-wwk)!XyTPPpBZ>X-tqgC0SkQwaZ)xmya9oM&!)dI(cZAZmy?sD zD|8>NJAp>z($Z25+LlPps}B8>BelM^oA4b~u6)A!uE_WW1H+v&=+X@H_lmJw{B;rW zYi@3~%T7v3IfW#A{(S$v-QKos{6lsrX=xv!%*x6q=+;{pC<^@kT`~SXTU~diT0tZh z@qI& zK^2O$HcYl(-#WLwxo}C8n~lvwIhKdE@Gu3CA{&6>LRH{c;EDk_xMtymwjpM|Wg zni(1T(zdG-(sOx5Mes1AQ+{P~c6xeqYV?f+iyf-GJ%4nSH}NWQZ85w!E?r;JS{V4b9v-^RYda2C`>fnaoA1*d* z%0%t%x^#7QG2XaA{p86LsAseQU__vi&*@Va*p^VA2iwM8XFoC1GprG zT}=*=s%Cgt)3o<dLT9~c~vQvk!0PxB=yHC5JxUV^8-uFjCtePi}f#X!fmO^aZhlEdgs zb&piM!&_Opx4?u#zwM28bMr0CLs66EwKb?#XqJ0*LsiV^3Kcap2)Vhr-(7o#{pXi; zXhm(wglS*yF#rlq*-VECB0-;Dv?5RNji|fGki!@uEQb%DJ`p^7_KcF(kl2TSN@wZY z;h;8E+hP*3i;F44S>r!`#LYA4Xb3-dd!9~Fa9g?-*JGk3ibX(xVt;=hAgZ*A3f1P~ z;3)$G13?-2!zB}v)$*9m&CN|?pI1a;Wn*!WW}wh4Pl5OFhmr?BCPM#~eql5JQw07k z8YPKZ=S{Y$X=tzz(Y^IE>FMdMM1D;#0|HL|XOu zesnw9|0%F1?@JADZ_M$S_47LWYM-sIuZPkjoK+jbzn#DK|5PU}+lTQYb*)sdv&d<~ z*vr?~-sw6Mt{1VAU(^2X?k=O37_7EC=3@}7tVUAjR>nd_XVu+)B;SQt$m6(oI68XH z8oAR~BUCe`ot#Q5YNvAnOTaIN(hGmqZ=)`eHFvxdeYtvwiS#h0d#n6M5kf;mFXZ?8 zw-$s5s4wCxE9s33s4FWg(V8ThN7r(D-3Vz*A?DJoq`iLq`f6ti?5+eTv=tN-mRDA8 z0>oX}UF(nlv{_sk(#=3jj96G$a43Hkn={n<@0|K42R8LpgZ4To{CxKJc66uOV_`jd zvdSsv>iG|uX{Cyq4gC4@r^xB%+n5;t{(e=@X9fC(J2TGNM{yPJzw*(A3l~VvoMC~` zhUS(!D1D{W)!$L_n?^&`@B+|1v2lSad{<@{o>Ls3NhwqHoOCdC($DU#l`%AbL2@_? z=!yc$77BlV|Ap?U+1d1sUr@!N;ox;tR3QB1lw5e-bDZO_wE^v&w70i^_WKJKT6mp2 zc~VkL%!BRHrK{;n)|0Iy_Jg)jO)V{Fpd>j9o2aI(?QZ#>A!odz=RA8xgKjD+F;GB} zvq#*@*aQ535^-Vx<#s49#;U-JinZ9zS~YbztDW`o)VEN81ILc%62roU-qRg@-e9 ze4aa;LEBT1T@b&T8ga+|jkz9g#WXT1Dyr4|?QN%#$;tDD7Qz2phIXonb5U@HZ6|;Os_!P#aE%BjCokS(% zXP}6;xbxZ3s-7QD4GfBCWLcS*;%oVD-M&q7_N+I+ri9c~d~tl%3m0hk6jNTkdev<$ z7oNW_|4&sK-oVd-#fF9kyC41LIJ(=38GD6$Dut#o)g;$XGl+aD;)Zgzsl9!1-Htzh z?~dc&C7Ww1;$$Ezi~sso(onUKDuq7sGA=Ik%^PY-8JXIamQ!MD2va1Z9O?PHZ@hr8 z82g@d8-`GtFq)K%3>UC2;zmUcie9nSCyC3_GD@Ncw@0K`*Y!BdLdM` zvYtFtG>_HL9QUtxk7^4P0oYmB(7?>Zgax}7QoV$^ITwuav)!#{h*-yaVFh*dAOK_V zC@hyQodT%TTl|a#!l&iRsMN>gJE#Z$JV*b2u^oez=$tiet0XP3Iu> zntXUUKCXu;C@sB_+57=4Z&w18DV?C+CumDC*}BpN=Ut#Qxg*t%xrA3Zu`EXyEl1xdHtXg@rNSf z6zt!Tr6q2D(_Sr`;g*(`dZ-1xfRLfQtmV;LW}gjIgP^3U>YM7LfV2c$BKj!PKQp2b zMsn^aze)sd;Xi}rY==`!cJ{1MTOxNB5Tcn+AygY%9`4ubzeyqJ!U^pE+&Rc;H}SC`CF$wu{kCx5dD}e)bg^G>u#}P#IV>jkzKgdG3-5&X9OKT%|F`P# z|4@gLk&!X7v!6uEiGd>S_wM_ShP-cx1~&ivk%43^rL(!QQQsPKb>;hQZmOU9uCBse z#(Yj2v&Uc!!m7hdxfh4$VYhb*AK#;>NPwI}`P-tb&TT=+zzh9LsKzGc$&UhGMF!)FJf30fS}sILLd~O%>SL zi2%t^vK0#3m#<%UX%AeGe<$YTbR#_@W1#-CY2Ou0{vC}_NOCP}x5K^Aa#LGdJ6h15 z3xIfbQRwYRj%vuPFCiZv>`sXm?<|(Be8D;W0a%(vO*@yW^2#88o%$+ovE zGbwS#1!w+G=E6YRY$(euC^X4? zZ4W_5>9GvD8A11bp*L^dT(V z#?DSUPQ-P53YHW4HcEm}?L0`n6Ba5`T3q}A*aBGMdCEscOzKXKYG;o^7%mbV9K7Ip z-_Tb3vATKY!kZilT-rl`fb>LRMzdhGm;VkgPapG*^ z4nA+(`SeR!-Pr}^7X$@q#PKs_#Km8<+aBpiR5}F-P)*~X^u`7%e>ESX%EN~bDb@K! zMH%?`_yEfIL`6|}Og@)iFVFyP)}$w^z{KP*Eu|f%rPv&ywbrX}T?2%Pm3j(I!wPaJ zO<`)rCnjKK03XbJKbM*4?t_wBX%DS_lOOKK+-pGn{9~2qdn@TmvFZK$#DLd7KO2*N z_~_9Hltr?4@BZxD1UN*(z;GH0xQz9QHi6-~Z1+8rch{|@O-f zirCSyv60cy7l18j1}ovmI=Z@HFJF29|KIiVClk~v&!0cP=C)g$RCXlZ4EjBiS%T_} z1(+fpkS2mCd9YtyxaR}Y_bxVeq6cmp07(q^vS1nkY{U)IqNxL=Jy6bKOG6br?)xt2 z%oudM_xAObDJz6M8SreZVFbjVMyQ2z^uC76H}+duZ!T5t5QVcE1!tu_WM8R1&X8ZW zNQ|Jg1n}l#@b5Zqcg$^wdnbXKDBjx~7}&@LSPe;eYqf=YrKoC;5ST8nU+o#$l>tD8 z>GbCtEpKi%fFS4nC__mbR&MO#slzC(a7u2v>~b4^ncYGS@Nf53R4&E^q}{f)mvx8ma=)-)Lu*y zKFZbwM?rX96R5CAZq1Niztp#Oc37@mqXf+jO|dRc7Y?6*L?!A`Mm7}Hx=`(Ps~?}gl}^L>Ix z>}4y5=}KDqGa^&j`fAdyTDmI*Y3&;?RFalqQx*w`crKD>+>E}O1` zIywikO8M4kAU**>9YBT4Hq-Z=eUCnj9JIr2ZYaA@z7dufC>41yS%vmos2HoatW*xTr- zJ_tkOPiLl&6hjz2Ou`my5)wMP;Hme{M8MP4MO=D3Pe+a}matA>1E_ujaoeNmP8a90 zdSZ2L?b)9}`?1WTq1?ZpC9T~lqxbPRgA1{vqhmCm2{{blauZAFZ{SP+4v{Q3h>ChI z{tUY_oFeD&c=hTGzr|1x?6`rWiAW%VHwrtU-~P@Ymy zKO)_6FZ5qtfG`o}t58@moj;EUB+}ND=z%|vZbR?id8Gn6BW5N{hQ1o~EF5=t_aGVp zA|4)|)U>pmzfX<8Y&7Y35ksZ_9BTV2x4qA11N`Wg{QJzM*I_PliT_UP_}5nefhmQZ zc%L|o90PuQP9qC=RipbW~t7djyqPE1c90u1$8$TzBMX2X?q&YSbqxldax zp_~wux{Xe2NKnr@iHZtHV@g@-{s7f=x>6M&HUnBmfJ6v_3T_lo0_1wA`z4+{p$8gu zf$j=Alh=vSx48nboAvy85Zf!ojEpX#M0wj=fw8od_u_CfcXADo5_RrE{`eX8+PSR{SDP;g&1{Kg2;-FB-K zs#E!Q*PcT?FmUY(x@N5;g(_Y(`Jh?ee({$bPHBGZ6bJ;;wzdU?N2)u>`k_reXx^e{)I*P;gIVejn?-6BB~4I+@|Jcaee*s zurHXrzOiHfH0A?--e5}h=+e4`(9}SP9Mxdj2>A4gV}7`b0fID?=|C!gq8MILi+TkT z)Pm{*Dt#H;C`GhqF6dlUwslYD?9@o`ulf z`VanqkkYch`!?U6QbP$I1+KAJx}5sfVJ@@C5^NOxf&|R7FWzzfgc^q&d=qV_X>!vc z4@0DC9MbOX`f$?|ow&rQ?5wN?SVEVLyV!oY?JOt-1(lZJ94Gn>`zQzQ-4AiW5}27F z2GOCR_?I{tpFT+f_q?=pOY(-wr)eAyqaVp5D~$|Adaoo^C!tzcA~vTABqT%BWZS;j&UhIS3EqpF=bi7_6Q$)5q--A3w5!n9*mvaK<(gvPt{9>p0#CCI@xO`GzFqTlC}p7ZWMU-M6+L`F$i%>hKwNlBheHy^q_J}bL;fd2bMK_4`yQW=kPyPFd5~-L z<2+^qM1L&9{hKQH?;{BQ>C=tiIte^Z?|>XXO`q@jO_FFDzEKOMmfHM4(P>y0(om9E zSy{b&^CkdhP6uRGhl9O8wpXkr@tw3Dtdu>h{MrZn~m;_7$5S}ADS^7Yq(t!{K z`C;ov+`bn`+A7y?G0!e4udBpB#sN5e8iCY|ifJB`G*(5G)1?zKEZ>AkXFX4g7!HW} z2iXA7v6HD#rH93>q_zZ8QP8~sdzH?8`$ydP6v*y$T#o^>f^~=yfC>81BCa>K*Jp5& z6i{^ZF_7a>je0p{|yL5`rd{ z6UeG;VS}4A=s{Yeb^btUPM8V%HO(ro_d%&9gdoJ?zhRXfLs0P@Qk*D6T`a?c`zR;+ zY}oZrpU8b?7Gcczt83Pi1h8a0ff&O!+{e0VJNq!UCBM>5x<^8@b7LY(vpF$rdwY8n zhEaHQbd;E!+y|f)9*7ikmL=}HV+>G(ere&Z3VJJGA)cwE3sZoSfwVuM>RJJGstdfi z)6U>e>+4WJoEF()1f3KWT|xNQ0ab!uO;kk*WSwr3X-FN|2%4OrXws<l5^PEbq z-U%WGksyGlj4UiTP*=e|z}qogoO*Fir>Ce*6_Sn)z^jGHHVBsz5;#y^T-N%6IF{zb zg#ork-iXRLlpSNvJx}h+ZwZ?mm|a4#H45BsyS0`zUMElq+GFxZ$NpN}!S7!s*3&;y zLS3k-6dnWT@f;0*$Y=YdCnZgeBixPefVq#X; zU`OfH`H|oatgftt9qiY0yI~z#=OBSByC3{<$BRnIn#pOTuSK64GLd?T)iG30)Y8&Q zIf(yYabP$yJ9`o;e2ilYUplg!BDyD+Q2`A6?$%i77NnSgg1LnSLT_>H%PAZyCEsdZ z_qN#rWCt8^1Q;S^`j8L6Jo71>P&rNbwLh4vsp|6A7L$98(sFqTp0z)!YHWg$gJZ zU5I(AKZ33|ALl)-`eV}qXc-aLZOQ^6+q<`t6uDMWrtqDhJrP8hIUB*`c?K*jtkvNI zOo#h;Rn^X-;4k}OR~WwUrI8B|$WY4i#-ug>F8LZ z|+XQ$(r{FDq0*-%i6mXg5XI6(Np;?;b6 z)pgs(bhxsjcH+I$>_srJ!6>*tzm-%2vx7<(diwgKF$V9e!9f!Suwr<3Qb5|mGwXE= zpfVs_a1a98Ys6?Nm<)^zC@n&(O)RRlEk82n4#Q;GlE}5|*JtR?J!a6@k+HJ60$w%z z517*2LYW@YGiP|f(}mF=wVrCP2UjLa+c>_5!2(wbDY_od-+NC0^i@f!6CiQhA=R2+ z9mlM7EXqt<3M!GyfT%FkuG!5~g5?e2Jv~ZBLv!i_#x({wRgVM0eBF72aj0p+ zVq#7}c!a1nGtWX2c22fNoWjk)$w{aB#W9qL;OwMT@CA?>2=*MBa)C$ocAN7mzW@$w zfwA)3@aTuKGVnzLX$6tuIe>TSMCsrc;Ou}6^9-&8onGtvN4ZU+Gh%D_@o)_o8NnCE zlR8nUyEfUDXV0e5edJgR21Pb{u)C4LJqT!gN{}0*QzD%qPkpu+7V+}-9*MOd;t?eJ z-rd{l_p~LF31TRO>FCSaFRGsc>|L%o#r+!=M;%B&ZIuNrX%AAMfKo^KJ(-LCeyU?) zT$1CCM`-)zw}+ze4Pfy&TVhI?>e=W z9^meu&T-eahh5i!$$?L}o(sCO=}>u$fYSx$SPrFiNIlv>io!5P8i%LGuCLo{ZEf`m z2644P1V9dHGpJPxt$D|rz~FQev>0dK&ZGjy;*HctqMrt(K}sJwvb6+d(-f~P7l+cWjjXGxJ8R$Qui7Lu7OWeTE` z%p_q?RVt30-gbh2o!-Zozej>MAIc@b#F8OD`}&G+)8Sxak8?|CgaTa|*my^e{r0%- z<5npIe9NiTF0dsTHVU;fD@h6_nudctFqkHFR_0s|cshtXpucoK3H}N`1jvUke*gY0 z&pkIk&*4sG%u9`Y0a;RoO&F%U6v{B7F`JZA!6Y$5os-l(kwM?Ts}kq=Ad+q4@gRpO#D!PCN>7B8#P({9IN;NIkW%{Zg4>oND=P~$Sn!s4mz9;F zm;kDyGk`&v85zZ1M?_E+guH%D_6#ci=8mz^(K_&!*8#oJuLtlerK6M3zNN~cQTh=1wzD;#*O4FDX~>K1(Oc>2?3oGRB&!^m zP=OfuWyTQ zE6wVD>2(?q=mv`1o=8mFdHT4uu`upxtIQzqUSUl96;CC!v=||C1p!aQ48<db}zu}UKUyX~<7yndX0N%EkVfzD}>63LSsCz)(H?qcdn2&TIT zp^7}7=>h51ySc3J1UWnB*24T!opX#jFUXNXjh6$N1Ox!xneR#XCr;c1fiY!(^nh-I zLr}16@Yv{y6DQC{n}-j{P*OS}ff=9a<-2#LU4}-eL-pEhFQh<0x;*jDl}U4&2Xopv zgd4A#9u|p8Gc#08csS8{*w*w_8|aR!vu)iro1;0?ca zpr1^uAx{BQT7n( z7U(BI`E>|?Bw+P`UMZ9T0c>3Cu)+*INtxd+t)ba>7qfQFY8)RBD9E?yWf)tDJ=QCa z>|10W=zJ>*j()2r7uhVMyLlU8hJt`z_u1!}HoPxeAS2{+4Yh+8>7V?EM-F=S|C58B z$T0?L@&FGHWMpRMA`}W2pxnnrK;gU*#Lds&{joA7Daokds%`^8W)IXH1yAmQm(s}< zs>*+I(c?3DRlC_;plzzZtbtRa%+4U#EX72yl(N0rNm}A?=%kQ7bW-R}UyU~#M)gyB z8rwg=aF1K@q$WOB?_XR3JsUw{}1 zyz_|yRWtudzZ<+x>xL*4JOFZFp6*tZ?OyytOn#tdGx+c4K3v^;&46~_hICx3gN4^%8I;RCe zF|4HYPG=)GS3;I2Mz+iLGK0TglR8k#C^;t6+P-+h!?Art2=~28(TJS0^ z?lgR%xiTUu3I{=P-o(VyDZ{4Dv*hIcf?iyiPj?`R_X`kP848&HLYAvK+?f*!AtM{{ zQtvL;o<^%s&Y!FbDB%aa8z?`j`t<2J2Z4Q z<-7ca8uyYES0?B6Y&iuCE(~t}SvvgzBXi!OUA~)DjugZoNwh>%Ye7))gpwVMm-elV?YbhQotdd> zWPg+NCec{rC?w_sO2dnas8#OX+p7j4u`J~q(CwfLac~-ZO{h@JUAct~BezM^G8r8j z+vt*C4&5-|&rH;;a#Joer8;bNdpZ9C)9VO3zw|8hlKs22>;HwTGIQb*JNsqp$veO7 z2Fy?t3_g35bp-YJ-0jHU{ukbRu*w0ydK`K9_;Jg@(;b-6fqE^r^9bHpGbqYXIiU?= zKpX<99++OxP8HqOxx~gM20;|afp0|?nnDM=YYQQi1?I!>bzXD4RL9(rVZB1Y16<2~ zXw$&c7V=;22YaX(xDVEjF#ySh+fHp?Wh8dEkie-#o*kbwjNu*k)~_ zUQ3m^uMmlmI$&1&EA)*;sPmRiYWKPDlD)c)F*J3*(AG993OiD}YaUC3@}l?c3hs z@x-Wnrb^@?6;N5oLv`lyzdWMBLtLCe3!z!P{kLx2A_e5)G{AlFqES!f#UM;rpF*q8 z;V=}^=1iOH8DgrbC@I;%hsRV2%!Pb*?ThU_Has47$e-u%`K?qr7Y;3SXqO@&s z&^Mkp$}OQfu50|fyf!z^>yF(5XDeFi19PkcXiT}toDP*iq28O((1fNF{zB;C0YI+Z znIN?V(i9{eZ}eJloZ}7HTUmW~sszgzwEj&8Z-A~P0hX*GM}qoKU?Exx<{22E-TAZB z2nq?uP!^}363Z+dC=LL0(AH0s+{}}X#kmm|3h;X2b8N`wVE017<#P&QH_^>xX&<+3At%L$33l%Xu>U#&2IUX1_0XoXQ#2bVt3CMW^NrBR9- zEUnumHPWz|!UFy$(B{FS$qU{Xg!f?Ua@XHZm}I_T@DWVB5bsIM(=;xnbg~HQ8=6!e zy5zp{FE0Q@km}0*)fKQLQJ%GL`9cGRDp(yijg8r1M}anGY**bwBC#Z4u&Hr~pM5SK z)`&i9oxSR~rH@6DQ&I+mkE@A7N0`aOU+wK}2QCu8n!cb&yxRvx%>7N^N)WJCeXyyyZI`N%`_Z1aiz`WodKTcyh(7SKL3XZY=@)FOQ z9YEyq3k@x54kgv4LI>$|DZr=S%|gFeV9%(@Ly(QQ=500AP^bV^B!XDjd~8R{Xf$9V~}=e z%^0I1!vxjv%u8%$!u#er`CbO1*@Ww)Hk9axnT3sl(2QFGV;HPXmM}NLLTkuK0f-kO zeK@QC^0H-)b}e3_jDB|tU~GbldoaurO9E@1P8h>!Ve*>Xzu-C}bjZl4)Xi`x0IcEA zj5!KclTD(D$Ps+0IJgxe2^Oe&RFeZ+wn68{G)={i@aRArAV$R2{7T@H(6kEnoWXfD z-uxt!b@|)6B6b#sk^C*exOf|CBtx~~p>oG!$F=kOt5H>2-#*-=yrSnRnOKM!GzJ!1O`HU$srk%{?2IX9t!oT1f$NQ;`{=e%1}2VE zV;NSHUlV0f>xFdCD%#_t%CL2H<^Vx~FsJWnn0o1oEwOi2^1psrZ|S)Zhc^by91X=! zL(mSU&I5y`_OXQY{e$OKi(d#BKYZ{gPWIwpUOcre_UzPM+^?~%w;0>lYlJFPhxk+_%Qus3K#uDJdiWtgS>hdY0HP zmMmE>mY2{jR!k8C851lMD*}H&>k7V6W9ko?qrWmOP}*+){p&}%2Yq5(9dV+Ks09Ra zSX#QQ_$(M|Y5)@bq?68oo_%4&qoSgsYdXh^*B3ZJZAcxnM2SC9auWF_`}(8GqtlVr za9M9}Z|Zewaaog(xp^<|vq^&Sv9rwH;?Y?-Svtsw2j423?QMNK(`{$IA44wdH4)n^ z{!(e{e7QLf7Rbxcwr5Y7scO5^J1~%Oo%B0Udz%F4uWbztp!9zXQNKot1@NKJP^e%}*@n7WXd9o8T?0uYm6Hf6GD})t zNS0}wc;ET&i|}(dOMRz&9oiPS8bGXR!OhBPD!e)sGd7dxaJkBhJVY~NWEzBxMH^Jo-dMj}~6MJ3RzCPG9+M$_?(wl z3#!0nhh;5<+u{}6@i?+<9Xwd+N&`&*%V1`QJQ1IgavTUr5MXbzk=Mjpy#nH{wl$N12Rzh2ezWsobvtq9^k|p1?KZI56?|#jX@ov zfO$|Q4>ZXu15DSh6<2R}aSeBMkwHbSu%R7pg~~p9dZ3mUR7cOUWb6Uasz-0~{mSb$ znWt7coBPzSLYpU<8ATa3*4L>`QFRP1zb_K5f8i2EVq)S``oxg%(ADNKwp7>;y3iXA zop04kRnXe|k(H8$Mt88(77Sc9(El8tnHda!B!VXE7Z@l3(vs#R97wVZr&6G2b2u#j z#sZT$F*Wr`Xv}eQ2Z<@7|A5A#{5dRHCO*EiAkEc68}4Q2P1EwteiP(LyTw(|pHSBZ z5fRZA*uZ$8VuWVCo93bY&^rW*bR&c_ZDZqr4v`%K;7{uzj-n0aB@3TjFspcT56X5I z2g^{i9~(P+`kBTzd?v(XWUN=af27<)Wz&r;C_`%?X@8!H$i&11EQmrX9l*w?-6?h$X=!Pc zeuFukdre14*(93X`7L8pZUKxk6LAl|!GRK`!A}d~B$*Yqm0wN9r@K5nPl45kNj2rd z4sL1Owo-T!ONK5!7?63t*&TXz=b(Nyk>-qhYE3eZ3o;m4d?Pu|?=xpkdV?3_^nnPd zpwN*Neck%}!R<$^q-c+9k@;Y_iV~bJ1Z)(chK7bE?eQGtAlOAQ1E5DIz%tr(mXMGT za^4Jbx6szcf^oSMCqk#Bqy$1R4lAkuFQ=z)1OZqdp~8L5UIyjchFhhbU{l6D*NL~$ z(O;n5+T1h%z1aQxB@T{ZGqpZo*oN;D?MWJB3 z()V>}z|R!U@x=1YL>E&!P%*VkOirn(seyl8X>cT85V}?xWEfmOE?$1EsH)oRE?{bA zhIT|oM8<`OzW}tvA}FYqG{OH->V_-!^L6udhJ?HF@>y#P0xCt?LXNArP@zW)IbNX` zbt7m%Rnl>h*BSBgOdFMxc`iSfbai!67ys3(SHaFw-QQ1rnqKIoin+v2=BH1e>>U^E zUOtZVY0lPQ_&veNu_{d`2%Eyww)4ji*m1dvW*ixNuZ6omVp5sWcN4P{VSgLQH>aTH zicu~jM1D>jE#i4Lm@`EYcBQz&m!Yfup{qHzTj1S*dM9b|a(^uF{-|)TlT+L#e>O2G z$vZzk-^u-PMh$4f&^1Q@$}OC2lB2Sz@Jcn=^mAdM51e}d?FUcYvh1cfps64DEx>;= z0Q9iRUGP=ULOce$r{(&z!mkQv3Rnryg%nW|oRoA97aI#Ga?Ddu(%9J8?iC-XFlSKo z0f>f~5Km75B{B$%pujRzgM23#X1BKaM&J6nLTl8N#hl zH$8Za@nmFVfZJPpyb8Px+GCB4fOE_jiqEtGRx>j*ECg7u$>F8`g4_DBbvir;ec&3@ z0)YsaC$jm41+u&{VQOoqsfg?20{Hi~uL zn46i=27(BJQZ;;5M1NFGbinl7Tpn~O%5k1LbxIf6dJNT9RaSofYe#42t7p!fNzTk{ zgQIX-!JqZ9u;c4jZKxr&`}_MrIC#dv%^gZ7~x9ZnZeTslX3A}>FGe7I_@T`-8J0c3?-gb=}=(*@X2AifbP%W4W% zz&mPcZ#R~Pto3 z-rdUSG8nJVhXOrq!P?sTX<;05qpE_Pot^2|@N>7!1{C0#J+ZVv_fl+m8y*TJe?zn) zruVhF-W@lQW@DU&{wE^c?Na`d%teHcVnUH#6fgA`Cia$CU&wGxkN6|}(dBhqAmsC4 z4BOJLrs&yKo{Ja#v9y2;H(W2Is|RkH5|=5>)b( z3OX%Vhy)1i1wF7d%tMae!&E9rB%p#5TU z;5fEeut=}iZo#QAec+tg>HvR_*huuk!U7e-4;F~XckfyoLK!-c%OJSJQ#2Gs$g=7{ zlGg>Z={y9I-Ab^Yu$`R(GXzu7T}Wur(08CI4g2@DtgKdYaxwwp3>N8+vzhVn)#%|1 zc5sAAq25>0O=tsZ3Krz#3?`(z@fu4DhIPKi`;_7W(4)rQqo3R^s!oAtN>DUN`#TUw2G?c@EA?Ue0PEJl%x|Ozb z8d|WzWW4}0qBa=t;~*ZuG)G5Av(f$m z%!rxM16mOE9?7zjqHzL2+h!n;ehtn$FE#4|>v+H9J2Z%+Ju-?<-vPct9ZtDNr! zU)8x={`!WL&)LFx2P4OsNltnrU+np>g`fu?LRMJb#aKfRl$?1h?3UdQcxwNyO8CAd zl#6b&uV?ojsTEr?f#eqej5ZjOMVmT0e4!K(r>lHJKuCy*%Lu&({qOsjDF(L^*hLSI z%Y_v$4VFDlQOCi-!N{nKT4hP`BPq^~^@4M`(!1CQG}WLvt+kZ^029i3 z-MK@+%aSL53_AnpM3BFB7nCy86h!5 z@d3_9fHpacQ&JMLjnWud*|`r*3^I@jqr@1Y5Eq7&gIZ?b_&(I_TeZJ^2|QINAwHKw zb0^x3_QZW}gQ!O#+cD5|<5vJAUYN|u9tJ@>0>GT33?`+(vp_q5AFSKoz?{L5N0qoi zdL>-cJX>VXF7YH5xN97U+xu%k;&UIY!B#i{Q4G2r@Nn=5kmcM3sF6Nq@q!FS1MP$8 z*g#M0b3T)9-W%<2)-;q3ILJ;WP10uG4J!spxPu#7w~r&xEzRZ{k$r>9(Kd_TR+*9b zHyC;!Icctbi#Q}HUJ|93;uM1}CFHP-8jzvEUlLBZ0xvdtL>Oul1=Iu_p?7Jm8643= zZfRqKi@*s7qYzB|f`V|DSG#MZ0?Dxuz(1%p5c*YrRk<^KY1y0yKd1Pd8jB;3nzHhj z>S`=x89Zwc@QHFf#gGF2`JC9txsvFJzPsyRZt*7 zjqsxTTR7m?K@GfcqMh%FGv~!1XoiG@D8so62cMMTGW5VLg!?!zoc6``*BXWLqE~ua zzIPdH8}}H2weWLJPBnOL5Txp0Ul97B!D!Li!B+yk9qV&F_u`{HJv~=8dNjwNCPXV= z$nI#9EcpJg4r$_|A*WN$e-`VZoBXr!PjSjbxy~Ayc_@jH1;LnSgu{vuTn`8awJ2B98)DSBtm4Y%tA7g zDMQ9e87fJHBn>Jul!%ZiGnFV!NamrCkWiA@zixHD@qGXPcU|W@=j!8qpW(jmz4uyc zuU#kGmsBUKvG*VU;qc}2trz#cQs!}ZJo1~7D)Jeo6%a^~YM{C-=_{aH3hgu+IG0yP za=|i66tDVOCNw&%5uCEYkbz63y&SBQMfb`DyatU%o=v@Y$1q8J4Evkj&eI3={k%oC zY&i?ZFT(JE3i*~41gAaK5!52e=p(%ECmoZ!23|W#_~#wavWbsXtAL#-5G7V@63?(1 zShS+u4vlGT{aA4Zo_xu2_v1uMrhf)_;$56VjC}V;1?o3qTdT)effgl2J|>}3R8mY1 zhY#Nu^+=>g5StvlpGlM~#L+;InHNYsSgDZn=PLoWq<&?H@xW<{{kuECo>BXj%%3^0 zae|yx=Tib547-6wQ=abz?*~qdeOyLbEs^}B5sXhoU};Mlmrf_lEn+wFa4O>D`gNt% zZ{BFXinka={wb4ssDA&LPX~7XQYLb}xay}s z`Lw$awc;N<5J9+}Q`I(6SGPex%YB_3qipiyvp%jz4qe8{pB&Ur*xB2kL)XH6*$aj) zXf(ljxfOnnjhwWO)voQ2P9Zzl6E5GZH#kI@hXlR3z>SNJj;{O5mkY8gI?M6&+?*oO;i9qdSwI2y_42aK#`-K zg^kRNjFpkolk+npJy7>F=ew|bd3n9lVOMHh7yIzk;!$zW;MMCcthVl?ejKF(v)RR=!RBvl~ zz;C~uK%#WWZueXzJ-wuboI;ckPoF+jpCf9q9yb+&`@qVyEBE$F$CTLE$jf0t_m>s(fU2 z+lP>J9!E@DO2LgK4?aTm z1#b@0szgZZg|7NR4`L9DR9U8vq256Ad3ATU<@G}2;*hgWUPW zt6}IzOaJcYvQIug*mL9U*ptbo&I@U3!#Mda&$Qr`?(~~x1~E)3eEEf0Pa5iP@rG3;oXdC|IJ0Od@;pTC8`sbC@kg_uQwp>U3TYY$e*2pACxn>FQ?`22FEyu%$ zb8rxj#-9%lKZG+6Ms$ZZ3kaOU`^i3#V<+~E)ziz%7H=*+IXMWx*F%KTEJ$sAYe_8( zcfEZ;WztRpUs%5D(-5iyJ@YwQDCih|AOZ#|0vW?{Lh2kSadwFeKHgXBA>x0{gHKiFJ^^S8-QeSVcD703E zT~A_>NmMNCM7fY!UclzjTf?|KkQiL;%AnEZ-0yevMqb0u-sfxi8EBeO4OMKdee=ei zGB_KxGEnZ==&rB+=x}Sn8O**}0SZj5sTWzfxwaR?CeRxwjx5s7#pM;^`ZLyO`Ru;_ zesQ9MLTNSD#F#Zmw!=PHHdZAm!uVc@`v{{GwT1#wbk#B zxTZhQCtQfGm`h>9Pvp}NQCdGlKTj6t-na^Y5C_e!`-PQdD1~u(#yrj9WmgSa1hdSvMRmYBwK zUZ$F_)D9Fj>rAD+R67o|JudR0M|Kg%3$=Pvw&<{3J>Rn>Z?aQ0_HOL6p4kp=0KMb~ zJFFP$ze)~WUY&mGoX1eHeR0`LcKpucORxio$_2RiavM76Y)=-hj>#m@sz1=G*&~gs zO9ARMSjus5aAYCR=8!+hiV8PI$(vfo{_}@c_eN6-Gc6Do&9$4dW^tU|tMp??ya%;T ziRtD2Wy0Te4MkprQeAw#{Xn5{4!J~*Uzey`XG(1nAg?EK=?byIKv#xDD&0TnGX8sP zM3@O|P+kCxgF#tju!Mc7dacuYt@yuzKxfcKfOPN)@JGl{mvhURI{NkPlT1}C_-+B zbzfbLnq)HE7pdvhnaEqngC!sNc{YaSme>uC>ob4&z$ql^+9@d6Ju# zcK}rX0o2jr+qPLl2L=EUu_Rg57Cc&X>paS-m+kF02Y_M<>EF$QeYPkCXL5no6wro4 zAivY`$ml5FCnj9IWpx5UR6x-;nW6#4pUA!;=&TPx-04y8^Jli_ANyHn3J8j<5EC3(L=!V&8d86h0>oUYxDsY&9zK2a=9T{mIqWXFdGw zMl4}DY#x5c`n0|9O_%ZZDbrpj+|u#F8smmdlxox-p3t&@fgfqpwmo!+XY1BAY7rcYSnoGZ6a-!L z)gh3PjtKt8o#PxHKVi`UEnydf{CNhjpKU+AzGV@k%H7>*+WINHODQN3(B{ACk2C2~ zddZ#2arg_8CLeg()crHg>plgvV`Dj2cj7zD}Q7D6|$|^w+P{tK#@fN=w^=I058g zSQYd?Y6pU+t~v4$h#rcwBnUa67oZ}zBiMrLeHh0=+BQ)ktf+Pe6}1c8JQ@&Jq33{= zm9?8#m?6B0`Ft^^ESk%rsRP}M38*yDsaye3kUNm#&jSOE=U>&<-mm*{KklX+SP8D0 zKR0a(v)H;dczRG0EXZ{izDzt0Y5tLCR?EDrvKpC+gza{=rd!FBQRq(cFL zYgI%KY-Ub@`lbYezXRsNEPuWH+lx)o2z{Qxnl!p^k>!e^UDo@YB8@IpL=T&3N+Nqd zjiZzV4+GDZd+`6k+o=LtO&}T40}T}i5EUhyT!B{wA_nQXD9)|9E5U^68`Thv5S8~A z^){l1F{wfC)F^I~IHbZ92V)|7wYeJs$|AasO%G*m`q3@5C@bP=#CeLJNA*IOQ3_0V zFHUjoMxx5Pe!Ufnv~6wzzkX1816Fgr67(@BTkh)!JOFcz3Qat`Ok+`t6nnvSgfDs7 zGHU8>s6&VY-aF3|lGtyko2;SA+`_?$52}}+L#axk09GhE6PzoAeLyjfsOc2G>&zWg zBfv}xWRSS)Hx--XX@eRuF7@?ZrKXCn$hAOOL7^bH%-buqUb;JLsA!dJxaNlz55jZ%unU2jvJ_>SJ%-$`xvi&KVjS+MdecdpGhT%jlOD zapJ*3o=Tbh2!DGGu$>^lePuPy=tx#I5zHYQ&$IX1Qg%)&J(7q-otH($JRB^Wju zffjC1QUFanBNmLzm~`VB*yNJese2WF`=qK$rNb)s8c1Cz3?h0!MI?MZB127H1|9Sl zK~_(Ktx#Al`p1z)4Mml=d;y9_Vn2#2M5f>ra+zGlAZmgg0d6O3oSbN%u@)U2dEmAF z)2B}-tFse~oSf2VlU&6w_!mEo$gxXJZACP{=3{^rQ!WsUlLlq4!FMZB{E@K| z5YW&@^bkV@N(Gss6wza8Z@+>9$;`)0B^5Qbrt=A@7!m=8I|v;Tl9JBgfqek=Oq+L> z_eazp2ho@bvdYTAfz%O}7;SUXKHX;u$J&VYoRC4KLAupaEu=!)6u!4gO zy6Q>y48kM>?b$*4aPok+QBzYBp)U3*0PUwZg{HJF!JH5{dletyP{WO33FHiWF&1pUoXb+Rl|`>;_K>Bt8;B2rRU zs4OAz=)Ov7%>G#cx!TzY-R$=T1TdIGosh%jr!)?>4iVyreat5)^Yy4*XtdUd!=EpsV4 zJ=xfFGRvBsHn6L-mSo)4;^GNs0F8lw`~fbX1e8YL1KgGJ>!Bqoy?_56D||J5vz4Rc zN>CuE?)(qHmQ$qZbf}$VIMZ7*(c>!2e0Aryi15T;n~q@_cqdDw ztz^-YE?D4N6)<@3up$c3_Z9zzg`F%q!aQ$%P)_a#c_Qy|VCH#f7Q)rgENfB_ps z1Q^_8VGo;o=(|PXo=#Xpd^op+gh6WI@xChRk`mrSZf=nf5#X{pAd?697%dxn%w|xZ z3-UBjB4E3dva%R})e;v2w#~ogFtZ_d26;!C1?xY#s7A-p@Fg#W_o(y`%pAo^t7?$To-%ZzHGH zIiNiv9i1BLa!_*bmSr&jjsFy9_0fnRZ z9tHB~RHD%%!B*622}(^e>5%lEbW3Y>BrL9+7|_^Ds@+Kj3CtD%N9xy$gBK;)_$kEg z6oO8K08}Qhk)y{Q+y;2?sph=!TV1~1g<}6X8RF7OPXdi)}0(-7yLYa|3D^Xuz3 zEKm8J+XR)l#%F;SZ&~<6O7p4JxR_cCH3N6M$_u& ze1IwurW8^TVZhGWFnPHXtjkZ%2cG1-#7%z5yR9#LATDiqRAV6L2@{7H^zOsP#h)3b zHyhZ<`5NnH41D4TAXSJ3)j-zjQuPJA[SXQyXqxST?S6=3KaIac$8*1mj6O+i&C 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 SqvI8RSq>R(e%#FdkN*dZ^mE?; literal 0 HcmV?d00001 diff --git a/advanced_source/visualizing_gradients_tutorial.py b/advanced_source/visualizing_gradients_tutorial.py index 6e9e81bfd98..cd9f0474c5d 100644 --- a/advanced_source/visualizing_gradients_tutorial.py +++ b/advanced_source/visualizing_gradients_tutorial.py @@ -2,60 +2,75 @@ Visualizing Gradients ===================== -**Author**: `Justin Silver `_ - -By performance and efficiency reasons, PyTorch does not save the -intermediate gradients when running back-propagation. To visualize the -gradients of these internal layer tensor, we have to explicitly tell -PyTorch to retain those values with the ``retain_grad`` parameter. +**Author:** `Justin Silver `__ + +When training neural networks with PyTorch, it’s possible to ignore some +of the library’s internal mechanisms. For example, running +backpropagation requires a simple call to ``backward()``. This tutorial +dives into how those gradients are calculated and stored in two +different kinds of PyTorch tensors: leaf vs. non-leaf. It will also +cover how we can extract and visualize gradients at any layer in the +network’s computational graph. By inspecting how information flows from +the end of the network to the parameters we want to optimize, we can +debug issues that occur during training such as `vanishing or exploding +gradients `__. By the end of this tutorial, you will be able to: -- Visualize gradients after backward propagation in a neural network -- Differentiate between *leaf* and *non-leaf* tensors -- Know when to use\ ``retain_grad`` vs. ``require_grad`` +- Differentiate leaf vs. non-leaf tensors +- Know when to use ``requires_grad`` vs. ``retain_grad`` +- Visualize gradients after backpropagation in a neural network -""" +We will start off with a simple network to understand how PyTorch +calculates and stores gradients, and then build on this knowledge to +visualize the gradient flow of a `ResNet +model `__. +Before starting, it is recommended to have a solid understanding of +`tensors and how to manipulate +them `__. +A basic knowledge of `how autograd +works `__ +would also be useful. -###################################################################### -# Introduction -# ------------ -# -# When training neural networks with PyTorch, it is easy to disregard the -# internal mechanisms of the PyTorch library. For example, to run -# back-propagation the API requires a single call to ``loss.backward()``. -# This tutorial will dive into how exactly those gradients are calculated -# and stored in two different kinds of PyTorch tensors: *leaf*, and -# *non-leaf*. It will also cover how we can extract and visualize -# gradients at any neuron in the computational graph. Some important -# barriers to efficient neural network training are vanishing/exploding -# gradients, which lead to slow training progress and/or broken -# optimization pipelines. Thus, it is important to understand how -# information flows from one end of the network, through the computational -# graph, and finally to the parameters we want to optimize. -# +""" ###################################################################### # Setup # ----- # -# First, make sure PyTorch is installed and then import the necessary -# libraries +# First, make sure `PyTorch is +# installed `__ and then import +# the necessary libraries. # import torch +import torchvision +from torchvision.models import resnet18 import torch.nn as nn +import torch.optim as optim import torch.nn.functional as F +import matplotlib.pyplot as plt ###################################################################### -# Next, we will instantiate an extremely simple network so that we can -# focus on the gradients. This will be an affine layer followed by a ReLU -# activation. Note that the ``requires_grad=True`` is necessary for the -# parameters (``W`` and ``b``) so that PyTorch tracks operations involving -# those tensors. We’ll discuss more about this attribute shortly. +# Next, we will instantiate a simple network so that we can focus on the +# gradients. This will be an affine layer, followed by a ReLU activation, +# and ending with a MSE loss between the prediction and label tensors. +# +# .. math:: +# +# \mathbf{y}_{\text{pred}} = \text{ReLU}(\mathbf{x} \mathbf{W} + \mathbf{b}) +# +# .. math:: +# +# L = \text{MSE}(\mathbf{y}_{\text{pred}}, \mathbf{y}) +# +# Note that the ``requires_grad=True`` is necessary for the parameters +# (``W`` and ``b``) so that PyTorch tracks operations involving those +# tensors. We’ll discuss more about this in a future +# `section <#requires-grad>`__. # # tensor setup @@ -70,183 +85,126 @@ loss = F.mse_loss(y_pred, y) # scalar loss -###################################################################### -# Before we perform back-propagation on this network, we need to know the -# difference between *leaf* and *non-leaf* nodes. This is important -# because the distinction affects how gradients are calculated and stored. -# - - ###################################################################### # Leaf vs. non-leaf tensors # ------------------------- # -# The backbone for PyTorch Autograd is a dynamic computational graph which -# keeps a record of input tensor data, all subsequent operations on those -# tensors, and finally the resulting new tensors. It is a directed acyclic -# graph (DAG) which can be used to compute gradients along every node all -# the way from the roots (output tensors) to the leaves (input tensors) -# using the chain rule from calculus. +# After running the forward pass, PyTorch autograd has built up a `dynamic +# computational +# graph `__ +# which is shown below. This is a `Directed Acyclic Graph +# (DAG) `__ which +# keeps a record of input tensors (leaf nodes), all subsequent operations +# on those tensors, and the intermediate/output tensors (non-leaf nodes). +# The graph is used to compute gradients for each tensor starting from the +# graph roots (outputs) to the leaves (inputs) using the `chain +# rule `__ from calculus: # -# In the context of a generic DAG then, a *leaf* is simply a node which is -# at the input (beginning) of the graph, and *non-leaf* nodes are -# everything else. +# .. math:: # -# To start the generation of the computational graph which can be used for -# gradient calculation, we need to pass in the ``requires_grad=True`` -# parameter to the tensor constructors. That is because by default, -# PyTorch is not tracking gradients on any created tensors. To verify -# this, try removing the parameter above and then run back-propagation: +# \mathbf{y} = \mathbf{f}_k\bigl(\mathbf{f}_{k-1}(\dots \mathbf{f}_1(\mathbf{x}) \dots)\bigr) # -# :: +# .. math:: # -# >>> loss.backward() -# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn +# \frac{\partial \mathbf{y}}{\partial \mathbf{x}} = +# \frac{\partial \mathbf{f}_k}{\partial \mathbf{f}_{k-1}} \cdot +# \frac{\partial \mathbf{f}_{k-1}}{\partial \mathbf{f}_{k-2}} \cdot +# \cdots \cdot +# \frac{\partial \mathbf{f}_1}{\partial \mathbf{x}} # -# This runtime error is telling us that the tensor is not tracking -# gradients and has no associated gradient function. Thus, it cannot -# back-propagate to the leaf tensors and calculate the gradients for each -# node. +# .. figure:: /_static/img/visualizing_gradients_tutorial/comp-graph-1.png +# :alt: Computational graph after forward pass # -# From the above discussion, we can see that ``x``, ``W``, ``b``, and -# ``y`` are leaf tensors, whereas ``z``, ``y_pred``, and ``loss`` are -# non-leaf tensors. We can verify this with the class attribute -# ``is_leaf()``: +# Computational graph after forward pass # -# prints all True because new tensors are leafs by convention -print(f"{x.is_leaf=}") -print(f"{W.is_leaf=}") -print(f"{b.is_leaf=}") -print(f"{y.is_leaf=}") - -# prints all False because tensors are the result of an operation -# with at least one tensor having requires_grad=True -print(f"{z.is_leaf=}") -print(f"{y_pred.is_leaf=}") -print(f"{loss.is_leaf=}") - ###################################################################### -# The distinction between leaf and non-leaf is important, because that -# attribute determines whether the tensor’s gradient will be stored in the -# ``grad`` property after the backward pass, and thus be usable for -# gradient descent optimization. We’ll cover this some more in the -# following section. -# -# Also note that by convention, when the user creates a new tensor, -# PyTorch automatically makes it a leaf node. This is the case even though -# is no computational graph associated with the tensor. For example: +# PyTorch considers a node to be a *leaf* if it is not the result of a +# tensor operation with at least one input having ``requires_grad=True`` +# (e.g. ``x``, ``W``, ``b``, and ``y``), and everything else to be +# *non-leaf* (e.g. ``z``, ``y_pred``, and ``loss``). You can verify this +# programmatically by probing the ``is_leaf`` attribute of the tensors: # -a = torch.tensor([1.0, 5.0, 2.0]) -a.is_leaf +# prints True because new tensors are leafs by convention +print(f"{x.is_leaf=}") + +# prints False because tensor is the result of an operation with at +# least one input having requires_grad=True +print(f"{z.is_leaf=}") ###################################################################### -# Now that we understand what makes a tensor a leaf vs. non-leaf, the -# second piece of the puzzle is knowing when PyTorch calculates and stores -# gradients for the tensors in its computational graph. +# The distinction between leaf and non-leaf determines whether the +# tensor’s gradient will be stored in the ``grad`` property after the +# backward pass, and thus be usable for gradient descent optimization. +# We’ll cover this some more in the `following section <#retain-grad>`__. +# +# Let’s now investigate how PyTorch calculates and stores gradients for +# the tensors in its computational graph. # ###################################################################### # ``requires_grad`` -# ================= +# ----------------- # -# To tell PyTorch to explicitly start tracking gradients, when we create -# the tensor, we can pass in the parameter ``requires_grad=True`` to the -# class constructor (by default it is ``False``). This tells PyTorch to -# treat the tensor as a leaf tensor, and all the subsequent operations -# will generate results which also need to require the gradient for -# back-propagation to work. This is because the backward pass uses the -# chain rule from calculus, where intermediate gradients ‘flow’ backward -# through the network. +# To start the generation of the computational graph which can be used for +# gradient calculation, we need to pass in the ``requires_grad=True`` +# parameter to a tensor constructor. By default, the value is ``False``, +# and thus PyTorch does not track gradients on any created tensors. To +# verify this, try not setting ``requires_grad``, re-run the forward pass, +# and then run backpropagation. You will see: # -# We already did this for the parameters we want to optimize, so we’re -# good. If you need to change the property though, you can call -# ``requires_grad_()`` on the tensor to change it (notice the ``_`` -# suffix). +# :: # -# Similar to the analysis above, we can sanity-check which nodes in our -# network have to calculate the gradient for back-propagation to work. +# >>> loss.backward() +# RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn +# +# PyTorch is telling us that because the tensor is not tracking gradients, +# autograd can’t backpropagate to any leaf tensors. If you need to change +# the property, you can call ``requires_grad_()`` on the tensor (notice +# the ’_’ suffix). +# +# We can sanity-check which nodes require gradient calculation, just like +# we did above with the ``is_leaf`` attribute: # -# prints all False because tensors are leaf nodes -print(f"{x.requires_grad=}") -print(f"{y.requires_grad=}") - -# prints all True because requires_grad=True in constructor -print(f"{W.requires_grad=}") -print(f"{b.requires_grad=}") - -# prints all True because tensors are non-leaf nodes -print(f"{z.requires_grad=}") -print(f"{y_pred.requires_grad=}") -print(f"{loss.requires_grad=}") +print(f"{x.requires_grad=}") # prints False because requires_grad=False by default +print(f"{W.requires_grad=}") # prints True because we set requires_grad=True in constructor +print(f"{z.requires_grad=}") # prints True because tensor is a non-leaf node ###################################################################### -# A useful heuristic to remember is that whenever a tensor is a non-leaf, -# it **has** to have ``requires_grad=True``, otherwise back-propagation -# would fail. If the tensor is a leaf, then it will only have +# It’s useful to remember that by definition a non-leaf tensor has +# ``requires_grad=True``. Backpropagation would fail if this wasn’t the +# case. If the tensor is a leaf, then it will only have # ``requires_grad=True`` if it was specifically set by the user. Another # way to phrase this is that if at least one of the inputs to the tensor # requires the gradient, then it will require the gradient as well. # -# There are two exceptions to the above guideline: -# -# 1. Using ``nn.Module`` and ``nn.Parameter`` -# 2. `Locally disabling gradient computation with context -# managers `__ +# There are two exceptions to this rule: # -# For the first case, if you subclass the ``nn.Module`` base class, then -# by default all of the parameters of that module will have -# ``requires_grad`` automatically set to ``True``. e.g.: -# - -class Model(nn.Module): - def __init__(self) -> None: - super().__init__() - self.conv1 = nn.Conv2d(1, 20, 5) - self.conv2 = nn.Conv2d(20, 20, 5) - - def forward(self, x): - x = F.relu(self.conv1(x)) - return F.relu(self.conv2(x)) - -m = Model() - -for name, param in m.named_parameters(): - print(name, param.requires_grad) - - -###################################################################### -# For the second case, if you wrap one of the gradient context managers -# around a tensor, then computations behave as if none of the inputs -# require grad. +# 1. Any ``nn.Module`` that has ``nn.Parameter`` will have +# ``requires_grad=True`` for its parameters (see +# `here `__) +# 2. Locally disabling gradient computation with context managers (see +# `here `__) # -z = (x @ W) + b # same as before - -with torch.no_grad(): # could also use torch.inference_mode() - z2 = (x @ W) + b - -print(f"{z.requires_grad=}") -print(f"{z2.requires_grad=}") - ###################################################################### # In summary, ``requires_grad`` tells autograd which tensors need to have -# their gradients calculated for back-propagation to work. This is +# their gradients calculated for backpropagation to work. This is # different from which gradients have to be stored inside the tensor, # which is the topic of the next section. # ###################################################################### -# Back-propagation -# ---------------- +# ``retain_grad`` +# --------------- # # To actually perform optimization (e.g. SGD, Adam, etc.), we need to run # the backward pass so that we can extract the gradients. @@ -257,8 +215,9 @@ def forward(self, x): ###################################################################### # This single function call populated the ``grad`` property of all leaf -# tensors which had their ``requires_grad=True``. The ``grad`` is the -# gradient of the loss with respect to the tensor we are probing. +# tensors which had ``requires_grad=True``. The ``grad`` is the gradient +# of the loss with respect to the tensor we are probing. Before running +# ``backward()``, this attribute is set to ``None``. # print(f"{W.grad=}") @@ -270,15 +229,15 @@ def forward(self, x): # check the remaining leaf nodes: # +# prints all None because requires_grad=False print(f"{x.grad=}") -print(f"{y.grad=}") +print(f"{y.grad=}") ###################################################################### -# Interestingly, these gradients haven’t been populated into the ``grad`` -# property and they default to ``None``. This is expected behavior though -# because we did not explicitly tell PyTorch to calculate gradient with -# the ``requires_grad`` parameter. +# The gradients for these tensors haven’t been populated because we did +# not explicitly tell PyTorch to calculate their gradient +# (``requires_grad=False``). # # Let’s now look at an intermediate non-leaf node: # @@ -288,139 +247,312 @@ def forward(self, x): ###################################################################### # We also get ``None`` for the gradient, but now PyTorch warns us that a -# non-leaf node’s ``grad`` attribute is being accessed. It might come as a -# surprise that we can’t access the gradient for intermediate tensors in -# the computational graph, since they **have** to calculate the gradient -# for back-propagation to work. PyTorch errs on the side of performance -# and assumes that you don’t need to access intermediate gradients if -# you’re trying to optimize leaf tensors. To change this behavior, we can -# use the ``retain_grad()`` function. +# non-leaf node’s ``grad`` attribute is being accessed. Although autograd +# has to calculate intermediate gradients for backpropagation to work, it +# assumes you don’t need to access the values afterwards. To change this +# behavior, we can use the ``retain_grad()`` function on a tensor. This +# tells the autograd engine to populate that tensor’s ``grad`` after +# calling ``backward()``. # +# we have to re-run the forward pass +z = (x @ W) + b +y_pred = F.relu(z) +loss = F.mse_loss(y_pred, y) + +# tell PyTorch to store the gradients after backward() +z.retain_grad() +y_pred.retain_grad() +loss.retain_grad() + +# have to zero out gradients otherwise they would accumulate +W.grad = None +b.grad = None + +# backpropagation +loss.backward() + +# print gradients for all tensors that have requires_grad=True +print(f"{W.grad=}") +print(f"{b.grad=}") +print(f"{z.grad=}") +print(f"{y_pred.grad=}") +print(f"{loss.grad=}") + ###################################################################### -# ``retain_grad`` -# --------------- +# We get the same result for ``W.grad`` as before. Also note that because +# the loss is scalar, the gradient of the loss with respect to itself is +# simply ``1.0``. # -# When we call ``retain_grad()`` on a tensor, this signals to the autograd -# engine that we want to have that tensor’s ``grad`` populated after -# calling ``backward()``. +# If we look at the state of the computational graph now, we see that the +# ``retains_grad`` attribute has changed for the intermediate tensors. By +# convention, this attribute will print ``False`` for any leaf node, even +# if it requires its gradient. # -# We can verify that PyTorch is not storing gradients for non-leaf tensors -# by accessing the ``retains_grad`` flag: +# .. figure:: /_static/img/visualizing_gradients_tutorial/comp-graph-2.png +# :alt: Computational graph after backward pass # +# Computational graph after backward pass +# + -# Prints all False because we didn't tell PyTorch to store gradients with `retain_grad()` -print(f"{z.retains_grad=}") -print(f"{y_pred.retains_grad=}") -print(f"{loss.retains_grad=}") +###################################################################### +# If you call ``retain_grad()`` on a non-leaf node, it results in a no-op. +# If we call ``retain_grad()`` on a node that has ``requires_grad=False``, +# PyTorch actually throws an error, since it can’t store the gradient if +# it is never calculated. +# +# :: +# +# >>> x.retain_grad() +# RuntimeError: can't retain_grad on Tensor that has requires_grad=False +# +# In summary, using ``retain_grad()`` and ``retains_grad`` only make sense +# for non-leaf nodes, since the ``grad`` attribute will already be +# populated for leaf tensors that have ``requires_grad=True``. By default, +# these non-leaf nodes do not retain (store) their gradient after +# backpropagation. We can change that by rerunning the forward pass, +# telling PyTorch to store the gradients, and then performing +# backpropagation. +# +# The following table can be used as a cheat-sheet which summarizes the +# above discussions. The following scenarios are the only ones that are +# valid for PyTorch tensors. +# +# +# +# +----------------+------------------------+------------------------+---------------------------------------------------+-------------------------------------+ +# | ``is_leaf`` | ``requires_grad`` | ``retains_grad`` | ``require_grad()`` | ``retain_grad()`` | +# +================+========================+========================+===================================================+=====================================+ +# | ``True`` | ``False`` | ``False`` | sets ``requires_grad`` to ``True`` or ``False`` | no-op | +# +----------------+------------------------+------------------------+---------------------------------------------------+-------------------------------------+ +# | ``True`` | ``True`` | ``False`` | sets ``requires_grad`` to ``True`` or ``False`` | no-op | +# +----------------+------------------------+------------------------+---------------------------------------------------+-------------------------------------+ +# | ``False`` | ``True`` | ``False`` | no-op | sets ``retains_grad`` to ``True`` | +# +----------------+------------------------+------------------------+---------------------------------------------------+-------------------------------------+ +# | ``False`` | ``True`` | ``True`` | no-op | no-op | +# +----------------+------------------------+------------------------+---------------------------------------------------+-------------------------------------+ +# ###################################################################### -# We can also check the other leaf tensors, but note that by convention, -# this attribute will print ``False`` for any leaf node, even if that -# tensor was set to require its gradient. This is true even if you call -# ``retain_grad()`` on a leaf node that has ``requires_grad=True``, which -# results in a no-op. +# (work-in-progress) Real world example with ResNet +# ------------------------------------------------- +# +# Let’s move on from the toy example above and study a realistic network: +# `ResNet `__. +# +# To illustrate the importance of gradient visualization, we will +# instantiate two versions of ResNet: one without batch normalization +# (``BatchNorm``), and one with it. `Batch +# normalization `__ is an extremely +# effective technique to resolve the vanishing/exploding gradients issue, +# and we will be verifying that experimentally. +# +# We first initiate the models without ``BatchNorm`` following the +# `documentation `__. # -# Prints all False because these are leaf tensors -print(f"{x.retains_grad=}") -print(f"{y.retains_grad=}") -print(f"{b.retains_grad=}") -print(f"{W.retains_grad=}") +# set up dummy data +x = torch.randn(1, 3, 224, 224) +y = torch.randn(1, 1000) -W.retain_grad() -print(f"{W.retains_grad=}") # still False +# init model +# model = resnet18(norm_layer=nn.Identity) +model = resnet18() +model.train() +optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) ###################################################################### -# If we try calling ``retain_grad()`` on a node that has -# ``require_grad=False``, PyTorch actually throws an error. +# Because we are using a ``nn.Module`` instead of individual tensors for +# our forward pass, we need another adopt our method to access the +# intermediate gradients. This is done by `registering a +# hook `__. # -# :: +# Note that using backward pass hooks to probe an intermediate nodes +# gradient is preferred over using ``retain_grad()``. It avoids the memory +# retention overhead if gradients aren’t needed after backpropagation. It +# also lets you modify and/or clamp gradients during the backward pass, so +# they don’t vanish or explode. # -# >>> x.retain_grad() -# RuntimeError: can't retain_grad on Tensor that has requires_grad=False +# The following code defines our forward pass hook (notice the call to +# ``retain_grad()``) and also collects names of all parameters and layers. # +def hook_forward(module, args, output): + output.retain_grad() # store gradient in ouput tensors + + # grads and layers are global variables + outputs.append((layers[module], output)) + +def get_all_layers(layer, hook_fn): + """Returns dict where keys are children modules and values are layer names""" + layers = dict() + for name, layer in model.named_modules(): + if any(layer.children()) is False: + # skip Sequential and/or wrapper modules + layers[layer] = name + layer.register_forward_hook(hook_fn) # hook_forward + return layers + +def get_all_params(model): + """return list of all leaf tensors with requires_grad=True and which are not bias terms""" + params = [] + for name, param in model.named_parameters(): + if param.requires_grad and "bias" not in name: + params.append((name, param)) + return params + +# register hooks +layers = get_all_layers(model, hook_forward) + +# get parameter gradients +params = get_all_params(model) + ###################################################################### -# In summary, using ``retain_grad()`` and ``retains_grad`` only make sense -# for non-leaf nodes, since the ``grad`` attribute has to be populated for -# leaf tensors that have ``requires_grad=True``. By default, these -# non-leaf nodes do not retain (store) their gradient after -# back-propagation. +# Let’s check a few of the layers and parameters to make sure things are +# as expected: # -# We can change that by rerunning the forward pass, telling PyTorch to -# store the gradients, and then performing back-propagation. + +num_layers = 5 +print("<--------Params-------->") +for name, param in params[0:num_layers]: + print(name, param.shape) + +count = 0 +print("<--------Layers-------->") +for layer in layers.values(): + print(layer) + count += 1 + if count >= num_layers: + break + + +###################################################################### +# Now let’s run a forward pass and verify our output tensor values were +# populated. # -# forward pass -z = (x @ W) + b -y_pred = F.relu(z) -loss = F.mse_loss(y_pred, y) +outputs = [] # list with layer name, output tensor tuple +optimizer.zero_grad() +y_pred = model(x) +loss = F.mse_loss(y_pred, y) -# tell PyTorch to store the gradients after backward() -z.retain_grad() -y_pred.retain_grad() -loss.retain_grad() +print("<--------Outputs-------->") +for name, output in outputs[0:num_layers]: + print(name, output.shape) -# have to zero out gradients otherwise they would accumulate -W.grad = None -b.grad = None -# back-propagation +###################################################################### +# Everything looks good so far, so let’s call ``backward()``, populate the +# ``grad`` values for all intermediate tensors, and get the average +# gradient for each layer. +# + loss.backward() -# print gradients for all tensors that have requires_grad=True -print(f"{W.grad=}") -print(f"{b.grad=}") -print(f"{z.grad=}") -print(f"{y_pred.grad=}") -print(f"{loss.grad=}") +def get_grads(): + layer_idx = [] + avg_grads = [] + print("<--------Grads-------->") + for idx, (name, output) in enumerate(outputs[0:-2]): + if output.grad is not None: + avg_grad = output.grad.abs().mean() + if idx < num_layers: + print(name, avg_grad) + avg_grads.append(avg_grad) + layer_idx.append(idx) + return layer_idx, avg_grads + +layer_idx, avg_grads = get_grads() ###################################################################### -# Note we get the same result for ``W.grad`` as before. Also note that -# because the loss is scalar, the gradient of the loss with respect to -# itself is simply ``1.0``. +# Now that we have all our gradients stored in ``grads``, we can plot them +# and see how the average gradient values change as a function of the +# network depth. # +def plot_grads(layer_idx, avg_grads): + plt.plot(layer_idx, avg_grads) + plt.xlabel("Layer depth") + plt.ylabel("Average gradient") + plt.title("Gradient flow") + plt.grid(True) + +plot_grads(layer_idx, avg_grads) + ###################################################################### -# (work-in-progress) Real-world example - visualizing gradient flow -# ----------------------------------------------------------------- -# -# We used a toy example above, but let’s now apply the concepts we learned -# to the visualization of intermediate gradients in a more powerful neural -# network: ResNet. +# Upon initialization, this is not very interesting. Let’s try running for +# several epochs, use gradient descent, and then see how the values +# change. # +epochs = 20 + +for epoch in range(epochs): + outputs = [] # list with layer name, output tensor tuple + optimizer.zero_grad() + y_pred = model(x) + loss = F.mse_loss(y_pred, y) + loss.backward() + optimizer.step() + +layer_idx, avg_grads = get_grads() +plot_grads(layer_idx, avg_grads) + + +###################################################################### +# Still not very interesting… surprised that the gradients don’t +# accumulate. Let’s check the leaf tensors… those tensors are probably +# just recreated whenever I rerun the forward pass, and thus they don’t +# accumulate. Let’s see if that’s the case with the parameters. +# + +def get_param_grads(): + layer_idx = [] + avg_grads = [] + print("<--------Params-------->") + for idx, (name, param) in enumerate(params): + if param.grad is not None: + avg_grad = param.grad.abs().mean() + if idx < num_layers: + print(name, avg_grad) + avg_grads.append(avg_grad) + layer_idx.append(idx) + return layer_idx, avg_grads + +layer_idx, avg_grads = get_param_grads() + + +plot_grads(layer_idx, avg_grads) + ###################################################################### # (work-in-progress) Conclusion # ----------------------------- # -# This table can be used as a cheat-sheet which summarizes the above -# discussions. The following scenarios are the only ones that are valid -# for PyTorch tensors. +# If you would like to learn more about how PyTorch’s autograd system +# works, please visit the `references <#references>`__ below. If you have +# any feedback for this tutorial (improvements, typo fixes, etc.) then +# please use the `PyTorch Forums `__ and/or +# the `issue tracker `__ to +# reach out. # -# ============ ================== ================ =================================== ============================= -# ``is_leaf`` ``requires_grad`` ``retains_grad`` ``require_grad()`` ``retain_grad()`` -# ============ ================== ================ =================================== ============================= -# True False False sets ``require_grad`` to True/False no-op -# True True False sets ``require_grad`` to True/False no-op -# False True False no-op sets ``retains_grad`` to True -# False True True no-op no-op -# ============ ================== ================ =================================== ============================= ###################################################################### # References # ---------- # -# https://docs.pytorch.org/tutorials/beginner/basics/autogradqs_tutorial -# -# https://docs.pytorch.org/docs/stable/notes/autograd.html#setting-requires-grad +# - `A Gentle Introduction to +# torch.autograd `__ +# - `Automatic Differentiation with +# torch.autograd `__ +# - `Autograd +# mechanics `__ # \ No newline at end of file From cc1aa325721dee00e9a979fce1ec1b29b2aa04d2 Mon Sep 17 00:00:00 2001 From: Justin Silver Date: Sun, 15 Jun 2025 14:38:38 -0700 Subject: [PATCH 3/3] Add batchnorm example and add thumbnail Instead of using resnet as the example for visualization of the gradients, I decided to use a simple fully-connected network with and without batchnorm. It is a contrived model, but the importance is on illustration of the gradients, not so much on which model to apply it for. I also wanted the positive effect of batch normalization to be clearly shown, and this was not the case with PyTorch's base resnet model. --- .../visualizing_gradients_tutorial.png | Bin 0 -> 41537 bytes .../visualizing_gradients_tutorial.py | 333 ++++++++++-------- 2 files changed, 184 insertions(+), 149 deletions(-) create mode 100644 _static/img/thumbnails/cropped/visualizing_gradients_tutorial.png diff --git a/_static/img/thumbnails/cropped/visualizing_gradients_tutorial.png b/_static/img/thumbnails/cropped/visualizing_gradients_tutorial.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff6d97f2e200d86705257ac22ca1a1c83993727 GIT binary patch literal 41537 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 literal 0 HcmV?d00001 diff --git a/advanced_source/visualizing_gradients_tutorial.py b/advanced_source/visualizing_gradients_tutorial.py index cd9f0474c5d..50b6da0ad17 100644 --- a/advanced_source/visualizing_gradients_tutorial.py +++ b/advanced_source/visualizing_gradients_tutorial.py @@ -22,9 +22,10 @@ - Visualize gradients after backpropagation in a neural network We will start off with a simple network to understand how PyTorch -calculates and stores gradients, and then build on this knowledge to -visualize the gradient flow of a `ResNet -model `__. +calculates and stores gradients. Building on this knowledge, we will +then visualize the gradient flow of a more complicated model and see the +effect that `batch normalization `__ +has on the gradient distribution. Before starting, it is recommended to have a solid understanding of `tensors and how to manipulate @@ -46,8 +47,6 @@ # import torch -import torchvision -from torchvision.models import resnet18 import torch.nn as nn import torch.optim as optim import torch.nn.functional as F @@ -192,9 +191,6 @@ # 2. Locally disabling gradient computation with context managers (see # `here `__) # - - -###################################################################### # In summary, ``requires_grad`` tells autograd which tensors need to have # their gradients calculated for backpropagation to work. This is # different from which gradients have to be stored inside the tensor, @@ -337,204 +333,222 @@ ###################################################################### -# (work-in-progress) Real world example with ResNet -# ------------------------------------------------- +# Real world example with BatchNorm +# --------------------------------- # -# Let’s move on from the toy example above and study a realistic network: -# `ResNet `__. +# Let’s move on from the toy example above and study a more realistic +# network. We’ll be creating a network intended for the MNIST dataset, +# similar to the architecture described by the `batch normalization +# paper `__. # # To illustrate the importance of gradient visualization, we will -# instantiate two versions of ResNet: one without batch normalization -# (``BatchNorm``), and one with it. `Batch -# normalization `__ is an extremely +# instantiate one version of the network with batch normalization +# (BatchNorm), and one without it. Batch normalization is an extremely # effective technique to resolve the vanishing/exploding gradients issue, # and we will be verifying that experimentally. # -# We first initiate the models without ``BatchNorm`` following the -# `documentation `__. +# The model we will use has a specified number of repeating +# fully-connected layers which alternate between ``nn.Linear``, +# ``norm_layer``, and ``nn.Sigmoid``. If we apply batch normalization, +# then ``norm_layer`` will use +# `BatchNorm1d `__, +# otherwise it will use the identity transformation +# `Identity `__. +# + +def fc_layer(in_size, out_size, norm_layer): + """Return a stack of linear->norm->sigmoid layers""" + return nn.Sequential(nn.Linear(in_size, out_size), norm_layer(out_size), nn.Sigmoid()) + +class Net(nn.Module): + """Define a network that has num_layers of linear->norm->sigmoid transformations""" + def __init__(self, in_size=28*28, hidden_size=128, + out_size=10, num_layers=3, batchnorm=False): + super().__init__() + if batchnorm is False: + norm_layer = nn.Identity + else: + norm_layer = nn.BatchNorm1d + + layers = [] + layers.append(fc_layer(in_size, hidden_size, norm_layer)) + + for i in range(num_layers-1): + layers.append(fc_layer(hidden_size, hidden_size, norm_layer)) + + layers.append(nn.Linear(hidden_size, out_size)) + + self.layers = nn.Sequential(*layers) + + def forward(self, x): + x = torch.flatten(x, 1) + return self.layers(x) + + +###################################################################### +# Next we set up some dummy data, instantiate two versions of the model, +# and initialize the optimizers. # # set up dummy data -x = torch.randn(1, 3, 224, 224) -y = torch.randn(1, 1000) +x = torch.randn(10, 28, 28) +y = torch.randint(10, (10, )) # init model -# model = resnet18(norm_layer=nn.Identity) -model = resnet18() -model.train() -optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) +model_bn = Net(batchnorm=True, num_layers=3) +model_nobn = Net(batchnorm=False, num_layers=3) + +model_bn.train() +model_nobn.train() + +optimizer_bn = optim.SGD(model_bn.parameters(), lr=0.01, momentum=0.9) +optimizer_nobn = optim.SGD(model_nobn.parameters(), lr=0.01, momentum=0.9) + + + +###################################################################### +# We can verify that batch normalization is only being applied to one of +# the models by probing one of the internal layers: +# + +print(model_bn.layers[0]) +print(model_nobn.layers[0]) ###################################################################### # Because we are using a ``nn.Module`` instead of individual tensors for -# our forward pass, we need another adopt our method to access the -# intermediate gradients. This is done by `registering a +# our forward pass, we need another method to access the intermediate +# gradients. This is done by `registering a # hook `__. # -# Note that using backward pass hooks to probe an intermediate nodes -# gradient is preferred over using ``retain_grad()``. It avoids the memory -# retention overhead if gradients aren’t needed after backpropagation. It -# also lets you modify and/or clamp gradients during the backward pass, so -# they don’t vanish or explode. +# .. warning:: +# +# Note that using backward pass hooks to probe an intermediate nodes gradient is preferred over using `retain_grad()`. +# It avoids the memory retention overhead if gradients aren't needed after backpropagation. +# It also lets you modify and/or clamp gradients during the backward pass, so they don't vanish or explode. +# However, if in-place operations are performed, you cannot use the backward pass hook +# since it wraps the forward pass with views instead of the actual tensors. For more information +# please refer to https://github.com/pytorch/pytorch/issues/61519. # # The following code defines our forward pass hook (notice the call to -# ``retain_grad()``) and also collects names of all parameters and layers. +# ``retain_grad()``) and also gathers descriptive names for the network’s +# layers. # -def hook_forward(module, args, output): - output.retain_grad() # store gradient in ouput tensors +def hook_forward_wrapper(module_name, outputs): + """Python function closure so we can pass args""" + def hook_forward(module, args, output): + """Hook for forward pass which retains gradients and saves intermediate tensors""" + output.retain_grad() + outputs.append((module_name, output)) + return hook_forward - # grads and layers are global variables - outputs.append((layers[module], output)) +def get_all_layers(model, hook_fn): + """Register forward pass hook to all outputs in model -def get_all_layers(layer, hook_fn): - """Returns dict where keys are children modules and values are layer names""" + Returns layers, a dict with keys as layer/module and values as layer/module names + e.g.: layers[nn.Conv2d] = layer1.0.conv1 + + Returns outputs, a list of tuples with module name and tensor output. e.g.: + outputs[0] == (layer1.0.conv1, tensor.Torch(...)) + + The layer name is passed to a forward hook which will eventually go into a tuple + """ layers = dict() + outputs = [] for name, layer in model.named_modules(): if any(layer.children()) is False: # skip Sequential and/or wrapper modules layers[layer] = name - layer.register_forward_hook(hook_fn) # hook_forward - return layers + layer.register_forward_hook(hook_forward_wrapper(name, outputs)) + return layers, outputs -def get_all_params(model): - """return list of all leaf tensors with requires_grad=True and which are not bias terms""" - params = [] - for name, param in model.named_parameters(): - if param.requires_grad and "bias" not in name: - params.append((name, param)) - return params - -# register hooks -layers = get_all_layers(model, hook_forward) - -# get parameter gradients -params = get_all_params(model) - - -###################################################################### -# Let’s check a few of the layers and parameters to make sure things are -# as expected: -# - -num_layers = 5 -print("<--------Params-------->") -for name, param in params[0:num_layers]: - print(name, param.shape) - -count = 0 -print("<--------Layers-------->") -for layer in layers.values(): - print(layer) - count += 1 - if count >= num_layers: - break +# register hooks +layers_bn, outputs_bn = get_all_layers(model_bn, hook_forward_wrapper) +layers_nobn, outputs_nobn = get_all_layers(model_nobn, hook_forward_wrapper) ###################################################################### -# Now let’s run a forward pass and verify our output tensor values were -# populated. +# Now let’s train the models for a few epochs: # -outputs = [] # list with layer name, output tensor tuple -optimizer.zero_grad() -y_pred = model(x) -loss = F.mse_loss(y_pred, y) +epochs = 10 -print("<--------Outputs-------->") -for name, output in outputs[0:num_layers]: - print(name, output.shape) +for epoch in range(epochs): + + # important to clear, because we append to + # outputs everytime we do a forward pass + outputs_bn.clear() + outputs_nobn.clear() + + optimizer_bn.zero_grad() + optimizer_nobn.zero_grad() + + y_pred_bn = model_bn(x) + y_pred_nobn = model_nobn(x) + + loss_bn = F.cross_entropy(y_pred_bn, y) + loss_nobn = F.cross_entropy(y_pred_nobn, y) + + loss_bn.backward() + loss_nobn.backward() + + optimizer_bn.step() + optimizer_nobn.step() ###################################################################### -# Everything looks good so far, so let’s call ``backward()``, populate the -# ``grad`` values for all intermediate tensors, and get the average -# gradient for each layer. +# After running the forward and backward pass, the ``grad`` values for all +# the intermediate tensors should be present in ``outputs_bn`` and +# ``outputs_nobn``. We reduce the gradient matrix to a single number (mean +# absolute value) so that we can compare the two models. # -loss.backward() - -def get_grads(): +def get_grads(outputs): layer_idx = [] avg_grads = [] - print("<--------Grads-------->") - for idx, (name, output) in enumerate(outputs[0:-2]): + for idx, (name, output) in enumerate(outputs): if output.grad is not None: avg_grad = output.grad.abs().mean() - if idx < num_layers: - print(name, avg_grad) avg_grads.append(avg_grad) layer_idx.append(idx) return layer_idx, avg_grads -layer_idx, avg_grads = get_grads() +layer_idx_bn, avg_grads_bn = get_grads(outputs_bn) +layer_idx_nobn, avg_grads_nobn = get_grads(outputs_nobn) ###################################################################### -# Now that we have all our gradients stored in ``grads``, we can plot them -# and see how the average gradient values change as a function of the -# network depth. +# Now that we have all our gradients stored in ``avg_grads``, we can plot +# them and see how the average gradient values change as a function of the +# network depth. We see that when we don’t have batch normalization, the +# gradient values in the intermediate layers fall to zero very quickly. +# The batch normalization model, however, maintains non-zero gradients in +# its intermediate layers. # -def plot_grads(layer_idx, avg_grads): - plt.plot(layer_idx, avg_grads) - plt.xlabel("Layer depth") - plt.ylabel("Average gradient") - plt.title("Gradient flow") - plt.grid(True) - -plot_grads(layer_idx, avg_grads) +fig, ax = plt.subplots() +ax.plot(layer_idx_bn, avg_grads_bn, label="With BatchNorm", marker="o") +ax.plot(layer_idx_nobn, avg_grads_nobn, label="Without BatchNorm", marker="x") +ax.set_xlabel("Layer depth") +ax.set_ylabel("Average gradient") +ax.set_title("Gradient flow") +ax.grid(True) +ax.legend() +plt.show() ###################################################################### -# Upon initialization, this is not very interesting. Let’s try running for -# several epochs, use gradient descent, and then see how the values -# change. +# Conclusion +# ---------- # - -epochs = 20 - -for epoch in range(epochs): - outputs = [] # list with layer name, output tensor tuple - optimizer.zero_grad() - y_pred = model(x) - loss = F.mse_loss(y_pred, y) - loss.backward() - optimizer.step() - -layer_idx, avg_grads = get_grads() -plot_grads(layer_idx, avg_grads) - - -###################################################################### -# Still not very interesting… surprised that the gradients don’t -# accumulate. Let’s check the leaf tensors… those tensors are probably -# just recreated whenever I rerun the forward pass, and thus they don’t -# accumulate. Let’s see if that’s the case with the parameters. -# - -def get_param_grads(): - layer_idx = [] - avg_grads = [] - print("<--------Params-------->") - for idx, (name, param) in enumerate(params): - if param.grad is not None: - avg_grad = param.grad.abs().mean() - if idx < num_layers: - print(name, avg_grad) - avg_grads.append(avg_grad) - layer_idx.append(idx) - return layer_idx, avg_grads - -layer_idx, avg_grads = get_param_grads() - - -plot_grads(layer_idx, avg_grads) - - -###################################################################### -# (work-in-progress) Conclusion -# ----------------------------- +# In this tutorial, we covered when and how PyTorch computes gradients for +# leaf and non-leaf tensors. By using ``retain_grad``, we can access the +# gradients of intermediate tensors within autograd’s computational graph. +# Building upon this, we then demonstrated how to visualize the gradient +# flow through a neural network wrapped in a ``nn.Module`` class. We +# qualitatively showed how batch normalization helps to alleviate the +# vanishing gradient issue which occurs with deep neural networks. # # If you would like to learn more about how PyTorch’s autograd system # works, please visit the `references <#references>`__ below. If you have @@ -545,6 +559,20 @@ def get_param_grads(): # +###################################################################### +# (Optional) Additional exercises +# ------------------------------- +# +# - Try increasing the number of layers (``num_layers``) in our model and +# see what effect this has on the gradient flow graph +# - How would you adapt the code to visualize average activations instead +# of average gradients? (*Hint: in the ``get_grads()`` function we have +# access to the raw tensor output*) +# - What are some other methods to deal with vanishing and exploding +# gradients? +# + + ###################################################################### # References # ---------- @@ -555,4 +583,11 @@ def get_param_grads(): # torch.autograd `__ # - `Autograd # mechanics `__ +# - `Batch Normalization: Accelerating Deep Network Training by Reducing +# Internal Covariate Shift `__ +# + + +###################################################################### +# # \ No newline at end of file