import streamlit as st import plotly.graph_objects as go import numpy as np import scipy.integrate as integrate def _false_positive_probability(threshold, b, r): def _probability(s): return 1 - (1 - s ** float(r)) ** float(b) a, err = integrate.quad(_probability, 0.0, threshold) return a def _false_negative_probability(threshold, b, r): def _probability(s): return 1 - (1 - (1 - s ** float(r)) ** float(b)) a, err = integrate.quad(_probability, threshold, 1.0) return a def _optimal_param(threshold, num_perm, false_positive_weight, false_negative_weight): """ Compute the optimal `MinHashLSH` parameter that minimizes the weighted sum of probabilities of false positive and false negative. """ min_error = float("inf") opt = (0, 0) for b in range(1, num_perm + 1): max_r = int(num_perm / b) for r in range(1, max_r + 1): fp = _false_positive_probability(threshold, b, r) fn = _false_negative_probability(threshold, b, r) error = fp * false_positive_weight + fn * false_negative_weight if error < min_error: min_error = error opt = (b, r) return opt col1, col2 = st.columns(2) s = col1.slider("Select a Jaccard similarity", 0.0, 1.0, 0.1) p = col2.slider("Select a number of permutations", 0, 1000, 10) optimal_b, optimal_r = _optimal_param(s, p, 1, 1) b = col1.slider("Select a number of bands", 1, 100, 1) r = col2.slider("Select a number of rows per band", 1, 100, 1) col1.metric(label="Optimal number of bands", value=optimal_b) col2.metric(label="Optimal number of rows per band", value=optimal_r) st.markdown("---") st.markdown(f"Two documents that have a Jaccard similarity of $s={s}$ will have:") st.markdown(f"1. ${s * 100:.2f}\%$ of their k-shingles will be the same") st.markdown(f"2. ${s * 100:.2f}\%$ of their k-shingles' hashes will be the same") st.markdown(f"4. ${s * 100:.2f}\%$ of the time, a particular hash will be the same for two documents") st.markdown( f"3. $s^r={100 * s ** r:.2f}\%$ of the time, they will have the same hashes for a particular band of $r={r}$ rows" ) st.markdown( f"5. $1 - s^r = {100 * (1 - s ** r):.2f}\%$ of the time, they will have at least one different hash for a particular band" ) st.markdown( f"6. $(1 - s^r)^b = {100 * (1 - s ** r)**b:.2f}\%$ of the time, they will have at least one different hash for all $b={b}$ bands" ) st.markdown( f"7. $1 - (1 - s^r)^b={100 * (1 - (1 - s ** r)**b):.2f}\%$ of the time, they will have at least one band with the same hashes" ) t = st.slider("Select a Jaccard similarity threshold", 0.0, 1.0, 0.1) x = np.linspace(0, 1, 1000) y = 1 - (1 - x**r) ** b fig = go.Figure( data=go.Scatter( x=x, y=y, showlegend=False, ) ) fig = fig.add_shape( type="line", x0=t, y0=0, x1=t, y1=1, line=dict( color="Red", width=4, ), ) false_positive_x = [d for d in x if d <= t] + [t] false_positive_y = [d for i, d in enumerate(y) if x[i] <= t] + [0] fig.add_trace( go.Scatter( x=false_positive_x, y=false_positive_y, fill="tozeroy", fillcolor="rgba(255, 0, 0, 0.2)", line_color="rgba(255, 0, 0, 0)", showlegend=False, ) ) false_negative_x = [d for d in x if d > t] false_negative_y = [d for i, d in enumerate(y) if x[i] > t] fig.add_trace( go.Scatter( x=[t] + false_negative_x + [1], y=[1] + false_negative_y + [1], fill="toself", fillcolor="rgba(0, 255, 0, 0.2)", line_color="rgba(0, 255, 0, 0)", showlegend=False, ) ) st.plotly_chart(fig) false_positive = integrate.quad(lambda x: 1 - (1 - x**r) ** b, 0, t)[0] false_negative = integrate.quad(lambda x: (1 - x**r) ** b, t, 1)[0] cols = st.columns(2) cols[0].metric(label="False positive area", value=f"{false_positive:.2f}") cols[1].metric(label="False negative area", value=f"{false_negative:.2f}")