Skip to content

Commit 17f8696

Browse files
authored
Fixes #393. label ordering in binary logistic regression,(#432)
it now assings positive/negative labels based on Ord ordering of the label values rather than encouter order in the training dat
1 parent c7c2af5 commit 17f8696

1 file changed

Lines changed: 39 additions & 24 deletions

File tree

  • algorithms/linfa-logistic/src

algorithms/linfa-logistic/src/lib.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -301,32 +301,33 @@ where
301301
{
302302
let y = y.as_single_targets();
303303

304-
// counts the instances of two distinct class labels
305304
let mut binary_classes = [None, None];
306-
// find binary classes of our target dataset
307305
for class in y {
308306
binary_classes = match binary_classes {
309-
// count the first class label
310307
[None, None] => [Some((class, 1)), None],
311-
// if the class has already been counted, increment the count
312308
[Some((c, count)), c2] if c == class => [Some((class, count + 1)), c2],
313309
[c1, Some((c, count))] if c == class => [c1, Some((class, count + 1))],
314-
// count the second class label
315310
[Some(c1), None] => [Some(c1), Some((class, 1))],
316-
317-
// should not be possible
318311
[None, Some(_)] => unreachable!("impossible binary class array"),
319-
// found 3rd distinct class
320312
[Some(_), Some(_)] => return Err(Error::TooManyClasses),
321313
};
322314
}
323315

324-
let (pos_class, neg_class) = match binary_classes {
316+
let (class_a, class_b) = match binary_classes {
325317
[Some(a), Some(b)] => (a, b),
326318
_ => return Err(Error::TooFewClasses),
327319
};
328320

329-
let mut target_array = y
321+
// Sort by label value (Ord), not by encounter order or count.
322+
// The smaller label is always negative (-1),
323+
// the larger label is always positive (+1).
324+
let (neg_class, pos_class) = if class_a.0 < class_b.0 {
325+
(class_a, class_b)
326+
} else {
327+
(class_b, class_a)
328+
};
329+
330+
let target_array = y
330331
.into_iter()
331332
.map(|x| {
332333
if x == pos_class.0 {
@@ -337,24 +338,14 @@ where
337338
})
338339
.collect::<Array1<_>>();
339340

340-
let (pos_cl, neg_cl) = if pos_class.1 < neg_class.1 {
341-
// If we found the larger class first, flip the sign in the target
342-
// vector, so that -1.0 is always the label for the smaller class
343-
// and 1.0 the label for the larger class
344-
target_array *= -F::one();
345-
(neg_class.0.clone(), pos_class.0.clone())
346-
} else {
347-
(pos_class.0.clone(), neg_class.0.clone())
348-
};
349-
350341
Ok((
351342
BinaryClassLabels {
352343
pos: ClassLabel {
353-
class: pos_cl,
344+
class: pos_class.0.clone(),
354345
label: F::POSITIVE_LABEL,
355346
},
356347
neg: ClassLabel {
357-
class: neg_cl,
348+
class: neg_class.0.clone(),
358349
label: F::NEGATIVE_LABEL,
359350
},
360351
},
@@ -989,7 +980,7 @@ mod test {
989980
let dataset = Dataset::new(x, y);
990981
let res = log_reg.fit(&dataset).unwrap();
991982
assert_abs_diff_eq!(res.intercept(), 0.0);
992-
assert!(res.params().abs_diff_eq(&array![-0.681], 1e-3));
983+
assert!(res.params().abs_diff_eq(&array![0.681], 1e-3));
993984
assert_eq!(
994985
&res.predict(dataset.records()),
995986
dataset.targets().as_single_targets()
@@ -1172,7 +1163,7 @@ mod test {
11721163
let dataset = Dataset::new(x, y);
11731164
let res = log_reg.fit(&dataset).unwrap();
11741165
assert_abs_diff_eq!(res.intercept(), 0.0_f32);
1175-
assert!(res.params().abs_diff_eq(&array![-0.682_f32], 1e-3));
1166+
assert!(res.params().abs_diff_eq(&array![0.682_f32], 1e-3));
11761167
assert_eq!(
11771168
&res.predict(dataset.records()),
11781169
dataset.targets().as_single_targets()
@@ -1375,4 +1366,28 @@ mod test {
13751366
}
13761367
));
13771368
}
1369+
1370+
#[test]
1371+
fn label_order_independent() {
1372+
let x1 = array![[-1.0], [1.0], [-0.5], [0.5]];
1373+
let y1 = array!["cat", "dog", "cat", "dog"];
1374+
1375+
let x2 = array![[1.0], [-1.0], [0.5], [-0.5]];
1376+
let y2 = array!["dog", "cat", "dog", "cat"];
1377+
1378+
let model1 = LogisticRegression::default()
1379+
.fit(&Dataset::new(x1, y1))
1380+
.unwrap();
1381+
let model2 = LogisticRegression::default()
1382+
.fit(&Dataset::new(x2, y2))
1383+
.unwrap();
1384+
1385+
assert_eq!(model1.labels().pos.class, "dog");
1386+
assert_eq!(model1.labels().neg.class, "cat");
1387+
assert_eq!(model2.labels().pos.class, "dog");
1388+
assert_eq!(model2.labels().neg.class, "cat");
1389+
1390+
assert_abs_diff_eq!(model1.intercept(), model2.intercept());
1391+
assert!(model1.params().abs_diff_eq(model2.params(), 1e-6));
1392+
}
13781393
}

0 commit comments

Comments
 (0)