<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Josiah Bryan</title>
    <description>The latest articles on DEV Community by Josiah Bryan (@josiahbryan).</description>
    <link>https://dev.to/josiahbryan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F232840%2Faf4e67e9-a9aa-4c39-8924-019ca35b3eb7.jpeg</url>
      <title>DEV Community: Josiah Bryan</title>
      <link>https://dev.to/josiahbryan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/josiahbryan"/>
    <language>en</language>
    <item>
      <title>Tackling JSON Perplexity in LLM Outputs: A Weekend Project</title>
      <dc:creator>Josiah Bryan</dc:creator>
      <pubDate>Mon, 15 Apr 2024 05:00:42 +0000</pubDate>
      <link>https://dev.to/josiahbryan/tackling-json-perplexity-in-llm-outputs-a-weekend-project-jm8</link>
      <guid>https://dev.to/josiahbryan/tackling-json-perplexity-in-llm-outputs-a-weekend-project-jm8</guid>
      <description>&lt;p&gt;This weekend, I dove deep into a problem we often encounter in natural language processing (NLP): ensuring the accuracy and reliability of JSON outputs from large language models (LLMs), particularly when dealing with key/value pairs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;We frequently face the issue of not having direct methods to measure perplexity or log probabilities on function calls from LLMs. This makes it tough to trust the reliability of the JSON generated by these models, especially when it's critical to ensure that each key and value in our outputs not only makes sense but is also based on predictable patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Solution
&lt;/h3&gt;

&lt;p&gt;To address this, I developed a robust JSON parser. The goal was to extract JSON directly from the stream of log probabilities provided by OpenAI when generating text outputs that contain JSON elements. This parser isn't just about pulling JSON out of the text—it's smart enough to calculate the perplexity and probabilities for each key/value, ensuring that what we get is as accurate as it can be. While JSON parsing can get a bit complex, and my solution isn't flawless, it has passed all my tests and is proving quite robust for my needs.&lt;/p&gt;

