使用SymbolTree.get_network处理conv2d算子时报错NameError:name "Cell" is not defined

1 系统环境

硬件环境(Ascend/GPU/CPU): CPU
MindSpore版本: mindspore=2.0
执行模式(PyNative/ Graph):不限
Python版本: Python=3.9
操作系统平台: 不限

2 报错信息

2.1 问题描述

在执行stree.get_network()语句时报错,报错信息如下图所示:

2.2 报错信息

2.3 脚本代码

import mindspore.nn as nn  
from mindspore.rewrite import SymbolTree  
old_model=nn.Conv2d(3,64,1,1)  
stree = SymbolTree.create(old_model)  
copy_model = stree.get_network()

3 根因分析

官网文档

涉及两个接口create 和 get_network



首先看Conv2d 继承了_Conv,_Conv又继承了Cell, 因此SymbolTree.create(old_model)old_model也是个Cell对象,因此不会报错。

class Conv2d(_Conv):

class _Conv(Cell):

报的错是get_network返回的。获取SymbolTree所对应的生成的网络对象。

def _get_cls_through_file(self):  
    """  
    Load rewritten network class of current SymbolTree.  
    1. Get source code of current SymbolTree.  
    2. Saving source code to a tempfile.  
    3. Import rewritten network class using "__import__" function.  

    Returns:  
        A class handle.  
    """  
    self._update_container()  
    file_path = os.getcwd()  
    file_path = os.path.join(file_path, "rewritten_network")  
    if not os.path.exists(file_path):  
        os.mkdir(file_path)  
    file_name = "{0}_{1}.py".format(self._opt_cls_name, id(self))  
    network_file = os.path.join(file_path, file_name)  
    with os.fdopen(os.open(network_file, os.O_WRONLY | os.O_CREAT, stat.S_IRWXU), 'wb') as f:  
        source = self.get_code()  
        f.write(source.encode('utf-8'))  
        f.flush()  
        os.fsync(f)

调用的是上面的函数,从当前网络中获取所对应的生成的网络对象的源码,然后再进行导入。
问题就出在这,自动生成的代码如下。

import sys  
sys.path.append('C:\\Users\\yuany\\.conda\\envs\\mindspore_py39\\lib\\site-packages\\mindspore\\nn\\layer')  
import mindspore  
import numpy as np  
from mindspore._extends import cell_attr_register  
    
class _Conv(Cell):  
    '\n    Applies a N-D convolution over an input signal composed of several input planes.\n    '  
    
    def __init__(self, obj):  
        super(_Conv, self).__init__()  
        for (key, value) in obj.__dict__.items():  
            setattr(self, key, value)  
    
    def construct(self, *inputs):  
        'Must be overridden by all subclasses.'  
        raise NotImplementedError  
    
    def extend_repr(self):  
        s = 'input_channels={}, output_channels={}, kernel_size={}, stride={}, pad_mode={}, padding={}, dilation={}, group={}, has_bias={}, weight_init={}, bias_init={}, format={}'.format(self.in_channels, self.out_channels, self.kernel_size, self.stride, self.pad_mode, self.padding, self.dilation, self.group, self.has_bias, self.weight_init, self.bias_init, self.data_format)  
        return s  
    
