trohith89/Gradient-Descent-Visualizer
1
1import streamlit as st2import numpy as np3import plotly.graph_objects as go4 5# Safe function evaluation6def safe_eval(func_str, x_val):7 """ Safely evaluates the function at a given x value. """8 allowed_names = {"x": x_val, "np": np}9 try:10 return eval(func_str, {"__builtins__": None}, allowed_names)11 except Exception as e:12 raise ValueError(f"Error evaluating the function: {e}")13 14# Function derivative using finite difference method15def derivative(func_str, x_val, h=1e-5):16 """ Numerically compute the derivative of the function at x using finite differences. """17 return (safe_eval(func_str, x_val + h) - safe_eval(func_str, x_val - h)) / (2 * h)18 19# Tangent line equation20def tangent_line(func_str, x_val, x_range):21 """ Compute the tangent line at a given x value. """22 y_val = safe_eval(func_str, x_val)23 slope = derivative(func_str, x_val)24 return slope * (x_range - x_val) + y_val25 26# Callback to reset session state27def reset_state():28 st.session_state.x = st.session_state.starting_point29 st.session_state.iteration = 030 st.session_state.x_vals = [st.session_state.starting_point]31 st.session_state.y_vals = [safe_eval(st.session_state.func_input, st.session_state.starting_point)]32 33# Initialize session state variables34if "func_input" not in st.session_state:35 st.session_state.func_input = "x**2 + x"36if "x" not in st.session_state:37 st.session_state.x = 4.038 st.session_state.iteration = 039 st.session_state.x_vals = [4.0]40 st.session_state.y_vals = [safe_eval(st.session_state.func_input, 4.0)]41 42# Full-width layout43st.set_page_config(layout="wide")44 45# CSS Styles for Borders, Font, Reduced Padding, and Custom Border Color46st.markdown(47 """48 <style>49 * {50 font-family: Cambria, Arial, sans-serif !important;51 }52 h1, h2, h3, h4, h5 {53 text-align: center;54 margin-top: 0;55 }56 input, .stButton button, .stDownloadButton button {57 border: 2px solid #ea445a;58 border-radius: 5px;59 padding: 10px;60 }61 .stInfo, .stSuccess {62 border: 2px solid #ea445a;63 border-radius: 5px;64 padding: 10px;65 }66 .stButton {67 margin-top: 10px;68 }69 /* Reduced Padding at the top */70 .css-1d391kg {71 padding-top: 0.5rem;72 }73 /* Centering the legend in the plot */74 .stPlotlyChart {75 display: block;76 margin: 0 auto;77 }78 /* Adjusting for full width without scrolling */79 .css-1lcbvhc {80 padding-left: 0;81 padding-right: 0;82 }83 /* Custom borders for input fields */84 .stTextInput input, .stNumberInput input {85 border: 2px solid #001A6E;86 border-radius: 5px;87 padding: 10px;88 }89 /* Tooltip styling */90 .tooltip {91 position: relative;92 display: inline-block;93 cursor: pointer;94 }95 .tooltip .tooltiptext {96 visibility: hidden;97 opacity: 0;98 width: 300px;99 background-color: #001A6E;100 color: #fff;101 text-align: center;102 border-radius: 5px;103 padding: 5px;104 position: absolute;105 z-index: 1;106 bottom: 125%; /* Position the tooltip above */107 left: 50%;108 margin-left: -150px;109 transition: opacity 0.3s;110 }111 .tooltip:hover .tooltiptext {112 visibility: visible;113 opacity: 1;114 }115 </style>116 """,117 unsafe_allow_html=True,118)119 120# Page Layout121st.title("๐ Gradient Descent Visualization Tool ๐")122 123col1, col2 = st.columns([1, 2])124 125# Left Section: User Input126with col1:127 st.subheader("๐ง Define Your Function")128 129 # Tooltip with instructions when hovering over the function input label130 st.markdown(131 """132 <div class="tooltip">133 <label for="func_input">Enter a function of 'x':</label>134 <span class="tooltiptext">135 **How to input your function:**136 - Please give the inputs as mentioned below 137 - x^n as x**n, 138 - sin(x) as np.sin(x) 139 - log(x) as np.log(x), 140 - e^x or exp(x) as np.exp(x).141 </span>142 </div>143 """,144 unsafe_allow_html=True145 )146 147 # Use text input for the user to define a function, but no value argument148 func_input = st.text_input(149 "๐", 150 key="func_input", 151 on_change=reset_state152 )153 154 st.subheader("โ๏ธ Gradient Descent Parameters")155 starting_point = st.number_input(156 "Starting Point (Xโ)", 157 value=4.0, 158 step=0.1, 159 format="%.2f", 160 key="starting_point", 161 on_change=reset_state162 )163 learning_rate = st.number_input(164 "Learning Rate (ล)", 165 value=0.25, 166 step=0.01, 167 format="%.2f", 168 key="learning_rate", 169 on_change=reset_state170 )171 172 col3, col4 = st.columns(2)173 with col3:174 if st.button("๐ Set Up Function"):175 reset_state()176 with col4:177 if st.button("โถ๏ธ Next Iteration"):178 try:179 grad = derivative(st.session_state.func_input, st.session_state.x)180 st.session_state.x = st.session_state.x - learning_rate * grad181 st.session_state.iteration += 1182 st.session_state.x_vals.append(st.session_state.x)183 st.session_state.y_vals.append(safe_eval(st.session_state.func_input, st.session_state.x))184 except Exception as e:185 st.error(f"โ ๏ธ Error: {str(e)}")186 187# Right Section: Visualization188with col2:189 st.subheader("๐ Gradient Descent Visualization")190 try:191 # Plot the function and all current and previous gradient descent points192 x_plot = np.linspace(-10, 10, 400)193 y_plot = [safe_eval(st.session_state.func_input, x) for x in x_plot]194 195 fig = go.Figure()196 197 # Function curve198 fig.add_trace(go.Scatter(199 x=x_plot, 200 y=y_plot, 201 mode="lines+markers", 202 line=dict(color="blue", width=2), 203 marker=dict(size=4, color="blue", symbol="circle"),204 name="Function"205 ))206 207 # All gradient descent points (red points without coordinates)208 fig.add_trace(go.Scatter(209 x=st.session_state.x_vals,210 y=st.session_state.y_vals,211 mode="markers",212 marker=dict(color="red", size=10),213 name="Gradient Descent Points"214 ))215 216 # Tangent line at the current gradient descent point217 current_x = st.session_state.x218 tangent_x = np.linspace(-10, 10, 200) # Adjusting range to cover entire plot219 tangent_y = tangent_line(st.session_state.func_input, current_x, tangent_x)220 fig.add_trace(go.Scatter(221 x=tangent_x,222 y=tangent_y,223 mode="lines",224 line=dict(color="orange", width=3),225 name="Tangent Line"226 ))227 228 # Dynamic zoom for better visibility229 fig.update_layout(230 xaxis=dict(231 title="x-axis",232 range=[-10, 10],233 showline=True,234 linecolor="white",235 tickcolor="white",236 tickfont=dict(color="white"),237 ticks="outside",238 ),239 yaxis=dict(240 title="y-axis",241 range=[min(y_plot) - 5, min(max(y_plot) + 5, 1000)], # Limiting the max y to 1000242 showline=True,243 linecolor="white",244 tickcolor="white",245 tickfont=dict(color="white"),246 ticks="outside",247 ),248 plot_bgcolor="black",249 paper_bgcolor="black",250 title="",251 margin=dict(l=10, r=10, t=10, b=10),252 width=800,253 height=400,254 showlegend=True,255 legend=dict(256 x=1.1,257 y=0.5,258 xanchor="left",259 yanchor="middle",260 orientation="v",261 font=dict(size=12, color="white"),262 bgcolor="black",263 bordercolor="white",264 borderwidth=2,265 )266 )267 268 # Axis lines for quadrants269 fig.add_shape(type="line", x0=-10, x1=10, y0=0, y1=0, line=dict(color="white", width=2)) # x-axis270 fig.add_shape(type="line", x0=0, x1=0, y0=-100, y1=100, line=dict(color="white", width=2)) # y-axis271 272 st.plotly_chart(fig, use_container_width=True)273 274 except Exception as e:275 st.error(f"โ ๏ธ Error in visualization: {str(e)}")276 277 # Iteration stats and download278 col5, col6 = st.columns(2)279 col5.info(f"๐งโ๐ป Iteration: {st.session_state.iteration}")280 col6.success(f"โ
Current x: {st.session_state.x:.4f}, Current f(x): {st.session_state.y_vals[-1]:.4f}")