| import dash
|
| from dash import dcc, html
|
| from dash.dependencies import Input, Output
|
| from utils.figures import serve_prediction_plot
|
|
|
| app = dash.Dash(
|
| __name__,
|
| meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}]
|
| )
|
| app.title = "code213 Supervised Learning Analytics Explorer"
|
| server = app.server
|
|
|
| app.layout = html.Div(
|
|
|
| style={
|
| 'fontFamily': 'system-ui, sans-serif',
|
| 'background': 'linear-gradient(135deg, #fff5f5 0%, #ffe4e6 50%, #fbcfe8 100%)',
|
| 'minHeight': '100vh',
|
| 'padding': '40px 20px'
|
| },
|
| children=[
|
|
|
| html.Div(
|
| style={'maxWidth': '1400px', 'margin': '0 auto'},
|
| children=[
|
|
|
| html.H1("Supervised Learning Module: KNN Explorer", style={'textAlign': 'center', 'color': '#1e1b4b', 'fontWeight': '800'}),
|
| html.P("code213 Data Science Bootcamp • Instructor: Latreche Sara", style={'textAlign': 'center', 'color': '#be123c', 'marginTop': '-10px', 'fontWeight': 'bold', 'letterSpacing': '0.5px'}),
|
|
|
| html.Div(
|
| style={'display': 'flex', 'flexWrap': 'wrap', 'gap': '30px', 'marginTop': '35px'},
|
| children=[
|
|
|
| html.Div(
|
| style={
|
| 'flex': '1',
|
| 'minWidth': '320px',
|
| 'backgroundColor': 'rgba(255, 255, 255, 0.9)',
|
| 'padding': '25px',
|
| 'borderRadius': '16px',
|
| 'border': '1px solid rgba(255, 255, 255, 0.7)',
|
| 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)'
|
| },
|
| children=[
|
| html.H4("Hyperparameters", style={'marginTop': '0', 'color': '#1e1b4b', 'borderBottom': '3px solid #e11d48', 'paddingBottom': '8px', 'fontWeight': '700'}),
|
|
|
| html.Label("Dataset Selection", style={'fontWeight': 'bold', 'display': 'block', 'marginTop': '15px', 'color': '#334155'}),
|
| dcc.Dropdown(
|
| id='dataset-selector',
|
| options=[
|
| {'label': 'Moons Topology', 'value': 'moons'},
|
| {'label': 'Circles Topology', 'value': 'circles'},
|
| {'label': 'Linear Separable', 'value': 'linear'}
|
| ],
|
| value='moons', clearable=False, style={'marginBottom': '15px'}
|
| ),
|
|
|
| html.Label("Dataset Noise Level", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '5px'}),
|
| dcc.Slider(id='noise-slider', min=0.0, max=0.6, step=0.05, value=0.15, marks={i/10: str(i/10) for i in range(7)}),
|
| html.Div(style={'height': '20px'}),
|
|
|
| html.Label("Number of Neighbors (k)", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '5px'}),
|
| dcc.Slider(id='k-slider', min=1, max=50, step=1, value=5, marks={1: '1', 5: '5', 15: '15', 30: '30', 50: '50'}),
|
| html.Div(style={'height': '20px'}),
|
|
|
| html.Label("Voting Weights", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '8px'}),
|
| dcc.RadioItems(
|
| id='weights-radio',
|
| options=[
|
| {'label': ' Uniform (Equal Vote)', 'value': 'uniform'},
|
| {'label': ' Distance (Inverse Proximity)', 'value': 'distance'}
|
| ],
|
| value='uniform', labelStyle={'display': 'block', 'marginBottom': '8px', 'color': '#475569'}
|
| ),
|
| html.Div(style={'height': '20px'}),
|
|
|
| html.Label("Distance Metric (Minkowski p-norm)", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '8px'}),
|
| dcc.RadioItems(
|
| id='p-norm-radio',
|
| options=[
|
| {'label': ' p = 1 (Manhattan Distance)', 'value': 1},
|
| {'label': ' p = 2 (Euclidean Distance)', 'value': 2}
|
| ],
|
| value=2, labelStyle={'display': 'block', 'marginBottom': '8px', 'color': '#475569'}
|
| )
|
| ]
|
| ),
|
|
|
|
|
| html.Div(
|
| style={'flex': '2.5', 'minWidth': '500px', 'display': 'flex', 'flexDirection': 'column', 'gap': '20px'},
|
| children=[
|
|
|
| html.Div(
|
| style={'display': 'flex', 'gap': '20px'},
|
| children=[
|
| html.Div(
|
| style={
|
| 'flex': '1', 'textAlign': 'center', 'padding': '20px',
|
| 'backgroundColor': '#ffffff', 'borderRadius': '12px',
|
| 'boxShadow': '0 4px 6px -1px rgba(0, 0, 0, 0.05)',
|
| 'borderLeft': '5px solid #e11d48'
|
| },
|
| children=[
|
| html.Div("Train Accuracy Score", style={'fontSize': '11pt', 'color': '#9f1239', 'fontWeight': 'bold'}),
|
| html.H2(id='train-acc-output', style={'margin': '5px 0 0 0', 'color': '#e11d48', 'border': 'none', 'padding': '0', 'fontWeight': '800'})
|
| ]
|
| ),
|
| html.Div(
|
| style={
|
| 'flex': '1', 'textAlign': 'center', 'padding': '20px',
|
| 'backgroundColor': '#ffffff', 'borderRadius': '12px',
|
| 'boxShadow': '0 4px 6px -1px rgba(0, 0, 0, 0.05)',
|
| 'borderLeft': '5px solid #16a34a'
|
| },
|
| children=[
|
| html.Div("Test Accuracy Score", style={'fontSize': '11pt', 'color': '#166534', 'fontWeight': 'bold'}),
|
| html.H2(id='test-acc-output', style={'margin': '5px 0 0 0', 'color': '#16a34a', 'border': 'none', 'padding': '0', 'fontWeight': '800'})
|
| ]
|
| )
|
| ]
|
| ),
|
|
|
|
|
| dcc.Loading(
|
| id="loading-plots",
|
| type="circle",
|
| children=[
|
|
|
| html.Div(
|
| style={
|
| 'backgroundColor': '#ffffff', 'padding': '20px', 'borderRadius': '16px',
|
| 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)'
|
| },
|
| children=[
|
| dcc.Graph(id='knn-boundary-plot', style={'height': '500px'}),
|
| ]
|
| ),
|
| html.Div(style={'height': '20px'}),
|
|
|
|
|
| html.Div(
|
| style={'display': 'flex', 'flexWrap': 'wrap', 'gap': '20px'},
|
| children=[
|
| html.Div(
|
| style={
|
| 'flex': '1', 'minWidth': '300px', 'backgroundColor': '#ffffff',
|
| 'padding': '15px', 'borderRadius': '16px', 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)'
|
| },
|
| children=[dcc.Graph(id='knn-confusion-matrix')]
|
| ),
|
| html.Div(
|
| style={
|
| 'flex': '1', 'minWidth': '300px', 'backgroundColor': '#ffffff',
|
| 'padding': '15px', 'borderRadius': '16px', 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)'
|
| },
|
| children=[dcc.Graph(id='knn-roc-curve')]
|
| )
|
| ]
|
| )
|
| ]
|
| )
|
| ]
|
| )
|
| ]
|
| )
|
| ]
|
| )
|
| ]
|
| )
|
|
|
| @app.callback(
|
| [
|
| Output('knn-boundary-plot', 'figure'),
|
| Output('knn-confusion-matrix', 'figure'),
|
| Output('knn-roc-curve', 'figure'),
|
| Output('train-acc-output', 'children'),
|
| Output('test-acc-output', 'children')
|
| ],
|
| [
|
| Input('dataset-selector', 'value'),
|
| Input('noise-slider', 'value'),
|
| Input('k-slider', 'value'),
|
| Input('weights-radio', 'value'),
|
| Input('p-norm-radio', 'value')
|
| ]
|
| )
|
| def update_dashboard(dataset_name, noise, n_neighbors, weights, p_value):
|
| fig_boundary, fig_cm, fig_roc, train_score, test_score = serve_prediction_plot(
|
| dataset_name=dataset_name,
|
| noise=noise,
|
| n_neighbors=n_neighbors,
|
| weights=weights,
|
| metric='minkowski',
|
| p_value=p_value
|
| )
|
|
|
| train_pct = f"{train_score * 100:.2f}%"
|
| test_pct = f"{test_score * 100:.2f}%"
|
|
|
| return fig_boundary, fig_cm, fig_roc, train_pct, test_pct
|
|
|
| if __name__ == '__main__':
|
| app.run(debug=True) |