Skip to content

Commit 071ab8a

Browse files
committed
Metrics: fix serializability
1 parent 734cbf3 commit 071ab8a

8 files changed

Lines changed: 62 additions & 59 deletions

File tree

client/src/components/tables/MetricsTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
]"
3232
#[cell]="data"
3333
>
34-
<div :key="cell">
34+
<div :key="cell" style="text-align: right">
3535
{{ `${data.value ? parseFloat(data.value).toString().slice(0, 4) : 0}` }}
3636
</div>
3737
</template>
@@ -41,7 +41,7 @@
4141
#[cell]="data"
4242
>
4343
<div :key="cell">
44-
<GButton :disabled="data.value?.count === 0" @click="openModal(data)">
44+
<GButton :disabled="data.value?.count === 0" @click="openModal(data)" style="text-align: right">
4545
{{ data.value?.count }}
4646
</GButton>
4747
</div>

client/src/stores/evaluation/metrics.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@ export const metricsPerPosColumns = [
2121
{ key: "recall", sortOn: (x: MetricsRow) => x.recall },
2222
{ key: "f1", sortOn: (x: MetricsRow) => x.f1 },
2323
{ key: "count", label: "count", sortOn: (x: MetricsRow) => x.count },
24-
{ key: "truePositive", label: "true positive", sortOn: (x: MetricsRow): number => x.truePositive.count / x.count },
24+
{ key: "truePositive", label: "true positive", sortOn: (x: MetricsRow): number => x.truePositive.count },
2525
{
2626
key: "falsePositive",
2727
label: "false positive",
28-
sortOn: (x: MetricsRow): number => x.falsePositive.count / x.count,
28+
sortOn: (x: MetricsRow): number => x.falsePositive.count,
2929
},
3030
{
3131
key: "falseNegative",
3232
label: "false negative",
33-
sortOn: (x: MetricsRow): number => x.falseNegative.count / x.count,
33+
sortOn: (x: MetricsRow): number => x.falseNegative.count,
3434
},
35-
{ key: "noMatch", label: "no match", sortOn: (x: MetricsRow): number => x.noMatch.count / x.count },
35+
{ key: "noMatch", label: "no match", sortOn: (x: MetricsRow): number => x.noMatch.count },
3636
]
3737