class Conv2dOpt(_Conv):  
    '\n    Calculates the 2D convolution on the input tensor.\n    The input is typically of shape :math:`(N, C_{in}, H_{in}, W_{in})`,\n    where :math:`N` is batch size, :math:`C_{in}` is a number of channels,\n    :math:`H_{in}, W_{in}` are the height and width of the feature layer respectively.\n    For the tensor of each batch, its shape is :math:`(C_{in}, H_{in}, W_{in})`, the formula is defined as:\n\n    .. math::\n\n        \\text{out}(N_i, C_{\\text{out}_j}) = \\text{bias}(C_{\\text{out}_j}) +\n        \\sum_{k = 0}^{C_{in} - 1} \\text{ccor}({\\text{weight}(C_{\\text{out}_j}, k), \\text{X}(N_i, k)})\n\n    where :math:`ccor` is the `cross-correlation <https://en.wikipedia.org/wiki/Cross-correlation>`_,\n    :math:`C_{in}` is the channel number of the input, :math:`out_{j}` corresponds to the :math:`j`-th channel of\n    the output and :math:`j` is in the range of :math:`[0, C_{out}-1]`. :math:`\\text{weight}(C_{\\text{out}_j}, k)`\n    is a convolution kernel slice with shape :math:`(\\text{kernel_size[0]}, \\text{kernel_size[1]})`,\n    where :math:`\\text{kernel_size[0]}` and :math:`\\text{kernel_size[1]}` are the height and width of the convolution\n    kernel respectively. :math:`\\text{bias}` is the bias parameter and :math:`\\text{X}` is the input tensor.\n    In this case, `data_format` of the input tensor is \'NCHW\' and the shape of full convolution kernel is\n    :math:`(C_{out}, C_{in} / \\text{group}, \\text{kernel_size[0]}, \\text{kernel_size[1]})`,\n    where `group` is the number of groups to split the input `x` in the channel dimension. If `data_format` of the\n    input tensor is \'NHWC\', the shape of full convolution kernel will be\n    :math:`(C_{out}, \\text{kernel_size[0]}, \\text{kernel_size[1]}), C_{in} / \\text{group}`.\n\n    For more details, please refers to the paper `Gradient Based Learning Applied to Document\n    Recognition <http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf>`_.\n\n    Note:\n        On Ascend platform, only group convolution in depthwise convolution scenarios is supported.\n        That is, when `group>1`, condition `in\\_channels` = `out\\_channels` = `group` must be satisfied.\n\n    Args:\n        in_channels (int): The channel number of the input tensor of the Conv2d layer.\n        out_channels (int): The channel number of the output tensor of the Conv2d layer.\n        kernel_size (Union[int, tuple[int]]): Specifies the height and width of the 2D convolution kernel.\n            The data type is an integer or a tuple of two integers. An integer represents the height\n            and width of the convolution kernel. A tuple of two integers represents the height\n            and width of the convolution kernel respectively.\n        stride (Union[int, tuple[int]]): The movement stride of the 2D convolution kernel.\n            The data type is an integer or a tuple of two integers. An integer represents the movement step size\n            in both height and width directions. A tuple of two integers represents the movement step size in the height\n            and width directions respectively. Default: 1.\n        pad_mode (str): Specifies padding mode. The optional values are\n            "same", "valid", "pad". Default: "same".\n\n            - same: The width of the output is the same as the value of the input divided by `stride`.\n              If this mode is set, the value of `padding` must be 0.\n\n            - valid: Returns a valid calculated output without padding. Excess pixels that do not satisfy the\n              calculation will be discarded. If this mode is set, the value of `padding` must be 0.\n\n            - pad: Pads the input. Padding `padding` size of zero on both sides of the input.\n              If this mode is set, the value of `padding` must be greater than or equal to 0.\n\n        padding (Union[int, tuple[int]]): The number of padding on the height and width directions of the input.\n            The data type is an integer or a tuple of four integers. If `padding` is an integer,\n            then the top, bottom, left, and right padding are all equal to `padding`.\n            If `padding` is a tuple of 4 integers, then the top, bottom, left, and right padding\n            is equal to `padding[0]`, `padding[1]`, `padding[2]`, and `padding[3]` respectively.\n            The value should be greater than or equal to 0. Default: 0.\n        dilation (Union[int, tuple[int]]): Dilation size of 2D convolution kernel.\n            The data type is an integer or a tuple of two integers. If :math:`k > 1`, the kernel is sampled\n            every `k` elements. The value of `k` on the height and width directions is in range of [1, H]\n            and [1, W] respectively. Default: 1.\n        group (int): Splits filter into groups, `in_channels` and `out_channels` must be\n            divisible by `group`. If the group is equal to `in_channels` and `out_channels`,\n            this 2D convolution layer also can be called 2D depthwise convolution layer. Default: 1.\n        has_bias (bool): Whether the Conv2d layer has a bias parameter. Default: False.\n        weight_init (Union[Tensor, str, Initializer, numbers.Number]): Initialization method of weight parameter.\n            It can be a Tensor, a string, an Initializer or a numbers.Number. When a string is specified,\n            values from \'TruncatedNormal\', \'Normal\', \'Uniform\', \'HeUniform\' and \'XavierUniform\' distributions as well\n            as constant \'One\' and \'Zero\' distributions are possible. Alias \'xavier_uniform\', \'he_uniform\', \'ones\'\n            and \'zeros\' are acceptable. Uppercase and lowercase are both acceptable. Refer to the values of\n            Initializer for more details. Default: \'normal\'.\n        bias_init (Union[Tensor, str, Initializer, numbers.Number]): Initialization method of bias parameter.\n            Available initialization methods are the same as \'weight_init\'. Refer to the values of\n            Initializer for more details. Default: \'zeros\'.\n        data_format (str): The optional value for data format, is \'NHWC\' or \'NCHW\'.\n            Default: \'NCHW\'.\n\n    Inputs:\n        - **x** (Tensor) - Tensor of shape :math:`(N, C_{in}, H_{in}, W_{in})` \\\n          or :math:`(N, H_{in}, W_{in}, C_{in})`.\n\n    Outputs:\n        Tensor of shape :math:`(N, C_{out}, H_{out}, W_{out})` or :math:`(N, H_{out}, W_{out}, C_{out})`.\n\n        pad_mode is \'same\':\n\n        .. math::\n            \\begin{array}{ll} \\\\\n                H_{out} = \\left \\lceil{\\frac{H_{in}}{\\text{stride[0]}}} \\right \\rceil \\\\\n                W_{out} = \\left \\lceil{\\frac{W_{in}}{\\text{stride[1]}}} \\right \\rceil \\\\\n            \\end{array}\n\n        pad_mode is \'valid\':\n\n        .. math::\n            \\begin{array}{ll} \\\\\n                H_{out} = \\left \\lceil{\\frac{H_{in} - \\text{dilation[0]} \\times (\\text{kernel_size[0]} - 1) }\n                {\\text{stride[0]}}} \\right \\rceil \\\\\n                W_{out} = \\left \\lceil{\\frac{W_{in} - \\text{dilation[1]} \\times (\\text{kernel_size[1]} - 1) }\n                {\\text{stride[1]}}} \\right \\rceil \\\\\n            \\end{array}\n\n        pad_mode is \'pad\':\n\n        .. math::\n            \\begin{array}{ll} \\\\\n                H_{out} = \\left \\lfloor{\\frac{H_{in} + padding[0] + padding[1] - (\\text{kernel_size[0]} - 1) \\times\n                \\text{dilation[0]} - 1 }{\\text{stride[0]}} + 1} \\right \\rfloor \\\\\n                W_{out} = \\left \\lfloor{\\frac{W_{in} + padding[2] + padding[3] - (\\text{kernel_size[1]} - 1) \\times\n                \\text{dilation[1]} - 1 }{\\text{stride[1]}} + 1} \\right \\rfloor \\\\\n            \\end{array}\n\n    Raises:\n        TypeError: If `in_channels`, `out_channels` or `group` is not an int.\n        TypeError: If `kernel_size`, `stride`, `padding` or `dilation` is neither an int not a tuple.\n        ValueError: If `in_channels`, `out_channels`, `kernel_size`, `stride` or `dilation` is less than 1.\n        ValueError: If `padding` is less than 0.\n        ValueError: If `pad_mode` is not one of \'same\', \'valid\', \'pad\'.\n        ValueError: If `padding` is a tuple whose length is not equal to 4.\n        ValueError: If `pad_mode` is not equal to \'pad\' and `padding` is not equal to (0, 0, 0, 0).\n        ValueError: If `data_format` is neither \'NCHW\' not \'NHWC\'.\n\n    Supported Platforms:\n        ``Ascend`` ``GPU`` ``CPU``\n\n    Examples:\n        >>> net = nn.Conv2d(120, 240, 4, has_bias=False, weight_init=\'normal\')\n        >>> x = Tensor(np.ones([1, 120, 1024, 640]), mindspore.float32)\n        >>> output = net(x).shape\n        >>> print(output)\n        (1, 240, 1024, 640)\n    '  
    
    @cell_attr_register  
    def __init__(self, obj):  
        super(Conv2dOpt, self).__init__(obj)  
        for (key, value) in obj.__dict__.items():  
            setattr(self, key, value)  
    
    def construct(self, x):  
        self_weight = self.weight  
        output = self.conv2d(x, self_weight)  
        # If node has been replaced by False branch.  
        return output

