def pyod_iforest_converter(scope, operator, container): op = operator.raw_operator opv = container.target_opset out = operator.outputs # We retrieve the unique input. X = operator.inputs[0] # In most case, computation happen in floats. # But it might be with double. ONNX is very strict # about types, every constant should have the same # type as the input. dtype = guess_numpy_type(X.type) detector = op.detector_ # Should be IForest from scikit-learn. lab_pred = OnnxSubEstimator(detector, X, op_version=opv) scores = OnnxIdentity(lab_pred[1], op_version=opv) # labels threshold = op.threshold_ above = OnnxLess(scores, np.array([threshold], dtype=dtype), op_version=opv) labels = OnnxCast(above, op_version=opv, to=onnx_proto.TensorProto.INT64, output_names=out[:1]) # probabilities train_scores = op.decision_scores_ scaler = MinMaxScaler().fit(train_scores.reshape(-1, 1)) scores_ = OnnxMul(scores, np.array([-1], dtype=dtype), op_version=opv) print(scaler.min_) print(scaler.scale_) scaled = OnnxMul(scores_, scaler.scale_.astype(dtype), op_version=opv) scaled_centered = OnnxAdd(scaled, scaler.min_.astype(dtype), op_version=opv) clipped = OnnxClip(scaled_centered, np.array([0], dtype=dtype), np.array([1], dtype=dtype), op_version=opv) clipped_ = OnnxAdd(OnnxMul(clipped, np.array([-1], dtype=dtype), op_version=opv), np.array([1], dtype=dtype), op_version=opv) scores_2d = OnnxConcat(clipped_, clipped, axis=1, op_version=opv, output_names=out[1:]) labels.add_to(scope, container) scores_2d.add_to(scope, container)
def validator_classifier_converter(scope, operator, container): input = operator.inputs[0] # input in ONNX graph outputs = operator.outputs # outputs in ONNX graph op = operator.raw_operator # scikit-learn model (mmust be fitted) opv = container.target_opset # We reuse existing converter and declare it as local # operator. model = op.estimator_ onnx_op = OnnxSubEstimator(model, input, op_version=opv) rmax = OnnxReduceMax(onnx_op[1], axes=[1], keepdims=0, op_version=opv) great = OnnxGreater(rmax, np.array([op.threshold], dtype=np.float32), op_version=opv) valid = OnnxCast(great, to=onnx_proto.TensorProto.INT64, op_version=opv) r1 = OnnxIdentity(onnx_op[0], output_names=[outputs[0].full_name], op_version=opv) r2 = OnnxIdentity(onnx_op[1], output_names=[outputs[1].full_name], op_version=opv) r3 = OnnxIdentity(valid, output_names=[outputs[2].full_name], op_version=opv) r1.add_to(scope, container) r2.add_to(scope, container) r3.add_to(scope, container)
def validator_classifier_converter(scope, operator, container): input0 = operator.inputs[0] # first input in ONNX graph outputs = operator.outputs # outputs in ONNX graph op = operator.raw_operator # scikit-learn model (mmust be fitted) opv = container.target_opset # The model calls another one. The class `OnnxSubEstimator` # calls the converter for this operator. model = op.estimator_ onnx_op = OnnxSubEstimator(model, input0, op_version=opv, options={'zipmap': False}) rmax = OnnxReduceMax(onnx_op[1], axes=[1], keepdims=0, op_version=opv) great = OnnxGreater(rmax, np.array([op.threshold], dtype=np.float32), op_version=opv) valid = OnnxCast(great, to=onnx_proto.TensorProto.INT64, op_version=opv) r1 = OnnxIdentity(onnx_op[0], output_names=[outputs[0].full_name], op_version=opv) r2 = OnnxIdentity(onnx_op[1], output_names=[outputs[1].full_name], op_version=opv) r3 = OnnxIdentity(valid, output_names=[outputs[2].full_name], op_version=opv) r1.add_to(scope, container) r2.add_to(scope, container) r3.add_to(scope, container)
def build_leaky_relu_decomposed_greater(alpha=0.5, target_opset=15): signo = OnnxGreater('X', numpy.array([0], dtype=numpy.float32), op_version=target_opset) sign = OnnxCast(signo, to=TensorProto.FLOAT, op_version=target_opset) fact = OnnxAdd(OnnxMul(sign, numpy.array([1 - alpha], dtype=numpy.float32), op_version=target_opset), numpy.array([alpha], dtype=numpy.float32), op_version=target_opset) x = OnnxMul('X', fact, op_version=target_opset, output_names=['Y']) return x.to_onnx({'X': FloatTensorType()}, outputs={'Y': FloatTensorType()}, target_opset=target_opset)
def woe_encoder_converter(scope, operator, container): op = operator.raw_operator opv = container.target_opset X = operator.inputs[0] sub = OnnxSubEstimator(op.ordinal_encoder, X, op_version=opv) cast = OnnxCast(sub, op_version=opv, to=np.float32) skl_ord = woeenc_to_sklearn(op.mapping) cat = OnnxSubEstimator(skl_ord, cast, op_version=opv, output_names=operator.outputs[:1], input_types=[FloatTensorType()]) cat.add_to(scope, container)
def ordwoe_encoder_converter(scope, operator, container): op = operator.raw_operator opv = container.target_opset X = operator.inputs[0] sub = OnnxSubEstimator(op.encoder_, X, op_version=opv) cast = OnnxCast(sub, op_version=opv, to=np.float32) cat = OnnxSubEstimator(op.woe_, cast, op_version=opv, input_types=[Int64TensorType()]) idcat = OnnxIdentity(cat, output_names=operator.outputs[:1], op_version=opv) idcat.add_to(scope, container)
def live_decorrelate_transformer_converter(scope, operator, container): # shortcuts op = operator.raw_operator opv = container.target_opset out = operator.outputs # We retrieve the unique input. X = operator.inputs[0] # We guess its type. If the operator ingests float (or double), # it outputs float (or double). proto_dtype = guess_proto_type(X.type) dtype = guess_numpy_type(X.type) # Lines in comment specify the numpy computation # the ONNX code implements. # mean_ = numpy.mean(X, axis=0, keepdims=True) mean = OnnxReduceMean(X, axes=[0], keepdims=1, op_version=opv) # This is trick I often use. The converter automatically # chooses a name for every output. In big graph, # it is difficult to know which operator is producing which output. # This line just tells every node must prefix its ouputs with this string. # It also applies to all inputs nodes unless this method # was called for one of these nodes. mean.set_onnx_name_prefix('mean') # X2 = X - mean_ X2 = OnnxSub(X, mean, op_version=opv) # V = X2.T @ X2 / X2.shape[0] N = OnnxGatherElements(OnnxShape(X, op_version=opv), numpy.array([0], dtype=numpy.int64), op_version=opv) Nf = OnnxCast(N, to=proto_dtype, op_version=opv) # Every output involved in N and Nf is prefixed by 'N'. Nf.set_onnx_name_prefix('N') V = OnnxDiv(OnnxMatMul(OnnxTranspose(X2, op_version=opv), X2, op_version=opv), Nf, op_version=opv) V.set_onnx_name_prefix('V1') # V += numpy.identity(V.shape[0]) * self.alpha V = OnnxAdd(V, op.alpha * numpy.identity(op.nf_, dtype=dtype), op_version=opv) V.set_onnx_name_prefix('V2') # L, P = numpy.linalg.eig(V) LP = OnnxEig(V, eigv=True, op_version=opv) LP.set_onnx_name_prefix('LP') # Linv = L ** (-0.5) # Notation LP[0] means OnnxPow is taking the first output # of operator OnnxEig, LP[1] would mean the second one # LP is not allowed as it is ambiguous Linv = OnnxPow(LP[0], numpy.array([-0.5], dtype=dtype), op_version=opv) Linv.set_onnx_name_prefix('Linv') # diag = numpy.diag(Linv) diag = OnnxMul(OnnxEyeLike(numpy.zeros((op.nf_, op.nf_), dtype=numpy.int64), k=0, op_version=opv), Linv, op_version=opv) diag.set_onnx_name_prefix('diag') # root = P @ diag @ P.transpose() trv = OnnxTranspose(LP[1], op_version=opv) coef_left = OnnxMatMul(LP[1], diag, op_version=opv) coef_left.set_onnx_name_prefix('coef_left') coef = OnnxMatMul(coef_left, trv, op_version=opv) coef.set_onnx_name_prefix('coef') # Same part as before. Y = OnnxMatMul(X2, coef, op_version=opv, output_names=out[:1]) Y.set_onnx_name_prefix('Y') # The last line specifies the final output. # Every node involved in the computation is added to the ONNX # graph at this stage. Y.add_to(scope, container)
def _onnx_grad_sigmoid_neg_log_loss_error(target_opset=None, dtype=numpy.float32, eps=1e-5, weight_name=None): """ The function the raw scores from a classifier, uses the sigmoid function to compute probabilities, then the log function to compute the loss. It creates the ONNX graph for this function and the associated gradient of the loss against the raw scores. Probabilites (class 1): :math:`p(s) = \\frac{1}{1 + \\exp(-s)}`. Loss (for two classes): :math:`L(y, s) = (1 - y)\\log(1 - p(s)) + y \\log(p(s))`. Gradient :math:`\\frac{dL(y, s)}{ds} = y - p(s)`. To avoid nan values, probabilies are clipped: :math:`p(s) = \\max(\\min(p(s), 1 - \\epsilon), \\epsilon)`. :math:`y \\in \\{0, 1\\}` (integer). *s* is a float. :param eps: to clip probabilities and avoid computing `log(0)` .. gdot:: :script: DOT-SECTION from mlprodict.onnxrt import OnnxInference from onnxcustom.utils.onnx_function import function_onnx_graph model_onnx = function_onnx_graph('grad_sigmoid_neg_log_loss_error') oinf = OnnxInference(model_onnx, inplace=False) print("DOT-SECTION", oinf.to_dot()) """ from onnx.mapping import NP_TYPE_TO_TENSOR_TYPE from skl2onnx.algebra.onnx_ops import (OnnxSub, OnnxMul, OnnxSigmoid, OnnxLog, OnnxNeg, OnnxReduceSum, OnnxReshape, OnnxAdd, OnnxCast, OnnxClip) p1c = OnnxSigmoid('X2', op_version=target_opset) p1 = OnnxClip(p1c, numpy.array([eps], dtype=dtype), numpy.array([1 - eps], dtype=dtype), op_version=target_opset) p0 = OnnxSub(numpy.array([1], dtype=dtype), p1, op_version=target_opset) y1 = OnnxCast('X1', to=NP_TYPE_TO_TENSOR_TYPE[numpy.dtype(dtype)], op_version=target_opset) y0 = OnnxSub(numpy.array([1], dtype=dtype), y1, op_version=target_opset) loss_obs = OnnxAdd(OnnxMul(y0, OnnxLog(p0, op_version=target_opset), op_version=target_opset), OnnxMul(y1, OnnxLog(p1, op_version=target_opset), op_version=target_opset), op_version=target_opset) loss_neg = OnnxNeg(loss_obs, op_version=target_opset) if weight_name is None: loss = OnnxReduceSum(loss_neg, op_version=target_opset) grad = OnnxSub(p1, y1, op_version=target_opset, output_names=['Y_grad']) else: loss = OnnxReduceSum(OnnxMul(loss_neg, OnnxReshape(weight_name, numpy.array( [-1, 1], dtype=numpy.int64), op_version=target_opset), op_version=target_opset), op_version=target_opset) grad = OnnxMul(OnnxSub(p1, y1, op_version=target_opset), OnnxReshape(weight_name, numpy.array([-1, 1], dtype=numpy.int64), op_version=target_opset), output_names=['Y_grad'], op_version=target_opset) res = OnnxReshape(loss, numpy.array([-1], numpy.int64), op_version=target_opset, output_names=['Y']) var_type_int64 = dtype_to_var_type(numpy.int64) var_type = dtype_to_var_type(dtype) varsx = [('X1', var_type_int64([None, None])), ('X2', var_type([None, None]))] if weight_name is not None: varsx.append((weight_name, var_type([None]))) onx = res.to_onnx(varsx, outputs=[('Y', var_type()), ('Y_grad', var_type())], target_opset=target_opset, other_outputs=[grad]) if weight_name is not None: onx = add_initializer(onx, weight_name, numpy.array([1], dtype=dtype)) return onx
def live_decorrelate_transformer_converter(scope, operator, container): op = operator.raw_operator opv = container.target_opset out = operator.outputs # We retrieve the unique input. X = operator.inputs[0] proto_dtype = guess_proto_type(X.type) dtype = guess_numpy_type(X.type) # new part # mean_ = numpy.mean(X, axis=0, keepdims=True) mean = OnnxReduceMean(X, axes=[0], keepdims=1, op_version=opv) mean.set_onnx_name_prefix('mean') # X2 = X - mean_ X2 = OnnxSub(X, mean, op_version=opv) # V = X2.T @ X2 / X2.shape[0] N = OnnxGatherElements(OnnxShape(X, op_version=opv), numpy.array([0], dtype=numpy.int64), op_version=opv) Nf = OnnxCast(N, to=proto_dtype, op_version=opv) Nf.set_onnx_name_prefix('N') V = OnnxDiv(OnnxMatMul(OnnxTranspose(X2, op_version=opv), X2, op_version=opv), Nf, op_version=opv) V.set_onnx_name_prefix('V1') # V += numpy.identity(V.shape[0]) * self.alpha V = OnnxAdd(V, op.alpha * numpy.identity(op.nf_, dtype=dtype), op_version=opv) V.set_onnx_name_prefix('V2') # L, P = numpy.linalg.eig(V) LP = OnnxEig(V, eigv=True, op_version=opv) LP.set_onnx_name_prefix('LP') # Linv = L ** (-0.5) Linv = OnnxPow(LP[0], numpy.array([-0.5], dtype=dtype), op_version=opv) Linv.set_onnx_name_prefix('Linv') # diag = numpy.diag(Linv) diag = OnnxMul(OnnxEyeLike(numpy.array([op.nf_, op.nf_], dtype=numpy.int64), k=0, op_version=opv), Linv, op_version=opv) diag.set_onnx_name_prefix('diag') # root = P @ diag @ P.transpose() trv = OnnxTranspose(LP[1], op_version=opv) coef_left = OnnxMatMul(LP[1], diag, op_version=opv) coef_left.set_onnx_name_prefix('coef_left') coef = OnnxMatMul(coef_left, trv, op_version=opv) coef.set_onnx_name_prefix('coef') # Same part as before. Y = OnnxMatMul(X2, coef, op_version=opv, output_names=out[:1]) Y.set_onnx_name_prefix('Y') Y.add_to(scope, container)