3838
/**

client/src/views/annotate/subviews/evaluate/subviews/GlobalMetricsView.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,13 @@ const items = computed(() => {
8787
return {
8888
id: i.name,
8989
column: i.name,
90-
name: i.name,
91-
group: i.name,
90+
name: i.settings.annotation,
91+
group: i.settings.group,
9292
count: i.classes.count,
9393
truePositive: i.classes.truePositive,
9494
falseNegative: i.classes.falseNegative,
9595
noMatch: i.classes.noMatch,
96+
microAccuracy: i.accuracy,
9697
macroPrecision: i.macro.precision,
9798
macroRecall: i.macro.recall,
9899
macroF1: i.macro.f1,

server/src/main/kotlin/org/ivdnt/galahad/evaluation/DocumentEvaluation.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package org.ivdnt.galahad.evaluation
22

3+
import org.ivdnt.galahad.annotations.Annotation
34
import org.ivdnt.galahad.annotations.Layer
45
import org.ivdnt.galahad.corpora.Corpus
56
import org.ivdnt.galahad.evaluation.comparison.LayerComparison
67
import org.ivdnt.galahad.evaluation.confusion.DocumentConfusion
78
import org.ivdnt.galahad.evaluation.distribution.DocumentDistribution
89
import org.ivdnt.galahad.evaluation.entities.DocumentEntities
910
import org.ivdnt.galahad.evaluation.metrics.DocumentMetric
10-
import org.ivdnt.galahad.evaluation.metrics.DocumentMetrics
1111
import org.ivdnt.galahad.files.GalahadFolder
1212
import org.ivdnt.galahad.files.ValidatedDiskValue
1313
import org.ivdnt.galahad.jobs.Job
@@ -30,6 +30,7 @@ class DocumentEvaluation(
3030
private val refModified: Long get() = refJob.results.readOrThrow(name).modified
3131
private val hypModified: Long get() = hypJob.results.readOrThrow(name).modified
3232
private val lastModified: Long get() = hypModified.coerceAtLeast(refModified)
33+
private val availableAnnotations: Set<Annotation> get() = refJob.metadata.annotations.annotations.keys.intersect(hypJob.metadata.annotations.annotations.keys).filter{ it != Annotation.TOKEN }.toSet()
3334

3435
val entities: DocumentEntities
3536
get() = object : ValidatedDiskValue<DocumentEntities>(dir.resolve(ENTITIES_FILE)) {
@@ -46,13 +47,13 @@ class DocumentEvaluation(
4647
val confusion: DocumentConfusion
4748
get() = object : ValidatedDiskValue<DocumentConfusion>(dir.resolve(CONFUSION_FILE)) {
4849
override fun isValid(modified: Long) = modified >= lastModified
49-
override fun set(): DocumentConfusion = DocumentConfusion.create(LayerComparison(hypLayer, refLayer))
50+
override fun set(): DocumentConfusion = DocumentConfusion.create(LayerComparison(hypLayer, refLayer), availableAnnotations)
5051
}.readOrCreate<DocumentConfusion>()
5152

5253
val metrics: DocumentMetric
5354
get() = object : ValidatedDiskValue<DocumentMetric>(dir.resolve(METRICS_FILE)) {
5455
override fun isValid(modified: Long) = modified >= lastModified
55-
override fun set(): DocumentMetric = DocumentMetric.create(LayerComparison(hypLayer, refLayer))
56+
override fun set(): DocumentMetric = DocumentMetric.create(LayerComparison(hypLayer, refLayer), availableAnnotations)
5657
}.readOrCreate<DocumentMetric>()
5758

5859
companion object {

server/src/main/kotlin/org/ivdnt/galahad/evaluation/confusion/DocumentConfusion.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ class DocumentConfusion(
1212
@JsonValue val confusion: Map<Annotation, Map<String, Map<String, EvaluationEntry>>>
1313
) {
1414
companion object {
15-
private val ANNOTATIONS = arrayOf(Annotation.POS, Annotation.UPOS, Annotation.NER, Annotation.DEPREL)
15+
private val ANNOTATIONS = setOf(Annotation.POS, Annotation.UPOS, Annotation.NER, Annotation.DEPREL)
1616

17-
fun create(layerComparison: LayerComparison): DocumentConfusion =
17+
fun create(layerComparison: LayerComparison, annotations: Set<Annotation>): DocumentConfusion =
1818
DocumentConfusion(buildMap<Annotation, MutableMap<String, MutableMap<String, EvaluationEntry>>> {
1919
layerComparison.matches.forEach { comparison ->
20-
ANNOTATIONS.forEach { annotation ->
20+
annotations.intersect(ANNOTATIONS).forEach { annotation ->
2121
val reference = comparison.ref.format(annotation)
2222
val hypothesis = comparison.hyp.format(annotation)
2323
val entry = EvaluationEntry(1, mutableListOf(comparison))

server/src/main/kotlin/org/ivdnt/galahad/evaluation/metrics/DocumentMetric.kt

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.ivdnt.galahad.evaluation.metrics
22

33
import com.fasterxml.jackson.annotation.JsonValue
4+
import org.ivdnt.galahad.annotations.Annotation
45
import org.ivdnt.galahad.annotations.Term
56
import org.ivdnt.galahad.evaluation.EvaluationEntry
67
import org.ivdnt.galahad.evaluation.comparison.LayerComparison
78
import org.ivdnt.galahad.evaluation.comparison.TermComparison
9+
import org.ivdnt.galahad.evaluation.metrics.ClsClasses.Companion.toMetrics
810

911
class ClsMetrics(
1012
val precision: Float = 0f,
@@ -28,76 +30,74 @@ class ClsMetrics(
2830
}
2931

3032
class NewMetric(
33+
val settings: MetricsSettings,
3134
val grouped: MutableMap<String, ClsClasses> = mutableMapOf(),
32-
) {
33-
val classes: ClsClasses
34-
get() = grouped.filter { it.key != TermComparison.MISSING_MATCH }.values.reduce { a, b -> a + b }
35-
36-
val micro: ClsMetrics get() = classes.metrics
37-
38-
val macro: ClsMetrics
39-
get() {
40-
val validClasses = grouped.filter { it.key != TermComparison.MISSING_MATCH }
41-
return validClasses.values.map { it.metrics }
42-
.reduce { a, b -> a + b } / validClasses.size
43-
}
44-
}
35+
val classes: ClsClasses = grouped.filter { it.key != TermComparison.MISSING_MATCH }.values.reduce { a, b -> a + b },
36+
val accuracy: Float = classes.truePositive.count / classes.count.toFloat(),
37+
val macro: ClsMetrics = grouped.values.map { it.metrics }.reduce { a, b -> a + b } / grouped.size,
38+
)
4539

4640
class ClsClasses(
4741
var truePositive: EvaluationEntry = EvaluationEntry(),
4842
var falsePositive: EvaluationEntry = EvaluationEntry(),
4943
var falseNegative: EvaluationEntry = EvaluationEntry(),
5044
var noMatch: EvaluationEntry = EvaluationEntry(),
45+
var count: Int = truePositive.count + falseNegative.count + noMatch.count,
46+
var metrics: ClsMetrics = toMetrics(truePositive, falsePositive, falseNegative, noMatch)
5147
) {
5248
fun add(other: ClsClasses, truncate: Boolean = true): ClsClasses {
5349
truePositive = EvaluationEntry.add(truePositive, other.truePositive, truncate)
5450
falsePositive = EvaluationEntry.add(falsePositive, other.falsePositive, truncate)
5551
falseNegative = EvaluationEntry.add(falseNegative, other.falseNegative, truncate)
5652
noMatch = EvaluationEntry.add(noMatch, other.noMatch, truncate)
53+
count = truePositive.count + falseNegative.count + noMatch.count
54+
metrics = toMetrics(truePositive, falsePositive, falseNegative, noMatch)
5755
return this
5856
}
5957

6058
operator fun plus(other: ClsClasses): ClsClasses = ClsClasses(
61-
truePositive = EvaluationEntry.from(other.truePositive,truePositive),
62-
falsePositive = EvaluationEntry.from(other.falsePositive,falsePositive),
63-
falseNegative = EvaluationEntry.from(other.falseNegative,falseNegative),
64-
noMatch = EvaluationEntry.from(other.noMatch,noMatch),
59+
truePositive = EvaluationEntry.from(other.truePositive, truePositive),
60+
falsePositive = EvaluationEntry.from(other.falsePositive, falsePositive),
61+
falseNegative = EvaluationEntry.from(other.falseNegative, falseNegative),
62+
noMatch = EvaluationEntry.from(other.noMatch, noMatch),
63+
count = truePositive.count + falseNegative.count + noMatch.count,
64+
metrics = toMetrics(truePositive, falsePositive, falseNegative, noMatch)
6565
)
6666

67-
val metrics: ClsMetrics
68-
get() {
69-
val precision = notNaN(truePositive.count / (truePositive.count + falsePositive.count).toFloat())
70-
val recall = notNaN(truePositive.count / (truePositive.count + falsePositive.count + noMatch.count).toFloat())
67+
companion object {
68+
fun notNaN(value: Float): Float = if (value.isNaN()) 0.0f else value
69+
fun toMetrics(
70+
truePositive: EvaluationEntry,
71+
falsePositive: EvaluationEntry,
72+
falseNegative: EvaluationEntry,
73+
noMatch: EvaluationEntry
74+
): ClsMetrics {
75+
val tp = truePositive.count.toFloat()
76+
val fp = falsePositive.count.toFloat()
77+
val fn = falseNegative.count.toFloat()
78+
val mm = noMatch.count.toFloat()
79+
val precision = notNaN(tp / (tp + fp))
80+
val recall = notNaN(tp / (tp + fn + mm))
7181
val f1 = notNaN(2.0f * (precision * recall) / (precision + recall))
7282
return ClsMetrics(precision, recall, f1)
7383
}
74-
75-
val count: Int
76-
get() = truePositive.count + falseNegative.count + noMatch.count
77-
78-
companion object {
79-
fun notNaN(value: Float): Float = if (value.isNaN()) 0.0f else value
8084
}
8185
}
8286

83-
8487
class DocumentMetric(
85-
@JsonValue
86-
val classesByGroup: MutableMap<String, NewMetric>
88+
@JsonValue val classesByGroup: MutableMap<String, NewMetric>
8789
) {
8890
companion object {
89-
fun create(layerComparison: LayerComparison): DocumentMetric = DocumentMetric(
90-
buildMap<String, NewMetric> {
91+
fun create(layerComparison: LayerComparison, annotations: Set<Annotation>): DocumentMetric = DocumentMetric(
92+
buildMap<String, MutableMap<String, ClsClasses>> {
9193
layerComparison.matches.forEach { tc ->
92-
METRIC_TYPES.forEach { metricType ->
94+
METRIC_TYPES.filter { annotations.containsAll(it.requiredAnnotations) }.forEach { metricType ->
9395
if (!metricType.filterBy(tc)) return@forEach
9496
val mapsToAdd = mutableListOf<MutableMap<String, ClsClasses>>()
9597
if (tc.hyp == Term.EMPTY) {
9698
// handle missing match
9799
val cls = ClsClasses(noMatch = EvaluationEntry(1, mutableListOf(tc)))
98-
val group = TermComparison.MISSING_MATCH
99-
val classesMap = mutableMapOf(group to cls)
100-
mapsToAdd.add(classesMap)
100+
mapsToAdd.add(mutableMapOf(metricType.groupBy(tc.ref) to cls))
101101
} else {
102102
// handle true positive & false negative
103103
val (trueEntry, falseEntry) = truesFalses(tc, metricType::termsEqual)
@@ -115,16 +115,18 @@ class DocumentMetric(
115115
}
116116
for (map in mapsToAdd) {
117117

118-
merge(metricType.id, NewMetric(grouped=map)) { oldMetricMap, newMetricMap -> oldMetricMap.apply {
119-
this.grouped.merge(newMetricMap.grouped.keys.first(), newMetricMap.grouped.values.first()) { oldCls, newCls ->
120-
oldCls.add(newCls)
121-
}
122-
}
118+
merge(
119+
metricType.id,
120+
map
121+
) {
122+
a, b ->
123+
a.apply { this.merge(b.keys.first(), b.values.first()) { x, y -> x.add(y) } }
123124
}
125+
124126
}
125127
}
126128
}
127-
}.toMutableMap()
129+
}.mapValues { NewMetric(METRIC_TYPES.first{mt -> mt.id == it.key}, it.value)}.toMutableMap()
128130
)
129131

130132
private fun truesFalses(

server/src/main/kotlin/org/ivdnt/galahad/evaluation/metrics/JobMetric.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import org.ivdnt.galahad.util.merge
77

88
class JobMetric(
99
@JsonValue
10-
val classesByGroup: MutableMap<String, NewMetric>
10+
val classesByGroup: Map<String, NewMetric>
1111
) {
1212
companion object {
1313
fun create(corpus: Corpus, docEvals: DocumentEvaluations): JobMetric = JobMetric(
1414
corpus.documents.readAllSequence().map { docEvals.createOrThrow(it.name).metrics.classesByGroup }
1515
.reduce { map1, map2 ->
1616
map1.merge(map2) { a, b -> a.apply { grouped.merge(b.grouped) { x, y -> x.add(y) } } } as MutableMap<String, NewMetric>
17-
}
17+
}.mapValues { NewMetric(it.value.settings, it.value.grouped) }
1818
)
1919
}
2020
}

server/src/main/kotlin/org/ivdnt/galahad/evaluation/metrics/Metrics.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore
44
import com.fasterxml.jackson.annotation.JsonProperty
55
import org.ivdnt.galahad.evaluation.comparison.TermComparison
66
import org.ivdnt.galahad.export.csv.CsvFile
7-
import org.ivdnt.galahad.export.csv.CSVHeader
87
import org.ivdnt.galahad.export.csv.CsvString
98
import org.ivdnt.galahad.jobs.Job
109
import org.ivdnt.galahad.taggers.Tagger

0 commit comments

Comments
 (0)