<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ishwari Mulye]]></title><description><![CDATA[Ishwari Mulye]]></description><link>https://ishwarimulye.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 12:05:58 GMT</lastBuildDate><atom:link href="https://ishwarimulye.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From PDFs to Plain Language: Building an AI Medical Report Simplifier

]]></title><description><![CDATA[I had just finished building a mental health text classifier and wanted to build something completely different. Not another model. Something more…]]></description><link>https://ishwarimulye.hashnode.dev/from-pdfs-to-plain-language-building-an-ai-medical-report-simplifier</link><guid isPermaLink="true">https://ishwarimulye.hashnode.dev/from-pdfs-to-plain-language-building-an-ai-medical-report-simplifier</guid><category><![CDATA[Python]]></category><category><![CDATA[FastAPI]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[student]]></category><dc:creator><![CDATA[Ishwari Mulye]]></dc:creator><pubDate>Tue, 14 Jul 2026 13:12:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f99e0d3dd16605714afa0/a66323d9-356d-4621-92a1-958b56a94c83.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>Introduction</strong></h2>
<p>I had just finished building a mental health text classifier and wanted to build something completely different. Not another model. Something more practical.</p>
<p>I had never worked with PDF documents programmatically before, and that gap bothered me. Almost every hospital gives patients their lab reports as PDFs. They're easy to download and share, but most people can't make sense of what's actually written inside them.</p>
<p>I wanted to understand how applications read PDFs. So I picked a project that would force me to learn.</p>
<p>The result was an AI-powered web application that takes a digitally generated medical report, extracts its contents, and explains the results in plain language using Google's Gemini API.</p>
<p>It started as an excuse to learn PDF parsing. It ended up teaching me quite a bit more.</p>
<h2><strong>Why This Project</strong></h2>
<p>Medical reports are interesting documents.</p>
<p>Unlike essays or articles, they're packed with structured information: test names, measured values, reference ranges, units, and patient details. To a person without a medical background, most of it looks intimidating. To a computer, it's simply text inside a PDF.</p>
<p>I wasn't trying to build a medical product or solve healthcare problems. I simply wanted a real-world problem that would push me to learn document processing, REST APIs, and AI integration.</p>
<p>Medical reports turned out to be a perfect fit. Structured, real-world, and actually useful to people.</p>
<h2><strong>What the Application Does</strong></h2>
<p>The application accepts a digitally generated PDF lab report uploaded by the user.</p>
<p>The backend extracts the report text using <strong>PyMuPDF</strong>, sends that text to <strong>Google Gemini</strong>, and asks the model to return a structured JSON response instead of a paragraph. The frontend then presents that information in a cleaner format, highlighting abnormal values, displaying gender-appropriate reference ranges where applicable, explaining each parameter in plain language, and suggesting a few questions the patient could discuss with their doctor.</p>
<p>It isn't designed to diagnose conditions or replace medical advice. The goal is simply to make medical reports easier to understand.</p>
<h2><strong>Learning Objective: PDF Parsing</strong></h2>
<p>Before starting this project, PDF parsing felt almost magical.</p>
<p>You upload a document, call a library function, and somehow all the text appears.</p>
<p>I quickly learned it isn't that simple.</p>
<p>The biggest realization was that <strong>not all PDFs are actually text documents.</strong></p>
<p>Digitally generated PDFs contain selectable text, which libraries like PyMuPDF can extract quite reliably.</p>
<p>Scanned reports are completely different.</p>
<p>They're just images embedded inside a PDF file. There's no text to extract, which means OCR becomes a completely separate problem.</p>
<p>That single distinction ended up defining the scope of the project. Rather than trying to solve both problems at once, I decided to support only digitally generated reports and mention that limitation clearly in the README.</p>
<p>Sometimes understanding what <strong>not</strong> to build is just as useful as understanding what to build.</p>
<h2><strong>How Everything Fits Together</strong></h2>
<p>The overall architecture stayed surprisingly simple.</p>
<p>User uploads PDF  →  FastAPI receives the file  →  PyMuPDF extracts text → Gemini analyzes the report  → Returns structured JSON → React renders the results</p>
<p>Each part has one responsibility.</p>
<p>FastAPI handles the upload.</p>
<p>PyMuPDF extracts the raw report text.</p>
<p>Gemini converts that text into structured information that the frontend can easily display.</p>
<p>React simply focuses on presenting the results in a readable way.</p>
<p>Keeping each component responsible for one job made debugging much easier later on.</p>
<p>I chose Gemini because it offered a free tier. Most other options required payment upfront, which wasn't practical for a learning project.</p>
<h2><strong>The Debugging</strong></h2>
<p>Like every project I've built so far, the interesting part wasn't writing the code.</p>
<p>It was figuring out why the code wasn't working.</p>
<h3><strong>The Wrong Model</strong></h3>
<p>The first API call returned a <strong>404 error</strong>.</p>
<p>At first I assumed something was wrong with authentication or my request format. After checking the documentation more carefully, I realized I was trying to use a model that wasn't actually available for the API version I was calling.</p>
<p>The fix took seconds.</p>
<p>Finding the real cause took much longer.</p>
<p><strong>The Free Tier Reminder</strong></p>
<p>Once the requests finally started working, I immediately ran into another error.</p>
<p><strong>429 : Resource Exhausted.</strong></p>
<p>I'd simply reached the free-tier quota.</p>
<p>There wasn't a clever engineering solution here.</p>
<p>I had to wait for the quota to reset before continuing development.</p>
<p>It was a useful reminder that building with cloud AI APIs also means understanding their usage limits.</p>
<p><strong>JSON That Wasn't Really JSON</strong></p>
<p>The last issue was probably my favorite.</p>
<p>I asked Gemini to return JSON.</p>
<p>It did.</p>
<p>Sort of.</p>
<p>Instead of returning raw JSON, it wrapped everything inside markdown code fences.</p>
<p>Perfectly readable for a human.</p>
<p>Not valid input for json.loads().</p>
<p>The backend kept failing until I stripped those wrappers before parsing the response.</p>
<p>It was a small bug, but one that taught me an important lesson: when another system generates your data, never assume the format is exactly what you requested.</p>
<h2><strong>Prompt Engineering Was More Important Than I Expected</strong></h2>
<p>Initially, I wrote prompts the way I would talk to ChatGPT.</p>
<p>"Explain this medical report."</p>
<p>The responses were well written.</p>
<p>They were also completely unusable.</p>
<p>Every response had a different structure, different formatting, and different wording. That made building the frontend almost impossible.</p>
<p>The project became much simpler once I stopped asking for explanations and started asking for <strong>structured data</strong>.</p>
<p>Instead of requesting a paragraph, I asked Gemini to return only valid JSON matching a predefined schema.</p>
<p>That single change made the entire application much more reliable.</p>
<p>It also changed how I think about prompt engineering. Good prompts aren't just about getting better answers. Sometimes they're about getting answers your software can actually work with.</p>
<h2><strong>Small Design Decisions That Mattered</strong></h2>
<p>There are a few features I'm particularly happy with, not because they're complicated, but because they were intentional.</p>
<p>Rather than simply labeling values as "normal" or "abnormal," I highlighted abnormal parameters visually so they stand out immediately. Where medical reference ranges differ by gender, I asked the model to account for that instead of assuming one universal range.</p>
<p>One feature I especially liked was generating a few suggested questions patients could ask their doctor. The application shouldn't replace professional advice, but helping someone understand what they might want to ask felt genuinely useful without crossing that line.</p>
<h2><strong>Responsible AI, Again</strong></h2>
<p>If you've read my previous post about the Mental Health Classifier, this section will probably sound familiar.</p>
<p>Whenever I build something involving health-related information, I try to be explicit about what the application is and what it isn't.</p>
<p>This project simplifies reports.</p>
<p>It does not diagnose illnesses.</p>
<p>It does not replace a doctor.</p>
<p>It does not provide medical advice.</p>
<p>That disclaimer isn't there because it looks professional.</p>
<p>It's there because I think setting the right expectations is part of building software responsibly, especially in domains where incorrect information could affect someone's health.</p>
<h2><strong>What I Learned</strong></h2>
<p>This project started because I wanted to learn PDF parsing, but that's only a small part of what I ended up taking away from it.</p>
<p>I learned that extracting text is usually the easy part; getting reliable, structured information from that text takes much more thought. I learned that prompt engineering often has more to do with software design than with writing clever prompts. I also realized that integrating an LLM into an application isn't really about AI , it's about building a reliable pipeline around it, handling unexpected outputs, validating responses, and making sure every component has a single responsibility.</p>
<p>Perhaps the biggest lesson, though, is that projects become much more enjoyable when they're driven by curiosity rather than by trying to build the biggest thing possible. I started this project because I wanted to understand how PDFs work. Everything else grew naturally from that.</p>
<h2><strong>Where It Lives Now</strong></h2>
<p>The application is deployed with a React frontend on Vercel and a FastAPI backend on Render.</p>
<p>At the moment, it supports only digitally generated PDFs. Supporting scanned reports would require adding an OCR pipeline, which is something I'd like to explore in a future version.</p>
<p>Like my previous projects, it's not perfect.</p>
<p>But it's deployed, it works, and it taught me far more than I expected when I first decided to learn how to read a PDF.</p>
<p><strong>Frontend:</strong> <a href="https://medical-report-simplifier-eta.vercel.app"><strong>https://medical-report-simplifier-eta.vercel.app</strong></a></p>
<p><strong>GitHub:</strong> <a href="https://github.com/ishwarimulye/medical-report-simplifier"><strong>https://github.com/ishwarimulye/medical-report-simplifier</strong></a></p>
<p><em>If you're also building ML projects as a student and want to talk about it, find me on</em> <a href="https://github.com/ishwarimulye"><em><strong>GitHub</strong></em></a> <em>or</em> <a href="https://www.linkedin.com/in/ishwari-mulye-94396332a/"><em><strong>LinkedIn</strong></em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[The Feature I Removed From My Mental Health AI App—and Why]]></title><description><![CDATA[Why I removed helpline links from my mental health AI app — a student's thoughts on responsible design in sensitive domains.]]></description><link>https://ishwarimulye.hashnode.dev/the-feature-i-removed-from-my-mental-health-ai-app-and-why</link><guid isPermaLink="true">https://ishwarimulye.hashnode.dev/the-feature-i-removed-from-my-mental-health-ai-app-and-why</guid><category><![CDATA[Mental Health]]></category><category><![CDATA[mentalhealth]]></category><category><![CDATA[AI ethics]]></category><category><![CDATA[responsible AI]]></category><category><![CDATA[student]]></category><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[Ishwari Mulye]]></dc:creator><pubDate>Sat, 04 Jul 2026 06:37:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f99e0d3dd16605714afa0/18115aa7-5773-45bb-8878-e85b67adf85b.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>A Quick Recap</strong> </p>
<p>In my last post, I wrote about building a mental health text classifier ,starting with TF-IDF and XGBoost, then fine-tuning DistilBERT to get from 74% to 81% accuracy. If you haven't read it yet, you can find it here: [<a href="https://ishwarimulye.hashnode.dev/from-tf-idf-to-transformers-rebuilding-my-mental-health-classifier">From TF-IDF to Transformers: Rebuilding My Mental Health Classifier</a>]</p>
<p>This post isn't about model accuracy or transformers. It's about a feature I almost added to my app, removed within seconds, and have thought about ever since.</p>
<p>The feature was a list of mental health helpline numbers.</p>
<p>The decision seemed minor, but the more I thought about it, the more I realized it carried real consequences. </p>
<h3><strong>The Suggestion That Made Sense on Paper</strong></h3>
<p>When I was designing the Gradio interface for the app, adding helpline numbers came up as a UI suggestion. On the surface, it made complete sense, having a helpline number after putting lines of assurance for people using the assistant.  The app classifies mental health-related text. Some of those categories like  suicidal ideation, severe depression, are serious. If someone is using the app and sees a concerning result, having a helpline number right there feels responsible,caring and thoughtful.</p>
<p>It was a good UI decision.</p>
<p>And my immediate reaction was: no. This doesn't feel right.</p>
<h3><strong>Why It Didn't Feel Right</strong></h3>
<p>I didn't have a long internal debate about it. The instinct was immediate, and when I sat down to think through why, a few things became clear.</p>
<p><strong>The tool is educational, not clinical.</strong></p>
<p>I built this as a learning project. It is not a diagnostic tool. It is not built or validated to the standard that would make it appropriate for someone in crisis to rely on. Adding helpline numbers would imply a level of responsibility and seriousness that the app simply does not have, at least at that moment. It would make a student project look like a mental health resource, and those are very different things.</p>
<p><strong>Inaccurate or outdated numbers could cause real harm.</strong></p>
<p>Helpline numbers change. Organizations shut down. Numbers get reassigned. If someone really needs help and they call a number from my app that no longer works, that moment of failed connection could matter in a way that's hard to overstate.</p>
<p>A broken link in a portfolio project is an inconvenience. A broken helpline number in a crisis is something else entirely.</p>
<p> I had no way to guarantee those numbers would stay accurate. I had no system to update them. I had no organization behind me validating them.</p>
<p>One more thing to note here is that HuggingFace Spaces is accessible to anyone, anywhere. A helpline number that works in India means nothing to someone in another country. And thus discovering that a number doesn't work isn't just unhelpful, it could make things worse, for a person in crisis. </p>
<p><strong>This information should come from proper organizations.</strong></p>
<p>There are government portals, established mental health organizations, and verified platforms whose entire purpose is to maintain accurate, up-to-date crisis resources. That responsibility belongs with them, not with a third-year student's ML project. </p>
<p>It felt wrong to take on that responsibility casually, just because it looked like a good feature to add. </p>
<h2><strong>What I Did Instead</strong></h2>
<p>I kept the disclaimer visible in the app and in the GitHub README:</p>
<p><em>"This tool is for educational purposes only and is NOT a substitute for professional mental health care."</em></p>
<p>That felt honest. It sets the right expectation without overstepping.</p>
<p>Anyone who needs real help should be directed to real resources. But I wasn't equipped to maintain or verify crisis resources myself, and I didn't want my project to imply a level of authority or responsibility that it didn't have.</p>
<p>Organizations dedicated to mental health support have the systems and expertise to keep this information accurate and up to date. As a student project, my app simply wasn't the right place to take on that responsibility.</p>
<h2><strong>What This Taught Me</strong></h2>
<p>I didn't know the term "responsible AI design" when I made this decision. I just knew something felt off.</p>
<p>But looking back, that instinct was about something real: the gap between what a feature looks like and what it actually does. Adding helpline numbers would have looked caring. But without accuracy, without maintenance, without the right organizational backing, it could have done more harm than good.</p>
<p>Building AI for sensitive domains means asking not just "does this feature make sense?" but "what happens if this feature fails?" For most features, failure is a minor inconvenience. For some, it isn't.</p>
<p>Every feature should be thought through carefully. User experience matters, but so does responsibility as a developer. </p>
<h2><strong>A Note to Other Student Developers</strong></h2>
<p>If you're building something that touches mental health, medical information, financial advice, or any other sensitive domain — pause before you add features that look helpful on the surface. Ask who is responsible for keeping that information accurate. Ask what happens if it's wrong. Ask whether your project is the right place for that responsibility.</p>
<p>That's something I'll carry into every project I build from here. Sometimes the most responsible thing you can do is leave something out. </p>
<p><em>Find me on</em> <a href="https://github.com/ishwarimulye"><em>GitHub</em></a> <em>or</em> <a href="https://www.linkedin.com/in/ishwari-mulye-94396332a/"><em>LinkedIn</em></a></p>
]]></content:encoded></item><item><title><![CDATA[From TF-IDF to Transformers: Rebuilding My Mental Health Classifier]]></title><description><![CDATA[A student's honest journey building a mental health text classifier, from TF-IDF and XGBoost to fine-tuning DistilBERT.]]></description><link>https://ishwarimulye.hashnode.dev/from-tf-idf-to-transformers-rebuilding-my-mental-health-classifier</link><guid isPermaLink="true">https://ishwarimulye.hashnode.dev/from-tf-idf-to-transformers-rebuilding-my-mental-health-classifier</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[machine learning models]]></category><category><![CDATA[nlp]]></category><category><![CDATA[nlp transformers]]></category><category><![CDATA[Python]]></category><category><![CDATA[DistilBert]]></category><category><![CDATA[student]]></category><dc:creator><![CDATA[Ishwari Mulye]]></dc:creator><pubDate>Sat, 27 Jun 2026 10:31:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f99e0d3dd16605714afa0/9137c641-0c3c-4264-a4e6-ff28f662008e.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><strong>Introduction</strong></h3>
<p>I finished a supervised machine learning course a few months ago and immediately wanted to build something with it. Not a tutorial project. Something that felt real.</p>
<p>I am a third-year Computer Engineering student, and I was always fascinated by how large language models like ChatGPT are able to detect emotion through text. Using that curiosity, I decided to build a model that could classify mental health-related text. Not because it was assigned. Not because it was easy. Because I wanted to see if I could.</p>
<h3><strong>Why This Project</strong></h3>
<p>Mental health conversations online are everywhere: Reddit posts, anonymous forums, support communities, Twitter. I wanted to explore whether a model could understand the emotional weight behind someone's words, what that person could be going through, whether that's anxiety, depression, stress, or something more serious like suicidal ideation, and categorize them meaningfully.</p>
<p>It felt important and exciting at the same time. And honestly, that feeling kept me going when things broke.</p>
<h3><strong>Version 1: TF-IDF + XGBoost</strong></h3>
<p>I started by reading about TF-IDF. This concept was completely new to me, and after reading about it I wanted to try everything I had just learned.</p>
<p>I used a publicly available mental health dataset from Kaggle: <a href="https://www.kaggle.com/datasets/suchintikasarkar/sentiment-analysis-for-mental-health">Sentiment Analysis for Mental Health by Suchintika Sarkar</a>. The categories for classification were anxiety, bipolar, depression, normal, personality disorder, stress, and suicidal.</p>
<p>The preprocessing pipeline alone taught me more than I expected. I had to handle HTML tags, URLs, contractions, stopwords, and numeric tokens. Each one is a small decision that affects what the model actually sees. I used TF-IDF vectorization with unigrams and bigrams, capped at 5000 features, which gives the model some sense of two-word phrases like "feeling hopeless" rather than just individual words.</p>
<p>For modeling, I went with Logistic Regression first as a baseline, then added class weighting to handle the imbalance in the dataset, and finally trained XGBoost. I evaluated everything with accuracy, macro F1, precision, recall, and 5-fold cross-validation to make sure results were stable and not just lucky splits.</p>
<p>Here's how they compared:</p>
<table style="width:597px"><colgroup><col style="width:417px"></col><col style="width:89px"></col><col style="width:91px"></col></colgroup><tbody><tr><td><p><strong>Model</strong></p></td><td><p><strong>Accuracy</strong></p></td><td><p><strong>Macro F1</strong></p></td></tr><tr><td><p>Logistic Regression</p></td><td><p>74.1%</p></td><td><p>0.66</p></td></tr><tr><td><p>Weighted Logistic Regression</p></td><td><p>72.0%</p></td><td><p>0.66</p></td></tr><tr><td><p>Logistic Regression (No Numbers)</p></td><td><p>72.1%</p></td><td><p>0.66</p></td></tr><tr><td><p><strong>XGBoost</strong></p></td><td><p><strong>74.6%</strong></p></td><td><p><strong>0.69</strong></p></td></tr></tbody></table>

<p>XGBoost won, particularly on minority classes, which matters in a mental health dataset where underrepresented categories are often the most clinically important ones. I also ran a small experiment removing numeric tokens entirely, which slightly hurt performance. A concrete reminder that "clean the numbers" isn't always the right call.</p>
<p>So the results were decent. And for about a week, I thought I was done. But at the same time I felt something was missing, that there must be something that could be improved.</p>
<p>Then I started looking at what the model got wrong.</p>
<p>The problem was fundamental. TF-IDF treats every word as an independent feature. It has no concept of meaning or context. Consider these two sentences:</p>
<p><em>"I feel empty"</em> and <em>"I feel hollow inside"</em></p>
<p>To TF-IDF, these share almost no features. To a human, they're saying the same thing. That gap, between surface-level pattern matching and actual semantic understanding, is what pushed me to start over.</p>
<h3><strong>Version 2: Fine-tuning DistilBERT</strong></h3>
<p>That gap pushed me to look for something better. I started researching what else could be used for text classification and came across transformers. Reading about attention mechanisms for the first time felt like a light switching on. The original transformer paper genuinely excited me, and it was enough to make me want to dive back into the project.</p>
<p>DistilBERT is a lighter, faster version of BERT that still captures contextual meaning. When it reads a sentence, it understands each word in relation to the words around it, not in isolation. For a dataset where categories like depression, stress, and anxiety overlap heavily in language, that distinction matters enormously.</p>
<p>The workflow looked like this: I used DistilBERT's subword tokenizer with a max length of 128 tokens, built a custom PyTorch Dataset class with attention masks, and fine-tuned for 3 epochs using AdamW optimizer with a linear learning rate scheduler. Nothing exotic, but every piece had to be wired together correctly, and that wiring is where the real learning happened.</p>
<p>The results were a significant jump:</p>
<table style="width:580px"><colgroup><col style="width:297px"></col><col style="width:133px"></col><col style="width:150px"></col></colgroup><tbody><tr><td><p><strong>Category</strong></p></td><td><p><strong>V1 F1 (XGBoost)</strong></p></td><td><p><strong>V2 F1 (DistilBERT)</strong></p></td></tr><tr><td><p>Anxiety</p></td><td><p>—</p></td><td><p>0.87</p></td></tr><tr><td><p>Bipolar</p></td><td><p>—</p></td><td><p>0.83</p></td></tr><tr><td><p>Depression</p></td><td><p>—</p></td><td><p>0.74</p></td></tr><tr><td><p>Normal</p></td><td><p>—</p></td><td><p>0.94</p></td></tr><tr><td><p>Personality Disorder</p></td><td><p>—</p></td><td><p>0.72</p></td></tr><tr><td><p>Stress</p></td><td><p>—</p></td><td><p>0.73</p></td></tr><tr><td><p>Suicidal</p></td><td><p>—</p></td><td><p>0.72</p></td></tr><tr><td><p><strong>Overall Accuracy</strong></p></td><td><p><strong>74.6%</strong></p></td><td><p><strong>81.2%</strong></p></td></tr></tbody></table>

<p>But the number I'm most proud of isn't the accuracy jump. It's what happened after the first training run.</p>
<h3><strong>Error Analysis</strong></h3>
<p>After initial training, I noticed the model was over-predicting Normal for statements that were actually depression or stress-related. At first I thought the model was just confused. Then I realized it was consistently wrong in the same direction, and that's a different problem entirely.</p>
<p>This wasn't random noise. It was a pattern, rooted in how the dataset was labeled.</p>
<p>Instead of retraining blindly, I did error analysis: looked at which cases were failing and why, then manually adjusted class weights based on observed failure patterns and applied weighted CrossEntropy loss based on those observations.</p>
<p>This is a standard real-world ML debugging technique, but it rarely shows up in beginner projects because it requires you to actually understand your model's mistakes, not just its metrics. That extra step pushed the model from "decent results" to results I could actually stand behind.</p>
<h3>The Debugging</h3>
<p>Here are the actual things that broke, in no particular order:</p>
<p><strong>token_type_ids incompatibility</strong> — DistilBERT doesn't use token type IDs, but the newer tokenizer version was returning them anyway.</p>
<p>The error was TypeError: DistilBertForSequenceClassification.forward() got an unexpected keyword argument 'token_type_ids'.</p>
<p>The fix was one line, filter out token_type_ids before passing inputs to the model. The hour spent figuring it out was not.</p>
<p><strong>Gradio version conflicts</strong> — Deploying to HuggingFace Spaces seemed straightforward until it wasn't. Different Gradio versions have different APIs, and the error messages don't always tell you which version is the problem. Each of these felt like a wall when I hit it.</p>
<p>In hindsight, they were the actual education.</p>
<p>I used AI tools throughout this project, not to write code for me, but to understand why things were breaking and what to try next. Every fix still had to make sense to me before I used it. That's a different thing from copy-pasting, and it changed how much I actually retained.  </p>
<h3><strong>What I Learned</strong></h3>
<ol>
<li><p><strong>Version 1 wasn't a failure, it was a baseline.</strong> Starting simple gave me something to compare against. Without v1, I wouldn't have been able to articulate why v2 was better. Build the simple thing first, gain a little confidence in yourself, and then go for the next version.</p>
</li>
<li><p><strong>Debugging is a skill, not a sign you're bad at this.</strong> Every senior engineer spends most of their time debugging. The difference is they've learned to read errors calmly. I'm still learning this.</p>
</li>
<li><p><strong>Deploying is a completely different problem from training.</strong> Getting a model to work in a Colab notebook and getting it to work for someone else are two different jobs. Plan for deployment from the start.</p>
</li>
<li><p><strong>Error analysis is not optional.</strong> If you skip it, you're optimizing a number. If you do it, you're building something trustworthy. Especially for sensitive domains, that distinction matters.</p>
</li>
<li><p><strong>The model is only one part of the product.</strong> Which brings me to a decision I made near the end, one I want to talk about in a separate post entirely.</p>
</li>
</ol>
<h3><strong>Where It Lives Now</strong></h3>
<p>The final model is hosted on HuggingFace Hub (<a href="https://huggingface.co/spaces/ishwarimulye03/mental-health-support-assistant">ishwarimulye03/mental-health-support-assistant</a>) with a Gradio UI on HuggingFace Spaces. It's not perfect. But it's real, it's deployed, and someone other than me can use it.</p>
<p>That still feels kind of unbelievable.</p>
<p>But before I fully close the chapter on this project, there's one decision I made that I haven't talked about yet. It has nothing to do with accuracy or loss functions. I'll cover it in the next post.</p>
<p><em>If you're also building ML projects as a student and want to talk about it, find me on</em> <a href="https://github.com/ishwarimulye"><em>GitHub</em></a> <em>or</em> <a href="https://www.linkedin.com/in/ishwari-mulye-94396332a/"><em>LinkedIn</em></a><em>.</em></p>
]]></content:encoded></item></channel></rss>