Can't assign to parameter, but is actually None

taha_entesari: <p>I have tried to recreate the error that I get for a code in this simple code snippet.<br>
Why does the following code throw an error?<br>
If you run this code, it will run into an error the second time it goes into the training loop on line 18 (“self.matrix2 = result”) that “TypeError: cannot assign ‘torch.FloatTensor’ as parameter ‘matrix2’ (torch.nn.Parameter or None expected)”. My issue is that when the code enters the if block, we are already sure that self.matrix2 is None. So why is this error happening?</p>
<p>Solving the error is simple and it can be done by simply using the commented line in the function miniBatchStep. My issue is just I don’t completely get why this error is happening.</p>
<pre><code class="lang-auto">import torch
import torch.nn as nn


def func(vv):
    uu = [vv[:, 0:1], vv[:, 1:2]]
    return torch.hstack(uu)

class Test(nn.Module):
    def __init__(self):
        super(Test, self).__init__()
        self.matrix = nn.Parameter(torch.randn(2, 2), requires_grad=True)
        self.matrix2 = None

    def getModifiedParam(self):
        if self.matrix2 is None:
            result = func(self.matrix)
            self.matrix2 = result
        return self.matrix2

    def forward(self, x):
        return x @ self.getModifiedParam()



    def miniBatchStep(self):
        self.matrix2 = None
        with torch.no_grad():
            self.matrix.data = self.getModifiedParam()
        self.matrix2 = self.matrix
        # self.matrix2 = self.matrix.clone()


def main():
    net = Test()
    optimizer = torch.optim.Adam(net.parameters(), lr=0.1)
    for _ in range(2):
        x = torch.randn(2, 2)
        net(x).sum().backward()
        optimizer.step()
        with torch.no_grad():
            optimizer.zero_grad()
        net.miniBatchStep()

if __name__ == "__main__":
    main()
</code></pre>
<p>I have got the error on python 3.11.3 and torch 2.2.1 on mac, and python 3.8.10 and torch 2.1.2+cu118 on ubuntu 20.04.4 LTS.</p>

KFrank: <p>Hi Taha!</p>
<aside class="quote no-group" data-username="taha_entesari" data-post="1" data-topic="198443" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/taha_entesari/48/34739_2.png" class="avatar"> taha_entesari:</div>
<blockquote>
<p>the second time it goes into the training loop on line 18 (“self.matrix2 = result”) that “TypeError: cannot assign ‘torch.FloatTensor’ as parameter ‘matrix2’ (torch.nn.Parameter or None expected)”.</p>
</blockquote>
</aside>
<p>The short story is that <code>Module</code>s treat <code>Parameter</code>s specially.</p>
<p>For example, quoting from <a href="https://pytorch.org/docs/2.2/generated/torch.nn.parameter.Parameter.html" rel="noopener nofollow ugc">the Parameter documentation:</a></p>
<blockquote>
<p>Parameters are <a href="https://pytorch.org/docs/2.2/tensors.html#torch.Tensor" rel="noopener nofollow ugc"><code>Tensor</code></a> subclasses, that have a very special property when used with <code>Module</code> s - when they’re assigned as Module attributes they are automatically added to the list of its parameters</p>
</blockquote>
<p>In particular, once a property of a <code>Module</code> has a <code>Parameter</code> assigned to it,<br>
the <code>Module</code> keeps track of this and going forward you can’t assign anything<br>
other than a <code>Parameter</code> or <code>None</code> to that property.</p>
<p>Consider this example script:</p>
<pre data-code-wrap="python"><code class="lang-python">import torch
print (torch.__version__)

class MyClass():
    def __init__ (self):
        pass

class MyModule (torch.nn.Module):
    def __init__ (self):
        super().__init__()

myClass = MyClass()
myModule = MyModule()

t = torch.zeros (3)
p = torch.nn.Parameter (torch.ones (3))

myClass.s = p       # no constraint on property s
print (type (myClass.s))
myClass.s = None
print (type (myClass.s))
myClass.s = t       # no issue
print (type (myClass.s))

myModule.s = p      # property s must from now on be a Parameter or None
print (type (myModule.s))
myModule.s = None   # type of s is NoneType
print (type (myModule.s))
myModule.s = t      # raises TypeError
</code></pre>
<p>Here is its output:</p>
<pre><code class="lang-plaintext">2.2.1
&lt;class 'torch.nn.parameter.Parameter'&gt;
&lt;class 'NoneType'&gt;
&lt;class 'torch.Tensor'&gt;
&lt;class 'torch.nn.parameter.Parameter'&gt;
&lt;class 'NoneType'&gt;
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
  File "&lt;string&gt;", line 33, in &lt;module&gt;
  File "&lt;path_to_pytorch_install&gt;\torch\nn\modules\module.py", line 1708, in __setattr__
    raise TypeError(f"cannot assign '{torch.typename(value)}' as parameter '{name}' "
TypeError: cannot assign 'torch.FloatTensor' as parameter 's' (torch.nn.Parameter or None expected)
</code></pre>
<aside class="quote no-group">
<blockquote>
<pre><code class="lang-auto">    def getModifiedParam(self):
        if self.matrix2 is None:
            result = func(self.matrix)
            self.matrix2 = result
</code></pre>
</blockquote>
</aside>
<p>The first time through, this line assigns a <code>Tensor</code> to <code>self.matrix2</code>.  At this<br>
point <code>self.matrix2</code> has not yet been a <code>Parameter</code>, so the assignment<br>
succeeds.</p>
<aside class="quote no-group">
<blockquote>
<pre><code class="lang-auto">    def miniBatchStep(self):
        self.matrix2 = None
        with torch.no_grad():
            self.matrix.data = self.getModifiedParam()
        self.matrix2 = self.matrix
        # self.matrix2 = self.matrix.clone()
</code></pre>
</blockquote>
</aside>
<p><code>self.matrix</code> is a <code>Parameter</code> so <code>self.matrix2 = self.matrix</code> assigns<br>
a <code>Parameter</code> to <code>self.matrix2</code>.  When you then try to execute<br>
<code>getModifiedParam()</code> a second time, the line <code>self.matrix2 = result</code><br>
tries to assign a <code>Tensor</code> to what is now flagged to be either a <code>Parameter</code><br>
or <code>None</code>.</p>
<p>However, the <code>.clone()</code> of a <code>Parameter</code> is just a <code>Tensor</code> (rather than still<br>
a <code>Parameter</code>), so when you use the commented-out line, you avoid ever<br>
assigning a <code>Parameter</code> to <code>self.matrix2</code> and no error occurs.</p>
<p>Best.</p>
<p>K. Frank</p>

taha_entesari: <p>Ah, ok. Thanks a lot. Now it makes sense.<br>
Once a parameter is created inside a child of nn.Module, it will always treat that variable name as a parameter.</p>
<p>Thanks a lot.</p>