Skip to content

Commit 634320d

Browse files
committed
working through last rocauc bug
1 parent 2638f84 commit 634320d

8 files changed

Lines changed: 130 additions & 113 deletions

File tree

src/valor_lite/cache/compute.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ def sort(
107107
table_sort_override : Callable[[pa.Table], pa.Table], optional
108108
Option to override sort function for singular cache fragments.
109109
"""
110-
111110
if source.count_tables() == 1:
112111
for tbl in source.iterate_tables(columns=columns):
113112
if table_sort_override is not None:

src/valor_lite/classification/computation.py

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77

88

99
def compute_rocauc(
10-
ids: NDArray[np.int64],
11-
scores: NDArray[np.float64],
1210
rocauc: NDArray[np.float64],
11+
array: NDArray[np.float64],
1312
gt_count_per_label: NDArray[np.uint64],
1413
pd_count_per_label: NDArray[np.uint64],
1514
n_labels: int,
15+
accumulated_tp: NDArray[np.uint64],
16+
accumulated_fp: NDArray[np.uint64],
17+
prev_fpr: NDArray[np.float64],
18+
prev_tpr: NDArray[np.float64],
1619
) -> tuple[NDArray[np.float64], NDArray[np.uint64], NDArray[np.uint64]]:
1720
"""
1821
Compute ROCAUC.
@@ -44,9 +47,9 @@ def compute_rocauc(
4447
NDArray[np.uint64]
4548
Final cumulative sum for TP's. Used as intermediate in chunking operations.
4649
"""
47-
gt_labels = ids[:, 1]
48-
pd_labels = ids[:, 2]
49-
mask_matching_labels = np.isclose(gt_labels, pd_labels)
50+
pd_labels = array[:, 0].astype(np.int64)
51+
scores = array[:, 1]
52+
mask_matching_labels = array[:, 2] > 0.5
5053

5154
positive_count = gt_count_per_label
5255
negative_count = pd_count_per_label - gt_count_per_label
@@ -57,44 +60,92 @@ def compute_rocauc(
5760
if pd_count_per_label[label_idx] == 0 or n_masked_pds == 0:
5861
continue
5962

60-
true_positives = np.zeros(n_masked_pds, dtype=np.uint64)
61-
false_positives = np.zeros_like(true_positives)
62-
tp_scores = np.zeros_like(true_positives, dtype=np.float64)
63-
6463
true_positives = mask_matching_labels[mask_pds]
6564
false_positives = ~mask_matching_labels[mask_pds]
6665
tp_scores = scores[mask_pds]
6766

68-
cumulative_fp = np.cumsum(false_positives)
69-
cumulative_tp = np.cumsum(true_positives)
67+
cumulative_fp = np.cumsum(false_positives) + accumulated_fp[label_idx]
68+
cumulative_tp = np.cumsum(true_positives) + accumulated_tp[label_idx]
7069

71-
fpr = np.zeros_like(true_positives, dtype=np.float64)
70+
accumulated_fp[label_idx] = cumulative_fp[-1]
71+
accumulated_tp[label_idx] = cumulative_tp[-1]
72+
73+
fpr = np.zeros(n_masked_pds, dtype=np.float64)
7274
np.divide(
7375
cumulative_fp,
7476
negative_count[label_idx],
7577
where=negative_count[label_idx] > 0,
7678
out=fpr,
7779
)
78-
tpr = np.zeros_like(true_positives, dtype=np.float64)
80+
tpr = np.zeros(n_masked_pds, dtype=np.float64)
7981
np.divide(
8082
cumulative_tp,
8183
positive_count[label_idx],
8284
where=positive_count[label_idx] > 0,
8385
out=tpr,
8486
)
8587

88+
if prev_fpr[label_idx] > -0.5 and prev_tpr[label_idx] > -0.5:
89+
fpr = np.concatenate([prev_fpr[label_idx:label_idx+1], fpr])
90+
tpr = np.concatenate([prev_tpr[label_idx:label_idx+1], tpr])
91+
8692
# sort by -tpr, -score
8793
indices = np.lexsort((-tpr, -tp_scores))
8894
fpr = fpr[indices]
8995
tpr = tpr[indices]
9096

97+
sfpr = fpr.copy()
98+
stpr = tpr.copy()
99+
91100
# running max of tpr
92101
np.maximum.accumulate(tpr, out=tpr)
93102

103+
104+
prev_fpr[label_idx] = fpr[-1]
105+
prev_tpr[label_idx] = tpr[-1]
106+
94107
# compute rocauc
95-
rocauc[label_idx] = npc.trapezoid(x=fpr, y=tpr, axis=0)
108+
rocauc[label_idx] += npc.trapezoid(x=fpr, y=tpr, axis=0)
109+
110+
print()
111+
print(label_idx, rocauc[label_idx])
112+
print("====")
113+
print(
114+
f"{'FP':4}",
115+
f"{'TP':4}",
116+
f"{'CFP':4}",
117+
f"{'CTP':4}",
118+
f"{'FPR':4}",
119+
f"{'TPR':4}",
120+
f"{'SFPR':4}",
121+
f"{'STPR':4}",
122+
f"{'SCO':4}",
123+
)
124+
for f, t, af, at, fr, tr, sf, st, s in zip(
125+
false_positives,
126+
true_positives,
127+
cumulative_fp,
128+
cumulative_tp,
129+
fpr,
130+
tpr,
131+
sfpr,
132+
stpr,
133+
tp_scores,
134+
):
135+
print(
136+
f"{f:.2f}",
137+
f"{t:.2f}",
138+
f"{af:.2f}",
139+
f"{at:.2f}",
140+
f"{fr:.2f}",
141+
f"{tr:.2f}",
142+
f"{sf:.2f}",
143+
f"{st:.2f}",
144+
f"{s:.2f}",
145+
)
146+
96147

97-
return rocauc, None, None
148+
return rocauc, accumulated_fp, accumulated_tp, prev_fpr, prev_tpr
98149

99150

100151
def compute_counts(

src/valor_lite/classification/evaluator.py

Lines changed: 38 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ class Evaluator(Base):
3737
def __init__(
3838
self,
3939
reader: MemoryCacheReader | FileCacheReader,
40-
sorted_reader: MemoryCacheReader | FileCacheReader,
40+
rocauc_reader: MemoryCacheReader | FileCacheReader,
4141
info: EvaluatorInfo,
4242
label_counts: NDArray[np.uint64],
4343
index_to_label: dict[int, str],
4444
path: str | Path | None,
4545
):
4646
self._path = Path(path) if path else None
4747
self._reader = reader
48-
self._sorted_reader = sorted_reader
48+
self._rocauc_reader = rocauc_reader
4949
self._info = info
5050
self._label_counts = label_counts
5151
self._index_to_label = index_to_label
@@ -82,8 +82,8 @@ def load(
8282

8383
# load cache
8484
reader = FileCacheReader.load(cls._generate_cache_path(path))
85-
sorted_reader = FileCacheReader.load(
86-
cls._generate_sorted_cache_path(path)
85+
rocauc_reader = FileCacheReader.load(
86+
cls._generate_rocauc_cache_path(path)
8787
)
8888

8989
# build evaluator meta
@@ -101,7 +101,7 @@ def load(
101101
return cls(
102102
path=path,
103103
reader=reader,
104-
sorted_reader=sorted_reader,
104+
rocauc_reader=rocauc_reader,
105105
info=info,
106106
label_counts=label_counts,
107107
index_to_label=index_to_label,
@@ -216,8 +216,7 @@ def delete(self):
216216
if self._path and self._path.exists():
217217
self.delete_at_path(self._path)
218218

219-
@staticmethod
220-
def iterate_values(reader: MemoryCacheReader | FileCacheReader):
219+
def iterate_values(self):
221220
columns = [
222221
"datum_id",
223222
"gt_label_id",
@@ -226,7 +225,7 @@ def iterate_values(reader: MemoryCacheReader | FileCacheReader):
226225
"winner",
227226
"match",
228227
]
229-
for tbl in reader.iterate_tables(columns=columns):
228+
for tbl in self._reader.iterate_tables(columns=columns):
230229
ids = np.column_stack(
231230
[
232231
tbl[col].to_numpy()
@@ -242,11 +241,8 @@ def iterate_values(reader: MemoryCacheReader | FileCacheReader):
242241
matches = tbl["match"].to_numpy()
243242
yield ids, scores, winners, matches
244243

245-
@staticmethod
246-
def iterate_values_with_tables(
247-
reader: MemoryCacheReader | FileCacheReader,
248-
):
249-
for tbl in reader.iterate_tables():
244+
def iterate_values_with_tables(self):
245+
for tbl in self._reader.iterate_tables():
250246
ids = np.column_stack(
251247
[
252248
tbl[col].to_numpy()
@@ -266,13 +262,6 @@ def compute_rocauc(self) -> dict[MetricType, list[Metric]]:
266262
"""
267263
Compute ROCAUC.
268264
269-
Parameters
270-
----------
271-
rows_per_chunk : int, default=10_000
272-
The number of sorted rows to return in each chunk.
273-
read_batch_size : int, default=1_000
274-
The maximum number of rows to load in-memory per file.
275-
276265
Returns
277266
-------
278267
dict[MetricType, list[Metric]]
@@ -281,42 +270,42 @@ def compute_rocauc(self) -> dict[MetricType, list[Metric]]:
281270
n_labels = self.info.number_of_labels
282271

283272
rocauc = np.zeros(n_labels, dtype=np.float64)
284-
cumulative_fp = np.zeros(n_labels, dtype=np.uint64)
285-
cumulative_tp = np.zeros(n_labels, dtype=np.uint64)
286-
287-
tpr = np.zeros(n_labels, dtype=np.float64)
288-
fpr = np.zeros(n_labels, dtype=np.float64)
273+
accumulated_fp = np.zeros(n_labels, dtype=np.uint64)
274+
accumulated_tp = np.zeros(n_labels, dtype=np.uint64)
289275

290-
positive_count = self._label_counts[:, 0]
291-
negative_count = self._label_counts[:, 1] - self._label_counts[:, 0]
276+
prev_tpr = np.ones(n_labels, dtype=np.float64) * -1
277+
prev_fpr = np.ones(n_labels, dtype=np.float64) * -1
292278

293-
for ids, scores, winners, matches in self.iterate_values(
294-
self._sorted_reader
295-
):
296-
for id_row, score in zip(ids, scores):
297-
glabel = id_row[1]
298-
plabel = id_row[2]
299-
if glabel < 0 or plabel < 0:
300-
continue
301-
elif glabel == plabel:
302-
cumulative_tp[plabel] += 1
303-
tpr_new = cumulative_tp[plabel] / positive_count[plabel]
304-
tpr[plabel]
305-
else:
306-
cumulative_fp[plabel] += 1
307-
308-
batch_rocauc, cumulative_fp, cumulative_tp = compute_rocauc(
309-
ids=ids,
310-
scores=scores,
279+
for loopid, array in enumerate(self._rocauc_reader.iterate_arrays(
280+
numeric_columns=[
281+
"pd_label_id",
282+
"score",
283+
"match",
284+
]
285+
)):
286+
# for id_row, score in zip(ids, scores):
287+
# glabel = id_row[1]
288+
# plabel = id_row[2]
289+
# if glabel < 0 or plabel < 0:
290+
# continue
291+
# elif glabel == plabel:
292+
# cumulative_tp[plabel] += 1
293+
# tpr_new = cumulative_tp[plabel] / positive_count[plabel]
294+
# tpr[plabel]
295+
# else:
296+
# cumulative_fp[plabel] += 1
297+
print("loop", loopid)
298+
rocauc, accumulated_fp, accumulated_tp, prev_fpr, prev_tpr = compute_rocauc(
311299
rocauc=rocauc,
312-
cumulative_fp=cumulative_fp,
313-
cumulative_tp=cumulative_tp,
300+
array=array,
314301
gt_count_per_label=self._label_counts[:, 0],
315302
pd_count_per_label=self._label_counts[:, 1],
316-
n_datums=self.info.number_of_datums,
317303
n_labels=self.info.number_of_labels,
304+
accumulated_fp=accumulated_fp,
305+
accumulated_tp=accumulated_tp,
306+
prev_fpr=prev_fpr,
307+
prev_tpr=prev_tpr,
318308
)
319-
rocauc += batch_rocauc
320309

321310
mean_rocauc = rocauc.mean()
322311

src/valor_lite/classification/loader.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(
2424
):
2525
self._path = Path(path) if path else None
2626
self._writer = writer
27-
self._sorted_writer = sorted_writer
27+
self._rocauc_writer = sorted_writer
2828
self._datum_metadata_fields = datum_metadata_fields
2929

3030
# internal state
@@ -53,7 +53,7 @@ def in_memory(
5353
batch_size=batch_size,
5454
)
5555
sorted_writer = MemoryCacheWriter.create(
56-
schema=cls._generate_sorted_schema(),
56+
schema=cls._generate_rocauc_schema(),
5757
batch_size=batch_size,
5858
)
5959
return cls(
@@ -104,8 +104,8 @@ def persistent(
104104
compression=compression,
105105
)
106106
sorted_writer = FileCacheWriter.create(
107-
path=cls._generate_sorted_cache_path(path),
108-
schema=cls._generate_sorted_schema(),
107+
path=cls._generate_rocauc_cache_path(path),
108+
schema=cls._generate_rocauc_schema(),
109109
batch_size=batch_size,
110110
rows_per_file=rows_per_file,
111111
compression=compression,
@@ -214,6 +214,8 @@ def finalize(
214214
self._writer.flush()
215215
if self._writer.count_rows() == 0:
216216
raise EmptyCacheError()
217+
elif self._rocauc_writer.count_rows() > 0:
218+
raise RuntimeError("data already finalized")
217219

218220
# sort in-place and locally
219221
self._writer.sort_by(
@@ -229,23 +231,20 @@ def finalize(
229231
reader = self._writer.to_reader()
230232
sort(
231233
source=reader,
232-
sink=self._sorted_writer,
234+
sink=self._rocauc_writer,
233235
batch_size=batch_size,
234236
sorting=[
235237
("score", "descending"),
238+
("match", "descending"),
236239
("pd_label_id", "ascending"),
237-
("gt_label_id", "ascending"),
238240
],
239241
columns=[
240-
"datum_id",
241-
"gt_label_id",
242242
"pd_label_id",
243243
"score",
244-
"winner",
245244
"match",
246245
],
247246
)
248-
sorted_reader = self._sorted_writer.to_reader()
247+
rocauc_reader = self._rocauc_writer.to_reader()
249248

250249
# generate evaluator meta
251250
(index_to_label, label_counts, info,) = self.generate_meta(
@@ -254,7 +253,7 @@ def finalize(
254253

255254
return Evaluator(
256255
reader=reader,
257-
sorted_reader=sorted_reader,
256+
rocauc_reader=rocauc_reader,
258257
info=info,
259258
label_counts=label_counts,
260259
index_to_label=index_to_label,

0 commit comments

Comments
 (0)