{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":44630,"databundleVersionId":4862933},{"sourceType":"competition","sourceId":21154,"databundleVersionId":1243559},{"sourceType":"competition","sourceId":34377,"databundleVersionId":3220602},{"sourceType":"competition","sourceId":3136,"databundleVersionId":26502},{"sourceType":"datasetVersion","sourceId":408,"datasetId":180,"databundleVersionId":408},{"sourceType":"datasetVersion","sourceId":666,"datasetId":306,"databundleVersionId":666},{"sourceType":"datasetVersion","sourceId":420,"datasetId":19,"databundleVersionId":420},{"sourceType":"datasetVersion","sourceId":974,"datasetId":478,"databundleVersionId":974},{"sourceType":"datasetVersion","sourceId":482,"datasetId":228,"databundleVersionId":482}],"dockerImageVersionId":30527,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<h2 align=\"center\"><font color='black'>Neural Networks from Scratch, No TF or Pytorch</font></h2>\n\n\n## Introduction\nNeural networks, the fundamental building blocks of artificial intelligence, have transformed the landscape of technology and our daily lives. These powerful algorithms are designed to mimic the human brain's way of learning and decision-making, making them adept at tackling complex tasks and providing accurate predictions.\n\nIn this tutorial, we'll delve into the basics of neural networks and their significance. We'll embark on a journey to create a simple 3-layer neural network, inspired by the insightful work of Samson. This tutorial offers a hands-on introduction to the essential steps involved in building a neural network from scratch.\n\n![image](https://media.geeksforgeeks.org/wp-content/cdn-uploads/20230602113310/Neural-Networks-Architecture.png)\n\nThis notebook draws inspiration from Samson's insightful work I found online, which includes a valuable video and a [Kaggle notebook](https://www.kaggle.com/code/wwsalmon/simple-mnist-nn-from-scratch-numpy-no-tf-keras) providing deeper insights into the intricacies of neural network development. \n\nAlthough you might never ever go this deep in real life world when building out models for your own use-case, its always better to know what's going under the hood. Let's dive in and explore the world of neural networks together.","metadata":{}},{"cell_type":"markdown","source":"## About the Dataset\n\nAccording to the World Health Organization (WHO) stroke is the 2nd leading cause of death globally, responsible for approximately 11% of total deaths. This dataset is used to predict whether a patient is likely to get stroke based on the input parameters like gender, age, various diseases, and smoking status. Each row in the data provides relavant information about the patient.\n\n**Attribute Information**\n\n1. **id:** Unique identifier\n2. **gender:** \"Male\", \"Female\", or \"Other\"\n3. **age:** Age of the patient\n4. **hypertension:** 0 if the patient doesn't have hypertension, 1 if the patient has hypertension\n5. **heart_disease:** 0 if the patient doesn't have any heart diseases, 1 if the patient has a heart disease\n6. **ever_married:** \"No\" or \"Yes\"\n7. **work_type:** \"Children\", \"Govt_job\", \"Never_worked\", \"Private\", or \"Self-employed\"\n8. **Residence_type:** \"Rural\" or \"Urban\"\n9. **avg_glucose_level:** Average glucose level in blood\n10. **bmi:** Body mass index\n11. **smoking_status:** \"Formerly smoked\", \"Never smoked\", \"Smokes\", or \"Unknown\"*\n12. **stroke:** 1 if the patient had a stroke, 0 if not\n\n*Note: \"Unknown\" in smoking_status indicates that smoking information is unavailable for the patient.","metadata":{}},{"cell_type":"markdown","source":"## Imports and Reading Data\n\n- While the deep learning landscape is dominated by powerful frameworks like `TensorFlow`and `PyTorch`, we'll take a unique approach. We won't rely on these main deep learning libraries, instead opting for a more hands-on exploration using basic tools.\n\n- Our toolkit for this tutorial primarily includes the versatile `NumPy` library for numerical computations and some other helpful libraries like `sklearn`, `pandas` for data manipulation and `seaborn` and `matplotlib` for visualization. NumPy provides us with an array-like structure that is efficient for performing mathematical operations on large datasets.","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom imblearn.over_sampling import RandomOverSampler\nfrom sklearn.metrics import confusion_matrix\nimport seaborn as sns\nimport sqlite3\nimport matplotlib.pyplot as plt\n%matplotlib inline\nplt.rcParams['font.size'] = 14\nplt.rcParams['figure.figsize'] = (22, 5)\nplt.rcParams['figure.dpi'] = 100","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:07.188042Z","iopub.execute_input":"2026-04-20T05:36:07.188827Z","iopub.status.idle":"2026-04-20T05:36:07.195090Z","shell.execute_reply.started":"2026-04-20T05:36:07.188799Z","shell.execute_reply":"2026-04-20T05:36:07.194156Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"conn = sqlite3.connect('playground.db')","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:07.196509Z","iopub.execute_input":"2026-04-20T05:36:07.196774Z","iopub.status.idle":"2026-04-20T05:36:07.205100Z","shell.execute_reply.started":"2026-04-20T05:36:07.196753Z","shell.execute_reply":"2026-04-20T05:36:07.204355Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/playground-series-s3e2/train.csv')\ntrain.head()","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:07.257178Z","iopub.execute_input":"2026-04-20T05:36:07.257640Z","iopub.status.idle":"2026-04-20T05:36:07.291807Z","shell.execute_reply.started":"2026-04-20T05:36:07.257613Z","shell.execute_reply":"2026-04-20T05:36:07.290907Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.to_sql('data', conn, if_exists='replace', index=False)","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:07.293310Z","iopub.execute_input":"2026-04-20T05:36:07.293584Z","iopub.status.idle":"2026-04-20T05:36:07.370481Z","shell.execute_reply.started":"2026-04-20T05:36:07.293563Z","shell.execute_reply":"2026-04-20T05:36:07.369602Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Exploratory Data Analysis","metadata":{}},{"cell_type":"code","source":"fig, ax = plt.subplots()\nN, bins, patches = ax.hist(np.array(train.avg_glucose_level), edgecolor='white', color='lightgray',linewidth=5, alpha=0.7)\nfor i in range(1,2):\n    patches[i].set_facecolor('orange')\n    plt.title('Avg Glucose Level Histogram', fontsize=18)\n    plt.gca().spines['top'].set_visible(False)\n    plt.gca().spines['right'].set_visible(False)\n    plt.xlabel('avg_glucose_level')\n    plt.ylabel('Count')\n    plt.axvline(train.avg_glucose_level.mean(), linestyle='--', lw=2, zorder=1, color='blue')\n    plt.annotate(f' mean', (90, 7500), fontsize=14,color='black')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:07.371610Z","iopub.execute_input":"2026-04-20T05:36:07.372011Z","iopub.status.idle":"2026-04-20T05:36:07.583010Z","shell.execute_reply.started":"2026-04-20T05:36:07.371987Z","shell.execute_reply":"2026-04-20T05:36:07.582204Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"query = '''\nselect gender, \n    round(sum(case when stroke=0 then 1 else 0 end)*100.0/(select count(*) from data),2) as No_Stroke,\n    round(sum(case when stroke=1 then 1 else 0 end)*100.0/(select count(*) from data),2) as Stroke\nfrom data\ngroup by 1\n'''\nGender_stroke = pd.read_sql(query, conn)\n\nax = Gender_stroke.plot(x='gender', kind='bar')\nplt.title('Gender Wise Stroke %', fontsize=18)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xlabel('Gender')\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\n\n# For labels on bars\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%')  \n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:39:02.042003Z","iopub.execute_input":"2026-04-20T05:39:02.042314Z","iopub.status.idle":"2026-04-20T05:39:02.598563Z","shell.execute_reply.started":"2026-04-20T05:39:02.042292Z","shell.execute_reply":"2026-04-20T05:39:02.597712Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Observation\n\n- The data reveals that **females have a slightly higher rate of stroke at 2.4% compared to males at 1.73%**. This may indicate a higher stroke risk among females within this dataset. Meanwhile, the “Other” category has negligible representation, with no recorded instances of stroke.\n\nSo What?\n\n- The higher stroke rate among females could reflect biological, lifestyle, or healthcare access differences, possibly influenced by factors such as hormonal changes, longer life expectancy, or different symptom recognition and management rates. However, without further context, it’s challenging to determine if this finding is due to inherent biological differences, healthcare biases, or other environmental factors.\n\nAction\n\n- To better understand gender differences in stroke risk, analyze additional demographic and health factors, such as age, lifestyle choices, and comorbid conditions like hypertension and diabetes. Also, exploring healthcare utilization patterns could reveal disparities in treatment or early warning sign recognition between genders. This deeper analysis can lead to more gender-tailored stroke prevention and intervention strategies.","metadata":{}},{"cell_type":"code","source":"query = '''\nselect smoking_status, \n    round(sum(case when stroke=0 then 1 else 0 end)*100.0/(select count(*) from data),2) as No_Stroke,\n    round(sum(case when stroke=1 then 1 else 0 end)*100.0/(select count(*) from data),2) as Stroke\nfrom data\ngroup by 1\n'''\nsmoking_status_stroke = pd.read_sql(query, conn)\nax = smoking_status_stroke.plot(x='smoking_status', kind='bar')\n\nplt.title('Smoking Status Wise Stroke %', fontsize=18)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xlabel('Smoking Status')\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\n\n# For labels on bars\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%')  \n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:07.603147Z","iopub.execute_input":"2026-04-20T05:36:07.603409Z","iopub.status.idle":"2026-04-20T05:36:07.924662Z","shell.execute_reply.started":"2026-04-20T05:36:07.603389Z","shell.execute_reply":"2026-04-20T05:36:07.923781Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Observation\n\n- **1.68% of individuals who have never smoked experienced a stroke, which is higher than those who formerly smoked (1.04%)** and those who currently smoke (0.71%). At first glance, this seems counterintuitive, as we typically expect smoking to increase stroke risk.\n\nSo What?\n\n- This unexpected pattern suggests the influence of other risk factors that may not be captured in the smoking status alone. Factors such as age, lifestyle, diet, and genetic predispositions could be playing a larger role among non-smokers in this dataset, potentially skewing the stroke rates. Additionally, the large “Unknown” category for smoking status (28.98%) might limit the accuracy of the insight, as unreported smoking behavior could be masking true correlations.\n\nAction\n\n- To clarify the relationship, further analysis is needed, controlling for other variables. Conducting a multivariable logistic regression that includes age, lifestyle habits, and medical history could help isolate smoking’s direct impact on stroke risk. Segmenting this analysis by age or other demographic details will also provide more reliable insights, enabling more accurately targeted health recommendations.","metadata":{}},{"cell_type":"code","source":"query = '''\nselect work_type, \n    round(sum(case when stroke=0 then 1 else 0 end)*100.0/(select count(*) from data),2) as No_Stroke,\n    round(sum(case when stroke=1 then 1 else 0 end)*100.0/(select count(*) from data),2) as Stroke\nfrom data\ngroup by 1\n'''\nwork_stroke = pd.read_sql(query, conn)\nax = work_stroke.plot(x='work_type', kind='bar')\n\nplt.title('Work Type Wise Stroke %', fontsize=18)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xlabel('Work Type')\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\n\n# For labels on bars\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%')\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:07.925798Z","iopub.execute_input":"2026-04-20T05:36:07.926260Z","iopub.status.idle":"2026-04-20T05:36:08.256503Z","shell.execute_reply.started":"2026-04-20T05:36:07.926220Z","shell.execute_reply":"2026-04-20T05:36:08.255648Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Observation\n\n- **Individuals in private employment have the highest stroke incidence at 2.64%, followed by self-employed individuals at 1.03% and government employees at 0.45%**. Those who have never worked or are classified as children show minimal to no recorded cases of stroke.\n\nSo What?\n\n- The higher stroke incidence in private-sector employees and self-employed individuals could reflect the potential impact of workplace stress, lifestyle factors, or limited access to health benefits often associated with these work types. The comparatively lower stroke rates in government employees might suggest better access to healthcare, stable work conditions, or early health screenings.\n\nAction\n\n- This insight suggests a need for targeted health programs focused on stroke prevention and stress management in private and self-employed sectors. Companies could benefit from integrating workplace wellness initiatives, such as stress management workshops, regular health check-ups, and awareness campaigns on stroke symptoms and prevention strategies.","metadata":{}},{"cell_type":"code","source":"query = '''\nwith cte as (\nselect smoking_status,work_type,\n    round(sum(case when work_type='Private' and stroke = 0 then 1 else 0 end)*100.0/(select count(*) from data),2) as no_stroke,\n    round(sum(case when work_type='Private' and stroke = 1 then 1 else 0 end)*100.0/(select count(*) from data),2) as stroke\nfrom data\ngroup by smoking_status, work_type)\n\nselect * from cte where work_type = 'Private'\n'''\nsmoking_status_pvt = pd.read_sql(query, conn)\nax = smoking_status_pvt.plot(x='smoking_status', kind='bar')\n\nplt.title('Smoking Status Wise Stroke %', fontsize=18)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xlabel('Smoking status')\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\n\n# For labels on bars\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%')  # change to %.0f%% if no decimals\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:08.257892Z","iopub.execute_input":"2026-04-20T05:36:08.258250Z","iopub.status.idle":"2026-04-20T05:36:08.572268Z","shell.execute_reply.started":"2026-04-20T05:36:08.258219Z","shell.execute_reply":"2026-04-20T05:36:08.571282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Observation\n\n- The data reveals that private individuals who have never smoked have the highest percentage of no stroke occurrences (28.50), while those who smoke exhibit a stroke incidence of 0.47. Among those who formerly smoked, 9.59 report no stroke, and 0.63 have experienced a stroke. Smokers in the private work category have a relatively low incidence of strokes at 0.47, comparable to those who currently smoke.\n\nSo What?\n\n- This indicates that smoking status plays a significant role in stroke risk, with non-smokers experiencing the lowest risk. The data highlights the potential protective effects of not smoking, suggesting that smoking cessation efforts could significantly reduce stroke incidence.\n\nAction\n\n- Health initiatives should focus on promoting smoking cessation programs, especially targeting private sector employees. Educational campaigns highlighting the direct correlation between smoking and stroke risk can be effective in reducing overall stroke incidence within this demographic.","metadata":{}},{"cell_type":"code","source":"query = '''\nWITH cte AS (\n    SELECT *,\n           CASE \n               WHEN age BETWEEN 0 AND 17 THEN '0 - 17'   -- Ages 0 to 17\n               WHEN age BETWEEN 18 AND 50 THEN '18 - 50'  -- Ages 18 to 50\n               WHEN age BETWEEN 51 AND 64 THEN '51 - 64'  -- Ages 51 to 64\n               WHEN age >= 65 THEN '65+'                  -- Age 65 and older\n               ELSE 'Unknown'                              -- For NULL or unexpected values\n           END AS age_category\n    FROM data\n)\n\nSELECT age_category, \n       ROUND(SUM(CASE WHEN stroke = 0 THEN 1 ELSE 0 END) * 100.0 / (SELECT COUNT(*) FROM data), 2) AS No_Stroke,\n       ROUND(SUM(CASE WHEN stroke = 1 THEN 1 ELSE 0 END) * 100.0 / (SELECT COUNT(*) FROM data), 2) AS Stroke\nFROM cte\nGROUP BY age_category\nORDER BY \n    CASE \n        WHEN age_category = '0 - 17' THEN 1 \n        WHEN age_category = '18 - 50' THEN 2 \n        WHEN age_category = '51 - 64' THEN 3 \n        WHEN age_category = '65+' THEN 4\n        ELSE 5          \n    END;\n'''\nage_stroke = pd.read_sql(query, conn)\nax = age_stroke.plot(x = 'age_category', kind='bar', )\nplt.title('Age Category Wise Stroke %', fontsize=18)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xlabel('Age Category')\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\n\n# Add labels on bars\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%')  \n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:08.573449Z","iopub.execute_input":"2026-04-20T05:36:08.574031Z","iopub.status.idle":"2026-04-20T05:36:08.882313Z","shell.execute_reply.started":"2026-04-20T05:36:08.574007Z","shell.execute_reply":"2026-04-20T05:36:08.881437Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Observations\n\n- In the age category of 0 - 17, 16.73% of individuals did not experience a stroke, while only 0.01% were affected. For the 18 - 50 age group, 45.65% were stroke-free, with a minor incidence of 0.25% for strokes. In the 51 - 64 category, 22.20% had no stroke history, with a slightly higher incidence of 1.22%. Lastly, the 65+ category shows 11.29% without a stroke and a more significant stroke rate of 2.65%.\n\nSo What?\n\n- The data shows a very low stroke risk in the youth demographic (0 - 17), indicating minimal need for preventive efforts. Young adults (18 - 50) have a low but notable incidence, suggesting lifestyle-focused health campaigns. **The middle-aged group (51 - 64) faces a higher risk, requiring stronger preventive measures**. Finally, seniors (65+) have the highest stroke incidence, highlighting the need for targeted health interventions.\n\nAction\n\n- Health organizations should develop educational programs for young adults and middle-aged individuals to raise awareness of stroke risk factors and promote lifestyle changes. Regular health screenings for those aged 51 and older are essential for early risk detection. Additionally, enhancing initiatives for seniors with regular health checks and lifestyle counseling will help manage stroke risk effectively. Tailoring strategies to these age groups can significantly reduce stroke incidence.","metadata":{}},{"cell_type":"code","source":"query = '''\nWITH cte AS (\n    SELECT *,\n           CASE \n               WHEN age BETWEEN 0 AND 17 THEN '0 - 17'   -- Ages 0 to 17\n               WHEN age BETWEEN 18 AND 50 THEN '18 - 50'  -- Ages 18 to 50\n               WHEN age BETWEEN 51 AND 64 THEN '51 - 64'  -- Ages 51 to 64\n               WHEN age >= 65 THEN '65+'                  -- Age 65 and older\n               ELSE 'Unknown'                              -- For NULL or unexpected values\n           END AS age_category\n    FROM data\n)\n\nSELECT '65+' as age_category, work_type, \n       ROUND(SUM(CASE WHEN stroke = 0 THEN 1 ELSE 0 END) * 100.0 / (SELECT COUNT(*) FROM data), 2) AS No_Stroke,\n       ROUND(SUM(CASE WHEN stroke = 1 THEN 1 ELSE 0 END) * 100.0 / (SELECT COUNT(*) FROM data), 2) AS Stroke\nFROM cte\nwhere age_category = '65+'\nGROUP BY work_type\norder by Stroke desc\n\n'''\nage_category_work_type = pd.read_sql(query, conn)\nax = age_category_work_type.plot(x = 'work_type', kind='bar', )\nplt.title('Senior Age Category Work Type Wise Stroke %', fontsize=18)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xlabel('Work Type')\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\n\n# Add labels on bars\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%') \n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:08.883371Z","iopub.execute_input":"2026-04-20T05:36:08.883640Z","iopub.status.idle":"2026-04-20T05:36:09.185972Z","shell.execute_reply.started":"2026-04-20T05:36:08.883619Z","shell.execute_reply":"2026-04-20T05:36:09.185151Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- **Observation**: In the senior demographic (65+), private sector employees show a higher stroke incidence (1.57%) compared to their self-employed (0.88%) and government job counterparts (0.21%).\n\n- **So What?** : This suggests that **seniors in private employment may be at a greater risk of strokes**, potentially due to lifestyle factors or workplace stressors.\n\n- **Action** : Healthcare providers should focus on tailored interventions for older adults in the private sector, such as stress management programs and regular health screenings, to mitigate their stroke risk.","metadata":{}},{"cell_type":"code","source":"query = '''\nselect ever_married, \n    Residence_type,\n    round(sum(case when stroke=0 then 1 else 0 end)*100.0/(select count(*) from data),2) as No_Stroke,\n    round(sum(case when stroke=1 then 1 else 0 end)*100.0/(select count(*) from data),2) as Stroke\nfrom data\ngroup by ever_married, Residence_type\n'''\never_married_stroke = pd.read_sql(query, conn)\nax = ever_married_stroke.set_index(\n    ['ever_married', 'Residence_type']\n)[['No_Stroke', 'Stroke']].plot(kind='bar')\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\n\nplt.xticks(rotation=0)\nplt.ylabel('Percent')\nplt.title('Stroke % by Marriage & Residence')\n\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%') \n    \nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:09.187274Z","iopub.execute_input":"2026-04-20T05:36:09.187633Z","iopub.status.idle":"2026-04-20T05:36:09.518369Z","shell.execute_reply.started":"2026-04-20T05:36:09.187603Z","shell.execute_reply":"2026-04-20T05:36:09.517398Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Observation\n\n- Among individuals who have never married, stroke incidence is slightly lower in rural areas (0.12) compared to urban areas (0.15). In contrast, those who are ever married show a higher incidence of strokes in both rural (1.95) and urban settings (1.91), with urban residents being marginally higher.\n\nSo What?\n\n- Marital status and residence type influence stroke risk, with **married individuals at a higher risk in both rural and urban residence types**. This may suggest that factors such as shared lifestyle choices or stressors in married life could contribute to increased risk.\n\nAction\n\n- Healthcare strategies should consider marital status and living conditions when targeting stroke prevention. Specific campaigns for married individuals, especially in rural and urban settings, could emphasize healthy lifestyle choices and stress management techniques to lower stroke risk.","metadata":{}},{"cell_type":"code","source":"query = '''\nselect stroke,round(avg(bmi),2) as Avg_BMI, round(avg(avg_glucose_level),2) as Avg_Glucose from data group by 1\n'''\nstroke_bmi_glucose = pd.read_sql(query, conn)\n\n\ndf = stroke_bmi_glucose.melt(\n    id_vars='stroke',\n    value_vars=['Avg_BMI', 'Avg_Glucose'],\n    var_name='Metric',\n    value_name='Value'\n)\n\nax = sns.barplot(data=df, x='stroke', y='Value', hue='Metric')\n\nplt.xticks([0,1], ['No Stroke', 'Stroke'])\nplt.title('BMI vs Glucose by Stroke')\nplt.ylabel('Value')\nplt.xlabel('Stroke')\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\n\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.1f%%') \n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:09.519520Z","iopub.execute_input":"2026-04-20T05:36:09.519918Z","iopub.status.idle":"2026-04-20T05:36:09.805560Z","shell.execute_reply.started":"2026-04-20T05:36:09.519860Z","shell.execute_reply":"2026-04-20T05:36:09.804707Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"People with lower BMI have more chances of NOT getting a stroke and those who have high glucose levels are more likely to get a stroke","metadata":{}},{"cell_type":"code","source":"query = '''\nselect stroke,\ncase when age > 30 then 'Young'\nwhen age between 31 and 50 then 'Middle Aged' else 'Old' end as Age_Group, \nround(avg(avg_glucose_level),2) as Avg_Glucose from data group by 1,2\n'''\nage_df = pd.read_sql(query, conn)\n\nage_df","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:09.806946Z","iopub.execute_input":"2026-04-20T05:36:09.807713Z","iopub.status.idle":"2026-04-20T05:36:09.827895Z","shell.execute_reply.started":"2026-04-20T05:36:09.807681Z","shell.execute_reply":"2026-04-20T05:36:09.826928Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Seeing at the above stats, we cannot say that Age and Glucose level are related. A higher glucose level usually indicates higher chance of stroke","metadata":{}},{"cell_type":"code","source":"sns.scatterplot(x=train['age'], y=train['avg_glucose_level'], hue=train['stroke'], alpha=0.5)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\n\nplt.axhline(train['avg_glucose_level'].mean(), linestyle='--', lw=2, zorder=1, color='red')\nplt.annotate(f'avg_glucose_level', (70, 100), fontsize=14, color='black')\n\nplt.title('Avg Glucose Level & age relation', fontsize=18)\nplt.ylabel('Avg Glucose Level')\nplt.xlabel('age')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:09.829042Z","iopub.execute_input":"2026-04-20T05:36:09.829356Z","iopub.status.idle":"2026-04-20T05:36:10.582950Z","shell.execute_reply.started":"2026-04-20T05:36:09.829326Z","shell.execute_reply":"2026-04-20T05:36:10.581947Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"People with age ~50 have higher glucose levels which might lead to a stroke","metadata":{}},{"cell_type":"code","source":"# Define the columns and create subplots\ncols = ['gender', 'hypertension', 'heart_disease', 'ever_married', \n        'work_type', 'Residence_type', 'smoking_status', 'stroke']\n\nfig, axes = plt.subplots(4, 2, figsize=(20, 13))\naxes = axes.flatten()  # Flatten the 2D array of axes to easily iterate\n\n# Loop through the columns and create bar plots in subplots\nfor ax, col in zip(axes, cols):\n    sns.barplot(data=train, x=col, y='avg_glucose_level', hue='stroke', ax=ax, ci=None)\n    ax.spines['top'].set_visible(False)\n    ax.spines['right'].set_visible(False)\n    ax.axhline(train['avg_glucose_level'].mean(), linestyle='--', lw=2, zorder=1, color='black')\n    ax.set_title(f'Avg Glucose Levels, {col} status wise')\n    ax.set_ylabel('Avg Glucose Level')\n    ax.set_xlabel(col)\n    ax.annotate('Avg Glucose Level', (0.2, 80))\n\n# Adjust layout\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:10.586265Z","iopub.execute_input":"2026-04-20T05:36:10.586918Z","iopub.status.idle":"2026-04-20T05:36:12.002686Z","shell.execute_reply.started":"2026-04-20T05:36:10.586854Z","shell.execute_reply":"2026-04-20T05:36:12.001541Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"With the above charts what we are tryin to understand is the avg glucose levels are dictated by what factors. \n\nIs it the **hypertension** or **marital status** or the **residence type** status?\n\nWith Neural Networks, one should always keep in mind the model explainablity is very low and they should be used only where the model explainability is not required or the stakeholders don't really need an explanation","metadata":{}},{"cell_type":"markdown","source":"## Data Preprocessing\n\nWe will be transforming the data in this section and will :-\n\n1. Convert the categorical data into numbers\n2. Scale the data so that our Neural Network Converges quickly\n3. Split the data for training and testing purposes\n4. Fix class Imbalance","metadata":{}},{"cell_type":"code","source":"train.bmi = train.bmi.fillna(round(train.bmi.mean(),2), axis=0)\ntrain_df = train[train.columns[1:-1]]\ntrain_df.head()","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:12.004019Z","iopub.execute_input":"2026-04-20T05:36:12.004287Z","iopub.status.idle":"2026-04-20T05:36:12.022624Z","shell.execute_reply.started":"2026-04-20T05:36:12.004265Z","shell.execute_reply":"2026-04-20T05:36:12.021650Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Converting Categorical Data Into Numbers\n\nFirst of all we are picking out the categorical columns and making a new dataframe and converting the entire dataframe into a `category` dtypes and the getting the codes for each columns and assigining the values back to the original dataframe column.","metadata":{}},{"cell_type":"code","source":"cat_df = train_df[['gender', 'ever_married','work_type', 'Residence_type','smoking_status']]\ncat_df = cat_df.astype('category')\ncat_df = cat_df.apply(lambda x : x.cat.codes)\ncat_df.head()","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:12.023633Z","iopub.execute_input":"2026-04-20T05:36:12.024824Z","iopub.status.idle":"2026-04-20T05:36:12.044891Z","shell.execute_reply.started":"2026-04-20T05:36:12.024802Z","shell.execute_reply":"2026-04-20T05:36:12.043933Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The `train_df` is now our dataframe with the X values","metadata":{}},{"cell_type":"code","source":"train_df[cat_df.columns] = cat_df.copy()\ntrain_df.head()","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:12.046009Z","iopub.execute_input":"2026-04-20T05:36:12.046281Z","iopub.status.idle":"2026-04-20T05:36:12.061288Z","shell.execute_reply.started":"2026-04-20T05:36:12.046260Z","shell.execute_reply":"2026-04-20T05:36:12.060312Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Fixing Class Imbalance\n\n**Class imbalance** refers to a situation in machine learning where the distribution of classes in the training dataset is not equal. In other words, one class has significantly more instances (samples) than another class or classes. Class imbalance is a common issue in many real-world machine learning problems, such as fraud detection, medical diagnosis, and text classification, where one class (usually the minority class) is of more interest, but it has fewer examples to learn from compared to the majority class.\n\n**Why Class Imbalance Is a Problem:**\n\nClass imbalance can cause machine learning models to perform poorly, especially when the model is biased towards the majority class. Here are some challenges posed by class imbalance:\n\n1. **Biased Models:** A model trained on imbalanced data may become biased towards the majority class. It may predict the majority class accurately but perform poorly on the minority class.\n\n2. **Poor Generalization:** Imbalanced datasets can lead to models that do not generalize well to unseen data, as they are often overly skewed towards the majority class.\n\n3. **Misleading Evaluation:** Traditional accuracy metrics can be misleading in imbalanced datasets because a model that predicts the majority class most of the time can still have high accuracy but perform poorly on the minority class.\n\n**Ways to Address Class Imbalance:**\n\nAlthough there are various ways of addressing this issue, we will be looking at resampling techniques. More Specifically Oversampling.\n\n   a. **Oversampling:** Increase the number of instances in the minority class by duplicating or generating synthetic samples. Techniques like Synthetic Minority Over-sampling Technique (SMOTE) are commonly used.\n\n   b. **Undersampling:** Reduce the number of instances in the majority class by randomly removing samples. This can help balance class distribution.\n\n\nBefore moving any further let's check if our classes are imbalanced or not.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(22, 6))\nax = train['stroke'].value_counts().plot(kind='bar')\nyticks = plt.gca().get_yticks()\nylabels = [f\"{round(i / 1000)}k\" if i != 0 else \"0\" for i in yticks]\nplt.gca().set_yticklabels(ylabels)\nplt.ylabel('Count')\nplt.title('Target Class Countplot',  fontsize=15)\nplt.gca().spines['top'].set_visible(False)\nplt.gca().spines['right'].set_visible(False)\nplt.xticks([0,1], ['No Stroke', 'Stroke'])\nplt.xticks(rotation=None)\n\nfor container in ax.containers:\n    ax.bar_label(container, fmt='%.0f') \nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:12.062438Z","iopub.execute_input":"2026-04-20T05:36:12.062893Z","iopub.status.idle":"2026-04-20T05:36:12.313712Z","shell.execute_reply.started":"2026-04-20T05:36:12.062838Z","shell.execute_reply":"2026-04-20T05:36:12.312892Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"It seems they are imbalanced so we would use `OverSampler` from `imblearn` library. Before that let's define our X and y","metadata":{}},{"cell_type":"code","source":"X = train_df.values\ny = train.stroke.values","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:12.315045Z","iopub.execute_input":"2026-04-20T05:36:12.315380Z","iopub.status.idle":"2026-04-20T05:36:12.321126Z","shell.execute_reply.started":"2026-04-20T05:36:12.315350Z","shell.execute_reply":"2026-04-20T05:36:12.320290Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Resampling\nrus = RandomOverSampler(random_state=0)\nX_resampled, y_resampled = rus.fit_resample(X,y)","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:12.322156Z","iopub.execute_input":"2026-04-20T05:36:12.323135Z","iopub.status.idle":"2026-04-20T05:36:12.335584Z","shell.execute_reply.started":"2026-04-20T05:36:12.323112Z","shell.execute_reply":"2026-04-20T05:36:12.334815Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = {i : len(y_resampled[y_resampled==i]) for i in np.unique(y_resampled)}\nprint(f'Instances of the class after re-sampling : {tuple(class_counts.items())}')","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:12.336984Z","iopub.execute_input":"2026-04-20T05:36:12.337265Z","iopub.status.idle":"2026-04-20T05:36:12.342632Z","shell.execute_reply.started":"2026-04-20T05:36:12.337244Z","shell.execute_reply":"2026-04-20T05:36:12.341822Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Using sklearn's `train_test_split` function, we will split the data into train and test sets for performance evaluation later on.","metadata":{}},{"cell_type":"code","source":"#Splitting Data\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:12.343827Z","iopub.execute_input":"2026-04-20T05:36:12.344194Z","iopub.status.idle":"2026-04-20T05:36:12.354438Z","shell.execute_reply.started":"2026-04-20T05:36:12.344169Z","shell.execute_reply":"2026-04-20T05:36:12.353579Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Let's now initialize the `StandardScaler` object and transform our data","metadata":{}},{"cell_type":"code","source":"#Scaling the values\nscaler = StandardScaler()\nX_train = scaler.fit_transform(X_train)\nX_test = scaler.transform(X_test)","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:12.355316Z","iopub.execute_input":"2026-04-20T05:36:12.355632Z","iopub.status.idle":"2026-04-20T05:36:12.367558Z","shell.execute_reply.started":"2026-04-20T05:36:12.355599Z","shell.execute_reply":"2026-04-20T05:36:12.366805Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Training data shapes:\")\nprint(\"X_train:\", X_train.shape)\nprint(\"y_train:\", y_train.shape)\n\nprint(\"\\nTesting data shapes:\")\nprint(\"X_test:\", X_test.shape)\nprint(\"y_test:\", y_test.shape)","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:12.368715Z","iopub.execute_input":"2026-04-20T05:36:12.369053Z","iopub.status.idle":"2026-04-20T05:36:12.374713Z","shell.execute_reply.started":"2026-04-20T05:36:12.369032Z","shell.execute_reply":"2026-04-20T05:36:12.373790Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Architecture\n\nThe neural network that we will be working with will have 3 layers. The neural network consists of:\n\n1. **Input Layer:** 10 nodes that receive the initial data.\n2. **Hidden Layer:** 10 nodes that process information from the input.\n3. **Output Layer:** 10 nodes that produce intermediate results.\n4. **Classification Neuron:** Positioned within the Output Layer to finalize binary classification using the 10 intermediate results.\n\nLet's plot out our architecture below:-","metadata":{}},{"cell_type":"code","source":"##Plotting the Architecture of the network\n\n# Define the architecture of the neural network\ninput_nodes = 10\nhidden_nodes = 10\noutput_nodes = 10\n\n# Create a figure and axis for plotting\nfig, ax = plt.subplots(figsize=(16, 6))\n\n# Plot input layer nodes\nfor i in range(input_nodes):\n    ax.scatter(0, i, color='blue', label='Input Layer' if i == 0 else \"\")\n\n# Plot hidden layer nodes\nfor i in range(hidden_nodes):\n    ax.scatter(1, i, color='orange', label='Hidden Layer' if i == 0 else \"\")\n\n# Plot output layer nodes\nfor i in range(output_nodes):\n    ax.scatter(2, i, color='green', label='Output Layer' if i == 0 else \"\")\n\n# Draw connections between layers\nfor i in range(input_nodes):\n    for j in range(hidden_nodes):\n        ax.plot([0, 1], [i, j], color='gray', alpha=0.5)\n\nfor i in range(hidden_nodes):\n    for j in range(output_nodes):\n        ax.plot([1, 2], [i, j], color='gray', alpha=0.5)\n\n# Draw connection to final classification neuron\nfor i in range(output_nodes):\n    ax.plot([2, 3], [i, 5], color='gray', alpha=0.5)\n\n# Add labels to layers\nax.text(-0.1, input_nodes // 2, 'Input', fontsize=12, va='center', ha='right')\nax.text(1, hidden_nodes // 2, 'Hidden', fontsize=12, va='center', ha='right')\nax.text(2.1, output_nodes // 2, 'Output', fontsize=12, va='center', ha='left')\nax.text(3, 5, 'Classification', fontsize=12, va='center', ha='left')\n\n# Set axis properties\nax.axis('off')\n\n# Add legend\nax.legend(bbox_to_anchor=(1, 1))\n\n# Set title\nplt.title(\"3-Layer Neural Network Architecture\")\n\n# Show the plot\nplt.tight_layout()\nplt.show()","metadata":{"tags":[],"_kg_hide-input":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:12.375938Z","iopub.execute_input":"2026-04-20T05:36:12.376245Z","iopub.status.idle":"2026-04-20T05:36:13.192564Z","shell.execute_reply.started":"2026-04-20T05:36:12.376217Z","shell.execute_reply":"2026-04-20T05:36:13.191706Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We will now go ahead and write some function and the code outlines the construction of a simple 3-layer neural network to predict the target. Let's break down what's happening at a high level:\n\n**Network Architecture:**\n\nThe neural network architecture consists of three layers: an input layer with 10 nodes, a hidden layer with 10 nodes, and an output layer with 10 nodes. The purpose of the hidden layer is to capture complex relationships within the data. The network's operation can be summarized as follows:\n\n1. **Initializing Parameters:** The network's parameters, including weights and biases, are initialized with random values to prevent issues like vanishing or exploding gradients.\n\n2. **Activation Functions:** Two activation functions are employed:\n   - The Rectified Linear Unit (ReLU) is used in the hidden layer, introducing non-linearity to the model.\n   - The sigmoid function is utilized in the output layer, producing values between 0 and 1 for binary classification.\n\n3. **Forward Propagation:** During forward propagation, input data (`X`) passes through the layers using the initialized parameters and activation functions. Activations for both the hidden layer (`a_hidden`) and the output layer (`a_output`) are computed. The dot product operation requires compatible matrix dimensions, and the ReLU function introduces non-linearity by applying it after the dot product and bias addition.\n\n4. **Activation Derivatives:** To calculate gradients during backpropagation, derivatives of the sigmoid and ReLU functions (`sigmoid_derivative` and `relu_derivative`) are used.\n\n5. **Backpropagation:** Gradients of the loss with respect to the parameters are computed using the chain rule. These gradients guide parameter updates in the direction that minimizes the loss.\n\n6. **Updating Parameters:** The `update_parameters` function modifies the weights and biases based on the calculated gradients and a specified learning rate.\n\n7. **Loss and Accuracy:** The `compute_loss` function determines the binary cross-entropy loss, quantifying the disparity between predicted and actual labels. The `compute_accuracy` function assesses prediction accuracy. Mathematically our loss would be calculated using this formula which is basically your cross entropy\n\n\n$$ L = \\frac {-1}{n} \\space \\Sigma\\: [y_{i}\\space log{y_i} + (1-y_{i}) \\space log({1-y_i}) ]$$ ","metadata":{}},{"cell_type":"code","source":"# Network architecture\ninput_size = input_nodes\nhidden_size = hidden_nodes\noutput_size = output_nodes  # The output layer before the final classification neuron\n\n# Initialize weights and biases\ndef initialize_parameters(input_size, hidden_size, output_size):\n    \"\"\"\n    Initialize weights and biases for the neural network.\n\n    Parameters:\n    input_size -- Number of input nodes\n    hidden_size -- Number of nodes in the hidden layer\n    output_size -- Number of output nodes\n\n    Returns:\n    w_input_hidden -- Initialized weights for input-hidden layer\n    b_hidden -- Initialized biases for hidden layer\n    w_hidden_output -- Initialized weights for hidden-output layer\n    b_output -- Initialized biases for output layer\n    w_output_classify -- Initialized weights for output-classification neuron\n    b_classify -- Initialized bias for classification neuron\n    \"\"\"\n    np.random.seed(42)  # For reproducibility\n    \n    # Initialize weights with small random values\n    w_input_hidden = np.random.randn(hidden_size, input_size) * 0.01\n    w_hidden_output = np.random.randn(output_size, hidden_size) * 0.01\n    w_output_classify = np.random.randn(1, output_size) * 0.01\n    \n    # Initialize biases as zeros\n    b_hidden = np.zeros((hidden_size, 1))\n    b_output = np.zeros((output_size, 1))\n    b_classify = np.zeros((1, 1))\n    \n    return w_input_hidden, b_hidden, w_hidden_output,b_output,w_output_classify, b_classify\n\ndef sigmoid(x):\n    return 1 / (1 + np.exp(-x))\n\ndef relu(Z):\n    return np.maximum(0, Z)\n\ndef forward_propagation(X, w_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify):\n    \"\"\"\n    Perform forward propagation through the neural network.\n\n    Parameters:\n    X -- Input data (features) of shape (input_size, batch_size)\n    w_input_hidden -- Weights for input-hidden layer\n    b_hidden -- Biases for hidden layer\n    w_hidden_output -- Weights for hidden-output layer\n    b_output -- Biases for output layer\n    w_output_classify -- Weights for output-classification neuron\n    b_classify -- Bias for classification neuron\n\n    Returns:\n    a_hidden -- Activations of the hidden layer after ReLU activation\n    a_output -- Activations of the output layer before classification after ReLU activation\n    a_classify -- Activations of the classification neuron after sigmoid activation\n    \"\"\"\n    # Input to Hidden Layer\n    z_hidden = np.dot(w_input_hidden, X) + b_hidden\n    a_hidden = relu(z_hidden)  # Use ReLU activation\n    \n    # Hidden to Output Layer (before classification)\n    z_output = np.dot(w_hidden_output, a_hidden) + b_output\n    a_output = relu(z_output)\n    \n    # Output (before classification) to Classification Neuron\n    z_classify = np.dot(w_output_classify, a_output) + b_classify\n    a_classify = sigmoid(z_classify)\n    \n    return a_hidden, a_output, a_classify\n\ndef sigmoid_derivative(Z):\n    return sigmoid(Z) * (1 - sigmoid(Z))\n\ndef relu_derivative(Z):\n    return np.where(Z > 0, 1, 0)\n\ndef backpropagation(X, Y, a_hidden, a_output, a_classify,\n                    w_input_hidden, w_hidden_output, w_output_classify):\n    \"\"\"\n    Perform backpropagation to calculate gradients of the loss with respect to parameters.\n\n    Parameters:\n    X -- Input data (features) of shape (input_size, batch_size)\n    Y -- True labels (ground truth) of shape (output_size, batch_size)\n    a_hidden -- Activations of the hidden layer after forward propagation\n    a_output -- Activations of the output layer before classification after forward propagation\n    a_classify -- Activations of the classification neuron after forward propagation\n    w_input_hidden -- Weights for input-hidden layer\n    w_hidden_output -- Weights for hidden-output layer\n    w_output_classify -- Weights for output-classification neuron\n\n    Returns:\n    dw_input_hidden -- Gradients of weights for input-hidden layer\n    db_hidden -- Gradients of biases for hidden layer\n    dw_hidden_output -- Gradients of weights for hidden-output layer\n    db_output -- Gradients of biases for output layer\n    dw_output_classify -- Gradients of weights for output-classification neuron\n    db_classify -- Gradient of bias for classification neuron\n    \"\"\"\n    # Compute gradients\n    dz_classify = a_classify - Y\n    dw_output_classify = np.dot(dz_classify, a_output.T) / X.shape[1]\n    db_classify = np.sum(dz_classify, axis=1, keepdims=True) / X.shape[1]\n\n    dz_output = np.dot(w_output_classify.T, dz_classify) * sigmoid_derivative(a_output)\n    dw_hidden_output = np.dot(dz_output, a_hidden.T) / X.shape[1]\n    db_output = np.sum(dz_output, axis=1, keepdims=True) / X.shape[1]\n\n    dz_hidden = np.dot(w_hidden_output.T, dz_output) * relu_derivative(a_hidden)\n    dw_input_hidden = np.dot(dz_hidden, X.T) / X.shape[1]\n    db_hidden = np.sum(dz_hidden, axis=1, keepdims=True) / X.shape[1]\n\n    return dw_input_hidden, db_hidden, dw_hidden_output, db_output, dw_output_classify, db_classify\n\ndef update_parameters(w_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify,\n                      dw_input_hidden, db_hidden, dw_hidden_output, db_output, dw_output_classify, db_classify,\n                      learning_rate):\n    \"\"\"\n    Update parameters using gradient descent.\n    \n    Parameters:\n    w_input_hidden -- weights for input-hidden layer\n    b_hidden -- biases for hidden layer\n    w_hidden_output -- weights for hidden-output layer\n    b_output -- biases for output layer\n    w_output_classify -- weights for output-classification neuron\n    b_classify -- bias for classification neuron\n    dw_input_hidden -- gradients of weights for input-hidden layer\n    db_hidden -- gradients of biases for hidden layer\n    dw_hidden_output -- gradients of weights for hidden-output layer\n    db_output -- gradients of biases for output layer\n    dw_output_classify -- gradients of weights for output-classification neuron\n    db_classify -- gradient of bias for classification neuron\n    learning_rate -- learning rate for gradient descent\n    \n    Returns:\n    updated_w_input_hidden -- updated weights for input-hidden layer\n    updated_b_hidden -- updated biases for hidden layer\n    updated_w_hidden_output -- updated weights for hidden-output layer\n    updated_b_output -- updated biases for output layer\n    updated_w_output_classify -- updated weights for output-classification neuron\n    updated_b_classify -- updated bias for classification neuron\n    \"\"\"\n    updated_w_input_hidden = w_input_hidden - learning_rate * dw_input_hidden\n    updated_b_hidden = b_hidden - learning_rate * db_hidden\n    updated_w_hidden_output = w_hidden_output - learning_rate * dw_hidden_output\n    updated_b_output = b_output - learning_rate * db_output\n    updated_w_output_classify = w_output_classify - learning_rate * dw_output_classify\n    updated_b_classify = b_classify - learning_rate * db_classify\n    \n    return updated_w_input_hidden, updated_b_hidden, updated_w_hidden_output, updated_b_output,updated_w_output_classify, updated_b_classify\n\ndef compute_loss(Y_true, Y_pred):\n    \"\"\"\n    Compute binary cross-entropy loss.\n    \n    Parameters:\n    Y_true -- true labels (ground truth)\n    Y_pred -- predicted labels\n    \n    Returns:\n    loss -- computed loss\n    \"\"\"\n    m = Y_true.shape[1]\n    loss = -1/m * np.sum(Y_true * np.log(Y_pred) + (1 - Y_true) * np.log(1 - Y_pred))\n    return loss\n\ndef compute_accuracy(Y_true, Y_pred):\n    \"\"\"\n    Compute accuracy.\n    \n    Parameters:\n    Y_true -- true labels (ground truth)\n    Y_pred -- predicted labels\n    \n    Returns:\n    accuracy -- computed accuracy\n    \"\"\"\n    m = Y_true.shape[1]\n    predictions = (Y_pred > 0.5).astype(int)\n    accuracy = np.sum(predictions == Y_true) / m\n    return accuracy","metadata":{"tags":[],"_kg_hide-input":true,"execution":{"iopub.status.busy":"2026-04-20T05:36:13.194070Z","iopub.execute_input":"2026-04-20T05:36:13.194661Z","iopub.status.idle":"2026-04-20T05:36:13.214326Z","shell.execute_reply.started":"2026-04-20T05:36:13.194630Z","shell.execute_reply":"2026-04-20T05:36:13.213274Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Below is an animation that shows how the single pass through the network:\n\n![Image](https://yogayu.github.io/DeepLearningCourse/03/video/layer.gif)\n\n\n**Forward Propagation**\n\nThe `forward_propagation` function is responsible for carrying input data through the neural network, ultimately generating predictions. It executes the following sequential steps:\n\n1. **Input to Hidden Layer (z_hidden and a_hidden):**\n   - `z_hidden`: This represents the linear combination of input features `X` with the weights `w_input_hidden` connecting the input layer to the hidden layer, along with the addition of the bias term `b_hidden`.\n   - `a_hidden`: After `z_hidden`, the result undergoes the ReLU (Rectified Linear Activation) function. This activation function acts by setting negative values to zero and preserving positive values.\n\n2. **Hidden to Output Layer (z_output and a_output):**\n   - `z_output`: This signifies the linear combination of the activations from the hidden layer, `a_hidden`, with the weights `w_hidden_output` connecting the hidden layer to the output layer. It also incorporates the bias term `b_output`.\n   - `a_output`: Similar to the hidden layer, the ReLU activation function is applied to `z_output`. This introduces non-linearity to the network's output.\n\n3. **Output to Classification Neuron (z_classify and a_classify):**\n   - `z_classify`: It represents the linear combination of activations from the output layer, `a_output`, with the weights `w_output_classify` connecting the output layer to the classification neuron. The bias term `b_classify` is added as well.\n   - `a_classify`: The final network output, just prior to classification, is derived by applying the sigmoid activation function to `z_classify`. The sigmoid function confines the output to the range of 0 to 1, making it suitable for binary classification tasks.\n\n\n**Back propogation**\n\n\n![image.png](https://miro.medium.com/v2/resize:fit:1280/1*VF9xl3cZr2_qyoLfDJajZw.gif)!\n\nLet's go over the Backpropogation in a bit detail since this is a very crucial part of Neural Networks. The `backpropagation` function is a integral part of training a neural network. It's responsible for computing the gradients of the loss function with respect to the network's parameters (weights and biases) so that those parameters can be updated using gradient descent. Let's break down the different steps in this function:\n\n\n1. **Compute Gradients for Output-Classification Layer:**\n   - `dz_classify`: Gradient of the loss with respect to the output of the classification neuron.\n     ```\n     dz_classify = a_classify - Y\n     ```\n\n   - `dw_output_classify`: Gradient of the loss with respect to the weights connecting the hidden layer to the classification neuron.\n     ```\n     dw_output_classify = (1/m) * dz_classify * a_output^T\n     ```\n\n   - `db_classify`: Gradient of the loss with respect to the bias of the classification neuron.\n     ```\n     db_classify = (1/m) * Σ(dz_classify)\n     ```\n\n2. **Compute Gradients for Hidden-Output Layer:**\n   - `dz_output`: Gradient of the loss with respect to the output of the hidden layer.\n     ```\n     dz_output = (w_output_classify^T * dz_classify) * g'(z_output)\n     ```\n     where `g'` is the derivative of the sigmoid activation function.\n\n   - `dw_hidden_output`: Gradient of the loss with respect to the weights connecting the hidden layer to the output layer.\n     ```\n     dw_hidden_output = (1/m) * dz_output * X^T\n     ```\n\n   - `db_output`: Gradient of the loss with respect to the bias of the output layer.\n     ```\n     db_output = (1/m) * Σ(dz_output)\n     ```\n\n3. **Compute Gradients for Input-Hidden Layer:**\n   - `dz_hidden`: Gradient of the loss with respect to the output of the hidden layer.\n     ```\n     dz_hidden = (w_hidden_output^T * dz_output) * g'(z_hidden)\n     ```\n     where `g'` is the derivative of the ReLU activation function.\n\n   - `dw_input_hidden`: Gradient of the loss with respect to the weights connecting the input layer to the hidden layer.\n     ```\n     dw_input_hidden = (1/m) * dz_hidden * X^T\n     ```\n\n   - `db_hidden`: Gradient of the loss with respect to the bias of the hidden layer.\n     ```\n     db_hidden = (1/m) * Σ(dz_hidden)\n     ```\n\nThe calculated gradients are then used to update the parameters of the network in the `update_parameters` function. \n\n> This iterative process of **forward propagation** and **backpropagation** helps the neural network learn the appropriate weights and biases that minimize the loss and improve its performance on the given task.\n\n**Derivatives**\n\nIn the specific case of activation functions like sigmoid and ReLU, let's understand why their derivatives are important:\n\n1. **Sigmoid Activation Function:**\n   - The sigmoid function maps any input value to a value between 0 and 1. It has a smooth curve and is commonly used in the past to introduce non-linearity in neural networks.\n   - The derivative of the sigmoid function, denoted as `sigmoid_derivative`, is calculated as `sigmoid(x) * (1 - sigmoid(x))`. It's worth noting that the derivative of the sigmoid function is relatively small when the input is very large or very small.\n   \n   \n   The derivative of a sigmoid function, such as the logistic sigmoid function, is given by:\n   \n\n$$f'(x) = f(x) \\cdot (1 - f(x))$$\n\n   where $f(x)$ is the sigmoid function itself:\n\n$$f(x) = \\frac{1}{1 + e^{-x}}$$\n\n   So, the derivative of a sigmoid function at any point $x$ can be computed using the sigmoid function's value at that point.\n\n2. **ReLU (Rectified Linear Activation) Function:**\n   - The ReLU function outputs the input value as is if it's positive, and outputs zero for negative input values.\n   - The derivative of the ReLU function, denoted as `relu_derivative`, is 1 for positive input values and 0 for negative input values.\n   - The derivative of a Rectified Linear Unit (ReLU) function, ReLU(x), is:\n\n$$f'(x) = \\begin{cases}\n1 & \\text{if } x > 0 \\\\\n0 & \\text{if } x \\leq 0\n\\end{cases}$$\n    \n   In this mathematical expression, the derivative of ReLU(x) is equal to 1 for $x$ greater than 0 and equal to 0 for $x$ less than or equal to 0.","metadata":{}},{"cell_type":"markdown","source":"## Training Loop\n\nThe loop iterates through the specified number of epochs, updating the network's parameters based on the calculated gradients. The printed accuracy and loss values provide insight into the model's performance during training.\n\n1. **Initialize Parameters:** Initialize the weights and biases for all layers using the `initialize_parameters` function.\n\n2. **Hyperparameters:** Set hyperparameters like the learning rate and the number of epochs.\n\n3. **Lists for Tracking:** Create lists `accuracy_list` and `loss_list` to store accuracy and loss values for each epoch.\n\n4. **Training Loop:** Loop through each epoch.\n\n5. **Forward Propagation:** Perform forward propagation on the entire training set to obtain predictions (`a_classify`).\n\n6. **Calculate Loss and Accuracy:** Calculate the loss using the predicted values and the ground truth (`y_train`). Also, calculate `accuracy` using the same values.\n\n7. **Backpropagation:** Perform backpropagation to compute gradients of the loss with respect to the parameters.\n\n8. **Update Parameters:** Update the parameters (weights and biases) using the computed gradients and the learning rate.\n\n9. **Print Progress:** Print accuracy and loss values after few epoch.\n\n10. **Save Values:** Append the accuracy and loss values to their respective lists.","metadata":{}},{"cell_type":"code","source":"# Initialize parameters\nw_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify = initialize_parameters(input_size, hidden_size, output_size)\n\n# Hyperparameters\nlearning_rate = 0.001\nnum_epochs = 10\n\n# Lists to store accuracy, loss\naccuracy_list = []\nloss_list = []\n#Dictionary to store weights\nWeights = {}\n#decay rate for decaying the learning rate over time\ndecay_rate = 5\n\nfor epoch in range(num_epochs):\n    # Forward propagation on the entire training set\n    a_hidden, a_output, a_classify = forward_propagation(X_train.T, w_input_hidden, b_hidden,\n                                                         w_hidden_output, b_output, w_output_classify, b_classify)\n    \n    # Calculate loss\n    loss = compute_loss(y_train.reshape(1, -1), a_classify)\n    \n    # Calculate accuracy\n    accuracy = compute_accuracy(y_train.reshape(1, -1), a_classify)\n    \n    # Backpropagation\n    dw_input_hidden, db_hidden, dw_hidden_output, db_output, dw_output_classify, db_classify = backpropagation(X_train.T, y_train.reshape(1, -1), \n                                                    a_hidden, a_output, a_classify, w_input_hidden, w_hidden_output, w_output_classify)\n    \n    learning_rate = (1 / (1 + decay_rate)) * learning_rate\n\n    \n    # Update parameters\n    w_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify = update_parameters(w_input_hidden, b_hidden, w_hidden_output, \n                            b_output, w_output_classify, b_classify, dw_input_hidden, db_hidden, dw_hidden_output, db_output, dw_output_classify, \n                                         db_classify, learning_rate)\n    \n    # Print accuracy and loss \n    if (epoch + 1) % 4 == 0:\n        print(f\"Epoch {epoch + 1}/{num_epochs} == accuracy : {accuracy:.4f} - loss : {loss:.4f}  - learning_rate : {learning_rate:.2e}\")\n    \n    # Save accuracy and loss values\n    accuracy_list.append(accuracy)\n    loss_list.append(loss)\n    \n    #Saving the weights at the last epoch\n    if epoch == num_epochs-1:\n        Weights[f'epoch_{epoch}'] = {\n        'w_input_hidden': w_input_hidden,\n        'b_hidden': b_hidden,\n        'w_hidden_output': w_hidden_output,\n        'b_output': b_output,\n        'w_output_classify': w_output_classify,\n        'b_classify': b_classify\n                                    }","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:13.215550Z","iopub.execute_input":"2026-04-20T05:36:13.216020Z","iopub.status.idle":"2026-04-20T05:36:13.321728Z","shell.execute_reply.started":"2026-04-20T05:36:13.215998Z","shell.execute_reply":"2026-04-20T05:36:13.319559Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Loss and Accuracy Plots","metadata":{}},{"cell_type":"code","source":"# Create a figure with two subplots\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 6))\nfig.subplots_adjust(wspace=0.3)\n\n# Plot Loss\nax1.plot(loss_list, color='tab:blue')\nax1.set_title(\"Loss Plot\")\nax1.set_xlabel('Epoch')\nax1.set_ylabel('Loss')\nax1.spines['top'].set_visible(False)\nax1.spines['right'].set_visible(False)\n\n# Plot Accuracy\nax2.plot(accuracy_list, color='tab:orange')\nax2.set_title(\"Accuracy Plot\")\nax2.set_xlabel('Epoch')\nax2.set_ylabel('Accuracy')\nax2.spines['top'].set_visible(False)\nax2.spines['right'].set_visible(False)\n\n# Add a suptitle\nplt.suptitle(\"Training Progress\", fontsize=16)\n\n# Show the plots\nplt.tight_layout()\nplt.show()","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:13.324531Z","iopub.execute_input":"2026-04-20T05:36:13.325372Z","iopub.status.idle":"2026-04-20T05:36:13.868440Z","shell.execute_reply.started":"2026-04-20T05:36:13.325323Z","shell.execute_reply":"2026-04-20T05:36:13.867537Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- We can see there is a steep increase in the accuracy which is not something very good for a stable training and this can be because of learning rate. The learning rate is a hyperparameter that controls how much to change the model in response to the estimated error each time the model weights are updated. \n\n- Choosing the learning rate is challenging as a value too small may result in a long training process that could get stuck, whereas a value too large may result in learning a sub-optimal set of weights too fast or an unstable training process. `Tensorflow` and `PyTorch` both implements the concept of [learning rate decay](https://machinelearningmastery.com/understand-the-dynamics-of-learning-rate-on-deep-learning-neural-networks/) which makes training much better and avoid such steep changes in the metrics causing the network to miss the maxima sometimes and create a haphazard weight finding path","metadata":{}},{"cell_type":"code","source":"acc_pct = round(accuracy_list[-1]*100,2)\nprint(f'The accuracy after training the neural network is {acc_pct}%')","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:37:46.447830Z","iopub.execute_input":"2026-04-20T05:37:46.448947Z","iopub.status.idle":"2026-04-20T05:37:46.454340Z","shell.execute_reply.started":"2026-04-20T05:37:46.448903Z","shell.execute_reply":"2026-04-20T05:37:46.453152Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Predictions\n\n\n- During forward propagation, the neural network calculates the probabilities of each class for the given input data. The class with the highest probability is considered the predicted class for each input example. By using the `np.argmax` function, the predicted class indices are obtained, which can be associated with class labels for interpretation and evaluation.\n\n- Let's now make predictions. But how do we do that. Well, time to define more functions. The `get_predictions` function takes an array of probabilities as input and returns the indices corresponding to the maximum probability along each column. This is effectively finding the predicted class for each input example. The function uses `np.argmax` from the NumPy library to achieve this. The `make_predictions` function takes the following inputs:\n    - `X_test`: The input data (features) on which predictions need to be made.\n    - Weights and biases for different layers of the neural network: `w_input_hidden`, `b_hidden`, `w_hidden_output`, `b_output`, `w_output_classify`, `b_classify`.\n\n\nHere's the process that takes place within the `make_predictions` function:\n\n1. **Forward Propagation:**- The `forward_propagation` function is called using the provided weights and biases, along with the `X_test` data. This calculates the activations of different layers in the neural network.\n\n2. **Getting Probabilities:** - The output of the forward propagation contains the activations of the last layer, which represents the probabilities of the different classes. The variable `_` is used to capture the intermediate activations that we don't need for predictions. The variable `probs` contains the probability values for each class.\n\n3. **Getting Predictions:** - The `get_predictions` function is called with the `probs` array as input. This function converts the probability values into predicted class indices using the maximum probability along each column.\n\n4. **Returning Predictions:** - The predicted class indices are stored in the `predictions` variable and then returned as the output of the `make_predictions` function. We also ","metadata":{}},{"cell_type":"code","source":"def get_predictions(probs):\n    return np.argmax(probs, 0)\n\ndef make_predictions(X_test, w_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify):\n    _, _, probs = forward_propagation(X_test.T, w_input_hidden, b_hidden,\n                                                         w_hidden_output, b_output, w_output_classify, b_classify)\n    predictions = get_predictions(probs)\n    return predictions","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:13.876741Z","iopub.execute_input":"2026-04-20T05:36:13.877084Z","iopub.status.idle":"2026-04-20T05:36:13.884086Z","shell.execute_reply.started":"2026-04-20T05:36:13.877062Z","shell.execute_reply":"2026-04-20T05:36:13.883371Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = make_predictions(X_test, w_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify)","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:36:13.885257Z","iopub.execute_input":"2026-04-20T05:36:13.885608Z","iopub.status.idle":"2026-04-20T05:36:13.896985Z","shell.execute_reply.started":"2026-04-20T05:36:13.885578Z","shell.execute_reply":"2026-04-20T05:36:13.894810Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Evaluating the Performance\n\nWe can see the neural network is performing well on the data and is only giving 4% error which is pretty good. Feel free to go ahead and play around with the architecture or the hyperparameters to see how far can you take this.","metadata":{}},{"cell_type":"code","source":"misclassified_count = len(y_test[y_test != preds])\ntotal_cases = len(y_test)\nerror_rate = misclassified_count / total_cases * 100\nprint(f\"{misclassified_count} / {total_cases} misclassified cases , error rate : {round(error_rate,2)}%\")","metadata":{"tags":[],"execution":{"iopub.status.busy":"2026-04-20T05:37:10.556954Z","iopub.execute_input":"2026-04-20T05:37:10.557974Z","iopub.status.idle":"2026-04-20T05:37:10.563904Z","shell.execute_reply.started":"2026-04-20T05:37:10.557936Z","shell.execute_reply":"2026-04-20T05:37:10.562937Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"misclassified = y_test[y_test != preds]\nmisclassification_counts = {}\nfor class_label in set(y_test):\n    misclassification_counts[class_label] = np.sum(misclassified == class_label)\nprint('Misclassified Classes Value Counts')\nmisclassification_counts","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:13.906178Z","iopub.execute_input":"2026-04-20T05:36:13.906494Z","iopub.status.idle":"2026-04-20T05:36:13.932963Z","shell.execute_reply.started":"2026-04-20T05:36:13.906463Z","shell.execute_reply":"2026-04-20T05:36:13.931832Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"There is another way to evaluate the model which is by calculating how many correct True Positives were correctly classified.\n\n- True Positive Rate (TPR), also known as Sensitivity or Recall, measures a model's ability to correctly identify positive cases, \n- False Positive Rate (FPR) quantifies how often the model incorrectly labels negative cases as positive. \n-  These metrics play a crucial role in evaluating binary classification models, particularly when dealing with imbalanced datasets.","metadata":{}},{"cell_type":"code","source":"def evaluate(y, y_preds):\n    cf = confusion_matrix(y_test, preds)\n    TP = cf[0][0]\n    FP = cf[0][1]\n    FN = cf[1][0]\n    TN = cf[1][1]\n    TPR = TP/(TP+FN)\n    FPR = FP / (FP + TN) if (FP + TN) > 0 else 0  # False Positive Rate\n    print(f'True Positive Rate : {round(TPR,4)}')\n    print(f'False Positive Rate : {round(FPR,4)}')","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:13.934270Z","iopub.execute_input":"2026-04-20T05:36:13.934720Z","iopub.status.idle":"2026-04-20T05:36:13.952287Z","shell.execute_reply.started":"2026-04-20T05:36:13.934670Z","shell.execute_reply":"2026-04-20T05:36:13.949994Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_train_preds = make_predictions(X_train, w_input_hidden, b_hidden, w_hidden_output, b_output, \n                                 w_output_classify, b_classify)\nprint('Model Evaluation, Train Dataset')\nevaluate(X_train, y_train_preds)","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:13.956606Z","iopub.execute_input":"2026-04-20T05:36:13.957070Z","iopub.status.idle":"2026-04-20T05:36:13.977253Z","shell.execute_reply.started":"2026-04-20T05:36:13.957030Z","shell.execute_reply":"2026-04-20T05:36:13.975814Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_preds = make_predictions(X_test, w_input_hidden, b_hidden, w_hidden_output, b_output, w_output_classify, b_classify)\nprint('Model Evaluation, Test Dataset')\nevaluate(y_test, y_preds)","metadata":{"execution":{"iopub.status.busy":"2026-04-20T05:36:13.978861Z","iopub.execute_input":"2026-04-20T05:36:13.979338Z","iopub.status.idle":"2026-04-20T05:36:13.999409Z","shell.execute_reply.started":"2026-04-20T05:36:13.979295Z","shell.execute_reply":"2026-04-20T05:36:13.997931Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"An ideal case is a TPR equal to 1 and an FPR equal to 0.","metadata":{}},{"cell_type":"markdown","source":"## Conclusion\n\n- In conclusion, this endeavor into the realm of neural networks without the aid of mainstream deep learning libraries has provided a foundational understanding of their mechanics and importance. While the world of artificial intelligence and machine learning has evolved significantly, it is imperative to grasp the basics of neural networks, as they have become integral to our daily lives. \n\n- Neural networks, with their ability to recognize patterns and relationships within complex datasets, underpin a multitude of applications that touch various aspects of modern living. From image and speech recognition to recommendation systems and even autonomous vehicles, neural networks have revolutionized industries and transformed the way we interact with technology.\n\n- By using numpy and other auxiliary libraries, we built a rudimentary three-layer neural network to predict temperatures. This provided us with a glimpse into the core concepts of data preprocessing, parameter initialization, activation functions, forward and backward propagation, and the significance of hyperparameters.\n\n- While this exploration offered only a glimpse into the vast world of neural networks, it lays a solid foundation for delving further into the intricacies of deep learning. As AI continues to reshape industries and societies, a fundamental understanding of neural networks becomes pivotal for harnessing their potential and contributing to the ongoing technological evolution.\n\n*P.s* I also have a notebook where I implemented [Gradient Descent from Scratch](https://www.kaggle.com/code/bhatnagardaksh/gradient-descent-from-scratch) which you might want to look at, if interested.\n\nThanks for taking the time to go through the notebook. Please consider leaving an upvote and a follow if you liked my work.","metadata":{}}]}