ceyda's picture
Duplicate from aseifert/ExplaiNER
2d4811a
"""
The metrics page contains precision, recall and f-score metrics as well as a confusion matrix over all the classes. By default, the confusion matrix is normalized. There's an option to zero out the diagonal, leaving only prediction errors (here it makes sense to turn off normalization, so you get raw error counts).
"""
import re
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import plotly.express as px
import streamlit as st
from seqeval.metrics import classification_report
from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix
from src.subpages.page import Context, Page
def _get_evaluation(df):
y_true = df.apply(lambda row: [lbl for lbl in row.labels if lbl != "IGN"], axis=1)
y_pred = df.apply(
lambda row: [pred for (pred, lbl) in zip(row.preds, row.labels) if lbl != "IGN"],
axis=1,
)
report: str = classification_report(y_true, y_pred, scheme="IOB2", digits=3) # type: ignore
return report.replace(
"precision recall f1-score support",
"=" * 12 + " precision recall f1-score support",
)
def plot_confusion_matrix(y_true, y_preds, labels, normalize=None, zero_diagonal=True):
cm = confusion_matrix(y_true, y_preds, normalize=normalize, labels=labels)
if zero_diagonal:
np.fill_diagonal(cm, 0)
# st.write(plt.rcParams["font.size"])
# plt.rcParams.update({'font.size': 10.0})
fig, ax = plt.subplots(figsize=(10, 10))
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels)
fmt = "d" if normalize is None else ".3f"
disp.plot(
cmap="Blues",
include_values=True,
xticks_rotation="vertical",
values_format=fmt,
ax=ax,
colorbar=False,
)
return fig
class MetricsPage(Page):
name = "Metrics"
icon = "graph-up-arrow"
def _get_widget_defaults(self):
return {
"normalize": True,
"zero_diagonal": False,
}
def render(self, context: Context):
st.title(self.name)
with st.expander("💡", expanded=True):
st.write(
"The metrics page contains precision, recall and f-score metrics as well as a confusion matrix over all the classes. By default, the confusion matrix is normalized. There's an option to zero out the diagonal, leaving only prediction errors (here it makes sense to turn off normalization, so you get raw error counts)."
)
st.write(
"With the confusion matrix, you don't want any of the classes to end up in the bottom right quarter: those are frequent but error-prone."
)
eval_results = _get_evaluation(context.df)
if len(eval_results.splitlines()) < 8:
col1, _, col2 = st.columns([8, 1, 1])
else:
col1 = col2 = st
col1.subheader("🎯 Evaluation Results")
col1.code(eval_results)
results = [re.split(r" +", l.lstrip()) for l in eval_results.splitlines()[2:-4]]
data = [(r[0], int(r[-1]), float(r[-2])) for r in results]
df = pd.DataFrame(data, columns="class support f1".split())
fig = px.scatter(
df,
x="support",
y="f1",
range_y=(0, 1.05),
color="class",
)
# fig.update_layout(title_text="asdf", title_yanchor="bottom")
col1.plotly_chart(fig)
col2.subheader("🔠 Confusion Matrix")
normalize = None if not col2.checkbox("Normalize", key="normalize") else "true"
zero_diagonal = col2.checkbox("Zero Diagonal", key="zero_diagonal")
col2.pyplot(
plot_confusion_matrix(
y_true=context.df_tokens_cleaned["labels"],
y_preds=context.df_tokens_cleaned["preds"],
labels=context.labels,
normalize=normalize,
zero_diagonal=zero_diagonal,
),
)