(y_true: Node, y_pred: Node, fp: &FixedPrecisionConfig)
| 116 | } |
| 117 | |
| 118 | fn compute_naive_auc(y_true: Node, y_pred: Node, fp: &FixedPrecisionConfig) -> Result<Node> { |
| 119 | // This function doesn't account for equal predictions. |
| 120 | // Assuming all predictions are distinct: |
| 121 | // AUC = (number of pairs (i, j) such that y_pred[i] < y_pred[j] and y_true[i] < y_true[j]) / (number of pairs (i, j) such that y_true[i] < y_true[j]). |
| 122 | let g = y_true.get_graph(); |
| 123 | let joined = |
| 124 | g.create_named_tuple(vec![("y_pred".into(), y_pred), ("y_true".into(), y_true)])?; |
| 125 | let joined = g.custom_op( |
| 126 | CustomOperation::new(SortByIntegerKey { |
| 127 | key: "y_pred".into(), |
| 128 | }), |
| 129 | vec![joined], |
| 130 | )?; |
| 131 | let y_true = joined.named_tuple_get("y_true".into())?; |
| 132 | |
| 133 | // Compute AUC denominator, i.e. num_ones * num_zeros. |
| 134 | let num_ones = y_true.sum(vec![0])?.truncate(fp.denominator())?; |
| 135 | let n = y_true.get_type()?.get_dimensions()[0] as i64; |
| 136 | let n = constant_scalar(&g, n, INT64)?; |
| 137 | let num_zeros = n.subtract(num_ones.clone())?; |
| 138 | let denominator = num_ones.multiply(num_zeros)?; |
| 139 | |
| 140 | // Compute AUC numerator. |
| 141 | let one = constant_scalar(&g, fp.denominator(), INT64)?; |
| 142 | let num_zeros_on_prefix = one.subtract(y_true.clone())?.cum_sum(0)?; |
| 143 | let num_zeros_before_one = num_zeros_on_prefix |
| 144 | .multiply(y_true)? |
| 145 | .truncate(fp.denominator())?; |
| 146 | let numerator = num_zeros_before_one |
| 147 | .sum(vec![0])? |
| 148 | .truncate(fp.denominator())?; |
| 149 | |
| 150 | // Compute AUC. |
| 151 | let numerator = i64_to_i128(numerator)?; |
| 152 | let denominator = i64_to_i128(denominator)?; |
| 153 | let denom_bits = MAX_LOG_ARRAY_SIZE * 2; |
| 154 | // Note: GoldschmidtDivision requires denominator to be smaller than 2 ** denominator_cap_2k. |
| 155 | // We use 2 ** 40 as a hard-coded constant, meaning that sqrt(denominator) must be smaller than 2 ** 20 (imposing restriction on the number of elements in the array). |
| 156 | // We can potentially support larger arrays, if we first divide by num_ones, and then - by num_zeros. |
| 157 | let result = g.custom_op( |
| 158 | CustomOperation::new(GoldschmidtDivision { |
| 159 | // Use 7 = log_2(128) iterations. |
| 160 | iterations: 7, |
| 161 | denominator_cap_2k: denom_bits, |
| 162 | }), |
| 163 | vec![numerator, denominator], |
| 164 | )?; |
| 165 | |
| 166 | let result = match denom_bits.cmp(&fp.fractional_bits) { |
| 167 | Ordering::Less => result.multiply(constant_scalar( |
| 168 | &g, |
| 169 | 1 << (fp.fractional_bits - denom_bits), |
| 170 | INT128, |
| 171 | )?)?, |
| 172 | Ordering::Equal => result, |
| 173 | Ordering::Greater => result.truncate(1 << (denom_bits - fp.fractional_bits))?, |
| 174 | }; |
| 175 |
no test coverage detected