&lt;p&gt;For example: for a given JSON object generated by an LLM, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;formalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Josiah Bryan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Joey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ageGuess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For that same object, my parser can generate a metadata object with the following data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;formalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;formalName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Josiah Bryan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyProb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.999996&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;valueProb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.999957&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyPerplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.000001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;valuePerplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.000014&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nickname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Joey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;keyProb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.999996&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;valueProb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.872926&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;keyPerplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.000004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;valuePerplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.070314&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;ageGuess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ageGuess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;keyProb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.999994&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;valueProb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.594872&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;keyPerplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.000003&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;valuePerplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.681035&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The &lt;code&gt;finished&lt;/code&gt; prop in this example is useful when parsing a stream of chunks. When parsing JSON from a firehose like that, the &lt;code&gt;finished&lt;/code&gt; prop is &lt;code&gt;false&lt;/code&gt; while the parser is still consuming more tokens for the value. Once the parser hits an end token (e.g. &lt;code&gt;,&lt;/code&gt; or &lt;code&gt;"&lt;/code&gt;, etc), it flips &lt;code&gt;finished&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; so you know the value is final.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It's Cool
&lt;/h3&gt;

&lt;p&gt;This is made practically useful part with a custom &lt;code&gt;yup&lt;/code&gt; decorator to actively manage the model's output. If the parser detects that the perplexity of a generated content goes above our comfort threshold, it can automatically tweak the prompt or inject additional grounding into the model’s inputs. This ensures that the generated JSON is not only precise but also deeply rooted in factual accuracy.&lt;/p&gt;

&lt;p&gt;For example, here's how the schema is specified with custom max &lt;code&gt;perplexity&lt;/code&gt; values per field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;formalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yup&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Formal name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perplexity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.125&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yup&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Generated nickname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perplexity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;ageGuess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;yup&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Generated age guess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perplexity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, when passing that to the &lt;code&gt;coaxLLm&lt;/code&gt; method, we can also include a callback to add more grounding when perplexity is too high on a given field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectWithMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;coaxLlm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;langfuseTrace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cacheMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;failureInjectCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;perplexity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nickname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;formalName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`My name is: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just in time for a busy upcoming week, this tool has become an indispensable asset in my toolkit, enhancing the grounding of LLM outputs and significantly speeding up JSON generation—a win-win for any developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Out the Code
&lt;/h3&gt;

&lt;p&gt;Interested in seeing this in action or integrating it into your own projects? Here’s the link to the full code on how to coax and re-ground the LLM effectively: &lt;a href="https://gist.github.com/josiahbryan/54dd184a9614882f1ee9ea413110d95e" rel="noopener noreferrer"&gt;coax-llm.js&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Real-Time Streaming
&lt;/h3&gt;

&lt;p&gt;This parser also works seamlessly with streaming outputs from LLMs. This means we can fetch JSON objects and log probabilities in real-time, without waiting for the entire text generation to complete. It’s efficient and allows for immediate adjustments or error handling, boosting both performance and reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dive Deeper
&lt;/h3&gt;

&lt;p&gt;For those who love digging into the nuts and bolts, here’s a direct link to the parser itself: &lt;a href="https://gist.github.com/josiahbryan/7a490834208dcd00534f471d9f40aac2" rel="noopener noreferrer"&gt;logprobsToAnnotatedJson.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While I haven’t made the underlying detailed benchwork public, the gists provided are self-contained and full of actionable insights. They're not just theoretical but are primed for real-world application, and I'm using them personally in production (pushing them to my k8s cluster tonight, even as I type.)&lt;/p&gt;

&lt;p&gt;Looking forward to your thoughts and any feedback you might have!&lt;/p&gt;

</description>
      <category>llm</category>
      <category>perplexity</category>
      <category>json</category>
      <category>yup</category>
    </item>
    <item>
      <title>React Draggable Bottom Panel</title>
      <dc:creator>Josiah Bryan</dc:creator>
      <pubDate>Sat, 21 Aug 2021 19:01:32 +0000</pubDate>
      <link>https://dev.to/josiahbryan/react-draggable-bottom-panel-17f0</link>
      <guid>https://dev.to/josiahbryan/react-draggable-bottom-panel-17f0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR: Source for &lt;code&gt;BottomPanel.jsx&lt;/code&gt; and &lt;code&gt;BottomPanel.module.scss&lt;/code&gt; is at&lt;br&gt;
&lt;a href="https://gist.github.com/josiahbryan/c220708256f7c8d79760aff37f64948f" rel="noopener noreferrer"&gt;https://gist.github.com/josiahbryan/c220708256f7c8d79760aff37f64948f&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Live Demo: &lt;a href="https://josiahbryan.com/#/bottompanel-demo" rel="noopener noreferrer"&gt;https://josiahbryan.com/#/bottompanel-demo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been working on a couple of different projects lately, one involves working on the next-generation marketplace for &lt;a href="http://fringe.us" rel="noopener noreferrer"&gt;fringe.us&lt;/a&gt;, and the other project is an app for a luxury driving service.&lt;/p&gt;

&lt;p&gt;Both of these projects called for a bottom panel that can be partially exposed and then dragged/swiped up to reveal content. &lt;/p&gt;

&lt;p&gt;I searched high and low and could not find any acceptable implementations of just such a UI component in React - which was rather shocking, I thought surely someone had solved this rather common UI paradigm already for React!&lt;/p&gt;

&lt;p&gt;I found many implementations of the paradigm in non-web-React formats, here's a couple examples that show what I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Native: &lt;a href="https://github.com/enesozturk/rn-swipeable-panel" rel="noopener noreferrer"&gt;https://github.com/enesozturk/rn-swipeable-panel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flutter: &lt;a href="https://github.com/enesozturk/rn-swipeable-panel" rel="noopener noreferrer"&gt;https://github.com/enesozturk/rn-swipeable-panel&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of those packages look beautiful and I would love to use them! However, the projects I'm working on require React in a browser, so those packages are not options.&lt;/p&gt;

&lt;p&gt;I almost gave up on finding a solution, but yesterday I decided to give it one last try. I thought surely I can implement it myself! I first tried extracting the &lt;code&gt;SwipeableDrawer&lt;/code&gt; component from @material-ui's source, but that proved incredibly painful and never got that working.&lt;/p&gt;

&lt;p&gt;Then I tried writing a simple implementation of a drawer myself using &lt;code&gt;react-swipeable&lt;/code&gt;'s awesome hook. That worked okay, but the FPS (especially on mobile) was HORRIBLE. I'm talking ~10-~12 fps when dragging. NOT accetable.&lt;/p&gt;

&lt;p&gt;Then, almost as if by providence, I stumbled upon this section in &lt;code&gt;react-swipeable&lt;/code&gt;'s docs: &lt;a href="https://github.com/FormidableLabs/react-swipeable#how-to-use-touch-action-to-prevent-scrolling" rel="noopener noreferrer"&gt;https://github.com/FormidableLabs/react-swipeable#how-to-use-touch-action-to-prevent-scrolling&lt;/a&gt; - that mentioned a package I hadn't looked at yet, &lt;a href="https://use-gesture.netlify.app/" rel="noopener noreferrer"&gt;&lt;code&gt;use-gesture&lt;/code&gt;&lt;/a&gt;. By this point, I was exhausted from reading docs and thought that I would just glance at that package, but didn't think anything would be useful. Boy, was I wrong.&lt;/p&gt;

&lt;p&gt;I read the docs in &lt;code&gt;use-gesture&lt;/code&gt; and was subtly impressed. Then I found &lt;a href="https://use-gesture.netlify.app/docs/examples/" rel="noopener noreferrer"&gt;their examples page&lt;/a&gt;, which led me to their example for an "Action Sheet": &lt;a href="https://codesandbox.io/embed/zuwji?file=/src/index.js&amp;amp;codemirror=1" rel="noopener noreferrer"&gt;https://codesandbox.io/embed/zuwji?file=/src/index.js&amp;amp;codemirror=1&lt;/a&gt; - needless to say, I was incredibly impressed!&lt;/p&gt;

&lt;p&gt;I set about porting their code with very minimal tweaks into a reusable &lt;code&gt;BottomDrawer&lt;/code&gt; component that had the various extra niceties I wanted: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag handle at the top&lt;/li&gt;
&lt;li&gt;Customizable open size / closed size&lt;/li&gt;
&lt;li&gt;Scrollable content area inside the sheet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a good two hours of banging my head against the keyboard, I finally solved all the things I needed and created the following beautiful component (screenshot is at the top of this post). I call it &lt;code&gt;&amp;lt;BottomPanel&amp;gt;&lt;/code&gt; - I know, so original - my excuse is I like to KISS.&lt;/p&gt;

&lt;p&gt;To see a live working example of this component, head over to my website: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://josiahbryan.com/#/bottompanel-demo" rel="noopener noreferrer"&gt;https://josiahbryan.com/#/bottompanel-demo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example of &lt;code&gt;&amp;lt;BottomPanel&amp;gt;&lt;/code&gt; closed:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmu9y1t95cm44z6opqz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmu9y1t95cm44z6opqz1.png" alt="BottomPanel Closed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of &lt;code&gt;&amp;lt;BottomPanel&amp;gt;&lt;/code&gt; open:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqwfsos69pe1f8llg4mu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqwfsos69pe1f8llg4mu.png" alt="BottomPanel Open"&gt;&lt;/a&gt;&lt;br&gt;
Usable like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;BottomPanel
    maxOpenHeight={window.innerHeight * 0.8} // px
    closedPanelSize={200} // px
&amp;gt;
    &amp;lt;LoremIpsum /&amp;gt;
&amp;lt;/BottomPanel&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can find the full source for &lt;code&gt;BottomPanel.jsx&lt;/code&gt; and the required styles (&lt;code&gt;BottomPanel.module.scss&lt;/code&gt;) in the following gist:&lt;br&gt;
&lt;a href="https://gist.github.com/josiahbryan/c220708256f7c8d79760aff37f64948f" rel="noopener noreferrer"&gt;https://gist.github.com/josiahbryan/c220708256f7c8d79760aff37f64948f&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;br&gt;
-Josiah Bryan&lt;/p&gt;

</description>
      <category>react</category>
      <category>ux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>LERPing and Cleaning Data to Improve AI Classification</title>
      <dc:creator>Josiah Bryan</dc:creator>
      <pubDate>Fri, 27 Sep 2019 15:15:39 +0000</pubDate>
      <link>https://dev.to/josiahbryan/lerping-and-cleaning-data-to-improve-ai-classification-34g1</link>
      <guid>https://dev.to/josiahbryan/lerping-and-cleaning-data-to-improve-ai-classification-34g1</guid>
      <description>&lt;h1&gt;
  
  
  More Training
&lt;/h1&gt;

&lt;p&gt;After &lt;a href="https://dev.to/josiahbryan/personal-safety-gps-and-machine-learning-are-you-running-from-danger-43bl"&gt;my last post on WalkSafe and machine learning classification on running&lt;/a&gt;, I've spent a lot of time testing WalkSafe in real-world scenarios personally. I've been mostly favorably impressed with the performance of the classification, but there's been something in the back of my mind telling me I could do better. &lt;/p&gt;

&lt;p&gt;I was experiencing a number of false-positives (Driving slow looked like Running, for example, or Walking fast looked like Running), so I decided to retrain my neural network to better generalize for unseen conditions and improve general classification performance from my last article.&lt;/p&gt;

&lt;h1&gt;
  
  
  Three Big Gains
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Normalize
&lt;/h2&gt;

&lt;p&gt;The first and biggest gain came when I realized that I was feeding raw speeds (15 m/s, for example) into the neural network and I discovered that it might perform better on 0-1 ranged data. So, I setup a simple normalization routine to normalize/unnormalize the data by setting a &lt;code&gt;MAX&lt;/code&gt; speed. Basically, I took the raw speed points and did this for every point:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const inputSpeed = rawSpeed / MAX_SPEED&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For my app, I've decided to use &lt;code&gt;33 m/s&lt;/code&gt; as a max speed, which is roughly 75 mph or 110 kph.&lt;/p&gt;

&lt;p&gt;I did try experimenting with bucketing speeds (e.g. "snapping to a grid" or rounding to every 2 m/s), as well as averaging speeds together (average two readings into one). These were both done in an attempt to get the network to better generalize with unseen data. However, testing with datasets the network had not seen (and even recall tests) showed that bucketing and averaging produced significant DROPS in performance (recall and generalization.) Therefore, those techniques were discarded.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Training Set Structure
&lt;/h2&gt;

&lt;p&gt;Another gain, albeit somewhat smaller, was made by changing the way I loaded by test data.&lt;/p&gt;

&lt;p&gt;Originally, I loaded all the data from ~8 separate CSV files, then concatenated all those points into a single array, and finally made ngrams out of that array of points.&lt;/p&gt;

&lt;p&gt;This had the unrealized effect of making ngrams out of two separate data sets - when one set ended and the new set was concatenated onto the end, an ngram could span both sets.&lt;/p&gt;

&lt;p&gt;Therefore, in order not to "confuse" the network by feeding it training data that was not real, I changed the loading process to something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const csvData = [
   getCsv('file1.csv'),
   getCsv('file2.csv'),
   getCsv('file3.csv')
];

const trainingData = csvData
  .map(lerpData) // see #3 "fill in the gaps", below
  .map(makeNgrams) // from last article: [1,2,3,4] into [[1,2],[3,4]]
  .reduce((list, ngrams) =&amp;gt; list.concat(ngrams), []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The end result is still a giant set of training data points in &lt;code&gt;trainingData&lt;/code&gt;, but it doesn't concatenate the points from the different data sets together until after they've been properly transformed&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fill in the Gaps
&lt;/h2&gt;

&lt;p&gt;The second largest fundamental generalization and classification gain was made when I realized that there were gaps in the GPS speed readings. Which, of course, is obvious in a real-world collection scenario. However, I came to the conclusion that training the network on a speed transition of &lt;code&gt;1m/s&lt;/code&gt; &amp;gt; &lt;code&gt;5m/s&lt;/code&gt; without any context as to how fast that transition happened would be to deprive it of valuable contextual information that could aid in classification.&lt;/p&gt;

&lt;p&gt;In order to capture this concept of time, I decided to normalize the inputs so that every input into the network represented a finite set of time stamps with a finite interval between each input. (Before, every input was NOT guaranteed to have a finite, fixed interval between each input.)&lt;/p&gt;

&lt;p&gt;In order to accomplish this "finite, fixed interval" guarantee, I used a very simple concept, &lt;a href="https://en.wikipedia.org/wiki/Linear_interpolation" rel="noopener noreferrer"&gt;Linear interpolation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://github.com/mattdesl" rel="noopener noreferrer"&gt;mattdes&lt;/a&gt; on GitHub, I've found this &lt;code&gt;lerp&lt;/code&gt; function (&lt;a href="https://github.com/mattdesl/lerp/blob/master/LICENSE.md" rel="noopener noreferrer"&gt;MIT licensed&lt;/a&gt;) useful in a number of my projects and I've reused it many times. Here it is in it's entirety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//https://github.com/mattdesl/lerp/blob/master/index.js
function lerp(v0, v1, t) {
    return v0*(1-t)+v1*t
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entirety of my lerping routine to normalize my data is shown below, in hopes that perhaps someone else might find it useful.&lt;/p&gt;

&lt;p&gt;In short, it takes a set of points that look like &lt;code&gt;{speed:1.5, timestamp: '2019-09-26 02:53:02'}&lt;/code&gt;, and if the points are more than 1 second apart, this routine interpolates the speeds between the two points at 1-second steps. &lt;/p&gt;

&lt;p&gt;The return list from this routine will be "guaranteed" to have data at 1 second intervals, so that every point into the neural network is guaranteed to have a difference of 1 second. This allows the network to better capture the idea of "speed of change" in the readings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function lerpRawData(rawData) {
    const lerped = [];
    rawData.map((row, idx) =&amp;gt; {

        const speed = parseFloat(row.speed);
        if(idx === rawData.length - 1) {
            // at end, don't do lerp
            lerped.push({ ...row });
            return;
        }

        // Already checked if we're at end, so this doesn't need check
        const nextIdx  = idx + 1,
            nextRow    = rawData[nextIdx],
            thisTime   = new Date(row.timestamp).getTime(),
            nextTime   = new Date(nextRow.timestamp).getTime(),
            nextSpeed  = parseFloat(nextRow.speed), 
            delta      = nextTime - thisTime;

        // Step between the two timestamps in 1000ms steps
        // and lerp the speeds between the timestamps based on percent distance
        for(let time=thisTime; time&amp;lt;nextTime; time+=1000) {
            const progress   = (time - thisTime) / delta;
            const interSpeed = lerp(speed, nextSpeed, progress);
            const interTimestamp = new Date(time);
            const d = {
                ...row,
                timestamp: interTimestamp,
                speed:     interSpeed,
                progress, // just for debugging
            };

            // Just for debugging
            if(time &amp;gt; thisTime &amp;amp;&amp;amp; time &amp;lt; nextTime)
                d._lerped = true;

            lerped.push(d);
        }
    });
    return lerped;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Hidden Layers
&lt;/h2&gt;

&lt;p&gt;I know the headline said three big gains, but it's worth mentioning here that an additional hidden layer appeared to aid in generalization as well. My hidden layer setup now looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hiddenLayers: [ inputSize * 2, inputSize * 1.5 ]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This produces a network similar to this hackish pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inputSize = 4
[ * , * , *, * ] # inputs (ngram size)
[ * , * , *, * , *, *, * ] # hidden layer 1
[ * , * , *, * , * ] # hidden layer 2
[ * , * , *, * ] # outputs (4 classes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With these tweaks, my network now has slightly reduced recall across the board but exhibits consistently improved generalization. Performance on unseen data is now consistently at greater than 85% accuracy. &lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>javascript</category>
      <category>node</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Personal Safety, GPS, and Machine Learning: Are You Running from Danger?</title>
      <dc:creator>Josiah Bryan</dc:creator>
      <pubDate>Fri, 20 Sep 2019 15:15:29 +0000</pubDate>
      <link>https://dev.to/josiahbryan/personal-safety-gps-and-machine-learning-are-you-running-from-danger-43bl</link>
      <guid>https://dev.to/josiahbryan/personal-safety-gps-and-machine-learning-are-you-running-from-danger-43bl</guid>
      <description>&lt;p&gt;Imagine that you're getting a text every minute from your best friend, and all it has in that text is their &lt;em&gt;current speed&lt;/em&gt;. Then you have to write back to them what you think they're doing - are they walking, running, driving, or sitting still?&lt;/p&gt;

&lt;p&gt;In my app, I went from "Hey, I've got some GPS points being streamed to my server" to "real-time machine learning classification triggering push notifications" and it took me less than a day of coding. Here's how I did it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Walk Safe
&lt;/h1&gt;

&lt;p&gt;That's exactly the scenario I'm addressing in an app I'm making. I get a GPS speed reading from the user, and I want to know if they're walking, running, etc. This app is called "WalkSafe", and making it available for free in the Play Store and App Store. (Not published yet - still in the review stages, hence why I have time to blog while waiting for the reviewers to approve it!)&lt;/p&gt;

&lt;p&gt;I decided to create WalkSafe after my sister moved into an apartment with her young son where she felt very unsafe. It was a good move for her, but being a single mom and out at night alone - well, she felt unsafe. My family lived near by, but sometimes she might not be able to whip out her phone and call if something happened. Enter the idea for "WalkSafe."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffyyqml69qsy0wb4st0og.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ffyyqml69qsy0wb4st0og.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With WalkSafe, you can set a timer when you're in danger. If the timer goes off before you stop it, a SMS and voice phone call is sent to your emergency contacts with your location and any notes you enter. Of course, if you get to where you're going safely, you just stop the timer and all is well! But if you can't stop it for whatever reason, our cloud servers will monitor your timer and if it goes off, the SOS is sent immediately. That means that even if your phone is destroyed, offline, or no service, the SOS still gets sent. &lt;/p&gt;

&lt;p&gt;When you set the timer in WalkSafe, it starts recording your GPS location and streaming it to the server for the duration of the timer. No GPS is stored before or after, only while you're in danger. However, I felt like simply logging the GPS while in danger wasn't enough. I thought there might be some way I can use the GPS to try to tell if the person using the app is in danger (or safe) without their interaction. &lt;/p&gt;

&lt;h1&gt;
  
  
  Drawing the Line
&lt;/h1&gt;

&lt;p&gt;That's how we arrive at this example at the start - how do we interpret a stream of speeds coming in with no other context? How do we decide if it represents running/driving/walking/etc?  &lt;/p&gt;

&lt;p&gt;Sure, sitting still is easy. Less than 0.5 m/s? Probably sitting still. What about driving? Over 15 m/s? Yeah, probably driving. But then it get's fuzzy. Where do you &lt;em&gt;draw the line&lt;/em&gt; at for walking? Running? How do you tell running from driving based on just speed? &lt;/p&gt;

&lt;p&gt;To answer those questions, you can do one of two things (or three, but I'll get back to that.) You can either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write a bunch of &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;then&lt;/code&gt; statements, taking into account the last few speed readings from them, how long they've been at that speed, what they did this time yesterday, etc.&lt;/li&gt;
&lt;li&gt;Train a simple neural network to classify data for you while you sit and drink tea. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Obviously, since this post is tagged #machinelearning, I decided to use a neural network.&lt;/p&gt;

&lt;p&gt;In my case, I used the excellent &lt;a href="https://brain.js.org" rel="noopener noreferrer"&gt;brain.js&lt;/a&gt; library since I was writing my server in javascript. I've also used &lt;code&gt;brain.js&lt;/code&gt; in the bast, and I've found it to be incredibly easy to use and quick to pick up and implement in a project. &lt;/p&gt;

&lt;p&gt;All in all, going from "Hey, I've got some GPS points being streamed to my server" to "real-time machine learning classification triggering push notifications" took me less than a day of coding. Here's basically how I did it.&lt;/p&gt;

&lt;p&gt;Client-side, I'm using the &lt;code&gt;Cordova&lt;/code&gt; project to make the Android/iOS apps, writing my UI in &lt;code&gt;React&lt;/code&gt;, and utilizing the excellent &lt;code&gt;@mauron85/cordova-plugin-background-geolocation&lt;/code&gt; plugin to stream GPS to my server in the background.&lt;/p&gt;

&lt;h1&gt;
  
  
  Server-Side Magic
&lt;/h1&gt;

&lt;p&gt;The server is where the magic happens.&lt;/p&gt;

&lt;p&gt;Everyone knows that to train a neural network you need labeled data. You put data in, run the training, get a trained set of weights, then use it later. Pretty simple, yes? Well, allow me to walk you though how I did it and the interesting parts along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gathering Data
&lt;/h2&gt;

&lt;p&gt;I started by just logging a ton of GPS points from my own usage of the app. Over the course of two days, I logged GPS points when I was walking, running, driving, walking to my car and driving, running up to my car and driving, driving, parking, then walking, and many other scenarios. I kept a notebook with timestamps of when I did each action as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Labeling Data
&lt;/h2&gt;

&lt;p&gt;Later, I dumped the timestamps and speeds to a CSV file and applied a simple naïve pre-labeling of the speeds. (E.g. &lt;code&gt;0m/s&lt;/code&gt;=&lt;code&gt;STILL&lt;/code&gt;, &lt;code&gt;&amp;lt;2m/s&lt;/code&gt;=&lt;code&gt;WALKING&lt;/code&gt;, &lt;code&gt;&amp;lt;10m/s&lt;/code&gt;=&lt;code&gt;RUNNING&lt;/code&gt;, &lt;code&gt;&amp;gt;10m/s&lt;/code&gt;=&lt;code&gt;DRIVING&lt;/code&gt;) Then I opened each of the CSV files and compared the timestamps to my notebook, making sure the naïve labels were correct. Changed a lot of DRIVING&amp;gt;RUNNING or RUNNING&amp;gt;DRIVING when I was driving slow, stuff like that. When I was done, I had a set of ~5,000 speed measurements in CSV files, all hand-labeled with activity labels from a simple set of &lt;code&gt;STILL&lt;/code&gt;, &lt;code&gt;WALKING&lt;/code&gt;, &lt;code&gt;RUNNING&lt;/code&gt;, or &lt;code&gt;DRIVING&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting Data: N-Grams
&lt;/h2&gt;

&lt;p&gt;Now I had a set of speed measurements in sequence, looking something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ 0, 1.2, 0.78, 1.9, 2.1, 1.8, 2.8, 3.3, 3.6, 4.1, 3.3, 4.9, 5.7 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can you see anything interesting in that? (Assume they are meters per second) If you look carefully, you'll notice an uptick where they start to trend above 2 m/s for a while - right there is where I started to run. Before that, I was walking.&lt;/p&gt;

&lt;p&gt;In order to capture sequentiality in my data, I decided to train my network with a set of points representing the previous X values, with the final value being the "current" point we are classifying. This is similar in concept to n-grams in language modeling, where they break up a sequence of text into a set of finite item sets. Ex. given "abcd" and an n-gram size of two, we could generate "ab", "bc", "cd".&lt;/p&gt;

&lt;p&gt;Therefore, I wrote a simple &lt;code&gt;makeNgramsTrainingNN&lt;/code&gt; routine that took the raw stream of speeds and packaged them into sets of speed readings. It was a lot like taking a sliding window of a fixed size and running it over my data set, one item at a time, and recording each set of data inside the window as a new "n-gram". So my &lt;code&gt;makeNgramsTrainingNN&lt;/code&gt; routine would take an array of speed objects (&lt;code&gt;speed&lt;/code&gt; and &lt;code&gt;label&lt;/code&gt;), and return a new array that looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  { input: { speed0: 0, speed1: 1.2, speed3: 0.78 }, output: { WALKING: 1 } }, 
  { input: { speed0: 1.2, speed1: 0.78, speed3: 1.9 }, output { WALKING: 1 } },
  { input: { speed0: 0.78, speed1: 1.9, speed3: 2.1 }, output { WALKING: 1 } }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The label is always the label from my hand-edited data set for the last speed value in the n-gram. &lt;/p&gt;

&lt;h2&gt;
  
  
  Training the Neural Network
&lt;/h2&gt;

&lt;p&gt;Then, I had to decide how I wanted to train my network - and what type of network to use. After much trial and error, I found that &lt;code&gt;brain.CrossValidate&lt;/code&gt; worked amazingly well to reduce error rates.&lt;/p&gt;

&lt;p&gt;Once I had all my n-grams in a nice big &lt;code&gt;ngrams&lt;/code&gt; array, all I had to do to train the network was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const trainingOptions = {
    iterations: 35000,
    learningRate: 0.2,
    hiddenLayers: [ngramSize+2],
    log: details =&amp;gt; console.log(details),
};

// Use CrossValidation because it seems to give better accuracy
const crossValidate = new brain.CrossValidate(brain.NeuralNetwork, trainingOptions);

// Found it doesn't do us any good to specify kfolds manually
const stats = crossValidate.train(ngrams, trainingOptions);

// Convert the CV to a nerual network for output (below)
const net = crossValidate.toNeuralNetwork();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I had the network trained, I saved it to a json file so I could use it in real time to classify GPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Stringify the nerual network 
const json = JSON.stringify(net.toJSON());
const outFile = 'gps-speed-classifier.net.json';
fs.writeFileSync(outFile, json);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was pure trial and error to discover that iterations of &lt;code&gt;35000&lt;/code&gt; was a good number, and to discover that adding a hidden layer sized at my &lt;code&gt;ngramSize&lt;/code&gt; + 2 was a good number. All just testing and re-testing and seeing what error rates came out. &lt;/p&gt;

&lt;p&gt;For what it's worth, I'm using an &lt;code&gt;ngramSize&lt;/code&gt; of 6 - which means my neural network sees 6 speed readings at once to make it's classification decision. I've configured the GPS plugin client-side to try to send me GPS readings every 1000ms, so an ngram size of 6 means approx 6 seconds of data is used in training and classification. It's important to note that I must use the same ngram size when using the trained network in production. &lt;/p&gt;

&lt;h2&gt;
  
  
  Proving to Myself it Worked
&lt;/h2&gt;

&lt;p&gt;To test the error rates, first I bucketed all my training ngrams by class and tested the recall rates on each of the classes. I considered the training a success when I received &amp;gt;95% recall rate for every class.&lt;/p&gt;

&lt;p&gt;The final test I did on every trained network was to take a single "session" of data and run it through as if it was being streamed live, and compare the predicted labels with the hand-labeled data. Once I hit over 90% accuracy on that, I was happy.&lt;/p&gt;

&lt;p&gt;Getting from "hand labeling data sets" to finally having a trained network that I was happy with took roughly 6 hours or so of testing and trial and error.&lt;/p&gt;

&lt;h1&gt;
  
  
  Integrating the Trained Network into the App
&lt;/h1&gt;

&lt;p&gt;Integrating it into the app was a very quick process by comparison - maybe two hours, if that. I created a "simple" class I call &lt;code&gt;GpsActivityClassifier&lt;/code&gt; that loads the trained network weights from &lt;code&gt;gps-speed-classifier.net.json&lt;/code&gt;. This class is responsible for the classification and updating of the user's "&lt;em&gt;motionState&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;The app's API into the &lt;code&gt;GpsActivityClassifier&lt;/code&gt; is deceptively simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const result = await GpsActivityClassifier.updateUserMotionState(gpsLogEntry);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;gpsLogEntry&lt;/code&gt; is our internal database record for the current GPS entry. Really the only thing the classifier needs from the log entry is the &lt;code&gt;speed&lt;/code&gt;, the current &lt;code&gt;timer&lt;/code&gt;, and the &lt;code&gt;user&lt;/code&gt; that we're classifying.&lt;/p&gt;

&lt;p&gt;Internally, it is rather simple, but the code looks a bit more complex, so I'll break it down here. Internally, &lt;code&gt;updateUserMotionState&lt;/code&gt; looks something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the timestamp of the given &lt;code&gt;gpsLogEntry&lt;/code&gt; and load the previous &lt;code&gt;ngramSize&lt;/code&gt; entries for the current &lt;code&gt;timer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Convert that list of X entries (which looks like &lt;code&gt;[{speed:0.1,...},{speed:0.5,...}, {speed:1.23,...}, ...]&lt;/code&gt;) into a single &lt;code&gt;ngram&lt;/code&gt; object that looks like &lt;code&gt;{speed0:0.1, speed1:0.5, speed2:1.23, ...}&lt;/code&gt;. The conversion code looks like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ngram = {};
Array.from(speedValues)
    .slice(0, TRAINED_NGRAM_SIZE)
    .forEach((value, idx) =&amp;gt; ngram[`speed${idx}`] = value);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After making the &lt;code&gt;ngram&lt;/code&gt;, it uses the preloaded &lt;code&gt;brain.js&lt;/code&gt; &lt;code&gt;NeuralNetwork&lt;/code&gt; object (with weights already loaded from disk) to &lt;code&gt;run&lt;/code&gt; the &lt;code&gt;ngram&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const rawClassification = this.net.run(ngram);
const classification = maxClass(rawClassification);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The utility &lt;code&gt;maxClass(...)&lt;/code&gt; just takes the raw output of the final layer of the network and returns the predicted class label that has the highest probability.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pressure to Change
&lt;/h1&gt;

&lt;p&gt;At this point, we have a predicted label (&lt;code&gt;predictedState&lt;/code&gt;) for the &lt;code&gt;gpsLogEntry&lt;/code&gt;. But here's where we do that "third thing" we hinted at earlier in this blog. &lt;/p&gt;

&lt;p&gt;Instead of just applying the &lt;code&gt;predictedState&lt;/code&gt; directly to the user and calling it that user's current &lt;code&gt;motionState&lt;/code&gt;, we apply a little bit of hard logic to the state.&lt;/p&gt;

&lt;p&gt;We don't just want the user's &lt;code&gt;motionState&lt;/code&gt; to oscillate wildly if the classification changes quickly from one point to the other, so I built in a simple "pressure" mechanism whereby the prediction must stay stable for at least &lt;code&gt;CLASSIFICATIONS_NEEDED_TO_CHANGE&lt;/code&gt; counts. Through trial and error, I found &lt;code&gt;5&lt;/code&gt; to be a good number.&lt;/p&gt;

&lt;p&gt;That means that for a given &lt;code&gt;gpsLogEntry&lt;/code&gt;, the classifier may return &lt;code&gt;RUNNING&lt;/code&gt;. Only after it returns &lt;code&gt;RUNNING&lt;/code&gt; for five continuous gps readings do we then update the user's &lt;code&gt;motionState&lt;/code&gt;. Should the classifier go to a different classification before it hits 5 times, the counter starts over. (For example, if on the 3rd point the classifier returns &lt;code&gt;DRIVING&lt;/code&gt;, we reset the counter and wait for 5 points until we actually set the user's &lt;code&gt;motionState&lt;/code&gt; to &lt;code&gt;DRIVING&lt;/code&gt;.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Change is Good (or Bad)
&lt;/h1&gt;

&lt;p&gt;Once the counter to change &lt;code&gt;motionStates&lt;/code&gt; is actually met, we update the user record in the database with the new &lt;code&gt;motionState&lt;/code&gt; and return to the caller of our &lt;code&gt;GpsActivityClassifier.updateUserMotionState&lt;/code&gt; method an object that looks like &lt;code&gt;{ changed: "DRIVING", confidence: 0.98, previousState: "RUNNING" }&lt;/code&gt;. I consider this an "&lt;em&gt;event&lt;/em&gt;", since we only get a return value of { changed: &lt;em&gt;truthy&lt;/em&gt; } if the user's &lt;code&gt;motionState&lt;/code&gt; ACTUALLY changed. All other times, if classification stayed the same or was "about to change", the object would look like &lt;code&gt;{changed: false, ...}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So what do we do with a &lt;code&gt;changed&lt;/code&gt; event when it occurs?&lt;/p&gt;

&lt;p&gt;In the case of WalkSafe, what we do with this event is we run a bit of "business logic" when the change happens. We take the &lt;code&gt;stateFrom&lt;/code&gt; (&lt;code&gt;previousState&lt;/code&gt;) and the &lt;code&gt;stateTo&lt;/code&gt; (&lt;code&gt;changed&lt;/code&gt;), build up a simple transition map (&lt;code&gt;txMap&lt;/code&gt;) that defines valid/useful transitions, and then react accordingly. &lt;/p&gt;

&lt;p&gt;For kicks and grins, here's what our &lt;code&gt;txMap&lt;/code&gt; looks like in WalkSafe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { WALK, RUN, DRIVE, STILL } = GpsActivityClassifier.CLASSIFIER_STATES,
    OK_30   = 'OK_30',
    OK_60   = 'OK_60',
    SAFE_60 = 'SAFE_60',
    SAFE_5  = 'SAFE_5',
    NOOP    = 'NOOP',
    txMap   = {
        [ WALK + RUN  ]: OK_30,
        [STILL + RUN  ]: OK_30,
        [DRIVE + RUN  ]: OK_60,
        [STILL + DRIVE]: SAFE_60,
        [ WALK + DRIVE]: SAFE_60,
        [  RUN + DRIVE]: SAFE_60,
        [  RUN + WALK ]: SAFE_5,
        [  RUN + STILL]: NOOP,
        [ WALK + STILL]: NOOP,
        [DRIVE + STILL]: NOOP,
        [STILL + WALK ]: NOOP,
        [DRIVE + WALK ]: NOOP,
    };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we just query the &lt;code&gt;txMap&lt;/code&gt; when the user's &lt;code&gt;motionState&lt;/code&gt; changes with the from and the to state, and react accordingly. For illustrations sake, here's what that looks like as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const txTest = stateFrom + stateTo,
    txAction = txMap[txTest];

if(!txAction) {
    // Should never encounter, but if we find a tx we don't have defined,
    // we throw which should be caught by Sentry and dashboarded/emailed
    throw new Error(`Undefined transition from ${stateFrom} to state ${stateTo})`);
}

switch(txAction) {
    case OK_30:
    case OK_60: {
        const time = txAction === OK_60 ? 60 : 30;
        return await this._txAreYouInDanger({ time, stateTo, stateFrom, ...props });
    }
    case SAFE_60:
    case SAFE_5: {
        const time = txAction === SAFE_60 ? 60 : 60 * 5;
        return await this._txAreYouSafe({ time, stateTo, stateFrom, ...props });
    }
    default: 
        // NOOP;
        break;
}   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Won't go into detail on the &lt;code&gt;_txAreYouSafe&lt;/code&gt; or &lt;code&gt;_txAreYouInDanger&lt;/code&gt; functions, but they basically add to (if safe) or set (if in danger) the remaining time in the running timer, and then send a push notification via Firebase to the user's device.&lt;/p&gt;

&lt;p&gt;To tie a bow on it though, here's what it looks like to send the push notification shown in the screenshot at the top of this article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Triggered possible danger scenario, so reduce time remaining
// to only `time` seconds...
await timer.setSecondsRemaining(time);

// Alert the user to this change ...
user.alert({
    // Channel is Android-specific and MUST EXIST OR 
    // NO NOTIFICATION DELIVERED on Androids. 
    // See list in client/src/utils/NativePushPlugin of valid channels.
    channel: "sos",
    title: "Are you running??",
    body:  `
        If you're not okay, KEEP RUNNING! We'll send an SOS in 
        less than a minute unless you stop the timer or add more time. 
        Don't stop unless it's safe to do so!
    `,

    // onClick is base64-encoded and sent via Firebase 
    // as the action URL for this push notification
    onClick: {
        // This event key is "special":
        // When the user clicks on the notification,
        // our app will emit this event on the ServerStore object...
        // Any other properties in this onClick handler are passed as
        // a data object to the event. This is emitted in PushNotifyService.
        // Obviously, the event does nothing unless some other part of the
        // app is listening for it.
        event:  'gps.areYouInDanger',
        // Extra args for the event:
        timerId: timer.id,
        stateTo, 
        stateFrom,
    },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Walk Safely but Run if Needed, We've Got You
&lt;/h1&gt;

&lt;p&gt;The combination all of this effects an additional safeguard for people using WalkSafe. If they set a danger timer, but start running in the middle of the timer, the server will recognize this state change, reduce the time left on the timer so it will send an SOS right away if they are in fact running from danger.&lt;/p&gt;

&lt;p&gt;And that's how we tie Personal Safety, GPS, and Machine Learning together to improve the real-world safety of people who use a simple personal safety SOS timer!&lt;/p&gt;

&lt;h1&gt;
  
  
  Beta Testers Wanted
&lt;/h1&gt;

&lt;p&gt;If you want to test out this app, send me a message. Or if you're interested in working with me on the app, I'd be open to talking! And if you're interested in hiring me for consulting work - drop me a line as well! You can reach me at &lt;a href="//mailto:josiahbryan@gmail.com"&gt;josiahbryan@gmail.com&lt;/a&gt;. Cheers and crackers!&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>javascript</category>
      <category>node</category>
    </item>
  </channel>
</rss>
