<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Andre Tan on Medium]]></title>
        <description><![CDATA[Stories by Andre Tan on Medium]]></description>
        <link>https://medium.com/@andrewrites?source=rss-50343650a290------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*9fSyRXHYCJqAC11gGeruKA.jpeg</url>
            <title>Stories by Andre Tan on Medium</title>
            <link>https://medium.com/@andrewrites?source=rss-50343650a290------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 00:05:01 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@andrewrites/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How close does Jev get to task-specific models?]]></title>
            <link>https://medium.com/data-science-collective/how-close-does-jev-get-to-task-specific-models-ab56905a4e21?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/ab56905a4e21</guid>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[frontier-tech]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Tue, 22 Sep 2026 08:29:38 GMT</pubDate>
            <atom:updated>2026-09-22T08:29:38.919Z</atom:updated>
            <content:encoded><![CDATA[<p>Since Jev launched, reactions have fallen into two camps. One sees a new way to build software. The other sees an intent classifier and wonders why everyone is so excited.</p><p>I think both have a point.</p><h3>The comparison Jev has been missing</h3><p>Classifying text, estimating probabilities, and returning labels are not new capabilities. We have built systems like this for years. But making them reliable in production requires more than a capable model. Infrastructure, reliability, and API shape matter just as much.</p><p>There is also a straightforward build-versus-buy argument. Being able to train a classifier does not mean every team wants to. We do not expect every company to operate bare-metal simply because servers have existed for decades. Cloud platforms exist for a reason.</p><p>Still, a convenient API needs a capable model behind it.</p><p>Most Jev comparisons so far have focused on general-purpose LLMs. They demonstrate its advantages in speed and cost, but leave a second question unanswered: how does Jev compare with traditional classifiers fine-tuned for specific tasks?</p><p>The Jev team has deliberately avoided benchmaxxing, and they have not published any comparisons against established zero-shot or supervised classifiers.</p><p>So I built a small benchmark suite to answer two questions:</p><ol><li>How much does Jev improve on existing zero-shot approaches?</li><li>How close can it get to models trained specifically for a task?</li></ol><h3>Benchmark methodology</h3><p>The benchmark covers five established datasets and all three Jev output types: Choice, Noul, and Score.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*smGTyxHi7yTqqS4ajKaB-w.png" /></figure><p>For each task, Jev 1.13.0 receives an instruction and the candidate labels or scoring rubric. It does not receive any few-shot examples.</p><p>The supervised baselines are task-specific models: <a href="https://github.com/AlibabaResearch/DAMO-ConvAI/tree/188835d4f9948563a6b9c8ac50cd0f3ae4021ed6/space-2">SPACE-2</a> for the three intent datasets, <a href="https://huggingface.co/philschmid/roberta-large-sst2">RoBERTa-large</a> for SST-2, and a <a href="https://huggingface.co/cross-encoder/stsb-roberta-large">RoBERTa-large cross-encoder</a> for STS-B. The zero-shot reference is <a href="https://huggingface.co/MoritzLaurer/deberta-v3-large-zeroshot-v2.0">DeBERTa-v3-large-zeroshot-v2.0</a>, an established NLI-based classifier. There is no zero-shot baseline for STS-B in this comparison.</p><p>I reproduced every result locally on the same evaluation examples rather than copying reported figures from papers. The dataset versions, prompts, checkpoints, and results are recorded in the repository. In total, the benchmark contains 10,907 examples across five datasets.</p><p>The evaluation metrics are straightforward:</p><ul><li><strong>Choice:</strong> Accuracy, since each example has one correct label and the classes are balanced.</li><li><strong>Noul:</strong> I ask whether a sentence expresses positive sentiment, threshold the returned probability at 0.5, and measure accuracy.</li><li><strong>Score:</strong> STS-B assigns human similarity ratings from 0 to 5. I map Jev onto the same six-level scale and measure Spearman correlation with the human ratings. This tests whether Jev ranks sentence pairs similarly to people, not whether its absolute scores are calibrated.</li></ul><h3>Jev materially improves zero-shot classification</h3><p>The most striking result is Jev’s improvement over zero-shot NLI on intent classification. It leads by 12.76 percentage points on BANKING77, 24.38 on CLINC150, and 26.77 on HWU64.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5VDEud3OKxQ7YWX-oCe8Vg.png" /></figure><p>In practice, the NLI baseline often identified the general topic but missed the distinction between related options. For example, it classified “How can I prevent my credit score from dropping?” as credit_score, while Jev selected the more specific improve_credit_score.</p><p>The example-level comparison makes the difference even clearer. Across the four classification datasets, Jev was correct when the NLI baseline was wrong on 2,058 examples. The reverse happened on only 267 examples.</p><p>I tested each difference with 100,000 paired-bootstrap resamples: the confidence intervals exclude zero for all three intent datasets, which suggests that Jev’s improvement is unlikely to be due to chance.</p><h3>Jev does not fully close the supervised accuracy gap</h3><p>When compared to the task-specific models, Jev does not perform as well. The gap is relatively small on SST-2 and STS-B, but widens to 5.84 percentage points on CLINC150, 11.15 on HWU64, and 14.87 on BANKING77.</p><p>Whether those gaps are acceptable depends on the cost of being wrong. Jev’s 91.96% accuracy on CLINC150 looks reasonably close to the supervised model’s 97.80%. In error-rate terms, however, the comparison is 8.04% vs. 2.20% (3.7x more mistakes). This difference becomes important when a prediction triggers an automated action.</p><p>However, the accuracy gap is not surprising. The supervised models have learned from labelled examples, including information that may be impossible to infer from the label names alone.</p><p>For example, when asked “What do I do with my card PIN?”, Jev made the very reasonable choice of change_pin. However, the correct label was get_physical_card, which sounds as though it should cover ordering a card instead. In fact, all 40 test examples assigned to it are questions about finding a card PIN. Jev missed this dataset-specific nuance and scored 0/40 on that class. Conversely, SPACE-2 got 39/40 right because it had learned what that label means within this particular dataset.</p><p>Interestingly, Jev performed closest to the supervised model on tasks with more labels. It performed best on CLINC150 which had 150 labels, as compared to 77 for BANKING77 and 64 for HWU64. This suggests that label cardinality alone does not determine task difficulty. Distinguishing between closely related labels may be harder than choosing among a larger set of more distinct requests.</p><h3>Scaling to many labels without external fan-out</h3><p>A standard NLI model is not inherently a multiclass zero-shot classifier. To use it as one, we must build a layer around the model, by turning each candidate label into a hypothesis, pairing every hypothesis with the input, running inference over all the resulting pairs, and combining their scores into a final decision.</p><p>For CLINC150, each input fans out into 150 {premise, hypothesis} pairs. Across 4,500 examples, the model evaluates 675,000 pairs. Batching makes this slightly more efficient, but we still need to construct and manage this structure outside the model.</p><p>Jev provides the multiclass abstraction directly. The caller supplies the shared state, question, and complete candidate set in one request, and the model returns a distribution over the options.</p><p>Its internal architecture has not been published, so we cannot say exactly how that request is processed. However, <a href="https://archerhume.com/posts/jevs-architecture-unmasked/">Archer’s black-box experiments</a> suggest that Jev might be jointly encoding the state, question, and candidate answers into a shared hidden-state representation, then making its output decision from there. If so, computation over the shared state could be reused across candidates, rather than independently pairing and processing the state with every option.</p><p>His latency experiments offer further support. Adding questions scaled differently from repeatedly extending the shared state, which is consistent with shared-state computation and batched branch processing.</p><h3>The benchmark cost less than fifty cents</h3><p>Across the five completed runs, Jev processed 11,286,248 input tokens and returned 9,068,060 output tokens. At TypeSafe’s published price of $0.042 per million input tokens (output tokens are free), the total calculated API cost was $0.47.</p><p>This does not prove Jev is cheaper than every self-hosted alternative. Such a comparison would depend on hardware, utilisation, engineering effort, and serving costs.</p><p>But it does show that API spend was a negligible barrier to running the benchmark.</p><h3>The real value is faster iteration</h3><p>For me, Jev’s practical appeal is how quickly it can turn a proposed decision into something testable.</p><p>Imagine designing a new support-routing flow. Before you have a stable taxonomy or enough labelled data to train a model, you can describe the possible destinations, run real requests through them, and inspect where the distinctions break down.</p><p>You may discover that two queues overlap, a label is poorly defined, or the underlying business rules are unclear.</p><p>That work remains valuable even if you eventually replace Jev with a trained model. It helps you decide what to train, which data to collect, and what level of accuracy the application actually requires.</p><p>These results make Jev an attractive starting point for that process. On the intent datasets, it delivers a meaningful improvement over the zero-shot NLI baseline without requiring task-specific training. Where the remaining accuracy gap matters, the supervised results show why investing in specialisation can still be worthwhile.</p><h3>How far can zero-shot take you?</h3><p>There are limits to what this benchmark can establish.</p><p>These are public datasets, so prior exposure during training cannot be ruled out. SST-2 uses its validation split, which was also used while developing the supervised checkpoint. CLINC150 excludes out-of-scope requests, so the results tell us nothing about rejecting an intent that the model was not offered. And testing three interfaces across five datasets is still far from evaluating every workflow those interfaces could support.</p><p>Even with those limitations, there is plenty to like.</p><p>Established machine-learning capabilities become much more useful when they are accessible through a well-designed API, inexpensive to test, and accurate enough to give a new workflow a credible first implementation.</p><p>Jev makes that combination extremely compelling.</p><h3>Try it yourself</h3><p>Code, pinned checkpoints, results: <a href="https://github.com/thisisandreeeee/jev-benchmarks">github.com/thisisandreeeee/jev-benchmarks</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab56905a4e21" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science-collective/how-close-does-jev-get-to-task-specific-models-ab56905a4e21">How close does Jev get to task-specific models?</a> was originally published in <a href="https://medium.com/data-science-collective">Data Science Collective</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Making LLMs Simpler: From SFT to GRPO]]></title>
            <link>https://pub.towardsai.net/making-llms-simpler-from-sft-to-grpo-2a48058f920b?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/2a48058f920b</guid>
            <category><![CDATA[reinforcement-learning]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[nlp]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Tue, 15 Sep 2026 20:01:01 GMT</pubDate>
            <atom:updated>2026-09-16T00:49:04.361Z</atom:updated>
            <content:encoded><![CDATA[<p>In my previous article “<a href="https://medium.com/@andrewrites/get-llms-to-say-more-with-less-41636ce31268"><em>Get LLMs To Say More With Less</em></a><em>”</em>, we established the following goal: To train a small language model to write simpler English without sacrificing correctness. Supervised fine-tuning (SFT) worked surprisingly well. With a relatively small dataset, the model learned to produce responses that were shorter, clearer, and closer to <a href="https://www.asd-ste100.org/">ASD-STE100</a>-style technical English.</p><p>But SFT also exposed a harder problem. Improving one behaviour can distort others. In our case, it sometimes made the model’s output distribution too sharp, leading to repetitive loops especially under greedy decoding strategies.</p><p>This article is a more technical follow-up to the original post. We will look at the failure modes encountered after SFT, why common inference-time fixes only treat the symptoms, and how Group Relative Policy Optimisation (GRPO) was used to optimise the model against explicit rewards for correctness, simplicity, clarity, and ASD-STE100 compliance.</p><h3>SFT worked… Until it didn’t</h3><p>Supervised fine-tuning produced a meaningful shift in behaviour. Semantic simplicity improved from <strong>65.82%</strong> to <strong>85.46%</strong>, while technical adequacy slightly improved from <strong>68.30%</strong> to <strong>70.15%</strong>. The model also preserved most of its task-fulfilment and clarity scores. In other words, SFT largely achieved what we wanted: simpler responses without an obvious collapse in correctness.</p><p>The problem appeared during generation.</p><p>Under some prompts, especially with greedy decoding, the SFT model could fall into repetitive loops. For example:</p><blockquote>…from a single exit point from a single next hop from a single next hop from a single next hop…</blockquote><p>Once the model entered one of these loops, it often had difficulty escaping.</p><p>This was not enough to invalidate the SFT result. Most generations were fine, and the aggregate evaluation metrics were strong. But it suggested that the behavioural shift we had learned came with an undesirable side effect.</p><h3>What went wrong?</h3><p>SFT trains the model to increase the probability of the target tokens in the training examples.</p><p>At a high level, it minimises the negative log-likelihood:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/645/1*MgyjbGQHSA3tW186_ex0gw.png" /></figure><p>If the training data consistently contains shorter, simpler, more direct answers, the model learns to assign more probability to those kinds of continuations.</p><p>But the objective does not directly care about the shape of the full output distribution.</p><p>In our case, the looping behaviour was consistent with the fine-tuned model becoming <strong>too confident in a small set of continuations under certain contexts</strong>.</p><p>This becomes especially problematic with greedy decoding:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*kMXMYIjfCcoD04y5YxF9EA.png" /></figure><p>Greedy decoding always picks the single highest-probability next token. That works well when the distribution is well behaved, but it can amplify local pathologies.</p><p>If one continuation produces a context that again strongly favours a similar continuation, the process can become self-reinforcing: context → high-probability continuation → similar context → same continuation → ⋯</p><p>The model had learned the right general behaviour, but in some local regions of the generation space, the learned update appeared to be too aggressive.</p><h3>A practical workaround</h3><p>Rather than retrain immediately, I first tried to make generation more robust at inference time.</p><p>First, I moved away from purely greedy decoding and introduced <strong>stochastic decoding</strong>.</p><p>Instead of always choosing the highest-probability token, sampling allows lower-ranked but still plausible tokens to be selected. This gives the model a way to escape deterministic cycles rather than repeatedly following the same local maximum.</p><p>Second, I added <strong>repetition and presence penalties</strong>.</p><p>These reduce the attractiveness of tokens or phrases that have already appeared in the generated text. They directly discourage the model from repeatedly returning to the same continuation.</p><p>Third, I reduced the <strong>LoRA adapter scale</strong>.</p><p>A LoRA-adapted layer can be written approximately as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/524/1*_xNcx3z39_KjayXS8UvNLw.png" /></figure><p>Reducing s weakens the contribution of the adapter while keeping the underlying base model unchanged.</p><p>This was particularly interesting because it suggested something about what the SFT adapter had learned. The direction of the update appeared useful: the adapter clearly pushed the model toward simpler language. But the full-strength update seemed too strong in some contexts.</p><p>Scaling the adapter down preserved much of the desired behaviour while reducing the tendency to fall into repetitive loops.</p><h3>But this was still unsatisfying</h3><p>These inference time fixes worked, but it felt like an operational hack.</p><p>Stochastic decoding, repetition penalties, and adapter scaling were effectively acting as guardrails around the fine-tuned model. Ideally, the model should learn the desired behavioural shift without needing those guardrails.</p><p>We could probably have squeezed out more performance from SFT by experimenting with a lower learning rate or earlier stopping. More diverse training data could also reduce the chances of over-specialising on a narrow set of response patterns.</p><p>But I decided to try a slightly different approach.</p><p>SFT only tells the model to imitate the examples it is given. What if we could instead tell the model directly which outputs were better?</p><h3>Enter, GRPO</h3><p>Group Relative Policy Optimisation (GRPO) is a reinforcement-learning method for post-training language models.</p><p>The key difference from SFT is the learning signal. SFT learns by imitation: given a prompt and a target response, it increases the probability of the target tokens. GRPO does not require a single “correct” target response. Instead, it generates several candidate responses for the same prompt, scores them with a reward function, and updates the model to make higher-reward responses more likely relative to the rest of the group.</p><p>This makes GRPO useful when the desired behaviour is easier to evaluate than to demonstrate perfectly in a dataset.</p><h3>A reward function with the wrong incentives</h3><p>The first reward function that we tried treated correctness as a multiplier over the style objectives:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/398/1*GHjqCh3bmC6oySkUolYZvw.png" /></figure><p>where C is correctness, S is simplicity, A is ASD-STE100 compliance, and the weights control the relative importance of the style objectives.</p><p>The intuition was that incorrect answers should receive little or no reward, while correct answers should be rewarded for writing simply.</p><p>In practice, however, correctness was something the model could improve just by adding more detail. Longer answers could include extra explanations and caveats that made them appear more technically complete, even when that detail was unnecessary. The result was a model that became more verbose instead of simpler.</p><p>Compared with the SFT baseline, semantic simplicity fell from <strong>85.46%</strong> to <strong>77.25%</strong>, while average sentence length increased slightly from <strong>15.32</strong> to <strong>15.85</strong> words. Technical adequacy was essentially unchanged at <strong>70.15%</strong> vs. <strong>70.50%</strong>, and task fulfilment also stayed roughly flat. The multiplicative reward added optimisation pressure without improving correctness, while making the model noticeably less simple.</p><h3>Treating correctness as a constraint</h3><p>The second reward formulation treated correctness as a <strong>constraint</strong>, rather than an objective:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/595/1*W6BSt3ojhsojI1oAvQ0inQ.png" /></figure><p>The idea was simple: first be correct enough, then optimise for simplicity.</p><p>Once a response passed the correctness threshold, adding more technically correct detail no longer increased its reward. The model instead had to compete on simplicity, ASD-STE100 compliance, and the other style objectives.</p><p>The TensorBoard logs suggested that this produced a much healthier optimisation signal: overall reward improved over training, while correctness, simplicity, ASD-STE100 compliance, and clarity generally improved alongside it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2JPUFir9f7oMB-LNXXT6Yg.png" /></figure><h3>The best model so far</h3><p>Compared with the previous SFT setup, semantic simplicity improved from <strong>85.46%</strong> to <strong>95.83%</strong>, while technical adequacy improved from <strong>70.15%</strong> to<strong> 72.42%</strong>. Average sentence length fell from <strong>15.32</strong> to <strong>11.80</strong> words, and mean output length dropped from <strong>622</strong> to <strong>331 </strong>tokens.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/743/1*MNE3ObYQSBWVRBpINelf0w.png" /></figure><p>The progression is clear. SFT produced the largest initial improvement in simplicity, while GRPO pushed it further and also achieved the highest technical adequacy. The final model wrote much shorter sentences and almost eliminated long sentences. This came with a modest trade-off in task fulfilment and clarity, suggesting that the reward function still has room for refinement.</p><p>Importantly, the looping problem did not return, even with the GRPO adapter <strong>at full strength</strong>. We kept the same repetition and presence penalties for consistency, so GRPO alone should not get all the credit.</p><h3>Try it yourself</h3><p>If you wish to reproduce the results or explore the artifacts:</p><ul><li><a href="https://github.com/thisisandreeeee/simple-llm">GitHub</a> — code and evaluation harness</li><li><a href="https://huggingface.co/datasets/thisisandreeeee/simple-llm-sft">HF Dataset</a> — GRPO training and validation data</li><li><a href="https://huggingface.co/thisisandreeeee/simple-llm-grpo-lora">HF Model</a> — trained GRPO LoRA adapter and model details</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a48058f920b" width="1" height="1" alt=""><hr><p><a href="https://pub.towardsai.net/making-llms-simpler-from-sft-to-grpo-2a48058f920b">Making LLMs Simpler: From SFT to GRPO</a> was originally published in <a href="https://pub.towardsai.net">Towards AI</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What I Learned from Running Hermes for 11 Weeks]]></title>
            <link>https://medium.com/data-science-collective/what-i-learned-from-running-hermes-for-11-weeks-ea1afa3244d1?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/ea1afa3244d1</guid>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[personal-growth]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Sat, 29 Aug 2026 07:24:09 GMT</pubDate>
            <atom:updated>2026-08-29T07:53:19.391Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qdHR60SNEAbZqvEzePPl8Q.png" /></figure><p>Eleven weeks ago, I set up my own personal AI agent.</p><p>I did not have a grand plan for it.</p><p>Sure, it would be nice if it made me more productive. Maybe it could help me learn faster, stay organised, or automate a few annoying tasks.</p><p>But I did not really know what I wanted it to do.</p><p>So I installed <a href="https://github.com/NousResearch/hermes-agent">Hermes</a> on a small DigitalOcean VPS, connected it to WhatsApp and Telegram, and gave it access to the things I used regularly: files, code, GitHub, Google Drive, Gmail, cron jobs, persistent memory, and reusable skills.</p><p>Because Hermes is designed to extend itself, its capabilities could grow with how we used it.</p><p>Naturally, I asked my wife to try it too.</p><p>Her response was very reasonable:</p><blockquote><strong><em>“What am I supposed to do with this?”</em></strong></blockquote><p>I did not have a good answer.</p><h3>At first, it looked like a learning tool</h3><p>Fast forward eleven weeks, we had sent more than 1,000 messages to Hermes.</p><p>My first uses of Hermes were fairly predictable.</p><p>Like many others, I use AI to help answer questions about difficult concepts I am learning. One day, I found myself reading more about the attention mechanisms in LLMs.</p><p>After we had discussed the topic, I asked Hermes to turn our conversation into a set of koans: small exercises designed to make me reason through the ideas myself.</p><p>Hermes:</p><ul><li>identified the key concepts;</li><li>designed the koans;</li><li>wrote and tested the exercises;</li><li>set up the repository and environment.</li></ul><p>All I needed to do was focus on <strong>solving the koans</strong>. If you’re feeling adventurous, you can try them out <a href="https://github.com/thisisandreeeee/llm-koans">here</a>.</p><p>What I appreciated was how easy this was. I was already talking to Hermes about attention. Turning that conversation into a learning system took just one additional message.</p><p>Later, I asked Hermes to send me revision questions every day.</p><p>Hermes figured out how to schedule the task using cron, remembered what I had been learning, and started sending me Socratic-style questions regularly.</p><p>I did not need to create a new app, configure a learning platform, or explain what I wanted to revise. Hermes already had the context.</p><h3>Then I started using it as a knowledge system</h3><p>For a while, I had wanted to build my own personal AI wiki, as inspired by Karpathy’s <a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f">llm-wiki</a>.</p><p>But I did not want to spend my weekends manually adding random things to a wiki just for the sake of having one. So I parked the idea.</p><p>Then, during one particularly busy period at work, I noticed that my inbox had accumulated a large backlog of newsletters.</p><p>Typically, I’d try to read them every week but this time I had fallen behind. And the longer the backlog grew, the less likely I was to start.</p><p>So I thought, what if the newsletters became the input to the wiki?</p><p>Hermes helped turn that fuzzy idea into an automated workflow.</p><p>Each week, it would process the newsletters, update the wiki, and send me a pull request to review. It would also send me a digest highlighting the most useful ideas and how they connected to concepts I had already encountered.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/377/1*WReAKN-iDYSWAbTAXKd7fA.png" /></figure><p>Because the wiki was just Markdown, I could also open it in Obsidian and explore the connections later. This made staying up to date considerably easier.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y0d1GgCCdbGgNR84WGF78A.png" /></figure><h3>Then my wife started finding uses for it</h3><p>A few months ago, my wife and I got a puppy.</p><p>And because dogs are pack animals, our puppy hated being alone.</p><p>So we started doing separation training: progressively increasing the duration the dog spends alone without pushing beyond what she can tolerate.</p><p>The annoying part was keeping track of everything.</p><blockquote>How long was the previous session?<br>Which set did she struggle with?<br>Should today’s session be harder or easier?<br>Were we progressing too quickly?</blockquote><p>My wife thought, why don’t we just ask Hermes to manage it?</p><p>We told Hermes to consume literature on separation training and had it design a program. After each session, we scored our puppy’s performance, and Hermes used that feedback to adjust the next one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TGXKzT18FbFui4UTHBdnDg.png" /></figure><p>This was probably the first real aha moment for my wife.</p><h3>“Let me know when this movie comes out”</h3><p>Getting a puppy also meant we spent much more time at home.</p><p>There were plenty of movies we wanted to watch but did not want to see badly enough to go to the cinema. Hence, we would usually wait for the movie to come out on streaming.</p><p>The problem was that we would often forget. A few months later, the movie would disappear into the endless pile of things we vaguely intended to watch.</p><p>Once again, my wife suggested using Hermes.</p><p>Now, whenever there is a movie we are interested in, we ask Hermes to track it. When it becomes available on a streaming service we can access, Hermes lets us know.</p><p>That is how Hermes reminded us about <em>Project Hail Mary</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/793/1*o-iWDEYCy9tU4buuHI1mtg.png" /></figure><p>It turned out to be one of our favourite movies of the year.</p><p>And there is a decent chance we would have forgotten about it otherwise.</p><h3>Next, we gave it our household accounting</h3><p>My wife and I track our household budget with <a href="https://www.ynab.com/">YNAB</a>.</p><p>I love the philosophy behind it: give every dollar a job and make spending decisions deliberately.</p><p>What I do not love is reconciling transactions.</p><p>YNAB’s automatic bank integrations are much better supported in the US than in Singapore, so a lot of our transaction management was manual.</p><p>Every month, we would sit down on a Sunday and work through everything over 3 hours.</p><p>That was pre-Hermes.</p><p>Now, we export our transactions as CSV files and send them over. Hermes reviews everything, resolves the straightforward cases, flags anything ambiguous, and inserts the prepared transactions into YNAB for us to review.</p><p>The process that used to take us 3 hours now takes around 30 minutes. And much of those 30 minutes is simply waiting for Hermes to work.</p><h3>The interesting part wasn’t productivity</h3><p>Did Hermes make our lives more productive?</p><p><em>Maybe.</em></p><p>Some workflows clearly save time.</p><p>Others took more time to build than they will ever save.</p><p>But after eleven weeks, I do not think productivity is the most meaningful way to evaluate the experiment.</p><p>The thing that changed was <strong>how use cases appeared</strong>.</p><p>I did not sit down on day one and decide to:</p><ol><li>Build a puppy separation-training system</li><li>Track movie streaming releases</li><li>Reconcile household finances</li></ol><p>Those ideas emerged because Hermes was already present when the problems appeared.</p><p>That led me to three lessons.</p><h4>1. You discover agent use cases by using them</h4><p>There is a temptation with new technology to first ask:</p><blockquote><strong>What is the killer use case?</strong></blockquote><p>I am increasingly unsure that this is the right question for personal agents.</p><p>Many of our useful workflows were things I would never have put on a requirements document.</p><p>They surfaced naturally.</p><p>A problem appeared.</p><p>We realised Hermes already had most of what it needed.</p><p>We asked it to help.</p><p>Then the workflow came to life.</p><h4>2. Proximity is a key enabler</h4><p>I think one reason this worked is extremely mundane:</p><p><strong>Hermes lives in WhatsApp and Telegram.</strong></p><p>Those are already places where my wife and I talk.</p><p>Movie ideas appear there.</p><p>Dog-training discussions happen there.</p><p>Random household problems appear there.</p><p>There is no need to remember to open an AI app and ask: “What processes in my life should I automate today?”</p><p>The agent is simply nearby when the problem occurs.</p><p>The best interface for a personal agent might be wherever your life is already happening.</p><h4>3. Tiny use cases matter</h4><p>Many of the use cases we found meaningful were actually quite small.</p><p>Tracking a movie release is a good example.</p><p>Nobody is going to buy an “AI movie reminder system.”</p><p>But I think that is exactly why it mattered to us.</p><p>It was a tiny annoyance that would normally never be worth solving properly. Because Hermes was already there, we could just ask it to handle it.</p><p>The same was true for puppy training, revision questions, and parts of our YNAB workflow.</p><p>None of these felt transformative on their own.</p><p>What I found interesting was how easy it became to keep adding small things that were specific to how we lived.</p><p>Over time, those small conveniences started to accumulate.</p><p>I am not sure any single one of them changed our lives very much.</p><p>But together, they did make parts of everyday life noticeably easier.</p><h3>So what is Hermes actually good for?</h3><p>Eleven weeks ago, my wife asked:</p><blockquote>“What am I supposed to do with this?”</blockquote><p>I still do not have a particularly good answer.</p><p><strong>The difference is that I no longer think that is a problem.</strong></p><p>A personal AI agent might be difficult to understand before you use it precisely because its most valuable uses are contextual.</p><p>They depend on your routines.</p><p>Your frustrations.</p><p>Your hobbies.</p><p>Your family.</p><p>The random problems that happen to appear on a Tuesday evening.</p><p>The useful thing about Hermes is not any single workflow.</p><p>It is that it sits close to our real lives, carries context forward, accumulates capabilities, and is ready when those workflows emerge.</p><p>So if you ask me today what my personal agent is for, my answer is still:</p><p><strong>I don’t know.</strong></p><p>But now it is followed by:</p><p><strong>Let’s see.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ea1afa3244d1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science-collective/what-i-learned-from-running-hermes-for-11-weeks-ea1afa3244d1">What I Learned from Running Hermes for 11 Weeks</a> was originally published in <a href="https://medium.com/data-science-collective">Data Science Collective</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Get LLMs to say more with less]]></title>
            <link>https://medium.com/data-science-collective/get-llms-to-say-more-with-less-41636ce31268?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/41636ce31268</guid>
            <category><![CDATA[qwen]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[fine-tuning]]></category>
            <category><![CDATA[llm-evaluation]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Sat, 22 Aug 2026 09:01:47 GMT</pubDate>
            <atom:updated>2026-09-16T00:54:02.193Z</atom:updated>
            <content:encoded><![CDATA[<h3>Get LLMs To Say More With Less</h3><p>Ask an LLM a technical question and it will try to be precise. The answer is usually correct, but often more complicated than it needs to be.</p><p>For example:</p><blockquote>Why does my model perform worse on new data?</blockquote><p>A typical answer might look like this:</p><blockquote>This behaviour is likely caused by overfitting, where the model learns patterns specific to the training dataset that do not generalise well to unseen examples. This can occur when model capacity is high relative to dataset size, training continues for too long, or regularisation is insufficient.</blockquote><p>A simpler answer says nearly the same thing:</p><blockquote>Your model is probably overfitting. It learned the training data too closely, so it does not work as well on new data.</blockquote><h3>Simpler does not mean less useful</h3><p>The second answer gives up some detail, but it is much easier to understand. That led to the central question behind this project:</p><p><strong>Can we post-train a small language model to produce answers that are both correct and simple?</strong></p><p>A frontier LLM with a well written system prompt could probably do this already.</p><p>But where is the fun in that?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qEeUC3RTvFe0G19fTzgUkg.png" /></figure><h3>Simplicity is hard to measure</h3><p>We start by defining what success looks like.</p><p>Shorter does not always mean simpler. Response length is a useful signal, but optimising for it too aggressively can produce fragmented answers that are harder to understand.</p><p>Simplicity also depends on less obvious details: sentence structure, ambiguous words, excessive contractions, semicolons, and abbreviations such as <em>i.e.</em> and <em>e.g.</em></p><p>So, what does “simple” actually mean?</p><p>Fortunately, some very smart people have already thought deeply about this and came up with <a href="https://www.asd-ste100.org/">ASD-STE100</a> Simplified Technical English, a controlled form of English originally developed for aircraft maintenance documentation. The goal is to make technical instructions clear, precise, and difficult to misinterpret, especially for non-native English speakers.</p><p>We use these rules as the basis for our deterministic checks.</p><h3>Building an evaluation harness</h3><p>Defining simplicity is only half the problem. We also need a way to measure it without rewarding answers that are short but wrong.</p><p>An answer can follow every style rule and still be incomplete, misleading, or unhelpful. So we complement our deterministic checks with an LLM judge across four dimensions:</p><ul><li><strong>Technical adequacy:</strong> Is the answer correct, complete, safe, and appropriately qualified?</li><li><strong>Task fulfilment:</strong> Does it fully address what the user asked?</li><li><strong>Clarity and coherence:</strong> Is it unambiguous, logically structured, and internally consistent?</li><li><strong>Semantic simplicity:</strong> Is it direct and proportionate, without unnecessary repetition or complexity?</li></ul><p>For example, here is the rubric used to judge semantic simplicity, including the criteria and evaluation steps given to the LLM judge:</p><pre>&quot;semantic_simplicity&quot;: {<br>    &quot;criteria&quot;: (<br>        &quot;Judge whether the answer expresses ideas directly and simply &quot;<br>        &quot;without removing necessary meaning or technical precision.&quot;<br>    ),<br>    &quot;steps&quot;: (<br>        &quot;Evaluate simplicity at the level of the whole answer, &quot;<br>        &quot;not only individual sentences.&quot;,<br>        &quot;Check whether the amount of detail and structure is &quot;<br>        &quot;proportionate to the user&#39;s request.&quot;,<br>        &quot;Penalize repetition, fragmented or choppy presentation, &quot;<br>        &quot;unnecessary headings, excessive itemization, and redundant summaries.&quot;,<br>        &quot;Do not award the highest score merely because sentences are short.&quot;,<br>    ),<br>}</pre><p>The final evaluation combines:</p><ul><li><strong>Deterministic checks</strong> → <em>How is the answer written?</em></li><li><strong>LLM-as-a-judge</strong> → <em>Is the answer actually good?</em></li></ul><p>Together, they let us measure simplicity without accidentally rewarding answers that are merely short. We use this evaluation harness to measure each experiment against a holdout set of 100 curated prompts spanning multiple domains.</p><h3>Building the SFT dataset</h3><p>The training dataset was generated using a two-phase approach:</p><ol><li><strong>Generate diverse prompts</strong> — Build a stratified mix of prompts across 20 domains, multiple task intents, target audiences, and expected response lengths, then filter out malformed or duplicate examples.</li><li><strong>Generate simple and correct answers</strong> — Use a teacher model guided by ASD-STE100-inspired rules and correctness checks to simplify each answer without dropping important caveats, qualifications, or technical meaning.</li></ol><p>This process produced 1,000 training examples.</p><p>Dataset generation used a mix of <strong>DeepSeek-V4-Pro</strong> at xhigh reasoning and <strong>GPT-5.6 Sol</strong> at medium reasoning. The prompts for both phases were refined iteratively through manual review of the teacher outputs.</p><p>Generating answers that were both simple and correct required explicit correctness instructions, with correctness taking priority over style. This ensured the teacher preserved important caveats, failure conditions, and technical terms before simplifying language and structure.</p><p>The Hugging Face dataset card can be viewed <a href="https://huggingface.co/datasets/thisisandreeeee/simple-llm-sft">here</a>.</p><h3>The training recipe</h3><p>We use <strong>Qwen3.5-4B</strong> as our starting point, as it is small enough to fine-tune cheaply, yet capable enough to be interesting.</p><p>Each training example contains a user prompt and an assistant answer. The prompt is provided as context, but loss is computed only on the assistant tokens, so the model learns to produce the target answer rather than reproduce the prompt. We use <strong>Unsloth</strong> for parameter efficient fine-tuning, with <strong>TRL</strong> configured to compute completion-only loss.</p><p>Fine-tuning was done using <strong>LoRA</strong>, freezing the base model and training only a small adapter with <strong>rank 16</strong> and <strong>alpha 16</strong>. The model was kept at <strong>bf16 </strong>precision rather than quantising with QLoRA since a 4B model fits comfortably on an NVIDIA <strong>L4</strong>.</p><p>Training and inference both run on <strong>Modal</strong>, which gives us serverless access to L4 GPUs without needing to manage infrastructure.</p><p>Training runs for <strong>two epochs</strong> on 900 examples, 100 for validation, and a separate 100 holdout set. The remaining settings are deliberately conservative: an effective batch size of 8, a learning rate of 1e-4, 8-bit AdamW, a linear learning-rate schedule, and 0.01 weight decay.</p><p>The result is a LoRA adapter, which we merge with the original base model for inference.</p><p>The Hugging Face model card can be viewed <a href="https://huggingface.co/thisisandreeeee/simple-llm-lora">here</a>.</p><h3>The experiment</h3><p>We compare three approaches of increasing complexity:</p><ol><li><strong>Base model</strong> — Qwen3.5-4B with no enhancements.</li><li><strong>Prompt engineering </strong>— Before changing the model, we test how far we can get with simple prompt engineering. If a good system prompt solves the problem, there is little value in post-training.</li><li><strong>LoRA fine-tuning</strong> — Using the SFT adapter trained on our curated dataset, scaled by 0.25 and merged into the base model.</li></ol><figure><img alt="Experiment result summary comparing performance of base model vs. prompt engineering vs. SFT" src="https://cdn-images-1.medium.com/max/868/1*TpBZ8BRgBMm4AdjSx3pcnA.png" /></figure><p>These results exposed an important trade-off: prompting made the model simpler, but often at the expense of being right.</p><h3>Prompting alone was too aggressive</h3><p>Prompt engineering had the strongest effect on simplicity:</p><ul><li>Average sentence length fell by <strong>38%</strong></li><li>Long sentences fell by <strong>83%</strong></li><li>Sentence mechanics improved by <strong>13%</strong></li><li>Verb-form and modal compliance improved by <strong>13%</strong></li><li>Semantic simplicity improved by <strong>14%</strong></li></ul><p>But the prompt-tuned model also scored worse on:</p><ul><li>Technical adequacy: -13%</li><li>Task fulfilment: -10%</li><li>Clarity and coherence: -7%</li></ul><p>This demonstrates that responses can be concise and readable while being wrong. Simplicity is useful only when the answer remains correct.</p><h3>SFT found a better balance</h3><p>Relative to the baseline, <strong>SFT improved semantic simplicity by almost 30%,</strong> while technical adequacy increased slightly.</p><p>It also produced:</p><ul><li>15% shorter sentences</li><li>40% fewer long sentences</li><li>Better terminology consistency</li><li>Almost unchanged task fulfilment and clarity</li></ul><p>That is arguably the more interesting result: the model learned to simplify the answer without merely optimising for shorter sentences or stricter writing rules.</p><p>Prompting made the model write less.</p><p><strong>SFT made it say more with less</strong>.</p><p>It also had an operational benefit: mean output length fell by about half, while generation time fell by 37%. Simpler answers were not only easier to read; they were cheaper to produce.</p><h3>But the model almost forgot how to stop</h3><p>The first training runs exposed an unexpected failure mode, where a phrase could begin repeating until the model hit its token limit.</p><blockquote>…from a single exit point from a single next hop from a single next hop from a single next hop…</blockquote><p>This is known as <strong>degenerate looping</strong>. The model gets trapped in a self-reinforcing sequence and effectively forgets how to stop.</p><p>Fine-tuning changes the model’s token probabilities. In some contexts, the LoRA adapter can make a narrow set of continuations much more likely than others. With <strong>greedy decoding</strong>, the model always selects the highest-probability token, so once it enters a repetitive pattern, there is no randomness to help it escape.</p><p>Greedy decoding was initially used to make the experiments deterministic, but it ended up amplifying a failure mode that appeared after fine-tuning.</p><h3>Fixing degenerate loops at inference time</h3><p>I tested three decoding changes:</p><ol><li><strong>Stochastic decoding</strong> —Sample from the token distribution instead of always taking the highest-probability token. This produced the largest improvement by giving the model a way to escape repetitive trajectories.</li><li><strong>Presence penalty</strong> — Apply a fixed penalty on tokens that have already appeared. This reduced repetition but degraded answers where repeating the same term is necessary.</li><li><strong>Repetition penalty</strong> — Reduce the probability of tokens that have already appeared using a multiplicative penalty.</li></ol><p>The best result came from combining stochastic decoding, a modest repetition penalty, and a lower presence penalty. This eliminated the looping behaviour (in our holdout set) while preserving simplicity and correctness.</p><h3>Try it yourself</h3><p>If you wish to reproduce the results or explore the artifacts:</p><ul><li><a href="https://github.com/thisisandreeeee/simple-llm">GitHub</a> — code and evaluation harness</li><li><a href="https://huggingface.co/datasets/thisisandreeeee/simple-llm-sft">HF Dataset</a> — SFT training and validation data</li><li><a href="https://huggingface.co/thisisandreeeee/simple-llm-sft-lora">HF Model</a> — trained LoRA adapter and model details</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=41636ce31268" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science-collective/get-llms-to-say-more-with-less-41636ce31268">Get LLMs to say more with less</a> was originally published in <a href="https://medium.com/data-science-collective">Data Science Collective</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[End-to-end Machine Learning with TFX on TensorFlow 2.x]]></title>
            <link>https://medium.com/data-science/end-to-end-machine-learning-with-tfx-on-tensorflow-2-x-6eda2fb5fe37?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/6eda2fb5fe37</guid>
            <category><![CDATA[tensorflow]]></category>
            <category><![CDATA[tensorflow-serving]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[keras]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Sat, 01 Feb 2020 10:14:19 GMT</pubDate>
            <atom:updated>2020-02-03T12:35:24.198Z</atom:updated>
            <content:encoded><![CDATA[<p>I was pretty excited when Tensorflow 2.0 was released late last year. After all, it promised a more enjoyable developer experience over its 1.x counterpart (otherwise known as the deep learning equivalent of Java from hell). Keras layers with strong support for deploying models? Count me in!</p><p>My excitement was short-lived, however, when I realized that support for taking TF 2.0 models to production was not what I imagined it to be. Sure, <a href="https://github.com/tensorflow/tfx">TensorFlow Extended</a> was an option, but it wasn’t a particularly pleasant one considering that the documentation had yet to catch up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/1*yWfQ4r1s1d5EPBRrhICEYg.png" /></figure><p>Still, I was impatient; and, <a href="https://www.tensorflow.org/guide/eager">eager to execute</a>. What ensued was an arduous journey filled with tears and cheers, all in search of the one true prize that is a working machine learning pipeline. I present to you my r̶a̶n̶t̶s̶ findings in the hope that you will not have to go through what I did. Well, until the next release anyway.</p><h4>What this is (not)</h4><p>This article is in no way an authoritative piece on <em>the</em> way to build production-ready TF 2.0 pipelines. It illustrates one possible workflow for deploying ML models that accounts for memory constraints and training-serving skew (amongst other things). If these concepts sound foreign, I recommend reading Google’s <a href="https://developers.google.com/machine-learning/guides/rules-of-ml/">Rules of Machine Learning</a>. Prior experience with neural networks and the machine learning lifecycle would certainly be helpful.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/0*MIuuJGz1DyeOEi3X.png" /><figcaption>An example of a machine learning lifecycle <a href="https://blog.gojekengineering.com/an-introduction-to-gojeks-machine-learning-platform-108968c85e64">adopted at Gojek</a></figcaption></figure><p>You will learn how to:</p><ul><li>Use <a href="https://www.tensorflow.org/tfx/transform/get_started">TF Transform</a> to perform feature imputation and scaling</li><li>Build models using the <a href="https://www.tensorflow.org/guide/keras/functional">Keras functional API</a> and <a href="https://www.tensorflow.org/tutorials/structured_data/feature_columns">feature columns</a></li><li>Export a model for <a href="https://www.tensorflow.org/tfx/serving/serving_basic">TF Serving</a> that reuses the Transform graph</li></ul><p>To keep things short, I will only be showing code snippets.</p><h4>1. Install dependencies</h4><p>You will need the following Python packages. Since we will be using Apache Beam to run our TF Transform pipelines, let’s install that too.</p><pre>pip install apache-beam==2.16.0 tensorflow==2.0.0 tensorflow-transform==0.15.0</pre><h4>2. Preprocess data using TF Transform</h4><p>This guide assumes familiarity with TF Transform, which involves writing transformations in a preprocessing_fn to be executed by a Beam pipeline. Further information on how to get started can be found <a href="https://www.tensorflow.org/tfx/transform/get_started">here</a>.</p><p>Let’s write a simple transformation for a numerical variable age that imputes missing values with the mean and applies feature scaling.</p><pre>import tensorflow as tf<br>import tensorflow_transform as tft</pre><pre>def preprocessing_fn(inputs):<br>    outputs = inputs.copy()<br>    <br>    age = outputs[&quot;age&quot;]<br>    mean_age = tft_mean(age)<br>    age = impute(age, -1, mean_age)<br>    outputs[&quot;age&quot;] = tft.scale_to_z_score(age)<br>    <br>    return outputs</pre><p>Hold up! Doesn’t TF Transform support calculation of means using thetft.mean() Analyzer? Why do we need to write our own implementation of tft_mean? This is because TF Transform has a <a href="https://github.com/tensorflow/transform/issues/152#issuecomment-568000987">known issue</a> where NaNs may produce unexpected results for some Analyzers. To address this, let’s only calculate the mean using non-null values.</p><pre>def tft_mean(tensor):<br>    finite_indices = tf.math.is_finite(tensor.values)<br>    finite_values = tf.boolean_mask(tensor.values, finite_indices)<br>    return tft.mean(finite_values)</pre><p>Now, let’s impute missing values using the mean age. There are two ways of representing missing values: first, by exclusion through the use of a <a href="https://www.tensorflow.org/api_docs/python/tf/sparse/SparseTensor">SparseTensor</a>; second, by using some arbitrary value (e.g. -1 for numerical variables or NULL for categorical variables). Let’s account for both scenarios.</p><pre>def impute(tensor, missing, replacement):<br>    sparse = tf.sparse.SparseTensor(<br>        tensor.indices, tensor.values, [tf.shape(tensor)[0], 1]<br>    )<br>    dense = tf.sparse.to_dense(sp_input=sparse, default_value=replacement)<br>    return tf.where(tf.equal(tensor, missing), replacement, dense)</pre><p>You can then run your TF Transform pipeline, don’t forget to export the Transform graph so that we can reuse it later!</p><pre>import apache_beam as beam <br>import tensorflow_transform.beam as tft_beam<br>import tempfile</pre><pre>with beam.Pipeline() as pipeline:<br>    with tft_beam.Context(temp_dir=tempfile.mkdtemp()):<br>        # read raw data<br>        raw_data = pipeline &gt;&gt; beam.io.ReadFromTFRecord(...)<br>        <br>        # apply transformation<br>        transformed_data, transform_fn = (<br>            (raw_data, raw_metadata) &gt;&gt; tft_beam.AnalyzeAndTransformDataset(preprocessing_fn)<br>        )<br>        <br>        # export transform graph<br>        _ = (<br>            transform_fn &gt;&gt; tft_beam.WriteTransformFn(&quot;data/tft/&quot;)<br>        )</pre><h4>3. Build a model using Keras layers and feature columns</h4><p>It’s time for some feature engineering. TensorFlow feature columns provide a concise API for common operations such as one-hot encoding. Let’s define feature columns for a numerical variable age and a categorical variablecountry.</p><pre>features = [<br>    tf.feature_column.numeric_column(&quot;age&quot;),<br>    tf.feature_column.indicator_column(<br>        tf.feature_column.categorical_column_with_vocabulary_list(<br>            &quot;country&quot;, [&quot;America&quot;, &quot;Japan&quot;, &quot;China&quot;],<br>        )<br>    ),<br>]</pre><p>Using feature columns with the Keras functional API can be a little tricky. You will need to instantiate a Keras tensor using an <a href="https://www.tensorflow.org/api_docs/python/tf/keras/Input">Input</a> layer.</p><pre>from tensorflow.keras.layers import Input</pre><pre>feature_inputs = {<br>    &quot;age&quot;: Input(name=&quot;age&quot;, shape=(), dtype=tf.float32),<br>    &quot;country&quot;: Input(name=&quot;country&quot;, shape=(), dtype=tf.string),<br>}</pre><p>We can now feed this into a Keras <a href="https://www.tensorflow.org/api_docs/python/tf/keras/layers/DenseFeatures">DenseFeatures</a> layer and proceed to define our neural network architecture.</p><pre>from tensorflow.keras.layers import Dense, DenseFeatures<br>from tensorflow.keras import Model</pre><pre>output_1 = DenseFeatures(features)(feature_inputs)<br>output_2 = Dense(16, activation=&quot;relu&quot;)(output_1)<br>preds = Dense(10, activation=&quot;softmax&quot;)(output_2)<br>model = Model(inputs=feature_inputs, outputs=preds)</pre><pre>model = model.compile(...)<br>model.fit(...)</pre><h4>4. Export model and call Transform graph</h4><p>Congratulations on making it this far! After training our model, we will export our model as a <a href="https://www.tensorflow.org/guide/saved_model">SavedModel</a> to deploy it. To prevent skew between training and serving, we’ll need to load the exported Transform graph to run the same transformations on serving inputs.</p><pre>tft_dir = &quot;data/tft/&quot;  # location of exported transform_fn</pre><pre>tft_output = tft.TFTransformOutput(tft_dir)<br>tft_output.transform_raw_features(raw_serving_inputs)</pre><p>In Tensorflow 1.x, you would include the above logic in the serving_input_receiver_fn <a href="https://www.tensorflow.org/api_docs/python/tf/estimator/Estimator#export_saved_model">when exporting your model</a>. However, as we move away from the Estimator API in TF 2.x, this functionality is being deprecated. Instead, we will need to modify the serving signature of the Keras model by overriding the save() method.</p><pre>class ExportModel(tf.keras.Model):<br>    def __init__(self, model, tft_dir):<br>        super().__init__(self)<br>        self.model = model<br>        self.tft_output = tft.TFTransformOutput(tft_dir)<br>        <br>    @tf.function(input_signature=[SERVING_FEATURE_SPEC])<br>    def serving_fn(self, inputs):<br>        transformed = self.tft_output.transform_raw_features(inputs)<br>        return {&quot;preds&quot;: self.model(transformed)}</pre><pre>    def save(self, output_dir):<br>        signatures = {&quot;serving_default&quot;: self.serving_fn} <br>        tf.saved_model.save(self, output_dir, signatures)</pre><pre>ExportModel(model, &quot;data/tft/&quot;).save(&quot;data/model/1/&quot;)</pre><p>We can then serve our model using TF Serving.</p><p>Hope this helps. Ciao!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6eda2fb5fe37" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/end-to-end-machine-learning-with-tfx-on-tensorflow-2-x-6eda2fb5fe37">End-to-end Machine Learning with TFX on TensorFlow 2.x</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What deep learning taught me about competition]]></title>
            <link>https://medium.com/@andrewrites/what-deep-learning-taught-me-about-competition-5157e9eb464?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/5157e9eb464</guid>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[agent-based-modeling]]></category>
            <category><![CDATA[cooperation]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Sat, 28 Oct 2017 15:55:59 GMT</pubDate>
            <atom:updated>2017-11-23T02:22:10.463Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qj44v3M7EWBdNjuUPvPnFQ.png" /></figure><p>Competition and cooperation are typically viewed to be mutually exclusive, particularly so within academic institutions that practice curve grading. It pays to cooperate insofar as individual performance is not affected, and many adopt competitive behaviors as examinations draw near. This doesn’t necessarily have to be the case; <strong>individuals can be competitive and cooperative at the same time</strong>, as I will demonstrate through the use of agent-based modeling and deep learning technology.</p><p>I was introduced to agent-based modeling in a class I took in the University Scholars Programme at NUS, on computational thinking. During one particular lecture, we studied the evolution of <em>altruistic and cooperative habits</em> using an agent-based model, that simulates the behavior of altruistic and selfish agents.</p><p>The model is first randomized with each patch in a field occupied by an agent. At each time step, an agent will try to plant “seeds” around its neighboring patches, which will then grow into an altruistic or selfish agent. The probability of each new agent being altruistic or selfish depends on the cost and benefits of being altruistic, which is a factor modeled between 0 and 1. Under these conditions, I ran two simulations to visualize the spread of altruism and selfishness in this imaginary world.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/710/1*vA9aWo0MqHRDL22UoN5Pyg.gif" /><figcaption><em>Starting with equal proportions of altruistic (pink) and selfish (green) agents, in a world where it is cheap and moderately beneficial to be altruistic, we observe that selfish agents quickly dominate the population, and altruism is quickly driven to extinction. Shouldn’t altruism </em>always<em> be rewarded?</em></figcaption></figure><p>Simulations are a great tool for understanding relationships within complex systems. Through them, we visualize how changing parameters can affect outcomes, which can be more enriching than learning by hearing or reading instead. In this simulation, I modify the stress imposed on agents and observed the following.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/709/1*VDozf-4jvsAp_thWb17CXQ.gif" /><figcaption><em>We introduce disease (probability that an agent dies) and harshness (resistance to a patch being populated) to the model, ceteris paribus. Sure, the selfish agents dominate… at first. However, the respective populations quickly converge and altruistic agents eventually drive the selfish agents to extinction. Stress from the environment is determining whether altruism is rewarded or not.</em></figcaption></figure><p>Contrasting these two simulations helped me to arrive at a rather puzzling conclusion. The selfish agents were more competitive, and this competitive nature did not always translate to good results especially under stressful conditions. So, what does healthy competition actually mean? When we eventually step out into society as working professionals, <strong>is it more important to be cooperative, or competitive</strong>?</p><p>I desired an alternative perspective on competition, which came in the form of a paper by artificial intelligence research scientist Ian Goodfellow. Goodfellow proposes a novel approach to designing generative models using mathematical abstractions of the brain. The mathematics behind these Generative Adversarial Networks (GANs) isn’t so important here, but know that it’s a straightforward approach involving an adversarial process.</p><p>Where previous advancements in deep learning revolved around discriminative models, GANs were <em>generative</em> in nature. On the one hand, discriminative tasks such as image recognition involve machines trying to differentiate between images of objects, and technological advancements have made such tasks relatively trivial. On the other hand, generative tasks are significantly harder because they require a machine to be <em>creative</em>, and to produce unique images of new objects.</p><p>GANs generate new images by <em>learning and generalizing features</em> from existing images, before piecing them together much like an artist would. I chanced upon a project by members of the open source community where they tried to create new Pokemon designs using GANs, and the results left me pretty amazed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZZmxX0M7PPcMyGFqHPgaag.png" /><figcaption><em>By feeding the GAN with pictures of Pokemon, we are able to generate images of new objects (pictured left) that bear some semblance with a Pokemon. I found it really impressive how the GAN was able to understand the features that constitute a Pokemon to create new ones, albeit of poor design. Notice how the GAN has learnt to pick out the spiral pattern characteristic of Poliwhirl (pictured right), and used it to generate other Pokemon. Its ability to learn is really impressive.</em></figcaption></figure><p>While the generated Pokemon are admittedly far from complete, the GANs ability to learn and generalize the spiral patterns on Poliwhirl demonstrates marked improvement over its predecessors. The relationship between the Generator and Discriminator is <strong>analogous to other iconic rivalries</strong>, such as that between soccer athletes Cristiano Ronaldo and Lionel Messi. Both are widely regarded as two of the all-time best players, and constantly try to best each other to establish themselves as the undisputed number one. Through that process, they are motivated to train harder to become the best versions of themselves.</p><blockquote>“We push each other sometimes, this is why the competition is so high.” — Cristiano Ronaldo</blockquote><p>In a similar fashion, the Generator and Discriminator in a GAN <strong>work in opposition</strong> to one another and try to outsmart each other, improving very rapidly in the process. Over time, Generators create more realistic images that are harder for Discriminators to differentiate, who are then forced to get better to catch the Generators.</p><p>As it turns out, competition and cooperation might not be too different after all. GANs offer an <strong>alternate view on relationships in the workplace that does not binarize competition and cooperation</strong> as separate concepts, but instead proposes that competition and cooperation are essentially the same. Generators and Discriminators alone are not powerful as compared to its predecessor neural networks, but together they cooperate and contribute to the overall success of the system through recursive competition.</p><p>While the real world is arguably more complex than can be modeled by an adversarial process, the understanding that <strong>competition and cooperation can coexist in a single relationship</strong> is a powerful fact — one that a prospective member of the workforce, or an aspiring manager, could adopt to better maximize the value of their teams.</p><p><em>Andre is an aspiring data scientist. Expected to graduate in May 2018, he is currently on the lookout for opportunities to break into the field. Check out his </em><a href="http://andretan.me/"><em>snazzy personal website</em></a><em>, or reach him via </em><a href="http://linkedin.com/in/andre-tan"><em>LinkedIn</em></a><em>! You can also </em><a href="https://drive.google.com/open?id=0Bx2NGZ7Sz3g0LTlaMWlOME9OcHM"><em>view his resume here</em></a><em>.</em></p><blockquote>Join <strong>30,000+ people</strong> who read the weekly <a href="http://subscribe.machinelearnings.co/?utm_source=medium&amp;utm_medium=blog&amp;utm_campaign=guest_post&amp;utm_content=andre_tan">🤖Machine Learnings🤖</a> newsletter to understand how AI will impact the way we work and live.</blockquote><figure><a href="http://subscribe.machinelearnings.co/?utm_source=medium&amp;utm_medium=blog&amp;utm_campaign=guest_post&amp;utm_content=andre_tan"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xkDPcjFLfIQAlXZRf0iUNA.png" /></a></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5157e9eb464" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What hackathons can teach us about running a startup]]></title>
            <link>https://medium.com/nus-silicon-valley/what-hackathons-can-teach-us-about-running-a-startup-1fb9b4dad69a?source=rss-50343650a290------2</link>
            <guid isPermaLink="false">https://medium.com/p/1fb9b4dad69a</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[hackathons]]></category>
            <category><![CDATA[startup]]></category>
            <dc:creator><![CDATA[Andre Tan]]></dc:creator>
            <pubDate>Wed, 25 Jan 2017 18:22:01 GMT</pubDate>
            <atom:updated>2017-08-08T17:20:09.674Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XOO4bMM6XqMJD6wn8QBOlQ.png" /></figure><p>I first set foot in Silicon Valley during the summer of 2016. Having taken part in a couple of hackathons back in Singapore, I was pretty excited to explore the bay area hacker scene. I had heard stories of triumph through my fellow Singaporeans, and was on the look out for upcoming hackathons to get my hands dirty.</p><p>3 months later, I was on track to achieve what I had set out to do. I got to work with many different talented individuals, and had won 75% of the hackathons we had taken part in, with over $8,000 in winnings.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/957/1*L0TaBe9pA9yjWDuZLVyKMw.png" /><figcaption>Our team for the Money 20/20 Fintech Hackathon, at the Venetian in Las Vegas.</figcaption></figure><p>I started to notice a trend. Each hackathon that we had taken part in, fell into one of three categories:</p><ol><li>The “<strong><em>Build whatever you want, as long as it’s cool.</em></strong>” hackathon</li><li>The “<strong><em>We built some new tech, please use it?</em></strong>” hackathon</li><li>The “<strong><em>Please solve my problem with tech!</em></strong>” hackathon</li></ol><p>My first exposure to hackathons in Singapore were of the first category. It was mostly about a bunch of programmers getting together for 24 hours of code, caffeine, and pizza. I realized teams that win at these hackathons aren’t always the ones using the hottest new technology, but instead, are exceedingly creative in how they use existing technology for the craziest ideas. Check out <a href="https://devpost.com/software/ramear">this amazing hack</a> by the recent grand prize winners of PennApps XIII, who were able to secretly transmit data out of your computer using basic hardware.</p><p>There are also those hackathons, where startups offer free pizza and company swag in exchange for you building applications with their technology and tweeting about it. If you’re lucky, you get laptop stickers and a nifty certificate too! While not particularly enriching, you cannot doubt their effectiveness as a growth hack.</p><p>The last category of hackathons, that I am starting to see increasingly more of, revolve around solving business problems with technical solutions. The winners aren’t necessarily the ones with the coolest hack. Rather, they win because they offer real solutions to a real problem that the organizers actually have. Hackathons attract large amounts of technical talent, and companies are starting to recognize them for the ability to generate interesting new perspectives <em>with a working prototype</em>.</p><p>Based on my personal experience, and through conversations I’ve had with other winners, I’ve distilled the winning process into pseudo-code.</p><pre>prototype = None<br>while hackathon.in_progress():<br>  requirements = ask_judges(prototype)<br>  if is_fulfilled(requirements):<br>    collect_your_prize_money()<br>  else:<br>    prototype = build(requirements)</pre><p>The hackathon process is an iterative one. You come up with an initial idea, and check with the judges if they like it. If so, your chances of taking home some moolah are pretty high, and you just need to execute well. Chances are they won’t, in which case you’d have gathered some feedback to refine your idea. You build out a better prototype, and consult with the judges 2 hours later. You repeat this process as many times as necessary, until you satisfy the judges or run out of time. The result is a prototype that the judges will <em>most probably</em> like, considering how it’s as much their brainchild as it is yours.</p><p>Such hackathons aren’t always <em>fair</em>, in the sense that the strongest or most creative developers may not win if their hack does not adequately address the business problem. But that’s the reality, because if you’re a company that is forking out money to organize a hackathon, you’d want someone to solve your problems.</p><p>Now, what does any of this have to do with actually running a startup?</p><p>I spent the last 3 years exploring the startup space, learning about why startups succeed and fail. While I may not know enough to succeed at the startup game yet, I have learnt a lot about understanding customers through countless previous failed ventures. I believe that there are similarities between winning a hackathon and building a product that consumers love.</p><p>The <strong>hackathon</strong> process begins with figuring out what <strong>the judges</strong> want. You build a quick and dirty <strong>prototype</strong>, before checking back with <strong>the judges</strong> if this is what they had in mind. Rinse and repeat.</p><p><em>Similarly</em>…</p><p>The <strong>startup</strong> process begins with figuring out what your <strong>customers</strong> want. You build a quick and dirty <strong>Minimum Viable Product</strong>, before checking back with your <strong>customers</strong> if this is what they had in mind. Rinse and repeat.</p><p>There is a lot that a hackathon can teach you about running a startup. You learn to work well with people under very stressful conditions. You learn about all the amazing ways technology can transform the world. And perhaps most importantly, you learn to listen to your customers.</p><p><em>This is the very first post for the “Everyone Can Write” Initiative by the </em><a href="https://medium.com/@NUSEA"><em>NUS Entrepreneurs’ Association</em></a><em>. Andre is an aspiring data scientist, and can be reached via </em><a href="https://www.linkedin.com/in/andre-tan"><em>LinkedIn</em></a><em> or </em><a href="https://github.com/thisisandreeeee"><em>Github</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1fb9b4dad69a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/nus-silicon-valley/what-hackathons-can-teach-us-about-running-a-startup-1fb9b4dad69a">What hackathons can teach us about running a startup</a> was originally published in <a href="https://medium.com/nus-silicon-valley">NUS Silicon Valley</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>