• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020-2021 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Provide random seed api."""
16from __future__ import absolute_import
17
18import numpy as np
19from mindspore import _checkparam as Validator
20
21# constants
22DEFAULT_GRAPH_SEED = 87654321
23_MAXINT32 = 2**31 - 1
24keyConstant = [3528531795, 2654435769, 3449720151, 3144134277]
25
26# set global RNG seed
27_GLOBAL_SEED = None
28_KERNEL_SEED = {}
29
30
31def _reset_op_seed():
32    """
33    Reset op seeds in the kernel's dictionary.
34    """
35    for (kernel_name, op_seed) in _KERNEL_SEED:
36        _KERNEL_SEED[(kernel_name, op_seed)] = op_seed
37
38
39def set_seed(seed):
40    """
41    Set global seed.
42
43    Note:
44        - The global seed is used by numpy.random, mindspore.common.Initializer and
45          mindspore.nn.probability.distribution.
46
47        - If global seed is not set, these packages will use their own default seed independently, numpy.random and
48          mindspore.common.Initializer will choose a random seed, mindspore.nn.probability.distribution will use zero.
49
50        - Seed set by numpy.random.seed() only used by numpy.random, while seed set by this API will also used by
51          numpy.random, so just set all seed by this API is recommended.
52
53        - In semi_auto_parallel/auto_parallel mode, when using set_seed, weights with same shape and same sharding
54          strategy in the same device would be initialized to the same result, otherwise, they would be initialized to
55          the different result.
56
57    Args:
58        seed (int): The seed to be set.
59
60    Raises:
61        ValueError: If seed is invalid (< 0).
62        TypeError: If seed isn't an int.
63
64    Examples:
65        >>> import numpy as np
66        >>> import mindspore as ms
67        >>> from mindspore import Tensor, set_seed, Parameter, ops
68        >>> from mindspore.common.initializer import initializer
69        >>> # Note: (1) Please make sure the code is running in PYNATIVE MODE;
70        >>> # (2) Because Composite-level ops need parameters to be Tensors, for below examples,
71        >>> # when using ops.uniform operator, minval and maxval are initialised as:
72        >>> minval = Tensor(1.0, ms.float32)
73        >>> maxval = Tensor(2.0, ms.float32)
74        >>>
75        >>> # 1. If global seed is not set, numpy.random and initializer will choose a random seed:
76        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1
77        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A2
78        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W1
79        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W2
80        >>> # Rerun the program will get different results:
81        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A3
82        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A4
83        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W3
84        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W4
85        >>>
86        >>> # 2. If global seed is set, numpy.random and initializer will use it:
87        >>> set_seed(1234)
88        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1
89        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A2
90        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W1
91        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W2
92        >>> # Rerun the program will get the same results:
93        >>> set_seed(1234)
94        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1
95        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A2
96        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W1
97        >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W2
98        >>>
99        >>> # 3. If neither global seed nor op seed is set, mindspore.ops.function.random_func and
100        >>> # mindspore.nn.probability.distribution will choose a random seed:
101        >>> c1 = ops.uniform((1, 4), minval, maxval) # C1
102        >>> c2 = ops.uniform((1, 4), minval, maxval) # C2
103        >>> # Rerun the program will get different results:
104        >>> c1 = ops.uniform((1, 4), minval, maxval) # C3
105        >>> c2 = ops.uniform((1, 4), minval, maxval) # C4
106        >>>
107        >>> # 4. If global seed is set, but op seed is not set, mindspore.ops.function.random_func and
108        >>> # mindspore.nn.probability.distribution will calculate a seed according to global seed and
109        >>> # default op seed. Each call will change the default op seed, thus each call get different
110        >>> # results.
111        >>> set_seed(1234)
112        >>> c1 = ops.uniform((1, 4), minval, maxval) # C1
113        >>> c2 = ops.uniform((1, 4), minval, maxval) # C2
114        >>> # Rerun the program will get the same results:
115        >>> set_seed(1234)
116        >>> c1 = ops.uniform((1, 4), minval, maxval) # C1
117        >>> c2 = ops.uniform((1, 4), minval, maxval) # C2
118        >>>
119        >>> # 5. If both global seed and op seed are set, mindspore.ops.function.random_func and
120        >>> # mindspore.nn.probability.distribution will calculate a seed according to global seed and
121        >>> # op seed counter. Each call will change the op seed counter, thus each call get different
122        >>> # results.
123        >>> set_seed(1234)
124        >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1
125        >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2
126        >>> # Rerun the program will get the same results:
127        >>> set_seed(1234)
128        >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1
129        >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2
130        >>>
131        >>> # 6. If op seed is set but global seed is not set, 0 will be used as global seed. Then
132        >>> # mindspore.ops.function.random_func and mindspore.nn.probability.distribution act as in
133        >>> # condition 5.
134        >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1
135        >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2
136        >>> # Rerun the program will get the different results:
137        >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1
138        >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2
139        >>>
140        >>> # 7. Recall set_seed() in the program will reset numpy seed and op seed counter of
141        >>> # mindspore.ops.function.random_func and mindspore.nn.probability.distribution.
142        >>> set_seed(1234)
143        >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1
144        >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1
145        >>> set_seed(1234)
146        >>> np_2 = np.random.normal(0, 1, [1]).astype(np.float32) # still get A1
147        >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # still get C1
148    """
149    if not isinstance(seed, int):
150        raise TypeError("The argument 'seed' must be type of int, but got {}.".format(type(seed)))
151    Validator.check_non_negative_int(seed, "seed", "global_seed")
152    import mindspore.dataset as de
153    np.random.seed(seed)
154    de.config.set_seed(seed)
155    _reset_op_seed()
156    global _GLOBAL_SEED
157    _GLOBAL_SEED = seed
158
159
160def get_seed():
161    """
162    Get global seed.
163
164    Returns:
165        Integer. The global seed.
166
167    Examples:
168        >>> import mindspore as ms
169        >>> ms.set_seed(1234)
170        >>> seed = ms.get_seed()
171        >>> print(seed)
172        1234
173    """
174    return _GLOBAL_SEED
175
176
177def _truncate_seed(seed):
178    """
179    Truncate the seed with MAXINT32.
180
181    Args:
182        seed (int): The seed to be truncated.
183
184    Returns:
185        Integer. The seed with MAXINT32.
186    """
187    return seed % _MAXINT32
188
189
190def _update_seeds(op_seed, kernel_name):
191    """
192    Update the seed every time when the op seed is called.
193
194    Args:
195        op_seed (int): The op seed to be updated.
196        kernel_name (string): The random op kernel.
197    """
198    global _KERNEL_SEED
199    if op_seed is not None:
200        _KERNEL_SEED[(kernel_name, op_seed)] = _KERNEL_SEED.get((kernel_name, op_seed)) + \
201                                               (keyConstant[0] ^ keyConstant[2])
202
203
204def _get_op_seed(op_seed, kernel_name):
205    """
206    Get op seed which is relating to the specific kernel.
207    If the seed does not exist, add it into the kernel's dictionary.
208
209    Args:
210        op_seed (int): The op seed to be updated.
211        kernel_name (string): The random op kernel.
212    """
213    if (kernel_name, op_seed) not in _KERNEL_SEED:
214        _KERNEL_SEED[(kernel_name, op_seed)] = op_seed
215    return _KERNEL_SEED[(kernel_name, op_seed)]
216
217
218def _get_graph_seed(op_seed, kernel_name):
219    """
220    Get the graph-level seed.
221    Graph-level seed is used as a global variable, that can be used in different ops in case op-level seed is not set.
222    If op-level seed is 0, use graph-level seed; if graph-level seed is also 0, the system would generate a
223    random seed.
224
225    Note:
226        For each seed, either op-seed or graph-seed, a random sequence will be generated relating to this seed.
227        So, the state of the seed regarding to this op should be recorded.
228        A simple illustration should be:
229          If a random op is called twice within one program, the two results should be different:
230          minval = Tensor(1.0, mstype.float32)
231          maxval = Tensor(2.0, mstype.float32)
232          print(C.uniform((1, 4), minval, maxval, seed=1))  # generates 'A1'
233          print(C.uniform((1, 4), minval, maxval, seed=1))  # generates 'A2'
234          If the same program runs again, it repeat the results:
235          print(C.uniform((1, 4), minval, maxval, seed=1))  # generates 'A1'
236          print(C.uniform((1, 4), minval, maxval, seed=1))  # generates 'A2'
237
238    Returns:
239        Integer. The current graph-level seed.
240
241    Examples:
242        >>> print(_get_graph_seed(0, 'normal'))
243        (0, 0)
244    """
245    global_seed = get_seed()
246    if global_seed == 0:
247        global_seed = DEFAULT_GRAPH_SEED
248    elif global_seed is None:
249        global_seed = 0
250    if op_seed is None:
251        op_seed = 0
252    # neither global seed or op seed is set, return (0, 0) to let kernel choose random seed.
253    if global_seed == 0 and op_seed == 0:
254        seeds = 0, 0
255    else:
256        Validator.check_non_negative_int(op_seed, "seed", kernel_name)
257        temp_seed = _get_op_seed(op_seed, kernel_name)
258        seeds = _truncate_seed(global_seed), _truncate_seed(temp_seed)
259        _update_seeds(op_seed, kernel_name)
260    return seeds
261