def keras_model_to_graph_def(keras_layer): """Returns a GraphDef representation of the Keras model in a dict form. Note that it only supports models that implemented to_json(). Args: keras_layer: A dict from Keras model.to_json(). Returns: A GraphDef representation of the layers in the model. """ input_to_layer = {} model_name_to_output = {} g = GraphDef() # Sequential model layers do not have a field "inbound_nodes" but # instead are defined implicitly via order of layers. prev_node_name = None for (name_scope, layer) in _walk_layers(keras_layer): if _is_model(layer): (input_to_layer, model_name_to_output, prev_node_name) = _update_dicts( name_scope, layer, input_to_layer, model_name_to_output, prev_node_name) continue layer_config = layer.get('config') node_name = _scoped_name(name_scope, layer_config.get('name')) node_def = g.node.add() node_def.name = node_name if layer.get('class_name') is not None: keras_cls_name = layer.get('class_name').encode('ascii') node_def.attr['keras_class'].s = keras_cls_name if layer_config.get('dtype') is not None: tf_dtype = dtypes.as_dtype(layer_config.get('dtype')) node_def.attr['dtype'].type = tf_dtype.as_datatype_enum if layer.get('inbound_nodes') is not None: for maybe_inbound_node in layer.get('inbound_nodes'): inbound_nodes = _norm_to_list_of_layers(maybe_inbound_node) for [name, size, index, _] in inbound_nodes: inbound_name = _scoped_name(name_scope, name) # An input to a layer can be output from a model. In that case, the name # of inbound_nodes to a layer is a name of a model. Remap the name of the # model to output layer of the model. Also, since there can be multiple # outputs in a model, make sure we pick the right output_layer from the model. inbound_node_names = model_name_to_output.get( inbound_name, [inbound_name]) node_def.input.append(inbound_node_names[index]) elif prev_node_name is not None: node_def.input.append(prev_node_name) if node_name in input_to_layer: node_def.input.append(input_to_layer.get(node_name)) prev_node_name = node_def.name return g
def make_ndarray(tensor): """Create a numpy ndarray from a tensor. Create a numpy ndarray with the same shape and data as the tensor. Args: tensor: A TensorProto. Returns: A numpy array with the tensor contents. Raises: TypeError: if tensor has unsupported type. """ shape = [d.size for d in tensor.tensor_shape.dim] num_elements = np.prod(shape, dtype=np.int64) tensor_dtype = dtypes.as_dtype(tensor.dtype) dtype = tensor_dtype.as_numpy_dtype if tensor.tensor_content: return (np.frombuffer(tensor.tensor_content, dtype=dtype).copy().reshape(shape)) elif tensor_dtype == dtypes.float16 or tensor_dtype == dtypes.bfloat16: # the half_val field of the TensorProto stores the binary representation # of the fp16: we need to reinterpret this as a proper float16 if len(tensor.half_val) == 1: tmp = np.array(tensor.half_val[0], dtype=np.uint16) tmp.dtype = tensor_dtype.as_numpy_dtype return np.repeat(tmp, num_elements).reshape(shape) else: tmp = np.fromiter(tensor.half_val, dtype=np.uint16) tmp.dtype = tensor_dtype.as_numpy_dtype return tmp.reshape(shape) elif tensor_dtype == dtypes.float32: if len(tensor.float_val) == 1: return np.repeat(np.array(tensor.float_val[0], dtype=dtype), num_elements).reshape(shape) else: return np.fromiter(tensor.float_val, dtype=dtype).reshape(shape) elif tensor_dtype == dtypes.float64: if len(tensor.double_val) == 1: return np.repeat(np.array(tensor.double_val[0], dtype=dtype), num_elements).reshape(shape) else: return np.fromiter(tensor.double_val, dtype=dtype).reshape(shape) elif tensor_dtype in [ dtypes.int32, dtypes.uint8, dtypes.uint16, dtypes.int16, dtypes.int8, dtypes.qint32, dtypes.quint8, dtypes.qint8, dtypes.qint16, dtypes.quint16, ]: if len(tensor.int_val) == 1: return np.repeat(np.array(tensor.int_val[0], dtype=dtype), num_elements).reshape(shape) else: return np.fromiter(tensor.int_val, dtype=dtype).reshape(shape) elif tensor_dtype == dtypes.int64: if len(tensor.int64_val) == 1: return np.repeat(np.array(tensor.int64_val[0], dtype=dtype), num_elements).reshape(shape) else: return np.fromiter(tensor.int64_val, dtype=dtype).reshape(shape) elif tensor_dtype == dtypes.string: if len(tensor.string_val) == 1: return np.repeat(np.array(tensor.string_val[0], dtype=dtype), num_elements).reshape(shape) else: return np.array([x for x in tensor.string_val], dtype=dtype).reshape(shape) elif tensor_dtype == dtypes.complex64: it = iter(tensor.scomplex_val) if len(tensor.scomplex_val) == 2: return np.repeat( np.array( complex(tensor.scomplex_val[0], tensor.scomplex_val[1]), dtype=dtype, ), num_elements, ).reshape(shape) else: return np.array([complex(x[0], x[1]) for x in zip(it, it)], dtype=dtype).reshape(shape) elif tensor_dtype == dtypes.complex128: it = iter(tensor.dcomplex_val) if len(tensor.dcomplex_val) == 2: return np.repeat( np.array( complex(tensor.dcomplex_val[0], tensor.dcomplex_val[1]), dtype=dtype, ), num_elements, ).reshape(shape) else: return np.array([complex(x[0], x[1]) for x in zip(it, it)], dtype=dtype).reshape(shape) elif tensor_dtype == dtypes.bool: if len(tensor.bool_val) == 1: return np.repeat(np.array(tensor.bool_val[0], dtype=dtype), num_elements).reshape(shape) else: return np.fromiter(tensor.bool_val, dtype=dtype).reshape(shape) else: raise TypeError("Unsupported tensor type: %s" % tensor.dtype)
def make_tensor_proto(values, dtype=None, shape=None, verify_shape=False): """Create a TensorProto. Args: values: Values to put in the TensorProto. dtype: Optional tensor_pb2 DataType value. shape: List of integers representing the dimensions of tensor. verify_shape: Boolean that enables verification of a shape of values. Returns: A `TensorProto`. Depending on the type, it may contain data in the "tensor_content" attribute, which is not directly useful to Python programs. To access the values you should convert the proto back to a numpy ndarray with `tensor_util.MakeNdarray(proto)`. If `values` is a `TensorProto`, it is immediately returned; `dtype` and `shape` are ignored. Raises: TypeError: if unsupported types are provided. ValueError: if arguments have inappropriate values or if verify_shape is True and shape of values is not equals to a shape from the argument. make_tensor_proto accepts "values" of a python scalar, a python list, a numpy ndarray, or a numpy scalar. If "values" is a python scalar or a python list, make_tensor_proto first convert it to numpy ndarray. If dtype is None, the conversion tries its best to infer the right numpy data type. Otherwise, the resulting numpy array has a convertible data type with the given dtype. In either case above, the numpy ndarray (either the caller provided or the auto converted) must have the convertible type with dtype. make_tensor_proto then converts the numpy array to a tensor proto. If "shape" is None, the resulting tensor proto represents the numpy array precisely. Otherwise, "shape" specifies the tensor's shape and the numpy array can not have more elements than what "shape" specifies. """ if isinstance(values, tensor_pb2.TensorProto): return values if dtype: dtype = dtypes.as_dtype(dtype) is_quantized = dtype in [ dtypes.qint8, dtypes.quint8, dtypes.qint16, dtypes.quint16, dtypes.qint32, ] # We first convert value to a numpy array or scalar. if isinstance(values, (np.ndarray, np.generic)): if dtype: nparray = values.astype(dtype.as_numpy_dtype) else: nparray = values elif callable(getattr(values, "__array__", None)) or isinstance( getattr(values, "__array_interface__", None), dict): # If a class has the __array__ method, or __array_interface__ dict, then it # is possible to convert to numpy array. nparray = np.asarray(values, dtype=dtype) # This is the preferred way to create an array from the object, so replace # the `values` with the array so that _FlattenToStrings is not run. values = nparray else: if values is None: raise ValueError("None values not supported.") # if dtype is provided, forces numpy array to be the type # provided if possible. if dtype and dtype.is_numpy_compatible: np_dt = dtype.as_numpy_dtype else: np_dt = None # If shape is None, numpy.prod returns None when dtype is not set, but raises # exception when dtype is set to np.int64 if shape is not None and np.prod(shape, dtype=np.int64) == 0: nparray = np.empty(shape, dtype=np_dt) else: _Assertconvertible(values, dtype) nparray = np.array(values, dtype=np_dt) # check to them. # We need to pass in quantized values as tuples, so don't apply the shape if (list(nparray.shape) != _GetDenseDimensions(values) and not is_quantized): raise ValueError( """Argument must be a dense tensor: %s""" """ - got shape %s, but wanted %s.""" % (values, list(nparray.shape), _GetDenseDimensions(values))) # python/numpy default float type is float64. We prefer float32 instead. if (nparray.dtype == np.float64) and dtype is None: nparray = nparray.astype(np.float32) # python/numpy default int type is int64. We prefer int32 instead. elif (nparray.dtype == np.int64) and dtype is None: downcasted_array = nparray.astype(np.int32) # Do not down cast if it leads to precision loss. if np.array_equal(downcasted_array, nparray): nparray = downcasted_array # if dtype is provided, it must be convertible with what numpy # conversion says. numpy_dtype = dtypes.as_dtype(nparray.dtype) if numpy_dtype is None: raise TypeError("Unrecognized data type: %s" % nparray.dtype) # If dtype was specified and is a quantized type, we convert # numpy_dtype back into the quantized version. if is_quantized: numpy_dtype = dtype if dtype is not None and (not hasattr(dtype, "base_dtype") or dtype.base_dtype != numpy_dtype.base_dtype): raise TypeError("Inconvertible types: %s vs. %s. Value is %s" % (dtype, nparray.dtype, values)) # If shape is not given, get the shape from the numpy array. if shape is None: shape = nparray.shape is_same_size = True shape_size = nparray.size else: shape = [int(dim) for dim in shape] shape_size = np.prod(shape, dtype=np.int64) is_same_size = shape_size == nparray.size if verify_shape: if not nparray.shape == tuple(shape): raise TypeError("Expected Tensor's shape: %s, got %s." % (tuple(shape), nparray.shape)) if nparray.size > shape_size: raise ValueError( "Too many elements provided. Needed at most %d, but received %d" % (shape_size, nparray.size)) tensor_proto = tensor_pb2.TensorProto( dtype=numpy_dtype.as_datatype_enum, tensor_shape=tensor_shape.as_shape(shape).as_proto(), ) if is_same_size and numpy_dtype in _TENSOR_CONTENT_TYPES and shape_size > 1: if nparray.size * nparray.itemsize >= (1 << 31): raise ValueError( "Cannot create a tensor proto whose content is larger than 2GB." ) tensor_proto.tensor_content = nparray.tostring() return tensor_proto # If we were not given values as a numpy array, compute the proto_values # from the given values directly, to avoid numpy trimming nulls from the # strings. Since values could be a list of strings, or a multi-dimensional # list of lists that might or might not correspond to the given shape, # we flatten it conservatively. if numpy_dtype == dtypes.string and not isinstance(values, np.ndarray): proto_values = _FlattenToStrings(values) # At this point, values may be a list of objects that we could not # identify a common type for (hence it was inferred as # np.object/dtypes.string). If we are unable to convert it to a # string, we raise a more helpful error message. # # Ideally, we'd be able to convert the elements of the list to a # common type, but this type inference requires some thinking and # so we defer it for now. try: str_values = [compat.as_bytes(x) for x in proto_values] except TypeError: raise TypeError("Failed to convert object of type %s to Tensor. " "Contents: %s. Consider casting elements to a " "supported type." % (type(values), values)) tensor_proto.string_val.extend(str_values) return tensor_proto # TensorFlow expects C order (a.k.a., eigen row major). proto_values = nparray.ravel() append_fn = GetNumpyAppendFn(proto_values.dtype) if append_fn is None: raise TypeError("Element type not supported in TensorProto: %s" % numpy_dtype.name) append_fn(tensor_proto, proto_values) return tensor_proto
def keras_model_to_graph_def(keras_layer): """Returns a GraphDef representation of the Keras model in a dict form. Note that it only supports models that implemented to_json(). Args: keras_layer: A dict from Keras model.to_json(). Returns: A GraphDef representation of the layers in the model. """ input_to_layer = {} model_name_to_output = {} g = GraphDef() # Sequential model layers do not have a field "inbound_nodes" but # instead are defined implicitly via order of layers. prev_node_name = None for (name_scope, layer) in _walk_layers(keras_layer): if _is_model(layer): ( input_to_layer, model_name_to_output, prev_node_name, ) = _update_dicts( name_scope, layer, input_to_layer, model_name_to_output, prev_node_name, ) continue layer_config = layer.get("config") node_name = _scoped_name(name_scope, layer_config.get("name")) node_def = g.node.add() node_def.name = node_name if layer.get("class_name") is not None: keras_cls_name = layer.get("class_name").encode("ascii") node_def.attr["keras_class"].s = keras_cls_name dtype_or_policy = layer_config.get("dtype") # Skip dtype processing if this is a dict, since it's presumably a instance of # tf/keras/mixed_precision/Policy rather than a single dtype. # TODO(#5548): parse the policy dict and populate the dtype attr with the variable dtype. if dtype_or_policy is not None and not isinstance( dtype_or_policy, dict): tf_dtype = dtypes.as_dtype(layer_config.get("dtype")) node_def.attr["dtype"].type = tf_dtype.as_datatype_enum if layer.get("inbound_nodes") is not None: for maybe_inbound_node in layer.get("inbound_nodes"): inbound_nodes = _norm_to_list_of_layers(maybe_inbound_node) for [name, size, index, _] in inbound_nodes: inbound_name = _scoped_name(name_scope, name) # An input to a layer can be output from a model. In that case, the name # of inbound_nodes to a layer is a name of a model. Remap the name of the # model to output layer of the model. Also, since there can be multiple # outputs in a model, make sure we pick the right output_layer from the model. inbound_node_names = model_name_to_output.get( inbound_name, [inbound_name]) node_def.input.append(inbound_node_names[index]) elif prev_node_name is not None: node_def.input.append(prev_node_name) if node_name in input_to_layer: node_def.input.append(input_to_layer.get(node_name)) prev_node_name = node_def.name return g