Dynamic Model Obfuscation
Overview
The MindSpore framework can protect a MindIR model through dynamic obfuscation. The structure of an obfuscated model is different from that of the original model. Therefore, even if someone steals the model, they do not know the network structure of the original model.
The model owner can use the native MindSpore API load()
to load the obfuscated model for inference. The model is also in the obfuscation state during inference, ensuring the security of the model at runtime.
Currently, obfuscated models can be exported from the Linux platform using the native Python API export()
of MindSpore or the Python API obfuscate_model()
.
The following uses an example to describe how to export and load an obfuscated model.
You can download the complete sample code at https://gitee.com/mindspore/docs/blob/master/docs/sample_code/dynamic_obfuscation/test_dynamic_obfuscation.py.
Using export()
to Export Obfuscated Models and Deploying Inference
export()
is a model export API provided by MindSpore. This API can export nn.Cell
networks to model files in multiple formats.
mindspore.export(net, *inputs, file_name, file_format="MINDIR", **kwargs)
To use this API to export an obfuscated model, you need to set the dictionary parameter obf_config
for dynamic obfuscation and input it as kwargs
. Dynamic obfuscation provides two modes to protect models: random_seed and customized function.
The following describes how to export and load obfuscated models in the two modes.
Exporting an Obfuscated Model in random_seed Mode
Prepare a test network ObfuscateNet:
import numpy as np import mindspore as ms import mindspore.ops as ops import mindspore.nn as nn from mindspore.common.initializer import TruncatedNormal ms.set_context(mode=ms.GRAPH_MODE) def weight_variable(): return TruncatedNormal(0.02) def conv(in_channels, out_channels, kernel_size, stride=1, padding=0): weight = weight_variable() return nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, weight_init=weight, has_bias=False, pad_mode="valid") def fc_with_initialize(input_channels, out_channels): weight = weight_variable() bias = weight_variable() return nn.Dense(input_channels, out_channels, weight, bias, has_bias=False) class ObfuscateNet(nn.Cell): def __init__(self): super(ObfuscateNet, self).__init__() self.batch_size = 32 self.conv1 = conv(1, 6, 5) self.conv2 = conv(6, 16, 5) self.matmul = ops.MatMul() self.matmul_weight1 = ms.Tensor(np.random.random((16 * 5 * 5, 120)).astype(np.float32)) self.matmul_weight2 = ms.Tensor(np.random.random((120, 84)).astype(np.float32)) self.matmul_weight3 = ms.Tensor(np.random.random((84, 10)).astype(np.float32)) self.relu = nn.ReLU() self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2) self.flatten = nn.Flatten() def construct(self, x): x = self.conv1(x) x = self.relu(x) x = self.max_pool2d(x) x = self.conv2(x) x = self.relu(x) x = self.max_pool2d(x) x = self.flatten(x) x = self.matmul(x, self.matmul_weight1) x = self.relu(x) x = self.matmul(x, self.matmul_weight2) x = self.relu(x) x = self.matmul(x, self.matmul_weight3) return x
Set the obfuscation dictionary parameter.
obf_config = {"obf_ratio": 0.8, "obf_random_seed": 3423}
As shown in the preceding information,
obf_ratio
indicates the obfuscation ratio, that is, the ratio of obfuscated nodes in the obfuscated model to all model nodes. The value can be a floating point number or a character string. If the value is a floating point number, the value range is (0,1]. If the value is a character string, the valid value is'small'
,'medium'
, or'large'
.obf_random_seed
indicates the obfuscation random seed. The valid value is an integer greater than 0 and less than or equal to int64_max (9223372036854775807).Export the obfuscated model.
net = ObfuscateNet() input_tensor = ms.Tensor(np.ones((1, 1, 32, 32)).astype(np.float32)) obf_config = {"obf_ratio": 0.8, "obf_random_seed": 3423} ms.export(net, input_tensor, file_name="obf_net", file_format="MINDIR", obf_config=obf_config)
After the preceding steps are complete, an obfuscated MindIR model is obtained. (Currently, only obfuscated models in MindIR format can be exported.)
Loading the Obfuscated Model in random_seed Mode
The load()
and nn.GraphCell()
APIs of MindSpore can be used to load obfuscated models for inference. Ensure that the input obf_random_seed
is correct when calling the nn.GraphCell()
API. Otherwise, the obtained inference result is incorrect.
obf_graph = ms.load("obf_net.mindir")
obf_net = nn.GraphCell(obf_graph, obf_random_seed=3423)
right_seed_result = obf_net(input_tensor).asnumpy()
print(right_seed_result)
# Note that the print results may be different under different software versions. The print here is only for comparison of results.
# [[743.6489 844.62427 716.82104 735.7657 802.5662 833.0927 861.00336 769.6415 857.3915 765.9037 ]]
To check whether the inference result is correct, you can obtain the inference result of the original model and compare right_seed_result
with the inference result as follows:
original_predict_result = net(input_tensor).asnumpy()
print(original_predict_result)
# Note that the print results may be different under different software versions. The print here is only for comparison of results.
# [[743.6489 844.62427 716.82104 735.7657 802.5662 833.0927 861.00336 769.6415 857.3915 765.9037 ]]
print(np.all(original_predict_result == right_seed_result))
# True
The comparison result shows that the inference result obtained by entering the correct obf_random_seed is the same as that of the original model, and the accuracy is lossless.
Exporting Obfuscated Models in Customized Function Mode (Only for the CPU Hardware Environment)
In addition to the random_seed mode, dynamic obfuscation also provides the customized function mode. Compared with the random_seed mode, the customized function mode is more secure, but the configuration is more complex. In customized function mode, you need to define a Python function that meets the following requirements: 1. There are 2 input parameters. 2. For any input (which comes from the output of any model layer during inference), the output value of this function is always True or False. Example:
def my_func(x1, x2):
if abs(x1) + abs(x2) < 0:
return True
return False
Note: You can search for Opaque predicate expression to construct functions that meet the preceding requirements. For example:
def opaque_predicate(x, y):
if 7*y^2 - 1 == x^2:
return True
return False
If x and y are integers, opaque_predicate(x, y)
is always False. You can refer to manufacturing cheap, resilient, and stealthy opaque constructs or other literature to learn more about opaque predicates.
After the customized_func
is ready, you can export the obfuscated model as follows:
obf_config = {"obf_ratio": 0.8, "customized_func": my_func}
net = ObfuscateNet()
ms.export(net, input_tensor, file_name="obf_net", file_format="MINDIR", obf_config=obf_config)
Loading the Obfuscated Model in Customized Function Mode
The load()
and nn.GraphCell()
APIs of MindSpore can be used to load obfuscated models for inference. Ensure that the input customized_func
must be correct (the function name and body must be the same as those set during obfuscation) when calling the load()
API. Otherwise, the obtained inference result is incorrect.
obf_graph = ms.load("obf_net.mindir", obf_func=my_func)
obf_net = nn.GraphCell(obf_graph)
right_func_result = obf_net(input_tensor).asnumpy()
Using obfuscate_model()
to Export Obfuscated Models and Deploying Inference
obfuscate_model()
is an API dedicated to MindIR model obfuscation.
ms.obfuscate_model(obf_config, **kwargs)
obfuscate_model()
also provides the random_seed and customized function modes.
The following describes how to export and load obfuscated models in the two modes.
Exporting an Obfuscated Model in random_seed Mode
Set the obfuscation dictionary parameter.
input_tensor = ms.Tensor(np.ones((1, 1, 32, 32)).astype(np.float32)) obf_config = {"original_model_path": "net.mindir", "save_model_path": "./obf_net", "model_inputs": [input_tensor], "obf_ratio": 0.8, "obf_random_seed": 3423}
As shown in the preceding information,
original_model_path
indicates the path of a model to be obfuscated,save_model_path
indicates the path for storing the output of the obfuscated model, andmodel_inputs
indicates the tensor input to the model. The tensor value can be a random number, which is similar to theinputs
ofexport()
. Theobf_ratio
andobf_random_seed
parameters are the same as those of theexport()
API.Export the obfuscated model.
ms.obfuscate_model(obf_config)
After the preceding steps are complete, an obfuscated MindIR model is obtained.
Loading the Obfuscated Model in random_seed Mode
The load()
and nn.GraphCell()
APIs of MindSpore can be used to load obfuscated models for inference. Ensure that the input obf_random_seed
is correct when calling the nn.GraphCell()
API. Otherwise, the obtained inference result is incorrect.
obf_graph = ms.load("obf_net.mindir")
obf_net = nn.GraphCell(obf_graph, obf_random_seed=3423)
right_seed_result = obf_net(input_tensor).asnumpy()
To check whether the inference result is correct, you can obtain the inference result of the original model and compare right_seed_result
with the inference result as follows:
ori_net = ObfuscateNet()
ms.export(ori_net, input_tensor, file_name="net", file_format="MINDIR")
original_graph = ms.load("net.mindir")
original_net = nn.GraphCell(original_graph)
original_predict_result = original_net(input_tensor).asnumpy()
print(np.all(original_predict_result == right_seed_result))
# True
Exporting Obfuscated Models in Customized Function Mode (Only for the CPU Hardware Environment)
Similar to the export
API, you need to prepare a customized function.
def my_func(x1, x2):
if abs(x1) + abs(x2) < 0:
return True
return False
Export the obfuscated model as follows:
net = ObfuscateNet()
input_tensor = ms.Tensor(np.ones((1, 1, 32, 32)).astype(np.float32))
obf_config = {"original_model_path": "net.mindir", "save_model_path": "./obf_net",
"model_inputs": [input_tensor], "obf_ratio": 0.8, "customized_func": my_func}
ms.obfuscate_model(obf_config)
Loading the Obfuscated Model in Customized Function Mode
The load()
and nn.GraphCell()
APIs of MindSpore can be used to load obfuscated models for inference. Ensure that the input customized_func
must be correct (the function name and body must be the same as those set during obfuscation) when calling the load()
API. Otherwise, the obtained inference result is incorrect.
obf_graph = ms.load("obf_net.mindir", obf_func=my_func)
obf_net = nn.GraphCell(obf_graph)
right_func_result = obf_net(input_tensor).asnumpy()
Note: Dynamic obfuscation currently does not support dynamic shape input, so inputs
/ model_inputs
cannot be in the form of dynamic shapes (some dimensions of Tensors are None) when calling export()
/ obfuscate_model()
. For example, inputs=Tensor(shape=[1, 1, None, None], dtype=ms.float32)
is not allowed.