以上代码的路径是在当前代码的rewritten_network目录下。
从上面的代码可以看出,它把nn.Conv2d(3,64,1,1)作为网络,获取的是nn.Conv2d的定义。
而nn.Conv2d是系统定义的,所以导入肯定会出错。即使解决Cell的导入问题,最终代码也不能运行,因为会有循环导入的问题存在。

4 解决方案

创建一个网络,然后再进行create操作。

import mindspore.nn as nn  
from mindspore.rewrite import SymbolTree  
    
    
class Net(nn.Cell):  
    def construct(self, x):  
        x = nn.Conv2d(3, 64, 1, 1)(x)  
        return x  
    
    
old_model = Net()  
stree = SymbolTree.create(old_model)  
copy_model = stree.get_network()

运行无报错。


rewritten_network下生成的代码如下:可以看出自动获取的网络对象的定义代码是正确的。

import sys  
sys.path.append('D:\\f')  
import mindspore  
from mindspore import nn  
import mindspore.nn as nn  
import numpy as np  
    
class NetOpt(nn.Cell):  
    
    def __init__(self, obj):  
        super().__init__()  
        for (key, value) in obj.__dict__.items():  
            setattr(self, key, value)  
    
    def construct(self, x):  
        x_1 = nn.Conv2d(3, 64, 1, 1)(x)  
        return x_1